Chapter 14 Project: The Purple Fruit Monster Game In this chapter we’ll make a two-dimensional jumping game. The player will use keyboard controls to make the Purple Fruit Monster jump and move to capture as much rolling fruit as possible, without touching the ground. It will end up looking something like this: This might seem like a simple game to write, but we’re going to use a lot of the skills and knowledge that we’ve been building up in the book. And to get the jumping and rolling and capturing, we’re going to introduce a whole new level of sophistication to our code.
This is going to be a fun one! Getting Started Start a new project in 3DE. Choose the 3D starter project (with Animation) template and name this project Purple Fruit Monster. Do not use the template with physics for this project—we’ll use that in later chapters. Let’s Make Physics! This game will need two JavaScript code collections and some settings to go along with them.
At the very top of the file, add two new <script> tags: <body></body> <script src="/three.js"></script> ① <script src="/physi.js"></script> ② <script src="/scoreboard.js"></script> ① We’re going to use code to simulate real-life motion like falling, rolling, and colliding. We use the Physijs (physics + JavaScript) code collection so we don’t have to write all the physics code ourselves. ② To keep score, we again use the scoreboard code collection. At the top of the code from the 3D starter project template, just below the <script> tag without an src attribute, make the changes noted below.
// Physics settings ① Physijs.worker = '/physijs_worker.js'; // The "scene" is where stuff in our game will happen: ③ var scene = new Physijs.setGravity(new THREE.Vector3( 0, -250, 0 )); var flat = {flatShading: true}; var light = new THREE.add(light); ① A setting that enables Physijs to decide when things bump into each other. ② “Worker” code that runs in the background, performing all of the physics calculations. ③ Instead of a THREEscene, we need to use a Physijsscene. ④ Even with physics, we won’t have gravity unless we add it to the scene.
In this case, we add gravity in the negative Y direction, which is down. Just one last bit of setup remains to get our scene to actively simulate physical activity. We’ll wait until after we add some objects to the scene before working on that. First, let’s convert from a 3D scene to a two-dimensional scene.
Vectors Are Direction and Magnitude We’re using THREE.Vector3 to set gravity. We’re going to use these a lot in this chapter. If you saw the first Despicable Me movie, then you already know what this is! The bad guy in that movie is Vector. He chose his super villain name because a vector is an arrow with direction and magnitude (Oh, yeah!).
That means a vector includes two pieces of information: the direction in which it points and how strongly it points in that direction. The vector that describes gravity in this game points in the negative Y direction (down). It has a high magnitude (250), which means that things will fall down fairly quickly. Let’s Make 2D The most important change to make for a 2D game is to use an orthographic camera.
Back in Chapter 9, What’s All That Other Code?, we talked about two uses for these cameras: long distance views and 2D games. We used an orthographic camera for the long distances of space in Chapter 13, Project: Phases of the Moon. Now we use one for a 2D game. Still working above the START CODING line, comment out (or delete) the code for the usual perspective camera.
Then add an OrthographicCamera as shown. » // var aspectRatio = window.innerHeight; » // var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000); » var w = window.innerWidth / 2; » var h = window.innerHeight / 2; » var camera = new THREE.add(camera); One other change that we’ll make is a blue sky. To change the color of the entire scene, set the “clear” color—the color that’s drawn when the scene is clear of anything else—to sky blue. var renderer = new THREE.domElement); With that, we’re ready to start coding our jumping game.
Outline the Game Let’s think about how we can organize our code. To have made it this far in the book, you’ve written a lot of code. At times, it must have gotten difficult to move through the code to see what you’ve done. You’re not the first programmer to run into this problem, and you won’t be the last.
Thankfully, you can learn from the mistakes of programmers before you. Keep Your Code Organized Programming is hard enough on its own. Don’t make it harder by writing messy code. Organizing code doesn’t matter too much with short programs.
But code grows as new stuff is added. Organized code— indented and with functions defined in the order that they are called—is code that can grow. One of the easiest ways to organize code is to treat it a little bit like writing. When you write an essay, it helps to start with an outline.
After you have the outline, you can fill in the details. When organizing code, it helps to write the outline first, then add the code below it. Since we’re programming, our outlines are also written in code. Type in the following, including the double slashes, below START CODING ON THE NEXT LINE.
//var ground = addGround(); //var avatar = addAvatar(); //var scoreboard = addScoreboard(); This outline doesn’t include everything in the game, but it’s a lot of it. The ground will be the playing area. The avatar is the player in the game. The scoreboard will keep score and display useful information.
The double slashes at the beginning of each of those lines introduce a JavaScript comment, which we first saw in Code Is for Computers and Humans, Comments Are Only for Humans. This means JavaScript will ignore those lines. This is a good thing since we haven’t defined those functions yet. Programmers call this “commenting out” code so it won’t run.
Programmers do this for many reasons. Here, we’re doing it to outline code without causing errors. We’ll define these functions in the same order as they are in the code outline. This makes it easier to find code.
By looking at the code outline, we know that the addGround function will be defined before the addAvatar function, which will be followed by addScoreboard(). The faster we can find code, the faster we can fix it or add things to it. When you write a lot of code, tricks like this can really help keep things straight. After we build each function, we’ll come back to this code outline to remove the double slashes before the function call—we’ll “uncomment” the calls when they’re ready.
Let’s get started writing the code that matches this outline. Adding Ground for the Game The first function call in our code outline is to the addGround function. Just below the code outline (after the commented-out //addScoreboard() line), define that function as follows: function addGround() { var shape = new THREE.BoxGeometry(2*w, h, 10); var cover = new THREE.MeshBasicMaterial({color: 'lawngreen'}); var ground = new Physijs.add(ground); return ground; } Our ground is a giant box. It is just like other boxes that we’ve built—with one twist.
Instead of a plain, old Mesh, we use a Physijs. Meshes from the Physijs code collection are just like regular meshes, except that they can also behave like real, physical objects—they fall down and bounce off of each other. When creating a Physijs mesh, we can pass a third argument in addition to the geometry and material. That third argument is the object’s mass, which lets us make things very heavy or very light.
In this case, we set the mass to a special number: 0. The 0 means that the shape never moves. If we didn’t set the ground’s mass to 0, the ground would fall down like anything else! Unlike regular meshes, the different shapes have different physical meshes. The list includes Physijs.SphereMesh, and for all other shapes, Physijs.
Once this function is defined, we uncomment the call to addGround() in our code outline. » var ground = addGround(); //var avatar = addAvatar(); //var scoreboard = addScoreboard(); If everything is working, we should see green ground with blue sky in the background as shown in the figure. Build a Simple Avatar In 3D programming, you can make simple graphics in two ways. We’ll use both in this game—one kind for the Purple Fruit Monster and the other kind for the fruit.
The simple graphic technique that we use for the Purple Fruit Monster is called a sprite. In the addAvatar() function, we create an invisible, physics-enabled box mesh, then we add the sprite to the box mesh. Add this function below the addGround() function. function addAvatar() { var shape = new THREE.CubeGeometry(100, 100, 1); var cover = new THREE.MeshBasicMaterial({visible: false}); var avatar = new Physijs.add(avatar); var image = new THREE.png"); var material = new THREE.SpriteMaterial({map: image}); var sprite = new THREE.setLinearFactor(new THREE.setAngularFactor(new THREE.Vector3(0, 0, 0)); return avatar; } Sprites are graphics that always face the camera, which is exactly what we want our 2D avatar to do in this game.
Sprites are super-efficient in graphics code. Any time we can use them, we make it much easier for the computer to do everything it needs to do to keep the game running smoothly. Sprites start as tiny 1 by 1 things in a scene. To see this sprite, we scale it by 100 in the X and Y directions—we stretch it in the left/right and up/down directions.
The box mesh at the beginning of addAvatar() is doing all the work of falling down, colliding with fruit, and colliding with the ground. We give it a small mass of 1 so it’ll be easy to push with the controls that we’ll add in a bit. It’s invisible because we set visible: false in its material, but it’s still there. We add the sprite to the box mesh so we know where the avatar is.
The last thing we do in addAvatar() is to set the angular and linear “factors.” These factors say how much an object can rotate or move in certain directions. By setting the angular factor to all 0s, we’re saying that our avatar cannot rotate in any direction. Even if it bounces off of spinning fruit, the avatar will always stay straight up and down. By setting the linear factor to two 1s and a 0, we’re saying that the avatar can move in the X and Y directions, but not the Z direction.
In other words, we’re telling our 3D code that even though we’re creating a three-dimensional shape, it will only move in two dimensions. Move back up to the code outline and uncomment the addAvatar call. var ground = addGround(); » var avatar = addAvatar(); //var scoreboard = addScoreboard(); With that, we have a Purple Fruit Monster avatar…that’s stuck in the ground. Resetting the Position We could have positioned the avatar in addAvatar(), but we just added it to the scene.
Instead, we’ll create a separate function to set the position. Why use a separate function? So we can re-use it! When we talked about functions in Chapter 5, Functions: Use and Use Again, we said that some functions tell part of a story. The functions in our code outline do that—they tell the story of setting up the game. Another kind of function is one that gets called over and over again.
Let’s create one of those functions that can start—or restart—the game by moving the avatar to its start position. Add the following after the addAvatar() function: function reset() { avatar.__dirtyPosition = true; avatar.setLinearVelocity(new THREE.Vector3(0, 250, 0)); } The name of the __dirtyPosition property is something of a programmer’s joke. We’re making a mess here, so we say that it’s “dirty.” What’s the mess?