Recently, in addition to doing business, I am also trying to learn H5 and mobile terminal. During this process, I have learned a lot and made a small game of caring fish with H5 and Canvas. Source code on Github, down directly to run. If you think it’s ok, give it a star! Source address point here

I’m going to share a summary of the H5 and Canvas apis and some common math functions. I recommend you to play the game first, in order to better understand these logic. Click here to play

preface

First of all, the most important thing in a game is animation. How do you get the elements moving? Let’s start with a sentence:

When the position of an element moves, it creates an animation.

Render the element frame by frame, and the position of the element is different from frame to frame, so our eyes see the animation. OK, let’s start with the requestAnimationFrame function.

We all know that we can use setTimeout and setInterval to re-render at intervals, so why not?

Let me give you a quick example:

  • setInterval(myFun, 1); This means executing myFun every millisecond, but there is a problem with that. For example, my myFun drawing takes a long time and is not fully drawn within 1ms, but this code forces the next frame to start drawing after 1ms, so it will appearThrow the frame“, which can occur if the time is set too longVisual catonThe problem.
  • requestAnimationFrame(myFun); What does it mean if we write it this way? Meaning according toA certain time interval, the myFun function is automatically executed to draw. This “interval” is determined by the performance of the browser or the speed of the network. In general, it ensures that you draw one frame before drawing the next, ensuring both performance and smooth animation.

Animation solved, so what is used to draw each frame of the page? This is where the magic of H5 — Canvas comes in, so the CANVAS API is very important.

The HTML file


       
Copy the code
  • Define two canvases and draw corresponding objects on the canvases respectively;
  • Painted on Canvas2, background, anemone, fruit;
  • Canvas1 drawing, big fish, small fish, text display, circle special effects;

Js file

function init(){ can1 = document.getElementById('canvas1'); // canvas ctx1 = can1.getContext('2d'); // brush can2 = document.getelementById ('canvas2'); ctx2 = can2.getContext('2d'); Canvas} function gameloop(){requestAnimFrame(gameloop); // Draw object... } var requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback,  element) { return window.setTimeout(callback, 1000 / 60); }; }) ();Copy the code
  • The init function initializes variables such as anemone objects, big fish, small fish, and so on.
  • The Gameloop function is used to draw the page for each frame. All of the drawing functions described below are performed here.
  • The requestAnimFrame function is meant to be compatible with all browsers.

Let’s start drawing what happens in the game and see what interesting API functions are used. Go! Go! Go!

Paint the background and anemones

The background is a graph, and the anemone is a class that has x coordinates, y coordinates, numbers, etc., 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); Ctx2.lineto (this.rootx[I], canhei-220,); // end point ctx2.stroke(); ctx2.restore();Copy the code
  • Ctx2. DrawImage (image, x,y, width, height) //
  • ctx2.save(); // Define the scope
  • Ctx2. GlobalAlpha = 0.7; // Define line transparency
  • ctx2.lineWidth = 20; / / width
  • Ctx2. LineCap = ’round; / / the rounded
  • Ctx2. StrokeStyle = ‘# 3 b154e; // Define the color of the drawn line
  • ctx2.beginPath(); // Start path
  • ctx2.moveTo(x,y); // The starting point of the line, x,y represents the coordinates (the origin of the coordinates is in the upper left corner)
  • ctx2.lineTo(x,y); // Lines connect from the starting point to this point
  • ctx2.stroke(); // Start drawing lines
  • ctx2.restore(); // End of action space

The anemone produces fruit

Fruit is also a class, and its attributes are: coordinates, type (yellow and blue), size, state (show or hide), speed (floating speed up) and so on; His methods include initializing init, born, and drawing draw.

The draw method:

