When you create a game, you may find yourself needing to accept keyboard input from the user. I’m not talking about using the keyboard to control your player, but instead to accept text from the keyboard to be used as a username or for the in-game chat.
If you’ve been keeping up with the Phaser content releasing on the blog, you might remember the tutorial titled, Creating a Multiplayer Drawing Game with Phaser and MongoDB. This tutorial demonstrated taking user input to be used as the game information, but it did so with a bit of HTML hacking.
There are better ways!
In this tutorial we’re going to see how to take user keyboard input directly within a Phaser 3.x game using embedded HTML and JavaScript, rather than HTML layered on top of the game.
To get an idea of what we want to accomplish, take a look at the following animated image:
In the above example we have a single game scene. This scene has rendered text as well as an input field directly in the game canvas. When you add input to the text field, the rendered text updates when you press the return key.
Like I mentioned, we could absolute position an HTML text input on our canvas, but then we lose out on some integration functionality as well as the automatic scaling that Phaser adds to the embedded HTML.
To get us started, we’re going to create a new Phaser 3.x project using some boilerplate code. The goal here is to not over-complicate the focus of the example, being the text input.
On your computer, create a new directory and within that directory create an index.html file with the following markup:
<!DOCTYPE html>
<html>
<head>
<script src="//cdn.jsdelivr.net/npm/phaser@3.24.1/dist/phaser.min.js"></script>
</head>
<body>
<div id="game"></div>
<script>
const phaserConfig = {
type: Phaser.AUTO,
parent: "game",
width: 1280,
height: 720,
scene: {
init: initScene,
preload: preloadScene,
create: createScene,
update: updateScene
}
};
const game = new Phaser.Game(phaserConfig);
function initScene() { }
function preloadScene() { }
function createScene() { }
function updateScene() { }
</script>
</body>
</html>
In the above markup, we’ve included the Phaser package in our project and configured a basic game with unused lifecycle events.
We know that the goal will be to eventually show the user provided text on the screen, so let’s get our text ready to go.
Within the createScene
function of our index.html file, add the following JavaScript code:
function createScene() {
this.message = this.add.text(640, 250, "Hello, --", {
color: "#FFFFFF",
fontSize: 60,
fontStyle: "bold"
}).setOrigin(0.5);
}
The above code will render some text on the screen. We’re storing a reference to the text object in the this.message
variable because we plan to change the text later.
The foundation of our project is ready, so now we can focus on the text input.
The thought process behind accepting text input from the user is that we want to create a standard HTML form and load it into our Phaser game as if it were just another game asset.
We’ll start by creating an HTML file with our form. Within the project directory, create a new form.html file with the following markup:
<!DOCTYPE html>
<html>
<head>
<style>
#input-form {
padding: 15px;
background-color: #931C22;
}
#input-form input {
padding: 10px;
font-size: 20px;
width: 400px;
}
</style>
</head>
<body>
<div id="input-form">
<input type="text" name="name" placeholder="Full Name" />
</div>
</body>
</html>
The above markup is a properly styled input element. If you take only one thing out of the above markup, take the <input>
tag with the name
attribute. The name
attribute is critical when it comes to what we do in our Phaser game.
We’ll continue, but this time from within the index.html file. Inside the index.html file, we need to modify the phaserConfig
so that we can work with DOM elements in our game.
const phaserConfig = {
type: Phaser.AUTO,
parent: "game",
width: 1280,
height: 720,
dom: {
createContainer: true
},
scene: {
init: initScene,
preload: preloadScene,
create: createScene,
update: updateScene
}
};
Take notice of the dom
property. Without this property, anything we attempt to do with the HTML file will not work.
Since the game is now properly configured, we can jump into the preloadScene
function:
function preloadScene() {
this.load.html("form", "form.html");
}
In the above code we are loading the form.html file and giving it a key so we can later reference it in our code. With the HTML file loaded, now we can make use of it in the createScene
function:
function createScene() {
this.nameInput = this.add.dom(640, 360).createFromCache("form");
this.message = this.add.text(640, 250, "Hello, --", {
color: "#FFFFFF",
fontSize: 60,
fontStyle: "bold"
}).setOrigin(0.5);
this.returnKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.ENTER);
this.returnKey.on("down", event => {
let name = this.nameInput.getChildByName("name");
if(name.value != "") {
this.message.setText("Hello, " + name.value);
name.value = "";
}
});
}
The above code has a bit more JavaScript than we previously saw. In addition to rendering our text, now we’re adding the DOM element to the canvas. Remember, in the preloadScene
function we were only loading the HTML. In the createScene
function we are adding it to the scene to be interacted with.
Being able to add text to it is great, but we need to know when the user wants to submit it. This is where the keyboard events come into play.
this.returnKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.ENTER);
The above line says that we are taking control of the return or enter key.
Since we have control of it, we can listen for down
events:
this.returnKey.on("down", event => {
let name = this.nameInput.getChildByName("name");
if(name.value != "") {
this.message.setText("Hello, " + name.value);
name.value = "";
}
});
When the key is pressed, we get the child element within the form.html file. Remember the name
attribute on the input element? In this example name
is not the attribute key, but the value that we set the attribute as. It is only coincidence that they share the same name.
Once we have the input field, we can check if it is empty. If it is not empty, we can change the rendered text and clear the input field.
You just saw how to allow text input in your Phaser 3.x game. This is useful if you need to take keyboard data beyond just knowing if a key was pressed. Maybe you’re creating an online game and you need to give your player a name, or maybe there is another reason. Whatever the reason, accepting user input is valuable.
Like I had previously mentioned, you can just create HTML and absolute position it on top of your game canvas rather than embedding it in the game. The problem with this approach is that you lose out on some scaling that Phaser does as well as the deep integration into the scene. It’s possible, but if I had to recommend something, I’d recommend the approach of embedding it into the game.