Objective
First of all, a game is the most important animation, how to let elements move up? Let's look at a word first:
The position of the element is moved, the animation is formed.
One frame to render this element, and each frame of the element is not the same position, our eyes see animation. OK, let's introduce the Requestanimationframe function first.
We all know that we can use the settimeout and SetInterval functions again and again, so why not here?
Let me give you a simple example:
SetInterval (Myfun, 1); It means that a myfun function is executed every millisecond, but then there is a problem, such as my myfun function of the things drawn in time, and 1ms has not been fully drawn out, but this code to force 1ms after the next frame began to draw, so there will be dropped the frame problem, And if the time set is too long, there will be the problem of visual cotton.
Requestanimationframe (Myfun); If we write this, what does it mean? This means that the Myfun function is automatically executed according to a certain time interval. This "certain time interval" is based on browser performance or speed to determine, in short, it will ensure that you draw this frame, will draw the next frame, to ensure performance, but also to ensure the smooth animation.
Animation is resolved, then what is the use of the page to draw each frame? It is time to use the magical--canvas of H5, so the API of canvas canvas is very important.
HTML file
<div class= "Page" >
<div class= "content" id = "main" >
<canvas id = "CANVAS1" width= "height=" >
</canvas>
<canvas id = "CANVAS2" width= "height=" >
</canvas>
</div>
</div>
Define two canvases and draw the corresponding objects on the canvas respectively;
CANVAS2, background, anemone, fruit;
CANVAS1, large fish, small fish, display text, circle effects;
JS file
function init () {
Can1 = document.getElementById (' canvas1 '); Canvas
CTX1 = Can1.getcontext (' 2d '); Brush
can2 = document.getElementById (' Canvas2 ');
CTX2 = Can2.getcontext (' 2d '); The canvas below
}
function Gameloop () {
Requestanimframe (Gameloop);
Draw an Object ...
}
var Requestanimframe = (function () {
return Window.requestanimationframe | | Window.webkitrequestanimationframe | | Window.mozrequestanimationframe | | Window.orequestanimationframe | | Window.msrequestanimationframe | |
function (callback, Element) {
Return Window.settimeout (callback, 1000/60);
};
})();
The init function initializes variables such as sea anemone objects, large fish, small fish objects, and so on.
The Gameloop function is used to draw a page for each frame. All of the drawing functions described below are executed here.
The Requestanimframe function is intended to be compatible with all browsers.
Let's start by drawing the things that appear in the game and see what interesting API functions are used. Go! Go! Go!
Draw background and Anemone
The background is a picture, and the anemone is a class, it has the x coordinates, y coordinates, number and so on attributes, there are initialization init and draw methods.
DrawImage (image, X, y, width, height)
Ctx2.save ();
Ctx2.globalalpha = 0.7;
Ctx2.linewidth = 20;
Ctx2.linecap = ' round ';
Ctx2.strokestyle = ' #3b154e ';
Ctx2.beginpath ();
Ctx2.moveto (This.rootx[i], Canhei); Starting point
Ctx2.lineto (This.rootx[i], canHei-220,); End point Ctx2.stroke ();
Ctx2.restore ();
Ctx2.drawimage (image, X, y, width, height)//x,y represent coordinates, width and height represent width and height
Ctx2.save (); Defining the scope of a space
Ctx2.globalalpha = 0.7; Define the transparency of a line
Ctx2.linewidth = 20; Width
Ctx2.linecap = ' round '; Rounded Corners
Ctx2.strokestyle = ' #3b154e '; Define the color of the drawn line
Ctx2.beginpath (); Start path
Ctx2.moveto (X,y); The starting point of the line, the x,y represents the coordinates (coordinate origin in the upper-left corner)
Ctx2.lineto (X,y); Lines are connected to this point from the start point
Ctx2.stroke (); Start drawing lines
Ctx2.restore (); End of function space
Anemone produces fruit
The fruit is also a class whose attributes are: coordinates, type (yellow and blue), size, state (show or hide), speed (floating upward), and so on; his methods are: Initialize init, birth born and draw draw.
Draw method:
for (var i =0;i< this.num; i++) {
if (This.alive[i]) {
Find a ane, grow, fly up ...
if (This.size[i] <= 16) {//Growth status
This.grow[i] = false;
This.size[i] + = this.speed[i] * diffframetime * 0.8;
}else{//has grown up, floating up
This.grow[i] = true;
This.y[i]-= this.speed[i] * 5 * diffframetime;
}
var pic = This.orange;
if (this.type[i] = = ' Blue ') pic = This.blue;
Ctx2.drawimage (pic, this.x[i]-this.size[i] * 0.5, this.y[i]-this.size[i] * 0.5, this.size[i], this.size[i]);
if (This.y[i] < 8) {
This.alive[i] = false;
}
}
}
Born method: Randomly find the coordinates of a sea anemone, born with a fruit on the sea anemone's coordinates.
Draw big fish and small fish
Big fish and small fish are a class, its properties are: coordinates, rotation angle, tail swing time interval, blink time interval, body image array ... wait a minute.
First draw the big Fish out, using the canvas DrawImage method.
The more difficult is the big fish animation, the big fish will move with the mouse movement animation, here defines two functions:
function Lerpangle (A, B, t) {//calculate the angle of rotation for each frame
var d = b-a;
if (d > Math.PI) d = d-2 * MATH.PI;
if (d <-math.pi) d = d + 2 * MATH.PI;
Return a + d * t;
}
function lerpdistance (aim, cur, ratio) {//aim: Target cur: Current ratio: percent calculates the distance each frame is approaching
var delta = Cur-aim;
Return aim + Delta * ratio
}
This.momtailtimer + = Diffframetime;
if (This.momtailtimer > 50) {
This.momtailindex = (this.momtailindex + 1)% 8; Change the tail image according to the time interval
This.momtailtimer%= 50;
}
Lerpdistance is the calculation of each frame of the big fish tightening to the mouse distance.
Lerpangle is used to calculate the angle at which each frame of a large fish rotates to the mouse. Define these two functions to make the big fish move more smoothly.
After gaining an angle, how do you make the big fish spin? Here's a few more APIs to use.
Ctx1.save (); It is recommended that you use Save and restore for each drawing to avoid defining styles and conflicts.
Ctx1.translate (this.x, THIS.Y); Turn the original point into (This.x, this.y);
Ctx1.rotate (This.angle); Rotate an angle clockwise from the origin.
Drawing small fish is the same as big fish, not to elaborate. But note that the time to draw a small fish when there is a judgment, when the color of small fish white, the game is over.
This.babybodytimer + = Diffframetime;
if (This.babybodytimer > 550) {//Body picture changing counter > 550ms
This.babybodyindex + 1; Body Picture Fades
This.babybodytimer%= 550;
Scoreob.strength = ((20-this.babybodyindex)/2). toFixed (0);
if (This.babybodyindex > 19) {//If the body turns white, game over;
This.babybodyindex = 19;
Scoreob.gameover = true;
Can1.style.cursor = "pointer";
}
}
The big fish eat the fruit
The big fish eats the fruit according to the distance to decide, if the big fish and the fruit distance is less than 30, then lets the fruit disappear, and appears the white Circle, and the score has the certain change.
Jzk.momeatfruit = function () {//judge the distance between the fruit and the big fish, less than 30 the description is eaten
for (var i = 0;i < Fruitob.num; i++) {
if (Fruitob.alive[i] && fruitob.grow[i]) {
var len = calLength2 (Fruitob.x[i], fruitob.y[i], momob.x, MOMOB.Y);
if (Len < 30) {
Fruitob.dead (i); If the distance is less than 30, it is eaten.
Waveob.born (i); When you eat, you create a circle.
Scoreob.fruitnum + +; The number of fruits to eat +1
Momob.mombodyindex = Momob.mombodyindex = = 7? Momob.mombodyindex: (Momob.mombodyindex + 1); The body color of the big fish is red
if (fruitob.type[i] = = ' Blue ') {
Scoreob.doublenum + +; Eat the blue fruit, multiply by +1
}
}
}
}
}
There is a calLength2 function that is used to compute the distance between the two points.
function CalLength2 (x1, y1, x2, y2) {//calculate the distance between the two points,, first squared sum, then square
Return Math.sqrt (Math.pow (X1-X2, 2) + Math.pow (Y1-y2, 2));
}
When the big fish eat the fruit, it will produce a white circle, how to achieve this effect?
First, we define a Waveobject class whose properties are: coordinate, quantity, radius, use state. Its methods are: initialization, drawing and birth.
Let's take a look at how to draw a circle:
for (var i = 0;i< this.num; i++) {
if (This.status[i]) {//If the circle is in use state, draw the circle
This.r[i] + = diffframetime * 0.04;
if (This.r[i] > 60) {
This.status[i] = false;
return false;
}
var alpha = 1-this.r[i]/60;
Ctx1.strokestyle = "Rgba (255, 255, 255," + Alpha + ")";
Ctx1.beginpath ();
Ctx1.arc (This.x[i], this.y[i], This.r[i], 0, 2 * math.pi); Draw a circle,
Ctx1.stroke ();
}
}
A frame-by-frame drawing each circle, the radius of the circle increases gradually, the transparency gradually decreases until the radius is greater than 60, the state is set to false, let it return to the object pool.
Here again a new method is used: Ctx1.arc (X,Y,R,DEG); Draw a circle, X,y is the center of the DOT, R is the radius, deg is the angle, 360 degrees is a whole circle.
Let's take a look at the birth method:
for (var i = 0; i< this.num; i++) {
if (!this.status[i]) {
This.status[i] = true; Set the state of the circle to use
This.x[i] = Fruitob.x[index];
This.y[i] = Fruitob.y[index];
This.r[i] = 10;
return false; Find an unused circle and end it.
}
}
The coordinates of the birth of a circle are the coordinates of the fruit eaten.
Big fish feed small fish
Big fish Feed The fish ditto, no longer detailed, here to feed small fish, big fish body white, small fish with the number of fruit corresponding increase, also need to note that at this time the coordinates of the production circle is small fish coordinates.
Game score Calculation
Define a data class, its properties are: The number of fruit to eat, multiples, total score, power value, game status (whether the end), etc., the method has: initialization, draw fractions.
Here we need to draw text on the canvas and use a new API:
Ctx1.save ();
Ctx1.font = ' 40px Verdana '; Define the size and font of the text;
Ctx1.shadowblur = 10; Define shadow width for text
Ctx1.shadowcolor = "White"; Defines the color of the text shadow;
Ctx1.fillstyle = "Rgba (255, 255, 255," + This.alpha + ")"; Define the color of the text (Rgba,a represents transparency)
Ctx1.filltext ("GAME over", Canwid 0.5, Canhei 0.5-25); Draws text, the first argument is a string, the expression is supported, and the latter two are coordinate values.
Ctx1.font = ' 25px Verdana ';
Ctx1.filltext ("Click to restart", Canwid 0.5, Canhei 0.5 + 25);
Ctx1.restore ();
Summarize
Well, the whole process of the game was shared, there have been many problems in the process, but all of them have been solved, deepened a lot of previously vague concepts, but also learned a lot of new knowledge, such as using RGBA () to control the color and transparency, has been really useless before.
The game itself is relatively simple, but the animation is relatively cool. This is a relatively basic animation framework, and there are many more difficult to understand, such as the asymptotic angle function lerpangle (a,b,c), as well as math.atan2 () This function, and so on. You are welcome to propose bugs or suggestions for improvement ~ ~ ~