Up to this point in this series, you have learned how to manipulate different entities and use the keyboard to move them around. In this part, you will learn how to use the game loop in Crafty to continuously check for various events and animate different entities.
The game loop in Crafty is implemented in Crafty.timer.step, which uses global events to communicate with the rest of the engines. The loop is driven by requestAnimationFrame when available. Each loop consists of one or more calls to the EnterFrame
event and a single call to RenderScene
which results in the redrawing of each layer.
The final value of all the properties and variables is resolved before a scene is rendered. For example, if you move your player 5 px to the right ten times inside a single EnterFrame
event, it will be directly drawn 50 px to the right by skipping all the intermediate drawings.
EnterFrame and RenderScene
Everything in your game that needs to change over time is ultimately linked to the EnterFrame
event. You can use the .bind()
method to bind different entities to this event. Functions bound to this event are also passed an object with properties like dt
which determines the number of milliseconds that have passed since the last EnterFrame
event.
You can use the dt
property to provide a smooth gaming experience by determining how far the game state should advance.
The RenderScene
event is used to make sure that everything visible on the screen matches the current state of the game at the last EnterFrame
event. Normally, you will not need to bind this event yourself unless you decide to implement your own custom rendering layer.
Using Tween to Animate 2D Properties
You can use the Tween
component when you just want to animate the 2D properties of an entity over a specific period of time. You can animate the x
, y
, w
, h
, rotation
, and alpha
properties using this component. Let's animate the x value and height of the orange and black boxes that you've been creating in the last two tutorials.
Here is the code you need:
blackBox.tween({x: 500}, 3000); orangeBox.tween({x: 50, h: 100, rotation: 360}, 3000);
You've probably noticed that the orange box is not rotating around its center but its top left corner. You can change the rotation center by using the .origin()
method. It can accept two integer arguments, which determine the pixel offset of origin in the x and y axes.
It also accepts a string value as its argument. The string value can be a combination of center, top, bottom, middle, left, and right. For example, .origin("center")
will rotate the entity around its center, and .origin("bottom right")
will rotate the entity around the bottom right corner.
You can pause or resume all tweens associated with a given entity using the .pauseTweens()
and .resumeTweens()
methods. Similarly, you can also use .cancelTween()
to cancel a specific tween.
Understanding the Crafty Timer
The Crafty.timer
object handles all the game ticks in Crafty. You can use the .FPS()
method with this object to get the target frame rate. Keep in mind that it is not the actual frame rate.
You can also use the .simulateFrames(Number frames[, Number timestep])
method to advance the game state by a given number of frames. The timestep
is the duration to pass each frame. If it is not specified, a default value of 20ms is used.
Another useful method is .step()
, which will advance the game by performing a step. A single step can consist of one or more frames followed by a render. The number of frames will depend on the timer's steptype
. This method triggers a variety of events like EnterFrame
and ExitFrame
for each frame and PreRender
, RenderScene
, and PostRender
events for each render.
There are three different modes of steptype
: fixed
, variable
, and semifixed
. In fixed
mode, each frame in Crafty is sent the same value of dt
. However, this steptype
can trigger multiple frames before each render to achieve the target game speed.
You can also trigger just one frame before each render by using the variable
mode. In this case, the value of dt
is equal to the actual time elapsed since the last frame.
Finally, the semifixed
mode triggers multiple frames per render, and the time since last frame is equally divided among them.
Creating a Very Basic Game
If you have read all the tutorials in this series, you should have gained enough knowledge by now to create a very basic game. In this section, you will learn how to put everything you learned to use and create a game where the main player has to eat a piece of food.
The food will be a rotating red square. As soon as the food comes in contact with the player, it disappears from the old location and spawns in a new random location. The player can be moved using A, W, S, D, or the arrow keys.
One more thing that you need to take care of is the position of the player. It is supposed to stay with the bounds of the game stage.
Let's write the code for the food first:
var foodBox = Crafty.e("2D, Canvas, Color, Food") .attr({x: 150, y: 250, w: 15, h: 15}) .color("red") .origin("center") .bind("EnterFrame", function(eventData) { this.rotation += 4; });
By default, Crafty would have used the top left corner of the food entity to rotate it. Setting origin to center makes sure that the food entity rotates around its center.
var playerBox = Crafty.e("2D, Canvas, Color, Fourway, Collision") .attr({x: 50, y: 360, w: 50, h: 50}) .color("black") .fourway(200) .bind("EnterFrame", function(eventData) { if(this.x < 0) { this.x = 0; } if(this.y < 0) { this.y = 0; } if(this.x > (stageWidth - this.w)) { this.x = stageWidth - this.w; } if(this.y > (stageHeight - this.h)) { this.y = stageHeight - this.h; } });
The player entity checks the current location of the player in each frame and resets the location if the player tries to go outside the game stage.
You can use a Text
entity to keep track of the score. The score is shown in the top left corner. The gameScore
variable stores the number of times the player hits the food entity.
var scoreText = Crafty.e('2D, DOM, Text') .attr({ x: 10, y: 10 }) .textFont({ size: '25px' }); scoreText.text(gameScore.toString());
Now, you just have to write the code to move the food to a different location when a hit is detected. The following code will do exactly that.
playerBox.checkHits("Food").bind("HitOn", function(hitData) { foodBox.x = Math.random() * (stageWidth - foodBox.w); foodBox.y = Math.random() * (stageHeight - foodBox.h); gameScore += 1; scoreText.text(gameScore.toString()); });
You need to keep in mind that you subtract the width and height of your food entity from the stage width and height respectively. This makes sure that the food is always completely inside the stage. Here is a demo of the game:
Final Thoughts
With the help of Crafty, you have created a very basic game by writing a few lines of code. Right now, the game lacks a few features which could make it more interesting. First, there are no sounds. Second, there is no way for the player to get out, and the difficulty level also remains the same throughout the game. You will learn about sound, sprites, mouse events, and other features of the library in the next series.
If you had any problems or doubts while going through all the examples in the series, let me know in the comments.
No comments:
Post a Comment