Runtime scripts

Plugin and behavior addons have separate scripts that run in the context of the runtime (the Construct game engine) rather than the editor.

Runtime documentation

For a reference on the C3 runtime, please refer to the section on the Runtime API reference.

C3 runtime API calls

In the runtime, much code is shared with the editor, such as the WebGL renderer class. However in the editor, API calls are behind a special SDK layer (usually within the SDK namespace), for security and compatibility reasons. On the other hand the runtime does not use a special SDK layer, for maximum performance and flexibility; these calls are usually within the C3 namespace. Be sure to use the right namespace depending on the context.

DOM calls in the C3 runtime

A major architectural feature of the runtime is the ability to host the runtime in a dedicated worker, off the main thread. In this mode it renders using OffscreenCanvas. With the modern web platform, many functions and classes are available in dedicated workers, and more are being added over time. Refer to this MDN guide on available APIs in workers.

Providing your addon's runtime calls only use APIs available in a worker, such as fetch() or IndexedDB, then it will not need any changes to support a worker. However if it does use APIs not normally available in a worker, then it will need some changes.

The principle behind making calls to the main thread in the C3 runtime is to split the runtime scripts in to two halves: the runtime side (that runs in the worker), and the DOM side (that runs on the main thread where the document is). The DOM side has full access to all browser APIs. The runtime side can issue DOM calls using a specially-designed messaging API built in to the runtime. Essentially instead of making a call, you simply post a message with parameters for the call to the script on the DOM side, where the API call is really made. The DOM side can then send a message back with a result, or send messages to the runtime on its own, such as in response to events. The messaging APIs make this relatively straightforward. However one consequence to note is that a synchronous API call will become asynchronous, since the process of messaging to or from a worker is asynchronous.

Once this approach is used, there is no need to change anything to support the normal (non-Worker) mode. In this case both scripts will run in the same context and the messaging API will simply forward messages within the same context too. Therefore this one approach covers both cases, and ensures code works identically regardless of whether the runtime is hosted in the main thread or a worker.

Using a DOM script

By default Construct 3 assumes no DOM scripts are used. If you want to use one, use the following call on IPluginInfo to enable one:

this._info.SetDOMSideScripts(["c3runtime/domSide.js"]);

Since an array of script paths is used, if you have a lot of DOM code, you can split it across different files. Don't forget to add these files to the file list in addon.json.

For documentation on the DOM messaging APIs, refer to DOMElementHandler (used in domSide.js), SDKDOMPluginBase (used in plugin.js), and SDKDOMInstanceBase (used in instance.js).

For an example demonstrating how to get started, see the domElementPlugin template in the C3 plugin SDK download. This demonstrates using the above APIs to create a simple <button> element in the DOM with a custom button text, and firing an On clicked trigger, with support for running in a Web Worker.

Supporting the debugger

Plugins and behaviors can display custom properties in the debugger by overriding the GetDebuggerProperties() method of the instance class. It should return an array of property sections of the form { title, properties }, where title is a string of the section title and properties is an array of property objects. Each property object is of the form { name, value } with an optional onedit callback. The name must be a string, and the value can be a string, number or boolean.

Editing properties

If an onedit callback is omitted, the debugger displays the property as read-only. If it is provided, the debugger allows the property to be edited. If it is changed, the callback is run with the new value as a parameter.

In many cases, editing a property does the equivalent of an action. To conveniently manage your code, you can implement actions as methods on your instance class, and call the same method from both the action and the debugger edit handler. These methods can then also be re-used for other interfaces, such as the scripting inferface, or an API for other addons to call.

Translation

By default, property section titles and property names are interpreted as language string keys. This allows them to be translated by looking them up in your addon's language file. Note property values do not have any special treatment. You can bypass the language file lookup by prefixing the string with a dollar character $, e.g. the property name "plugins.sprite.debugger.foo" will look up a string in the language file, but "$foo" will simply display the string foo.

The debugger runs in a separate context to the editor, and as such not all language strings are available. The language keys available in the debugger are:

  • The addon name
  • All property names
  • All combo property items
  • Everything under the "debugger" key

In general, if you need a language string for the debugger, simply place it under the "debugger" key, e.g. at "plugins.sprite.debugger.foo".

Sample code

The following code is used by the Sprite plugin to display its animation-related debugger properties. Notice how it uses language keys and calls actions to update properties.

GetDebuggerProperties()
{
	const Acts = C3.Plugins.Sprite.Acts;
	const prefix = "plugins.sprite.debugger.animation-properties";
	return [{
		title: prefix + ".title",
		properties: [
			{name: prefix + ".current-animation",	value: this._currentAnimation.GetName(),	onedit: v => this._SetAnimation(v)},
			{name: prefix + ".current-frame",		value: this._currentFrameIndex,				onedit: v => this._SetAnimationFrame(v)},
			{name: prefix + ".is-playing",			value: this._isAnimationPlaying,			onedit: v => this._SetAnimationPlaying(v)},
			{name: prefix + ".speed",				value: this._currentAnimationSpeed,			onedit: v => this._SetAnimationSpeed(v)},
			{name: prefix + ".repeats",				value: this._animationRepeats,				onedit: v => this._SetAnimationRepeats(v)}
		]
	}];
}
Addon SDK Manual 2021-06-16