Learn JavaScript in Construct, part 11: Construct APIs

14

Index

Features on these Courses

Attached Files

The following files have been attached to this tutorial:

.c3p

move-sprite.c3p

Download now 62.55 KB
.c3p

using-script-file.c3p

Download now 62.64 KB
.c3p

keyboard-controls-template.c3p

Download now 62.62 KB
.c3p

modify-sprites-template.c3p

Download now 62.92 KB

Stats

9,283 visits, 22,918 views

Tools

Translations

This tutorial hasn't been translated.

License

This tutorial is licensed under CC BY-NC 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.

Published on 3 Dec, 2021. Last updated 16 Dec, 2021

Keyboard controls

Let's implement a simple four-direction movement using the arrow keys with JavaScript code. Download the project below and open it in Construct. This time it provides just a sprite instance (with no button), the Keyboard object has been added to the project, and main.js has enough code to call OnBeforeProjectStart.

.C3P

keyboard-controls-template.c3p

Download now 62.62 KB

Implementing keyboard controls means checking whether each of the four arrow keys is down, and moving the sprite in a direction accordingly. This has to be done repeatedly every frame to keep the movement going. In Construct event sheets, actions can be run every frame - also known as every tick, like the ticking of a clock - with the Every tick system condition. In JavaScript code, this is instead handled with the "tick" runtime event. It can be handled like this (with the full code for main.js):

runOnStartup(runtime =>
{
	runtime.addEventListener("beforeprojectstart", () => OnBeforeProjectStart(runtime));
});

function OnBeforeProjectStart(runtime)
{
	runtime.addEventListener("tick", () => OnTick(runtime));
}

function OnTick(runtime)
{
	// this function is now called every tick
}

Notice how we're using the same pattern as before to handle another kind of event - this time so the "tick" event calls OnTick with runtime passed as a parameter again.

Now any code we add in OnTick will run every tick. We can add some code here to check which arrow keys are down, and move the sprite accordingly.

Handling keyboard input

Notice the Keyboard object has been added to the project. This is necessary to receive keyboard input.

The Keyboard object's properties and methods can be accessed with runtime.objects.Keyboard, but since keyboard input is commonly used, runtime.keyboard is also provided as a shortcut.

The Keyboard object is global and has no instances. This means its properties and methods are accessed directly on runtime.keyboard. There is no need to call getFirstInstance().

The Keyboard script interface provides a method isKeyDown() which we can use to detect if a key is currently pressed at the time of the call. The key is specified with a string. A full list can be found on this MDN page, but the keys we need are the arrow keys, represented by the strings "ArrowLeft", "ArrowRight", "ArrowUp" and "ArrowDown".

So calling runtime.keyboard.isKeyDown("ArrowLeft") will return a boolean indicating if the left arrow key is currently held down. If that's the case, then we can move the sprite to the left.

Add the following code to the OnTick function.

function OnTick(runtime)
{
	let inst = runtime.objects.Sprite.getFirstInstance();
	
	if (runtime.keyboard.isKeyDown("ArrowLeft"))
		inst.x -= 10;
	
	if (runtime.keyboard.isKeyDown("ArrowRight"))
		inst.x += 10;
	
	if (runtime.keyboard.isKeyDown("ArrowUp"))
		inst.y -= 10;
	
	if (runtime.keyboard.isKeyDown("ArrowDown"))
		inst.y += 10;
}

Notice how there are four 'if' statements that check if each of the four arrow keys is down. If a key is down, the Sprite instance's position is moved in that direction. Preview the project and try pressing the arrow keys. The sprite will move around!

Framerate independence

One problem with moving objects by a fixed number of pixels every tick is that the speed depends on the framerate. For example on a system with a 120Hz display the sprite will move twice as fast as on a system with a 60Hz display, because ticks happen twice as fast on the 120Hz display. This problem is explained in more detail in the tutorial Delta-time and framerate independence.

The solution is to instead move the position by delta-time (the time this frame takes in seconds) multiplied by the speed in pixels per second. In JavaScript runtime.dt returns the current delta-time, which is the same as the dt expression in Construct's event sheets. Try the following code for OnTick.

function OnTick(runtime)
{
	let inst = runtime.objects.Sprite.getFirstInstance();
	
	if (runtime.keyboard.isKeyDown("ArrowLeft"))
		inst.x -= 400 * runtime.dt;
	
	if (runtime.keyboard.isKeyDown("ArrowRight"))
		inst.x += 400 * runtime.dt;
	
	if (runtime.keyboard.isKeyDown("ArrowUp"))
		inst.y -= 400 * runtime.dt;
	
	if (runtime.keyboard.isKeyDown("ArrowDown"))
		inst.y += 400 * runtime.dt;
}

Now the sprite moves at a regular speed of 400 pixels per second regardless of the framerate, because it is moved by a distance of 400 * runtime.dt.

As the previously linked tutorial notes, Construct's built-in behaviors like 8 Direction handle delta-time for you automatically. However when making movements using JavaScript code - or event blocks - you'll need to factor in delta-time to ensure the movement works the same across different devices. Using delta-time also enables useful time-based effects like pausing or slow-motion, as these work by modifying the value of dt.

You can also take a look at the Simple keyboard movement example that comes with Construct, which also handles changing the sprite angle.

  • 3 Comments

  • Order by
Want to leave a comment? Login or Register an account!
  • How can I pick an instance by an instance variable?

    • Just in case this is still relevant (for anyone with the same question): check out the fairly new example titled "Generator Function" for one way; but using generator functions is not required, you can just do that too:

      for (const inst of runtime.objects.SomeObjectType.instances())

      {

      if (inst.instVars.SomeInstanceVariable === 42) {...}

      }

  • does not help