Module 7: Player movement and mouse input
Objective: explore objects and conditional statements in JS.
- Adding your first object
- Adding mouse input
- Adding a move function to the box
- Improving the box movement with easing and conditionals
- For next time
Adding your first object
Welcome back! When we last looked at our budding block dodger game, we had an animation of a shape moving on the screen. Humble beginnings! Before moving forward by adding more features to turn the animation into a legitimate game, it's time to do some organization that will help keep the code simple in the long haul. Programming involves a lot of this; it's called refactoring, or cleaning up existing code before moving forward.
To do this, we'll use an object. As I hinted at in previous sessions, objects are collections of properties and actions that are grouped together to represent something in a program. In terms of a game, the box we've rendered on screen represents a player, which has an x and y location, a size, a color, and the ability to move.
In JS, objects can be created in a number of ways. For this game, we'll use a pattern that looks a bit like
the update
function you wrote last session, but with parameters and
saved in a variable; we'll discuss other patterns as they arise:
// Represents a box, defined by an x and y
// position, size, and color
let Box = function (x, y, size, color) {
this.x = x;
this.y = y;
this.size = size;
this.color = color;
};
And poof--we have a function that can make boxes, and we've saved it in a variable called Box
(note
the capital B, which is standard style to designate a constructor function in JS).
It works by taking four parameters, x
,
y
, size
, and color
and assigns
them to variables that begin with this.
. this.
refers to
a variable of a particular object once it's been created. Forgetting to write this.
has to be one of the most common mistakes in JS. Let's see how this works by
integrating the constructor function into the main script and making a new box using the new
keyword. Once that's done, we can now refer to the player
object's
x
and y
properties. Lastly, while I'm at it, I'll position the player at the bottom of the screen using
the canvas
object's height
and width
properties (but feel free to play with the numbers):
See the Pen block dodger (part 3) by ggorlen (@ggorlen) on CodePen.
Don't worry if this isn't all clear yet--we'll be making many more objects, so the purpose of going through all this trouble to get essentially the same result will be clear as our program grows.
Adding mouse input
Last session, we added movement to the box, updating its x and y position on every frame until it left the screen.
However, we can't consider this box truly a representation of the person playing the game until we offer the user the ability to control it. The two common inputs are keyboard and mouse; touch is another input that JS supports for compatibility with mobile devices (we'll save mobile development for another course).
For this game, let's try mouse input--it's a little bit simpler to implement than keyboard, but not by much.
To add user interaction in JS, we attach an event listener to an HTML element or to the entire webpage. The event listener function has two parameters: a type of event to listen for and a function to execute when the specified event occurs. Here's what our listener will look like:
// A variable to keep track of the x coordinate of the mouse
let mouseX = 0;
// Listen for mouse movements
document.addEventListener("mousemove", function (e) {
mouseX = e.pageX - canvas.getBoundingClientRect().left;
});
Personally, the first time I saw this, it just messed my head up. Sending a function into a function? That's insane. However, try thinking of it like this and it should be easier to follow:
(Talking to the addEventListener()
function) "Hey
addEventListener
, please wait for the mousemove
event,
then whenever that happens, execute this other function I'm giving you."
This event listener uses a little bit of positioning math to establish the mouse's actual location
unadultered by the flexbox centering. A more comprehensive example of listeners (with keyboard!)
is available here. But don't sweat the details here; this
is an advanced concept which we'll revisit this later on! The important part is that whenever the mouse
moves, the mouseX
variable will be updated to contain the mouse's current
X position along the horizontal axis. Try adding console.log(mouseX);
just after the mouseX =
line and watch the value change as you move your
mouse around!
Adding a move function to the box
Now that we have a variable representing the mouse position, let's add a function to the Box class that will enable it to follow the mouse:
// Updates the position of the box
Box.prototype.move = function (x) {
// Move to the parameter x position
this.x = x;
};
Here's some new syntax: Box.prototype.move
, which attaches a new function
called move
to the Box object prototype. In other words, this function
definition will belong to all Box objects. I can go to any box and call box.move()
and the code in the function will be executed. As we'll see in the next couple of classes,
it's very handy to write one function that will be shared among dozens of objects.
We also need to call this function for it to do anything. Let's wonder out loud: how often
do we want to move the player box? If the answer is "every frame," then we call
player.move()
inside the update()
function, which is responsible for updating the game state and redrawing stuff. We'll
send in the mouseX
variable, which contains the mouse's x position,
as a parameter. Let's put it all together and see what we've got:
See the Pen block dodger (part 4) by ggorlen (@ggorlen) on CodePen.
There are a couple glaring issues here, to my mind. Let's fix them in the next section.
Improving the box movement with easing and conditionals
The first issue is that the box follows the mouse, but the movement is unsatisfying from a gameplay standpoint. Let's simulate realistic movement with a technique called easing, which involves a little basic arithmetic:
// Updates the position of the box
Box.prototype.move = function (x) {
// Ease towards the mouse position
this.x += (x - this.x) * 0.003;
};
The idea is to move the box towards the mouse by a tiny amount proportional to the distance
between the two instead of just plopping it right on top of the mouse. As always, try tweaking the easing amount of
0.03
.
Another problem is that the box goes right off the edge of the screen, which leads us to an important programming concept: conditionals. Conditionals determine which parts of a program are actually executed while a program runs. The programmer decides what conditions need to be present and directs the program's flow accordingly, depending on whether these conditions evaluate to true or false. In our game, we want to check every frame whether any part of the box is off screen, and if so, execute some code to put it back on screen.
// Updates the position of the box
Box.prototype.move = function (x) {
// Ease towards the mouse position
this.x += (x - this.x) * 0.05;
// Make sure the box can't go off the screen
if (this.x + this.size > canvas.width) {
this.x = canvas.width - this.size;
}
else if (this.x < 0) {
this.x = 0;
}
};
There's a little math going on here, so feel free to play around with the logic and numbers and then restore the code when you've convinced yourself of how it works.
Let's put everything from this module together:
See the Pen block dodger (part 5) by ggorlen (@ggorlen) on CodePen.
For next time
Well, that was a heap of information to take in! Again, don't worry if it doesn't stick on first contact; this stuff is dense! Read through a few times and play with the concepts you're learning as much as possible. The nice thing is that these basic patterns and concepts are so fundamental, you'll encounter them in nearly every program you'll write, so they're well worth the energy investment, I promise. Stick with it!
Next module, the game gets interesting--we'll add a ton of enemies!