Falln87/godot_editor
0
1<!DOCTYPE html>2<html xmlns="https://www.w3.org/1999/xhtml" lang="en">3 4<head>5 <meta charset="utf-8" />6 <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />7 <meta name="author" content="Godot Engine" />8 <meta name="description" content="Use the Godot Engine editor directly in your web browser, without having to install anything." />9 <meta name="mobile-web-app-capable" content="yes" />10 <meta name="apple-mobile-web-app-capable" content="yes" />11 <meta name="application-name" content="Godot" />12 <meta name="apple-mobile-web-app-title" content="Godot" />13 <meta name="theme-color" content="#202531" />14 <meta name="msapplication-navbutton-color" content="#202531" />15 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />16 <meta name="msapplication-starturl" content="/latest" />17 <meta property="og:site_name" content="Godot Engine Web Editor" />18 <meta property="og:url" name="twitter:url" content="https://editor.godotengine.org/releases/latest/" />19 <meta property="og:title" name="twitter:title" content="Free and open source 2D and 3D game engine" />20 <meta property="og:description" name="twitter:description" content="Use the Godot Engine editor directly in your web browser, without having to install anything." />21 <meta property="og:image" name="twitter:image" content="https://godotengine.org/themes/godotengine/assets/og_image.png" />22 <meta property="og:type" content="website" />23 <meta name="twitter:card" content="summary" />24 <link id="-gd-engine-icon" rel="icon" type="image/png" href="favicon.png" />25 <link rel="apple-touch-icon" type="image/png" href="favicon.png" />26 <link rel="manifest" href="manifest.json" />27 <title>Godot Engine Web Editor (3.4.4.rc.custom_build)</title>28 <style>29 *:focus {30 /* More visible outline for better keyboard navigation. */31 outline: 0.125rem solid hsl(220, 100%, 62.5%);32 /* Make the outline always appear above other elements. */33 /* Otherwise, one of its sides can be hidden by tabs in the Download and More layouts. */34 position: relative;35 }36 37 body {38 touch-action: none;39 font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";40 margin: 0;41 border: 0 none;42 padding: 0;43 text-align: center;44 background-color: #333b4f;45 overflow: hidden;46 }47 48 a {49 color: hsl(205, 100%, 75%);50 text-decoration-color: hsla(205, 100%, 75%, 0.3);51 text-decoration-thickness: 0.125rem;52 }53 54 a:hover {55 filter: brightness(117.5%);56 }57 58 a:active {59 filter: brightness(82.5%);60 }61 62 .welcome-modal {63 display: none;64 position: fixed;65 z-index: 1;66 left: 0;67 top: 0;68 width: 100%;69 height: 100%;70 overflow: auto;71 background-color: hsla(0, 0%, 0%, 0.5);72 }73 74 .welcome-modal-content {75 background-color: #333b4f;76 box-shadow: 0 0.25rem 0.25rem hsla(0, 0%, 0%, 0.5);77 line-height: 1.5;78 max-width: 38rem;79 margin: 4rem auto 0 auto;80 color: white;81 border-radius: 0.5rem;82 padding: 1rem 1rem 2rem 1rem;83 }84 85 #tabs-buttons {86 /* Match the default background color of the editor window for a seamless appearance. */87 background-color: #202531;88 }89 90 #tab-game {91 /* Use a pure black background to better distinguish the running project */92 /* from the editor window, and to use a more neutral background color (no tint). */93 background-color: black;94 /* Make the background span the entire page height. */95 min-height: 100vh;96 }97 98 #canvas,99 #gameCanvas {100 display: block;101 margin: 0;102 color: white;103 }104 /* Don't show distracting focus outlines for the main tabs' contents. */105 106 #tab-editor canvas:focus,107 #tab-game canvas:focus,108 #canvas:focus,109 #gameCanvas:focus {110 outline: none;111 }112 113 .godot {114 color: #e0e0e0;115 background-color: #3b3943;116 background-image: linear-gradient(to bottom, #403e48, #35333c);117 border: 1px solid #45434e;118 box-shadow: 0 0 1px 1px #2f2d35;119 }120 121 .btn {122 appearance: none;123 color: #e0e0e0;124 background-color: #262c3b;125 border: 1px solid #202531;126 padding: 0.5rem 1rem;127 margin: 0 0.5rem;128 }129 130 .btn:not(:disabled):hover {131 color: #e0e1e5;132 border-color: #666c7b;133 }134 135 .btn:active {136 border-color: #699ce8;137 color: #699ce8;138 }139 140 .btn:disabled {141 color: #aaa;142 border-color: #242937;143 }144 145 .btn.tab-btn {146 padding: 0.3rem 1rem;147 }148 149 .btn.close-btn {150 padding: 0.3rem 1rem;151 margin-left: -0.75rem;152 font-weight: 700;153 }154 /* Status display155 * ============== */156 157 #status {158 position: absolute;159 left: 0;160 top: 0;161 right: 0;162 bottom: 0;163 display: flex;164 justify-content: center;165 align-items: center;166 /* don't consume click events - make children visible explicitly */167 visibility: hidden;168 }169 170 #status-progress {171 width: 366px;172 height: 7px;173 background-color: #38363A;174 border: 1px solid #444246;175 padding: 1px;176 box-shadow: 0 0 2px 1px #1B1C22;177 border-radius: 2px;178 visibility: visible;179 }180 181 @media only screen and (orientation:portrait) {182 #status-progress {183 width: 61.8%;184 }185 }186 187 #status-progress-inner {188 height: 100%;189 width: 0;190 box-sizing: border-box;191 transition: width 0.5s linear;192 background-color: #202020;193 border: 1px solid #222223;194 box-shadow: 0 0 1px 1px #27282E;195 border-radius: 3px;196 }197 198 #status-indeterminate {199 visibility: visible;200 position: relative;201 }202 203 #status-indeterminate>div {204 width: 4.5px;205 height: 0;206 border-style: solid;207 border-width: 9px 3px 0 3px;208 border-color: #2b2b2b transparent transparent transparent;209 transform-origin: center 21px;210 position: absolute;211 }212 213 #status-indeterminate>div:nth-child(1) {214 transform: rotate( 22.5deg);215 }216 217 #status-indeterminate>div:nth-child(2) {218 transform: rotate( 67.5deg);219 }220 221 #status-indeterminate>div:nth-child(3) {222 transform: rotate(112.5deg);223 }224 225 #status-indeterminate>div:nth-child(4) {226 transform: rotate(157.5deg);227 }228 229 #status-indeterminate>div:nth-child(5) {230 transform: rotate(202.5deg);231 }232 233 #status-indeterminate>div:nth-child(6) {234 transform: rotate(247.5deg);235 }236 237 #status-indeterminate>div:nth-child(7) {238 transform: rotate(292.5deg);239 }240 241 #status-indeterminate>div:nth-child(8) {242 transform: rotate(337.5deg);243 }244 245 #status-notice {246 margin: 0 100px;247 line-height: 1.3;248 visibility: visible;249 padding: 4px 6px;250 visibility: visible;251 }252 </style>253</head>254 255<body>256 <div id="welcome-modal" class="welcome-modal" role="dialog" aria-labelledby="welcome-modal-title" aria-describedby="welcome-modal-description" onclick="if (event.target === this) closeWelcomeModal(false)">257 <div class="welcome-modal-content">258 <h2 id="welcome-modal-title">Important - Please read before continuing</h2>259 <div id="welcome-modal-description">260 <p>261 The Godot Web Editor has some limitations compared to the native version. Its main focus is education and experimentation;262 <strong>it is not recommended for production</strong>.263 </p>264 <p>265 Refer to the266 <a href="https://docs.godotengine.org/en/latest/tutorials/editor/using_the_web_editor.html" target="_blank" rel="noopener">Web editor documentation</a> for usage instructions and limitations.267 </p>268 </div>269 <button id="welcome-modal-dismiss" class="btn" type="button" onclick="closeWelcomeModal(true)" style="margin-top: 1rem">270 OK, don't show again271 </button>272 </div>273 </div>274 <div id="tabs-buttons">275 <button id="btn-tab-loader" class="btn tab-btn" onclick="showTab('loader')">Loader</button>276 <button id="btn-tab-editor" class="btn tab-btn" disabled="disabled" onclick="showTab('editor')">Editor</button>277 <button id="btn-close-editor" class="btn close-btn" disabled="disabled" onclick="closeEditor()">×</button>278 <button id="btn-tab-game" class="btn tab-btn" disabled="disabled" onclick="showTab('game')">Game</button>279 <button id="btn-close-game" class="btn close-btn" disabled="disabled" onclick="closeGame()">×</button>280 </div>281 <div id="tabs">282 <div id="tab-loader">283 <div style="color: #e0e0e0;" id="persistence">284 <br />285 <img src="logo.svg" alt="Godot Engine logo" width="1024" height="414" style="width: auto; height: auto; max-width: min(85%, 50vh); max-height: 250px" />286 <br /> 3.4.4.rc.custom_build287 <br />288 <a href="releases/">Need an old version?</a>289 <br />290 <br />291 <br />292 <label for="videoMode" style="margin-right: 1rem">Video driver:</label>293 <select id="videoMode">294 <option value="" selected="selected">Auto</option>295 <option value="GLES2">WebGL</option>296 <option value="GLES3">WebGL 2</option>297 </select>298 <br />299 <br />300 <label for="zip-file" style="margin-right: 1rem">Preload project ZIP:</label> <input id="zip-file" type="file" name="files" style="margin-bottom: 1rem" />301 <br />302 <a href="demo.zip">(Try this for example)</a>303 <br />304 <br />305 <button id="startButton" class="btn" style="margin-bottom: 4rem; font-weight: 700">Start Godot editor</button>306 <br />307 <button class="btn" onclick="clearPersistence()" style="margin-bottom: 1.5rem">Clear persistent data</button>308 <br />309 <a href="https://docs.godotengine.org/en/3.4/tutorials/editor/using_the_web_editor.html">Web editor documentation</a>310 </div>311 </div>312 <div id="tab-editor" style="display: none;">313 <canvas id="editor-canvas" tabindex="1">314 HTML5 canvas appears to be unsupported in the current browser.<br />315 Please try updating or use a different browser.316 </canvas>317 </div>318 <div id="tab-game" style="display: none;">319 <canvas id="game-canvas" tabindex="2">320 HTML5 canvas appears to be unsupported in the current browser.<br />321 Please try updating or use a different browser.322 </canvas>323 </div>324 <div id="tab-status" style="display: none;">325 <div id="status-progress" style="display: none;" oncontextmenu="event.preventDefault();">326 <div id="status-progress-inner"></div>327 </div>328 <div id="status-indeterminate" style="display: none;" oncontextmenu="event.preventDefault();">329 <div></div>330 <div></div>331 <div></div>332 <div></div>333 <div></div>334 <div></div>335 <div></div>336 <div></div>337 </div>338 <div id="status-notice" class="godot" style="display: none;"></div>339 </div>340 </div>341 <script>342 window.addEventListener("load", () => {343 if ("serviceWorker" in navigator) {344 navigator.serviceWorker.register("service.worker.js");345 }346 347 if (localStorage.getItem("welcomeModalDismissed") !== 'true') {348 document.getElementById("welcome-modal").style.display = "block";349 document.getElementById("welcome-modal-dismiss").focus();350 }351 });352 353 function closeWelcomeModal(dontShowAgain) {354 document.getElementById("welcome-modal").style.display = "none";355 if (dontShowAgain) {356 localStorage.setItem("welcomeModalDismissed", 'true');357 }358 }359 </script>360 <script src="godot.tools.js"></script>361 <script>362 //<![CDATA[363 364 var editor = null;365 var game = null;366 var setStatusMode;367 var setStatusNotice;368 var video_driver = "";369 370 function clearPersistence() {371 function deleteDB(path) {372 return new Promise(function(resolve, reject) {373 var req = indexedDB.deleteDatabase(path);374 req.onsuccess = function() {375 resolve();376 };377 req.onerror = function(err) {378 reject(err);379 };380 req.onblocked = function(err) {381 reject(err);382 }383 384 });385 }386 if (!window.confirm("Are you sure you want to delete all the locally stored files?\nClicking \"OK\" will permanently remove your projects and editor settings!")) {387 return;388 }389 Promise.all([390 deleteDB("/home/web_user"),391 ]).then(function(results) {392 alert("Done.");393 }).catch(function(err) {394 alert("Error deleting local files. Please retry after reloading the page.");395 });396 }397 398 function selectVideoMode() {399 var select = document.getElementById('videoMode');400 video_driver = select.selectedOptions[0].value;401 }402 403 var tabs = [404 document.getElementById('tab-loader'),405 document.getElementById('tab-editor'),406 document.getElementById('tab-game')407 ]408 409 function showTab(name) {410 tabs.forEach(function(elem) {411 if (elem.id == 'tab-' + name) {412 elem.style.display = 'block';413 if (name == 'editor' || name == 'game') {414 const canvas = document.getElementById(name + '-canvas');415 canvas.focus();416 }417 } else {418 elem.style.display = 'none';419 }420 });421 }422 423 function setButtonEnabled(id, enabled) {424 if (enabled) {425 document.getElementById(id).disabled = "";426 } else {427 document.getElementById(id).disabled = "disabled";428 }429 }430 431 function setLoaderEnabled(enabled) {432 setButtonEnabled('btn-tab-loader', enabled);433 setButtonEnabled('btn-tab-editor', !enabled);434 setButtonEnabled('btn-close-editor', !enabled);435 }436 437 function setGameTabEnabled(enabled) {438 setButtonEnabled('btn-tab-game', enabled);439 setButtonEnabled('btn-close-game', enabled);440 }441 442 function closeGame() {443 if (game) {444 game.requestQuit();445 }446 }447 448 function closeEditor() {449 closeGame();450 if (editor) {451 editor.requestQuit();452 }453 }454 455 function startEditor(zip) {456 const INDETERMINATE_STATUS_STEP_MS = 100;457 const persistentPaths = ['/home/web_user'];458 459 var editorCanvas = document.getElementById('editor-canvas');460 var gameCanvas = document.getElementById('game-canvas');461 var statusProgress = document.getElementById('status-progress');462 var statusProgressInner = document.getElementById('status-progress-inner');463 var statusIndeterminate = document.getElementById('status-indeterminate');464 var statusNotice = document.getElementById('status-notice');465 var headerDiv = document.getElementById('tabs-buttons');466 467 var initializing = true;468 var statusMode = 'hidden';469 470 showTab('status');471 472 var animationCallbacks = [];473 474 function animate(time) {475 animationCallbacks.forEach(callback => callback(time));476 requestAnimationFrame(animate);477 }478 requestAnimationFrame(animate);479 480 var lastScale = 0;481 var lastWidth = 0;482 var lastHeight = 0;483 484 function adjustCanvasDimensions() {485 var scale = window.devicePixelRatio || 1;486 var headerHeight = headerDiv.offsetHeight + 1;487 var width = window.innerWidth;488 var height = window.innerHeight - headerHeight;489 if (lastScale !== scale || lastWidth !== width || lastHeight !== height) {490 editorCanvas.width = width * scale;491 editorCanvas.height = height * scale;492 editorCanvas.style.width = width + "px";493 editorCanvas.style.height = height + "px";494 lastScale = scale;495 lastWidth = width;496 lastHeight = height;497 }498 }499 animationCallbacks.push(adjustCanvasDimensions);500 adjustCanvasDimensions();501 502 function replaceCanvas(from) {503 const out = document.createElement("canvas");504 out.id = from.id;505 out.tabIndex = from.tabIndex;506 from.parentNode.replaceChild(out, from);507 lastScale = 0;508 return out;509 }510 511 setStatusMode = function setStatusMode(mode) {512 if (statusMode === mode || !initializing)513 return;514 [statusProgress, statusIndeterminate, statusNotice].forEach(elem => {515 elem.style.display = 'none';516 });517 animationCallbacks = animationCallbacks.filter(function(value) {518 return (value != animateStatusIndeterminate);519 });520 switch (mode) {521 case 'progress':522 statusProgress.style.display = 'block';523 break;524 case 'indeterminate':525 statusIndeterminate.style.display = 'block';526 animationCallbacks.push(animateStatusIndeterminate);527 break;528 case 'notice':529 statusNotice.style.display = 'block';530 break;531 case 'hidden':532 break;533 default:534 throw new Error('Invalid status mode');535 }536 statusMode = mode;537 };538 539 function animateStatusIndeterminate(ms) {540 var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8);541 if (statusIndeterminate.children[i].style.borderTopColor == '') {542 Array.prototype.slice.call(statusIndeterminate.children).forEach(child => {543 child.style.borderTopColor = '';544 });545 statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf';546 }547 }548 549 setStatusNotice = function setStatusNotice(text) {550 while (statusNotice.lastChild) {551 statusNotice.removeChild(statusNotice.lastChild);552 }553 var lines = text.split('\n');554 lines.forEach((line) => {555 statusNotice.appendChild(document.createTextNode(line));556 statusNotice.appendChild(document.createElement('br'));557 });558 };559 560 const gameConfig = {561 'persistentPaths': persistentPaths,562 'unloadAfterInit': false,563 'canvas': gameCanvas,564 'canvasResizePolicy': 1,565 'onExit': function() {566 gameCanvas = replaceCanvas(gameCanvas);567 setGameTabEnabled(false);568 showTab('editor');569 game = null;570 },571 };572 573 var OnEditorExit = function() {574 showTab('loader');575 setLoaderEnabled(true);576 };577 578 function Execute(args) {579 const is_editor = args.filter(function(v) {580 return v == '--editor' || v == '-e'581 }).length != 0;582 const is_project_manager = args.filter(function(v) {583 return v == '--project-manager'584 }).length != 0;585 const is_game = !is_editor && !is_project_manager;586 if (video_driver) {587 args.push('--video-driver', video_driver);588 }589 if (is_game) {590 if (game) {591 console.error("A game is already running. Close it first");592 return;593 }594 setGameTabEnabled(true);595 game = new Engine(gameConfig);596 showTab('game');597 game.init().then(function() {598 requestAnimationFrame(function() {599 game.start({600 'args': args,601 'canvas': gameCanvas602 }).then(function() {603 gameCanvas.focus();604 });605 });606 });607 } else { // New editor instances will be run in the same canvas. We want to wait for it to exit.608 OnEditorExit = function(code) {609 setLoaderEnabled(true);610 setTimeout(function() {611 editor.init().then(function() {612 setLoaderEnabled(false);613 OnEditorExit = function() {614 showTab('loader');615 setLoaderEnabled(true);616 };617 editor.start({618 'args': args,619 'persistentDrops': is_project_manager,620 'canvas': editorCanvas621 });622 });623 }, 0);624 OnEditorExit = null;625 };626 }627 }628 629 const editorConfig = {630 'unloadAfterInit': false,631 'onProgress': function progressFunction(current, total) {632 if (total > 0) {633 statusProgressInner.style.width = current / total * 100 + '%';634 setStatusMode('progress');635 if (current === total) {636 // wait for progress bar animation637 setTimeout(() => {638 setStatusMode('indeterminate');639 }, 100);640 }641 } else {642 setStatusMode('indeterminate');643 }644 },645 'canvas': editorCanvas,646 'canvasResizePolicy': 0,647 'onExit': function() {648 editorCanvas = replaceCanvas(editorCanvas);649 if (OnEditorExit) {650 OnEditorExit();651 }652 },653 'onExecute': Execute,654 'persistentPaths': persistentPaths,655 };656 editor = new Engine(editorConfig);657 658 function displayFailureNotice(err) {659 var msg = err.message || err;660 console.error(msg);661 setStatusNotice(msg);662 setStatusMode('notice');663 initializing = false;664 };665 666 if (!Engine.isWebGLAvailable()) {667 displayFailureNotice('WebGL not available');668 } else {669 setStatusMode('indeterminate');670 editor.init('https://huggingface.co/spaces/thomwolf/test_godot_editor/resolve/main/godot.tools').then(function() {671 if (zip) {672 editor.copyToFS("/tmp/preload.zip", zip);673 }674 try {675 // Avoid user creating project in the persistent root folder.676 editor.copyToFS("/home/web_user/keep", new Uint8Array());677 } catch (e) {678 // File exists679 }680 selectVideoMode();681 showTab('editor');682 setLoaderEnabled(false);683 const args = ['--project-manager'];684 if (video_driver) {685 args.push('--video-driver', video_driver);686 }687 editor.start({688 'args': args,689 'persistentDrops': true690 }).then(function() {691 setStatusMode('hidden');692 initializing = false;693 });694 }).catch(displayFailureNotice);695 }696 };697 document.getElementById("startButton").onclick = function() {698 preloadZip(document.getElementById('zip-file')).then(function(zip) {699 startEditor(zip);700 });701 }702 703 function preloadZip(target) {704 return new Promise(function(resolve, reject) {705 if (target.files.length > 0) {706 target.files[0].arrayBuffer().then(function(data) {707 resolve(data);708 });709 } else {710 resolve();711 }712 });713 }714 //]]>715 </script>716</body>717 718</html>