Module 7: Player movement and mouse input

Objective: explore objects and conditional statements in JS.

  1. Adding your first object
  2. Adding mouse input
  3. Adding a move function to the box
  4. Improving the box movement with easing and conditionals
  5. 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.

Refactoring meme

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."

functional programming meme

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!

Programming is hard meme