Is this something I can watch for free? Re-engraving classic games hand in hand

Is this something I can watch for free? Re-engraving classic games hand in hand

Starter: Is this something I can watch for free? Re-engraving classic games hand in hand

Game demo: classic game re-engraved version


Because the author is just an ordinary page boy, not a developer belonging to the game industry. I usually write some small games just because of my interest, and I often get some small ideas in my mind. So a lot of knowledge is just pieced together by groping.

Therefore, this article is just to introduce to you how I went from zero to one to complete a playable page game step by step. If you are a developer in the game industry or a developer planning to enter the game industry, it is recommended to read more professional books and learn professional game frameworks and game knowledge.

Thanks in advance.

Reading threshold

  • How much to know Typescript

  • Know how much Canvas

  • Have seen a little bit of ES6 tutorial by teacher Ruan Yifeng

  • How much remember a little high school math

Finished picture

There is a problem with the QQ screen recording. The speed is accelerated and the colors are flashing. There is an online experience link at the end of the article. If you are interested, you can experience it.


  1. Initialize the development environment

The environment is actually not important, we only need a canvas tag and an environment that supports typescript. I chose Parcel, the simplest and fastest packaging tool. No additional configuration is required, just use it out of the box .

Then we can start to introduce our main game object

The reason for not directly using index.ts to write game content here is to increase the UI interface for subsequent convenience. By passing the canvas component and configuring the width and height to create a new game object, the subsequent management of the game process and the rendering of the canvas will be implemented here.

A light gray background is randomly added here to test whether it can be rendered normally

