This week I completed the Svelte course. Now I can start experimenting. I’d like to recode my blog and my template repository with SvelteKit. However, there are some problems related to the folders structure. For the moment I have not decided yet.

animation

Better to start with a more limited project. I wanted to understand how to make an html page communicate with a Construct 3 project inserted in the page itself. My idea is to explore the possibility of transparently integrating small games into a larger project using C3 for the dynamic part and Svelte for data management. To test my idea I chose a very simple game, Petits Chevaux:

animation

As seen in the GIF, the app consists of two different parts. The central part, the one with the concentric circles and the spinning balls, is created with C3. But the button on the right and the table on the left are in HTML. Data is passed to and from C3 via the Window.postMessage() API.

I start with the Construct 3 code. In the main.js file I insert the attachListeners function

import { attachListeners } from "./postMessage.js";

runOnStartup(async runtime => {
	globalThis.g_runtime  =  runtime;
	runtime.addEventListener("beforeprojectstart", () => attachListeners());
});

I use this function to create an interface to allow communication to and from the page hosting the game.

The postMessage.js file contains some functions. The main ones are 3:

  • attachListeners
  • getMessage
  • sendMessage

attachListeners adds a simple observer to the message event

function attachListeners() {
	if (globalThis.addEventListener) {
		globalThis.addEventListener("message", getMessage, false);
	} else {
		globalThis.attachEvent("onmessage", getMessage);
	}
}

When the page receives a message event it executes the getMessage function:

function getMessage(e) {
	if( !trustedOrigin.includes(e.origin)) {
		console.log("Error, wrong origin");
	} else {
		const message = e.data;
		const messageType = message.type;
		const messageContent = message.content;

		match(messageType)
      .on(t => t.toLowerCase() === "set", () => getMessageSet(messageContent))
		  .on(t => t.toLowerCase() === "status", () => getMessagePlay(messageContent))
      .otherwise(t => () => 0);
	}
}

The event contains a lot of information, the most useful in this case are event.origin and event.data.

event.origin allows us to control the page that originated the message. If it is a trusted page then we read the content of the message and decide what to do. The message is contained in event.data. To make it easier, I decided to use a standard for sent and received messages. They are all objects with type and content properties.

The third function is sendMessage:

function sendMessage(message) {
	const targetWindow = globalThis.parent;
	targetWindow.postMessage(message, "*");
}

Through this function we can send a message to another page, to another window or to an iFrame element. Since I will be using the game inside an iFrame I send the message to Window.parent. Window.parent is the page that contains the iFrame with the game code. However, it is advisable to use globalThis to ensure greater compatibility with the Construct 3 features.

Once the C3 code has been fixed, it’s time to move on to the page that will host the game. The code is basically the same with one small difference: sendMessage will send the message to an iFrame instead to the parent.

export interface Message {
    type: string;
    content: string;
}

export function sendMessage(iframe: HTMLIFrameElement, 
                            message: Message, 
                            targetOrigin: string = "*") {
    iframe.contentWindow.postMessage(message, targetOrigin);
}

Because I am using Svelte I have created a Construct.svelte component:

<script lang="ts">
    export let construct3: HTMLIFrameElement;
    const src = "./c3/game.html"
</script>

<iframe title="C3" bind:this="{construct3}" {src} scrolling="no" noresize="noresize" /> 

To allow the application to read the iFrame I have inserted a construct3 prop. In App.svelte I write:

<script lang="ts">
  import { onMount } from "svelte";
  import { attachListeners, sendMessage } from "./PostMessage/postMessage";

  import Construct from "./Construct/Construct.svelte";

  let construct3: HTMLIFrameElement;

  onMount(() => {
    attachListeners();
  });
</script>

<Construct bind:construct3 />

This way the construct3 variable is linked to the iFrame of the game. I can use it to send commands. I can, for example, make a new spin of the wheel by pressing a button using a code similar to this:

<script>
  function spin() {
    sendMessage(construct3, {
      type: "status",
      content: "SPIN"
    }, "*");
  }
</script>

<button  on:click={spin}>Spin</button>

To show the complete code I created a new repository (el3um4s/petits-chevaux) and a page on Itch.io.

The Construct 3 file code is also uploaded to the usual GitHub repository: