Serving HTML5 build as part of a Vue3/Nuxt3 webapp

I’d like to serve my Defold game as part of a Nuxt3 application without using iframes. I am planning on using JsToDef to interact with the engine from the javascript component.

Is there any guide or examples of how to serve Defold’s wasm files in Vue? I was previously able to do this with Unity following this snippet and I’m wondering if something similar to createUnityInstance() exists for Defold.

In other words, how would I load Defold’s HTML5 output without using the default index page?

If you take a look at the generated index.html you see that it uses dmloader.js to setup and load the engine. You should be able to do the same.

1 Like

I struggled with this for awhile but the way dmloader.js parses the archive contents folder does not appear to work outside of its index.html page.

Do Defold devs typically use iframes to source their games in another webpage?

What is it that you struggle with specifically? And what could be done in the dmloader to help with your usecase specifically?

I think this is what happens on itch.io, poki.com and many of the other popular websites where you can play HTML5 games (made in Defold or other engines).

I figured out that the dmloader script can find its dependencies if you dump the contents of the Defold build folder into “/public”.

I didn’t manage to get a screenshot of the error message beforehand, but I understand that loadArchiveDescription was unable to parse the subdirectories in order to load the archive files. The json “description” was coming out as null. Unsure if this is specific to Nuxt3 and Vite/Nitro or dmloader.js.

Either way I will include a basic snippet for future reference:

<template>
	<div id="app-container" class="canvas-app-container">
		<div id="canvas-container" class="canvas-app-canvas-container">
			<canvas id="canvas" class="canvas-app-canvas" tabindex="1" width="1280" height="800"></canvas>
		</div>
		<div class="buttons-background">
			<div class="button" onclick="Module.toggleFullscreen();">Fullscreen</div>
		</div>
	</div>
</template>

<script setup="setup" lang="ts">

onMounted(() => {
    const script = document.createElement("script");
    script.onload = () => {
        var extra_params = {
		archive_location_filter: function( path ) {
			return ("archive" + path + "");
		},
		engine_arguments: [],
		custom_heap_size: 268435456,
		full_screen_container: "#canvas-container",
		disable_context_menu: true
	}

	Module['INITIAL_MEMORY'] = extra_params.custom_heap_size;

	Module['onRuntimeInitialized'] = function() {
		Module.runApp("canvas", extra_params);
	};

	Module["locateFile"] = function(path, scriptDirectory)
	{
		// dmengine*.wasm is hardcoded in the built JS loader for WASM,
		// we need to replace it here with the correct project name.
		if (path == "dmengine.wasm" || path == "dmengine_release.wasm" || path == "dmengine_headless.wasm") {
			path = "HashSlashFrontend.wasm";
		}
		return scriptDirectory + path;
	};

	var is_iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
	var buttonHeight = 0;
	var prevInnerWidth = -1;
	var prevInnerHeight = -1;
	buttonHeight = 42;
	buttonHeight = 42;
	function resize_game_canvas() {
		// Hack for iOS when exit from Fullscreen mode
		if (is_iOS) {
			window.scrollTo(0, 0);
		}

		var app_container = document.getElementById('app-container');
		var game_canvas = document.getElementById('canvas');
		var innerWidth = window.innerWidth;
		var innerHeight = window.innerHeight - buttonHeight;
		if (prevInnerWidth == innerWidth && prevInnerHeight == innerHeight)
		{
			return;
		}
		prevInnerWidth = innerWidth;
		prevInnerHeight = innerHeight;
		var width = 1280;
		var height = 800;
		var targetRatio = width / height;
		var actualRatio = innerWidth / innerHeight;
	
		//Downscale fit
		if (innerWidth < width || innerHeight < height) {
			if (actualRatio > targetRatio) {
				width = innerHeight * targetRatio;
				height = innerHeight;
				app_container.style.marginLeft = ((innerWidth - width) / 2) + "px";
				app_container.style.marginTop = "0px";
			}
			else {
				width = innerWidth;
				height = innerWidth / targetRatio;
				app_container.style.marginLeft = "0px";
				app_container.style.marginTop = ((innerHeight - height) / 2) + "px";
			}
		}
		else {
			app_container.style.marginLeft = ((innerWidth - width) / 2) + "px";
			app_container.style.marginTop = ((innerHeight - height) / 2) + "px";
		}
	
	
	
	
		app_container.style.width = width + "px";
		app_container.style.height = height + buttonHeight + "px";
		game_canvas.width = width;
		game_canvas.height = height;
	}
	resize_game_canvas();
	window.addEventListener('resize', resize_game_canvas, false);
	window.addEventListener('orientationchange', resize_game_canvas, false);
	window.addEventListener('focus', resize_game_canvas, false);
        EngineLoader.stream_wasm = "false" === "true";
        EngineLoader.load("canvas", "HashSlashFrontend");
    };
    script.src = "/dmloader.js";
    document.head.appendChild(script);
})
  
</script>

Now the next step is getting JsToDef to pass messages to the running game.

Hmm, ok, i would have thought that the locateFile function could be modified to find files of they were put in a subfolder.