WOW, it appeared! In this way, our first step is to set up the development environment. (No technical content = =

  1. Canvas introduction

The canvas is actually an element. We can use it to create a context, which is the ctx in the code above. By calling the api on the ctx, we can draw the content we want to display on the canvas.

Solve the problem of blur under HD screen

One point that needs to be considered when creating a canvas is the DPR problem, which is the device pixel ratio. For example, in the code in the above figure, we render a 600x600 canvas on a 600px x 600px element. In a high-definition screen (DPR >= 2) scene, blur will appear. If you are specifically interested in why it is vague, you can search by yourself. All in all, to solve the problem of blurring on high-definition screens, we have to enlarge the canvas in equal proportions.

In this way, in the scene of DPR = 2, Canvas will not appear blurry .

Let the canvas move

Game game, won't it move, is it still a game? So we have to move the canvas next. Here we mainly use an api window.requestAnimationFrame to tell the browser to run our game as smoothly as possible (60 frames per second). An additional point to note is that you need to clear the canvas before redrawing each time.

In this way, our canvas is refreshed at 60 frames per second (although there is only a gray background and no difference can be seen.

Performance optimization

1. Multi-canvas rendering

If your background is complex enough, you can consider creating a separate canvas to render the background. This eliminates the need to redraw the background 60 times per second. Because the game we are doing this time has a solid color background, so only a single canvas rendering is complete.

2. off-screen rendering

If your game screen is very fancy, the game screen appears to be stuck with insufficient frames. You can consider off-screen rendering. The principle of off-screen rendering is to create an off-screen Canvas as a buffer area to cache the content that needs to be drawn repeatedly in advance, thereby reducing the loss of API calls and improving rendering efficiency. If you are interested, you can go to search, I haven't used it either.

  1. sprite

A sprite is actually an object, and each individual element on the canvas can be regarded as a sprite. Sprites can contain various attributes such as position, shape, and behavior. No amount of code is intuitive.

In this way, a basic sprite abstract class is realized, which contains the most basic position information of an element, and provides two methods for canvas rendering and updating sprite information. Our subsequent wizard implementations will inherit the development of this abstract class.


  1. Realize bullet wizard

First of all, we need to confirm the attributes that a bullet sprite should have. In addition to the position, the radius and color of the bullet, as well as the moving direction and speed of the bullet.

Because the bullets are random, the position and radius of the bullet should be randomly generated within a range. For the specific game design, I set it like this:

  • The bullet is generated off-screen and moves to a certain range near the target

  • The larger the radius of the bullet, the slower the movement speed

  • Remove the bullets when they fly out of the screen, keep the number of bullets on the screen fixed

After confirming the game settings, you can start typing. First of all, we must first determine the scope of the bullet sprite. We only need to give the bullet sprite a position, a size, and a target. The bullet wizard needs to realize the corresponding movement direction and movement speed according to the target generation.

For the movement direction and speed of the bullet, let s leave a TODO for now, and fix the bullet s position radius and other attributes. Also, in order to make the subsequent games easier to maintain, we put all game configuration-related values in the config for management.

Then you can implement it step by step according to the design:

First generate a random bullet radius

Then randomly generate the position of the bullet. Here we generate a bullet at a random position on the off-screen edge in four directions.

Because we have not yet become a player sprite, we will mock a target temporarily. And make an array to add bullets, and then you have to control the length of this array to control the density of bullets on the screen. The final method is this:

At this point, the position and radius of the bullet are there. Next, realize the direction and speed of the movement, and return to our bullet wizard. First of all, we have to calculate our moving speed based on the radius, because the larger the radius, the slower the speed, so use the maximum speed to subtract the ratio of the radius within the radius and multiply the speed range:

The speed is there, and now we have to divide our speed into horizontal speed and vertical speed.

First of all, under the popular science method, Math.atan2, which everyone doesn't usually use, can get the angle of two points. Post an overview of mdn:

Math.atan2() returns the plane angle (radian value) between the line segment from the origin (0,0) to the (x,y) point and the positive direction of the x-axis, which is Math.atan2(y,x)

So suppose our target is in situ (0, 0) and the coordinates of the bullet are (bullet x-target x, bullet y-target y).

This way we can get the angle (here by the way, the target is also randomly offset, otherwise it will be very stiff to go straight to the target)

Once we have an angle, we can easily divide our speed into horizontal speed and vertical speed by simply using the knowledge of trigonometric functions in high school.

Finally, write the methods of drawing bullets and updating bullets casually

Remember to add that the game has to be updated every time it is rendered, and then add the bullet rendering and bullet updates.

Finally, we modify the update logic to control the density of the barrage on the screen at a fixed value. Add all the bullets and you're done!

Wuhu! Once successful, the barrage came out!

  1. Realize player sprites

Relatively speaking, the attributes of the player sprite will be much simpler. The old rules go directly to the game settings:

  • The shape of the player is triangle , and the direction is always towards the direction of movement

  • Can be controlled by keyboard wsad and

The first step is to initialize the player sprite when starting the game

Then start to draw the triangle in the second step, x and y are the center of gravity of the triangle, and then set a distance d from the center of gravity to the three corners, and then we can calculate the coordinates of the three points

A: (x, y-d)

B: (x-Math.cos(30deg) * d, y + Math.sin(30deg) * d)

C: (x + Math.cos(30deg) * d, y + Math.sin(30deg) * d)

After adding the code according to the formula, save it and see~

With that, a small triangle came out.

Because we need the triangle to face the direction of movement, we have to add the angle of rotation, because rotate is based on the point (0, 0) by default, and we need to rotate based on the center of gravity of the triangle, so we first use translate to offset and offset Move back until the center of gravity is rotated, and don't forget to reset the rotation at the end.

Next, you can deal with the player control movement. First of all, we have to support diagonal movement, such as upper left, upper right, and so on. There are a total of eight directions, so we add a field to indicate which direction the player is currently moving.

Here we use the binary method, using 0001 for up, 0010 for down, 0100 for left, and 1000 for right. And direction and direction can be combined (for example, 0101 represents upper left), so that we can use one number to represent eight directions.

We only need to press the button or (|) the corresponding digit, and then release the button and then (&) the corresponding digit to reverse (~). You can easily record the current direction of progress.

When updating later, update the position and rotation angle according to the direction and you're done.

Don't forget that there is also edge detection to prevent players from running out of the area.

Save the code and let's test it!

There is it! Look at this flexible little arrow, but now nothing happens to the bullet, it's the last step to completion!

  1. Collision detection

To judge whether a triangle collides with a circle, we need to judge two situations, one is that the center of the circle is in the triangle, then a collision occurs. The other is to determine whether the distance from the center of the circle to the three sides is less than the radius, and if so, a collision occurs.

The first is better to judge: whether the center of the circle is within the path of the triangle. So we have to extract the code for drawing the triangle path separately, and several angles will be used later, so the acquisition of several angles is also separately extracted into a method:

Then we need to use isPointInPath to determine whether the center of the circle is within this path:

Next, the second judgment is more complicated: to judge the distance from the center of the circle to the three sides, the knowledge of the vector is needed here:

Assuming that the AB vector of the triangle side is v1, and the AO vector from the corner to the center of the circle is v2, we need to require the length of the projection AC of AO on the AB vector to be u.

According to the dot product formula of the vector:

Then we unitize (normalize) v1, which is

Then according to the knowledge of trigonometric functions, we know that |v2|cos is the projection u we need, so we can quickly implement it with code:

There are also three cases of projection u here (corresponding to Figure 123 below):

  • The first is when u is negative when it is on the left of point A, and the nearest point is point A

  • The second is the length of the projected side when it is projected to the right of point B, the closest point is point B

  • The third type is that the circle is just above the edge, and the nearest point is point C.

After obtaining the point closest to the center of the circle, use the two-point distance formula to calculate the distance, and then judge whether the distance is less than the center of the circle to detect collision:

Then when updating the bullet, judge whether the player has been shot (remember to render again after the game is over, otherwise it will cause the screen to stay at the moment before the collision, which looks like a BUG)

After the test, it was found that something was wrong, because before the player sprite was rotated using the API rotate that comes with the canvas, and then the collision detection was determined by the unrotated triangle, so there will be a situation where the collision is triggered even if there is no contact.

The solution is to change the rotate rotation to a three-angle rotation of a solid triangle. Here you need to use the axis formula:

Get it done, run quickly and try

Yay! You can play normally, but it's boring to play dry like this. Next, we will improve it and add the score count.


Because this is a game that sticks to the length of time, we use the number of seconds as the result. There is no difficulty in this part, so I won't go into details. One thing to note is that the recording time cannot simply be a timestamp, because the game is automatically paused by rAF when the browser tab is switched, and the score will continue to count.

Compatible with mobile

This paragraph was added after this article was written. Considering that many people now use mobile phones to scan articles, I decided to add mobile support. There are two implementation options here

  1. Move to the position touched by the player

  2. Add virtual joystick

Because if you use the first option, the player's fingers will be blocked from the field of view, resulting in a poor gaming experience, so we decided to adopt the second option and add a virtual joystick.

Related configuration items of joystick:

The implementation is actually very simple, that is, add one more parameter to the player sprite, and you can choose the control method. If you use touch control, add a joystick. Here we set the center of the joystick to the lower left corner by default.

Then determine if it is a touch control, then monitor the touch event

Then add a field to record the place where the finger is pressed

It is worth noting that when we touch the position in the center of the joystick, the player does not move, so the game operability is much higher. So we add a getter to facilitate subsequent judgments:

Then, when updating the player's position, differentiate the processing according to the different control methods, and calculate the angle between the finger touch position and the center of the joystick is the angle of the player's movement:

Finally, we finish drawing the joystick on the screen. The specific implementation is also very simple. It is to draw two circles, one is the large background circle, and the other is the joystick circle of the player's current moving direction.

That's it! It took less than half an hour to complete the compatible mobile terminal, so a complete code structure and clear code logic are very important, which can make subsequent maintenance and functional iterations easy.

Credits summary

In general, the implementation is still very simple, not counting the time to write this small game can be completed in almost a day. At present, there is still a lot of room for optimization of code quality. In order to facilitate reading and comprehension, how many repetitive logical calculations have not been extracted.

Thinking expansion

At present, only the most basic functions have been implemented. If you want to expand, there are many directions you can do.

For example, you can increase the level design, because the bullet speed and bullet density can be dynamically configured. Or increase boosters, such as speeding up the player, and reducing the size of the player to reduce the chance of being hit.

Also, it s more fun to play with friends than by yourself. You can add a player sprite to control with wsad and arrow keys to achieve local battles (I ve done it four or five years ago in my impression, two arrows The collision will spin for one second, increasing the interactivity).

You can also add a virtual joystick compatible with the mobile terminal, and the main body of the game relies on a canvas, and transplant the ui interface to a small program more beautifully. Maybe it will become popular in minutes:

Warehouse link