Spaces:
Running
Running
| <html xmlns="https://www.w3.org/1999/xhtml" lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" /> | |
| <meta name="author" content="Godot Engine" /> | |
| <meta name="description" content="Use the Godot Engine editor directly in your web browser, without having to install anything." /> | |
| <meta name="mobile-web-app-capable" content="yes" /> | |
| <meta name="apple-mobile-web-app-capable" content="yes" /> | |
| <meta name="application-name" content="Godot" /> | |
| <meta name="apple-mobile-web-app-title" content="Godot" /> | |
| <meta name="theme-color" content="#202531" /> | |
| <meta name="msapplication-navbutton-color" content="#202531" /> | |
| <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> | |
| <meta name="msapplication-starturl" content="/latest" /> | |
| <meta property="og:site_name" content="Godot Engine Web Editor" /> | |
| <meta property="og:url" name="twitter:url" content="https://editor.godotengine.org/releases/latest/" /> | |
| <meta property="og:title" name="twitter:title" content="Free and open source 2D and 3D game engine" /> | |
| <meta property="og:description" name="twitter:description" content="Use the Godot Engine editor directly in your web browser, without having to install anything." /> | |
| <meta property="og:image" name="twitter:image" content="https://godotengine.org/themes/godotengine/assets/og_image.png" /> | |
| <meta property="og:type" content="website" /> | |
| <meta name="twitter:card" content="summary" /> | |
| <link id="-gd-engine-icon" rel="icon" type="image/png" href="favicon.png" /> | |
| <link rel="apple-touch-icon" type="image/png" href="favicon.png" /> | |
| <link rel="manifest" href="manifest.json" /> | |
| <title>Godot Engine Web Editor (3.4.4.rc.custom_build)</title> | |
| <style> | |
| *:focus { | |
| /* More visible outline for better keyboard navigation. */ | |
| outline: 0.125rem solid hsl(220, 100%, 62.5%); | |
| /* Make the outline always appear above other elements. */ | |
| /* Otherwise, one of its sides can be hidden by tabs in the Download and More layouts. */ | |
| position: relative; | |
| } | |
| body { | |
| touch-action: none; | |
| 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"; | |
| margin: 0; | |
| border: 0 none; | |
| padding: 0; | |
| text-align: center; | |
| background-color: #333b4f; | |
| overflow: hidden; | |
| } | |
| a { | |
| color: hsl(205, 100%, 75%); | |
| text-decoration-color: hsla(205, 100%, 75%, 0.3); | |
| text-decoration-thickness: 0.125rem; | |
| } | |
| a:hover { | |
| filter: brightness(117.5%); | |
| } | |
| a:active { | |
| filter: brightness(82.5%); | |
| } | |
| .welcome-modal { | |
| display: none; | |
| position: fixed; | |
| z-index: 1; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100%; | |
| overflow: auto; | |
| background-color: hsla(0, 0%, 0%, 0.5); | |
| } | |
| .welcome-modal-content { | |
| background-color: #333b4f; | |
| box-shadow: 0 0.25rem 0.25rem hsla(0, 0%, 0%, 0.5); | |
| line-height: 1.5; | |
| max-width: 38rem; | |
| margin: 4rem auto 0 auto; | |
| color: white; | |
| border-radius: 0.5rem; | |
| padding: 1rem 1rem 2rem 1rem; | |
| } | |
| #tabs-buttons { | |
| /* Match the default background color of the editor window for a seamless appearance. */ | |
| background-color: #202531; | |
| } | |
| #tab-game { | |
| /* Use a pure black background to better distinguish the running project */ | |
| /* from the editor window, and to use a more neutral background color (no tint). */ | |
| background-color: black; | |
| /* Make the background span the entire page height. */ | |
| min-height: 100vh; | |
| } | |
| #canvas, #gameCanvas { | |
| display: block; | |
| margin: 0; | |
| color: white; | |
| } | |
| /* Don't show distracting focus outlines for the main tabs' contents. */ | |
| #tab-editor canvas:focus, | |
| #tab-game canvas:focus, | |
| #canvas:focus, | |
| #gameCanvas:focus { | |
| outline: none; | |
| } | |
| .godot { | |
| color: #e0e0e0; | |
| background-color: #3b3943; | |
| background-image: linear-gradient(to bottom, #403e48, #35333c); | |
| border: 1px solid #45434e; | |
| box-shadow: 0 0 1px 1px #2f2d35; | |
| } | |
| .btn { | |
| appearance: none; | |
| color: #e0e0e0; | |
| background-color: #262c3b; | |
| border: 1px solid #202531; | |
| padding: 0.5rem 1rem; | |
| margin: 0 0.5rem; | |
| } | |
| .btn:not(:disabled):hover { | |
| color: #e0e1e5; | |
| border-color: #666c7b; | |
| } | |
| .btn:active { | |
| border-color: #699ce8; | |
| color: #699ce8; | |
| } | |
| .btn:disabled { | |
| color: #aaa; | |
| border-color: #242937; | |
| } | |
| .btn.tab-btn { | |
| padding: 0.3rem 1rem; | |
| } | |
| .btn.close-btn { | |
| padding: 0.3rem 1rem; | |
| margin-left: -0.75rem; | |
| font-weight: 700; | |
| } | |
| /* Status display | |
| * ============== */ | |
| #status { | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| right: 0; | |
| bottom: 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| /* don't consume click events - make children visible explicitly */ | |
| visibility: hidden; | |
| } | |
| #status-progress { | |
| width: 366px; | |
| height: 7px; | |
| background-color: #38363A; | |
| border: 1px solid #444246; | |
| padding: 1px; | |
| box-shadow: 0 0 2px 1px #1B1C22; | |
| border-radius: 2px; | |
| visibility: visible; | |
| } | |
| @media only screen and (orientation:portrait) { | |
| #status-progress { | |
| width: 61.8%; | |
| } | |
| } | |
| #status-progress-inner { | |
| height: 100%; | |
| width: 0; | |
| box-sizing: border-box; | |
| transition: width 0.5s linear; | |
| background-color: #202020; | |
| border: 1px solid #222223; | |
| box-shadow: 0 0 1px 1px #27282E; | |
| border-radius: 3px; | |
| } | |
| #status-indeterminate { | |
| visibility: visible; | |
| position: relative; | |
| } | |
| #status-indeterminate > div { | |
| width: 4.5px; | |
| height: 0; | |
| border-style: solid; | |
| border-width: 9px 3px 0 3px; | |
| border-color: #2b2b2b transparent transparent transparent; | |
| transform-origin: center 21px; | |
| position: absolute; | |
| } | |
| #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); } | |
| #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); } | |
| #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); } | |
| #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); } | |
| #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); } | |
| #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); } | |
| #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); } | |
| #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); } | |
| #status-notice { | |
| margin: 0 100px; | |
| line-height: 1.3; | |
| visibility: visible; | |
| padding: 4px 6px; | |
| visibility: visible; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <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)" | |
| > | |
| <div class="welcome-modal-content"> | |
| <h2 id="welcome-modal-title">Important - Please read before continuing</h2> | |
| <div id="welcome-modal-description"> | |
| <p> | |
| The Godot Web Editor has some limitations compared to the native version. | |
| Its main focus is education and experimentation; | |
| <strong>it is not recommended for production</strong>. | |
| </p> | |
| <p> | |
| Refer to the | |
| <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. | |
| </p> | |
| </div> | |
| <button id="welcome-modal-dismiss" class="btn" type="button" onclick="closeWelcomeModal(true)" style="margin-top: 1rem"> | |
| OK, don't show again | |
| </button> | |
| </div> | |
| </div> | |
| <div id="tabs-buttons"> | |
| <button id="btn-tab-loader" class="btn tab-btn" onclick="showTab('loader')">Loader</button> | |
| <button id="btn-tab-editor" class="btn tab-btn" disabled="disabled" onclick="showTab('editor')">Editor</button> | |
| <button id="btn-close-editor" class="btn close-btn" disabled="disabled" onclick="closeEditor()">×</button> | |
| <button id="btn-tab-game" class="btn tab-btn" disabled="disabled" onclick="showTab('game')">Game</button> | |
| <button id="btn-close-game" class="btn close-btn" disabled="disabled" onclick="closeGame()">×</button> | |
| </div> | |
| <div id="tabs"> | |
| <div id="tab-loader"> | |
| <div style="color: #e0e0e0;" id="persistence"> | |
| <br /> | |
| <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" /> | |
| <br /> | |
| 3.4.4.rc.custom_build | |
| <br /> | |
| <a href="releases/">Need an old version?</a> | |
| <br /> | |
| <br /> | |
| <br /> | |
| <label for="videoMode" style="margin-right: 1rem">Video driver:</label> | |
| <select id="videoMode"> | |
| <option value="" selected="selected">Auto</option> | |
| <option value="GLES2">WebGL</option> | |
| <option value="GLES3">WebGL 2</option> | |
| </select> | |
| <br /> | |
| <br /> | |
| <label for="zip-file" style="margin-right: 1rem">Preload project ZIP:</label> <input id="zip-file" type="file" name="files" style="margin-bottom: 1rem"/> | |
| <br /> | |
| <a href="demo.zip">(Try this for example)</a> | |
| <br /> | |
| <br /> | |
| <button id="startButton" class="btn" style="margin-bottom: 4rem; font-weight: 700">Start Godot editor</button> | |
| <br /> | |
| <button class="btn" onclick="clearPersistence()" style="margin-bottom: 1.5rem">Clear persistent data</button> | |
| <br /> | |
| <a href="https://docs.godotengine.org/en/3.4/tutorials/editor/using_the_web_editor.html">Web editor documentation</a> | |
| </div> | |
| </div> | |
| <div id="tab-editor" style="display: none;"> | |
| <canvas id="editor-canvas" tabindex="1"> | |
| HTML5 canvas appears to be unsupported in the current browser.<br /> | |
| Please try updating or use a different browser. | |
| </canvas> | |
| </div> | |
| <div id="tab-game" style="display: none;"> | |
| <canvas id="game-canvas" tabindex="2"> | |
| HTML5 canvas appears to be unsupported in the current browser.<br /> | |
| Please try updating or use a different browser. | |
| </canvas> | |
| </div> | |
| <div id="tab-status" style="display: none;"> | |
| <div id="status-progress" style="display: none;" oncontextmenu="event.preventDefault();"><div id="status-progress-inner"></div></div> | |
| <div id="status-indeterminate" style="display: none;" oncontextmenu="event.preventDefault();"> | |
| <div></div> | |
| <div></div> | |
| <div></div> | |
| <div></div> | |
| <div></div> | |
| <div></div> | |
| <div></div> | |
| <div></div> | |
| </div> | |
| <div id="status-notice" class="godot" style="display: none;"></div> | |
| </div> | |
| </div> | |
| <script> | |
| window.addEventListener("load", () => { | |
| if ("serviceWorker" in navigator) { | |
| navigator.serviceWorker.register("service.worker.js"); | |
| } | |
| if (localStorage.getItem("welcomeModalDismissed") !== 'true') { | |
| document.getElementById("welcome-modal").style.display = "block"; | |
| document.getElementById("welcome-modal-dismiss").focus(); | |
| } | |
| }); | |
| function closeWelcomeModal(dontShowAgain) { | |
| document.getElementById("welcome-modal").style.display = "none"; | |
| if (dontShowAgain) { | |
| localStorage.setItem("welcomeModalDismissed", 'true'); | |
| } | |
| } | |
| </script> | |
| <script src="godot.tools.js"></script> | |
| <script>//<![CDATA[ | |
| var editor = null; | |
| var game = null; | |
| var setStatusMode; | |
| var setStatusNotice; | |
| var video_driver = ""; | |
| function clearPersistence() { | |
| function deleteDB(path) { | |
| return new Promise(function(resolve, reject) { | |
| var req = indexedDB.deleteDatabase(path); | |
| req.onsuccess = function() { | |
| resolve(); | |
| }; | |
| req.onerror = function(err) { | |
| reject(err); | |
| }; | |
| req.onblocked = function(err) { | |
| reject(err); | |
| } | |
| }); | |
| } | |
| 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!")) { | |
| return; | |
| } | |
| Promise.all([ | |
| deleteDB("/home/web_user"), | |
| ]).then(function(results) { | |
| alert("Done."); | |
| }).catch(function (err) { | |
| alert("Error deleting local files. Please retry after reloading the page."); | |
| }); | |
| } | |
| function selectVideoMode() { | |
| var select = document.getElementById('videoMode'); | |
| video_driver = select.selectedOptions[0].value; | |
| } | |
| var tabs = [ | |
| document.getElementById('tab-loader'), | |
| document.getElementById('tab-editor'), | |
| document.getElementById('tab-game') | |
| ] | |
| function showTab(name) { | |
| tabs.forEach(function (elem) { | |
| if (elem.id == 'tab-' + name) { | |
| elem.style.display = 'block'; | |
| if (name == 'editor' || name == 'game') { | |
| const canvas = document.getElementById(name + '-canvas'); | |
| canvas.focus(); | |
| } | |
| } else { | |
| elem.style.display = 'none'; | |
| } | |
| }); | |
| } | |
| function setButtonEnabled(id, enabled) { | |
| if (enabled) { | |
| document.getElementById(id).disabled = ""; | |
| } else { | |
| document.getElementById(id).disabled = "disabled"; | |
| } | |
| } | |
| function setLoaderEnabled(enabled) { | |
| setButtonEnabled('btn-tab-loader', enabled); | |
| setButtonEnabled('btn-tab-editor', !enabled); | |
| setButtonEnabled('btn-close-editor', !enabled); | |
| } | |
| function setGameTabEnabled(enabled) { | |
| setButtonEnabled('btn-tab-game', enabled); | |
| setButtonEnabled('btn-close-game', enabled); | |
| } | |
| function closeGame() { | |
| if (game) { | |
| game.requestQuit(); | |
| } | |
| } | |
| function closeEditor() { | |
| closeGame(); | |
| if (editor) { | |
| editor.requestQuit(); | |
| } | |
| } | |
| function startEditor(zip) { | |
| const INDETERMINATE_STATUS_STEP_MS = 100; | |
| const persistentPaths = ['/home/web_user']; | |
| var editorCanvas = document.getElementById('editor-canvas'); | |
| var gameCanvas = document.getElementById('game-canvas'); | |
| var statusProgress = document.getElementById('status-progress'); | |
| var statusProgressInner = document.getElementById('status-progress-inner'); | |
| var statusIndeterminate = document.getElementById('status-indeterminate'); | |
| var statusNotice = document.getElementById('status-notice'); | |
| var headerDiv = document.getElementById('tabs-buttons'); | |
| var initializing = true; | |
| var statusMode = 'hidden'; | |
| showTab('status'); | |
| var animationCallbacks = []; | |
| function animate(time) { | |
| animationCallbacks.forEach(callback => callback(time)); | |
| requestAnimationFrame(animate); | |
| } | |
| requestAnimationFrame(animate); | |
| var lastScale = 0; | |
| var lastWidth = 0; | |
| var lastHeight = 0; | |
| function adjustCanvasDimensions() { | |
| var scale = window.devicePixelRatio || 1; | |
| var headerHeight = headerDiv.offsetHeight + 1; | |
| var width = window.innerWidth; | |
| var height = window.innerHeight - headerHeight; | |
| if (lastScale !== scale || lastWidth !== width || lastHeight !== height) { | |
| editorCanvas.width = width * scale; | |
| editorCanvas.height = height * scale; | |
| editorCanvas.style.width = width + "px"; | |
| editorCanvas.style.height = height + "px"; | |
| lastScale = scale; | |
| lastWidth = width; | |
| lastHeight = height; | |
| } | |
| } | |
| animationCallbacks.push(adjustCanvasDimensions); | |
| adjustCanvasDimensions(); | |
| function replaceCanvas(from) { | |
| const out = document.createElement("canvas"); | |
| out.id = from.id; | |
| out.tabIndex = from.tabIndex; | |
| from.parentNode.replaceChild(out, from); | |
| lastScale = 0; | |
| return out; | |
| } | |
| setStatusMode = function setStatusMode(mode) { | |
| if (statusMode === mode || !initializing) | |
| return; | |
| [statusProgress, statusIndeterminate, statusNotice].forEach(elem => { | |
| elem.style.display = 'none'; | |
| }); | |
| animationCallbacks = animationCallbacks.filter(function(value) { | |
| return (value != animateStatusIndeterminate); | |
| }); | |
| switch (mode) { | |
| case 'progress': | |
| statusProgress.style.display = 'block'; | |
| break; | |
| case 'indeterminate': | |
| statusIndeterminate.style.display = 'block'; | |
| animationCallbacks.push(animateStatusIndeterminate); | |
| break; | |
| case 'notice': | |
| statusNotice.style.display = 'block'; | |
| break; | |
| case 'hidden': | |
| break; | |
| default: | |
| throw new Error('Invalid status mode'); | |
| } | |
| statusMode = mode; | |
| }; | |
| function animateStatusIndeterminate(ms) { | |
| var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8); | |
| if (statusIndeterminate.children[i].style.borderTopColor == '') { | |
| Array.prototype.slice.call(statusIndeterminate.children).forEach(child => { | |
| child.style.borderTopColor = ''; | |
| }); | |
| statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf'; | |
| } | |
| } | |
| setStatusNotice = function setStatusNotice(text) { | |
| while (statusNotice.lastChild) { | |
| statusNotice.removeChild(statusNotice.lastChild); | |
| } | |
| var lines = text.split('\n'); | |
| lines.forEach((line) => { | |
| statusNotice.appendChild(document.createTextNode(line)); | |
| statusNotice.appendChild(document.createElement('br')); | |
| }); | |
| }; | |
| const gameConfig = { | |
| 'persistentPaths': persistentPaths, | |
| 'unloadAfterInit': false, | |
| 'canvas': gameCanvas, | |
| 'canvasResizePolicy': 1, | |
| 'onExit': function () { | |
| gameCanvas = replaceCanvas(gameCanvas); | |
| setGameTabEnabled(false); | |
| showTab('editor'); | |
| game = null; | |
| }, | |
| }; | |
| var OnEditorExit = function () { | |
| showTab('loader'); | |
| setLoaderEnabled(true); | |
| }; | |
| function Execute(args) { | |
| const is_editor = args.filter(function(v) { return v == '--editor' || v == '-e' }).length != 0; | |
| const is_project_manager = args.filter(function(v) { return v == '--project-manager' }).length != 0; | |
| const is_game = !is_editor && !is_project_manager; | |
| if (video_driver) { | |
| args.push('--video-driver', video_driver); | |
| } | |
| if (is_game) { | |
| if (game) { | |
| console.error("A game is already running. Close it first"); | |
| return; | |
| } | |
| setGameTabEnabled(true); | |
| game = new Engine(gameConfig); | |
| showTab('game'); | |
| game.init().then(function() { | |
| requestAnimationFrame(function() { | |
| game.start({'args': args, 'canvas': gameCanvas}).then(function() { | |
| gameCanvas.focus(); | |
| }); | |
| }); | |
| }); | |
| } else { // New editor instances will be run in the same canvas. We want to wait for it to exit. | |
| OnEditorExit = function(code) { | |
| setLoaderEnabled(true); | |
| setTimeout(function() { | |
| editor.init().then(function() { | |
| setLoaderEnabled(false); | |
| OnEditorExit = function() { | |
| showTab('loader'); | |
| setLoaderEnabled(true); | |
| }; | |
| editor.start({'args': args, 'persistentDrops': is_project_manager, 'canvas': editorCanvas}); | |
| }); | |
| }, 0); | |
| OnEditorExit = null; | |
| }; | |
| } | |
| } | |
| const editorConfig = { | |
| 'unloadAfterInit': false, | |
| 'onProgress': function progressFunction (current, total) { | |
| if (total > 0) { | |
| statusProgressInner.style.width = current/total * 100 + '%'; | |
| setStatusMode('progress'); | |
| if (current === total) { | |
| // wait for progress bar animation | |
| setTimeout(() => { | |
| setStatusMode('indeterminate'); | |
| }, 100); | |
| } | |
| } else { | |
| setStatusMode('indeterminate'); | |
| } | |
| }, | |
| 'canvas': editorCanvas, | |
| 'canvasResizePolicy': 0, | |
| 'onExit': function() { | |
| editorCanvas = replaceCanvas(editorCanvas); | |
| if (OnEditorExit) { | |
| OnEditorExit(); | |
| } | |
| }, | |
| 'onExecute': Execute, | |
| 'persistentPaths': persistentPaths, | |
| }; | |
| editor = new Engine(editorConfig); | |
| function displayFailureNotice(err) { | |
| var msg = err.message || err; | |
| console.error(msg); | |
| setStatusNotice(msg); | |
| setStatusMode('notice'); | |
| initializing = false; | |
| }; | |
| if (!Engine.isWebGLAvailable()) { | |
| displayFailureNotice('WebGL not available'); | |
| } else { | |
| setStatusMode('indeterminate'); | |
| editor.init('godot.tools').then(function() { | |
| if (zip) { | |
| editor.copyToFS("/tmp/preload.zip", zip); | |
| } | |
| try { | |
| // Avoid user creating project in the persistent root folder. | |
| editor.copyToFS("/home/web_user/keep", new Uint8Array()); | |
| } catch(e) { | |
| // File exists | |
| } | |
| selectVideoMode(); | |
| showTab('editor'); | |
| setLoaderEnabled(false); | |
| const args = ['--project-manager']; | |
| if (video_driver) { | |
| args.push('--video-driver', video_driver); | |
| } | |
| editor.start({'args': args, 'persistentDrops': true}).then(function() { | |
| setStatusMode('hidden'); | |
| initializing = false; | |
| }); | |
| }).catch(displayFailureNotice); | |
| } | |
| }; | |
| document.getElementById("startButton").onclick = function() { | |
| preloadZip(document.getElementById('zip-file')).then(function(zip) { | |
| startEditor(zip); | |
| }); | |
| } | |
| function preloadZip(target) { | |
| return new Promise(function(resolve, reject) { | |
| if (target.files.length > 0) { | |
| target.files[0].arrayBuffer().then(function(data) { | |
| resolve(data); | |
| }); | |
| } else { | |
| resolve(); | |
| } | |
| }); | |
| } | |
| //]]></script> | |
| </body> | |
| </html> | |