for(var i =0; i< this.num; i++){ if(this.alive[i]){ //find an ane, grow, fly up... If (this. Size [I] < = = "" 16) {=" "grow up state =" this. "turns up [I] =" false;" Enclosing the size [I] = "" + =" this. Speed [I] "* =" "diffframetime = 0.8;" " = ""} else {=" "have grown up, floating up =" this. "[I] y =" - "=" this. Speed [I] "diffframetime; ="" }="" var="" pic="this.orange;" If (this. Type = "=" [I] 'blue') = "" ctx2 drawImage (PIC =" this. "[I] x = 0.5," "=" "enclosing the size [I], =" this. "the size [I]); ="" if(this.y[i]="" <="" 8){="" this.alive[i]="false;" }<="" code=""/>Copy the code

Born method: randomly find the coordinates of an anemone and give birth to a fruit on the coordinates of anemone.

Draw big fish and small fish

Big fish and small fish are the same class, its attributes are: coordinates, rotation Angle, tail swing time interval, blink time interval, body picture array… ., etc.

Draw the big fish first, using the drawImage method of canvas.

The more difficult one is the animation of the big fish, which moves with the mouse. Here we define two functions:

Function lerpAngle(a, b, t) {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) {function lerpDistance(aim, cur, ratio) {var delta = cur-aim; return aim + delta * ratio } this.momTailTimer += diffframetime; if(this.momTailTimer > 50){ this.momTailIndex = (this.momTailIndex + 1) % 8; This. momTailTimer %= 50; }Copy the code
  • LerpDistance calculates the distance between the big fish and the mouse for each frame.
  • LerpAngle is used to calculate the rotation Angle of the big fish towards the mouse in each frame. Define these two functions to make the big fish move smoothly.

Once you’ve got an Angle, how do you get the big fish to spin? So we’re going to need a couple more apis here.

  • ctx1.save(); // It is recommended to use save and restore for each drawing to avoid defining styles and conflicts.
  • ctx1.translate(this.x, this.y); // Change the origin to (this.x, this.y);
  • ctx1.rotate(this.angle); // Rotate an Angle clockwise according to the origin

Draw small fish and big fish are the same, do not elaborate. However, it is important to note that there is a judgment when drawing the fish. When the fish becomes white, the game ends.

this.babyBodyTimer += diffframetime; If (this.babyBodyTimer > 550){this.babyBodyTimer += 1; // Body image fades this. babytimer %= 550; scoreOb.strength = ((20 - this.babyBodyIndex)/2).toFixed(0); If (this.babyBodyIndex > 19){// If the body becomes white, game over; this.babyBodyIndex = 19; scoreOb.gameOver = true; can1.style.cursor = "pointer"; }}Copy the code

Big fish eat the fruit

If the distance between the big fish and the fruit is less than 30, let the fruit disappear, and white rings appear, and the score has a certain change.

Jzk. momEatFruit = function(){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); WaveOb. Born (I); // If the distance is less than 30, it is eaten. // When eaten, scoreob. fruitNum ++ is generated; +1 momob.momBodyIndex = momob.momBodyIndex == 7? momOb.momBodyIndex : (momOb.momBodyIndex + 1); If (fruitob. type[I] == 'blue'){scoreob.doublenum ++; // Eat the blue fruit, multiple +1}}}}}Copy the code

There is a calLength2 function that calculates the distance between two points.

Function calLength2(x1, y1, x2, y2) {function calLength2(x1, y1, x2, y2) { SQRT (math.pow (x1 - x2, 2) + math.pow (y1-y2, 2)); }Copy the code

When a big fish eats the fruit, it creates a white circle. How to achieve this effect?

First we define a waveObject class with its properties: coordinates, Quantity, Radius, usage state. Its methods are: initialization, drawing, and birth.

Let’s look at drawing circles:

for(var i = 0; i< this.num; I ++){if(this.status[I]){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(); }}Copy the code

Draw each circle frame by frame, increasing its radius and decreasing its opacity until the radius is greater than 60. Set the state to false and return it to the pool.

Arc (x,y,r,deg); arc(x,y,r,deg); // Draw the circle, x,y are the central point, r is the radius, deg is the Angle, 360 degrees is a full circle.

Let’s take a look at birth methods:

for(var i = 0; i< this.num; i++){ if(! this.status[i]){ this.status[i] = true; Fruitob.x [I] = fruitob.x [index]; this.y[i] = fruitOb.y[index]; this.r[i] = 10; return false; // Find an unused circle, end. }}Copy the code

The coordinates of the birth of the circle are the coordinates of the fruit being eaten.

Big fish for little fish

Here, after feeding small fish, the big fish’s body turns white, and the number of small fish increases with the fruit. In addition, it should be noted that the coordinates of the circle generated at this time are the coordinates of small fish.

Game score calculation

Define a data class with attributes such as: number of fruit eaten, multiple, total score, strength, game state (whether it is over), etc. Methods are: initialization, draw scores.

Here we need to draw text on the canvas, again using the new API:

  • ctx1.save();
  • Ctx1. The font = ’40 px verdana; Define text size and font;
  • ctx1.shadowBlur = 10; Defines the shadow width of the text
  • Ctx1. ShadowColor = “white”; Define the color of the text shadow.
  • Ctx1.fillstyle = “rgba(255, 255, 255,” + this.alpha + “) “; Define the color of the text (RGBA, A for transparency)
  • Ctx1. FillText (” GAME OVER “, canWid 0.5, canHei 0.5-25); Draws text. The first argument is a string that supports expressions, and the last two arguments are coordinate values.
  • Ctx1. The font = ’25 px verdana;
  • Ctx1. FillText (” CLICK TO RESTART “, canWid 0.5, canHei 0.5 + 25);
  • ctx1.restore();

conclusion

Well, the whole process of making the game is shared, there were a lot of problems in the process, but they were solved one by one, deepened a lot of vague concepts, also learned a lot of new knowledge, such as using RGBA () to control the color and transparency together, really did not use before.

The game itself is relatively simple, but the animation is still quite cool. This is a fairly basic animation framework, but there are a lot of tricky things to understand, such as the approach Angle function lerpAngle(a,b,c) and math.atan2 (). Welcome to put forward bugs or improvement suggestions ~ ~ ~