Address: game resources link: pan.baidu.com/s/1rL82cUYM… Extraction code: R10D

1. Download the game engine

I use CoCOS Creator, official website address: link

You can download the latest stable version of the engine for free by clicking on the download button. At the time of writing this article, the latest version suitable for writing 2D mini games seems to be: V2.4.8

We usually download a DASHBOARD, which can be interpreted as a cocos box for various game tools.

Then download the corresponding version of Cocos Creator from DASHBOARD.

2. Create a game project

After installation, we enter the big screen:

Click New to create a new Empty game project:

After seeing this screen, it means that our development environment is set up and we can start developing small games!

3. Create the game canvas

First we import the game resources into our game project.

Then check the size of the background image. The size of the background image in resources is: 640 x 1136

So we set the canvas size to 640 x 1136

4. Set the background

To reduce the performance cost of loading images, we combined all the resources into one image (which cocos automatically recognizes) using TexturePacker, the official download link

The simple usage method is as follows:

  1. Open the images directory and drag all the images into TexurePacker

  1. save

After making the plist file, we created a new folder named plist to store the plist file, and we obtained the pictures through PList afterwards.

Then we drag the background image bg onto the node tree:

To make A rotating background, we need two background images, let’s say A and B (to make the background image move, we must always have the background in the canvas). See: Link for a full explanation

First we create a TypeScript script that controls the game logic:

Then associate the script file with the canvas:

After associating with the canvas, we write a script to control the rotation of the background image. First, we need to define two nodes (we only use the coordinate attribute of the background image, so it is enough to define the node type) :

@property(cc.Node)
bg1: cc.Node = null;

@property(cc.Node)
bg2: cc.Node = null;
Copy the code

Define the postscript to the canvas for binding:

After binding, we write the code for round casting:

// Define the position of the background image when the game loads
protected onLoad() {
    this.bg1.y = 0
    this.bg2.y = this.bg1.y + this.bg2.height
}

update (dt) {
    // The background image moving speed
    this.bg1.y -= 10;
    this.bg2.y -= 10;
    // Background image rotation logic
    if(this.bg1.y <= -this.bg1.height){
        // When a background is moved out of the screen, immediately add another background image behind it
        this.bg1.y = this.bg2.y + this.bg1.height
    }
    if(this.bg2.y <= -this.bg2.height){
        this.bg2.y = this.bg1.y + this.bg2.height
    }
}
Copy the code

After writing, we have the background image of the rotation.

5. Add a start game slogan

First we add the game slogan to the node tree and select an appropriate location:

To prompt the player to click the screen to start the game, we add a label node under this node to display the text:

In order to imitate the display effect of wechat plane war, we added an animation effect of shaking up and down to the line of clicking the screen to start the game, using the animation function of Cocos:

  1. First we create an animation resource
  2. Bind the animation resource toTap the screen to start the gameOn the node where the
  3. Then set the key frame and set the appropriate Angle for each key frame. For example, the Angle of the first frame is 0, the Angle of the second frame is 15, the Angle of the third frame is 0, the Angle of the fourth frame is -15, and the Angle of the fifth frame is 0.

When they were played together, it looked something like this:

6. Write the three states of game preparation, game in progress and game pause

Although we wrote a rotating background with a dynamic effect, we wanted the background to be stationary at the beginning of the game, and then the background to move after the player entered the game, so that the small plane has the effect of flying.

First we define a variable that determines whether the background image is moving

isBgMove = false
Copy the code

We then wrap the code to move the background in a method that controls whether the background moves by checking the isBgMove variable in the update method:

update (dt) {
    if(this.isBgMove){
        this.moveBg()
    }
}

moveBg(){
    // Make the background image move
    this.bg1.y -= 10;
    this.bg2.y -= 10;
    if(this.bg1.y <= -this.bg1.height){
        this.bg1.y = this.bg2.y + this.bg1.height
    }
    if(this.bg2.y <= -this.bg2.height){
        this.bg2.y = this.bg1.y + this.bg2.height
    }
}
Copy the code

With the switch that controls the background rotation, we write the game in three states: ready, game, and game pause.

First we rename the shoot_copyright node defined above to the game ready node status_ready;

Then create two empty status_playing and status_pause nodes in turn:

Then create a pause button on the game start screen:

To create a click event for the pause button, we write a generic method to handle the click event and debug it from the console:

clickButton(sender, str){
    if(str == "pause") {console.log("Hit the pause button.")}}Copy the code

Then write the pause page:

  1. Start by adding a paused background image with transparency to cover the original game background
  2. Add three buttons and set the style and content of the buttons
  3. Set a BlockInputEvents to block the underlying node for the pause background

Then write the explicit and implicit relationship of these three pages, and the general logic is:

  1. After entering the game, a static background is displayed. When clicking the screen, hide the game preparation interface and enter the game start interface
  2. Players can click the pause button in the game interface, click the pause button to enter the game pause interface from the game start interface
  3. There are three buttons in the game pause interface: click to continue the game will return to the game start interface; click to restart the game will return to the game start interface; click to return to the home page will return to the game preparation interface

The above logic can be handled by a unified keystroke method:

clickButton(sender, str){
    if(str == "pause") {// Click pause to display the pause page
        this.pause.active = true
    }else if(str == "continue") {// Click continue to hide the pause page
        this.pause.active = false
    }else if(str == "restart") {// Click restart to hide the pause page
        this.pause.active = false
    }else if(str == "backHome") {// Click back to the home page to hide the pause interface, stop the game, stop the background movement
        this.pause.active = false
        this.playing.active = false
        this.isBgMove = false
        this.ready.active = true}}Copy the code

The implementation looks something like this:

7. Write the main character of the game, our plane

With the game scene switching functionality in place, we started writing our main character.

First add a node, give it a picture (Sprite property), and then make a little animation:

Then write the plane to follow the finger (mouse) movement logic, in simple terms, to register a touch movement listening event:

setTouch() {
    / /...
    this.node.on("touchmove".(event) = > {
        // Get the position of the aircraft
        let hero_pos = this.hero.getPosition()
        // Gets the finger (mouse) distance from the last event movement relative to the lower-left corner of the object
        let move_pos = event.getDelta()
        // Add the position of the aircraft to the moving relative position to get the latest position of the aircraft
        this.hero.setPosition(cc.v2(hero_pos.x + move_pos.x, hero_pos.y + move_pos.y))
    }, this);
    / /...
}
Copy the code

Here’s what it looks like:

8. Make the plane capable of firing bullets

Since bullets are reusable resources, we use prefabricated resources here. First, we create a bullet node in the node tree, and then assign a script to the bullet:

Change the bullet’s Y value every frame to give it the effect of firing.

update(dt) {
    this.node.y += 10
}
Copy the code

After the script is configured, we drag the bullet node into explorer to make it a precast, and then write the main logic script, defining a precast first:

/ / the bullet
@property(cc.Prefab)
pre_bullet: cc.Prefab
Copy the code

Then try to generate a bullet at the end of each mouse click (touch your finger away from the screen) :

setTouch() {
    this.node.on("touchend".(event) = > {
        / /...
        // Generate a bullet
        let bullet = cc.instantiate(this.pre_bullet)
        // Attach the bullet to the node tree
        bullet.parent = this.node
        // Get the position of the main character of the plane
        let pos = this.hero.getPosition()
        // Set the initial position of the bullet to the nose of the aircraft
        bullet.setPosition(cc.v2(pos.x, pos.y + this.hero.height / 2))},this);
}
Copy the code

So the plane can fire bullets:

9. Object pool and singleton

It is now possible to keep firing bullets, but it is not possible to create bullet instances without deleting them. If the game is played for a long time, the game will become more and more stuck.

We use the object pool provided by COcos to cache bullets. First, we write a method to generate bullets:

createBullet() {
    // Create bullet method
    let bullet = null
    // Generate bullets from the object pool first
    if (this.bulletPool.size() > 0) {
        // If there are bullet objects in the object pool, use them directly
        bullet = this.bulletPool.get()
    } else {
        // If there are no bullets in the object pool, a new bullet is created
        bullet = cc.instantiate(this.pre_bullet)
    }
    // Get the bullet and hang it under the heel node
    bullet.parent = this.node
    // Get the position of the aircraft
    let pos = this.hero.getPosition()
    // Sets the initial position of the bullet
    bullet.setPosition(cc.v2(pos.x, pos.y + this.hero.height / 2))}Copy the code

Then write the logic for the bullet to die. There are currently three scenarios in which bullets can be recovered:

  1. Restart the game
  2. Back to the home page
  3. Bullet out of the canvas
// Retrieve a single bullet
bulletKilled(bullet) {
    // Method of recovering bullets
    bullet.setPosition(cc.v2(0.0))
    this.bulletPool.put(bullet)
}

// Retrieve all bullets
removeBullets() {
    let children = this.node.children
    for (let i = children.length - 1; i >= 0; i--) {
        let bullet = children[i].getComponent("bullet")
        if (bullet) {
            this.bulletKilled(children[i])
        }
    }
}
Copy the code

The problem I had trouble with during the development of Ammo was how to reference the methods in the main logic class in the bullet script after I created a separate script for the bullet.

By searching the Cocos forum, the big guy gives the answer is to use singletons. The simple use template of singletons is as follows:

@ccclass
export default class Singleton extends cc.Component {

    / / the singleton
    public static instance: Singleton = null

    onLoad() {
        // Initialize the singleton
        if (Singleton.instance == null) {
            Singleton.instance = this
        } else {
            this.destroy()
            return
        }
Copy the code

Export the main logical object with the above code, which can be used in bullet scripts like this:

const {ccclass, property} = cc._decorator;
// Import the main logical class
import Singleton from "./main";

@ccclass
export default class NewClass extends cc.Component {

    update(dt) {
        this.node.y += 15
        if(this.node.y > 590) {// Use the main logical singleton
            Singleton.instance.bulletKilled(this.node)
        }
    }
}
Copy the code

10. Add enemy planes

The logic for adding enemy planes is similar to the logic for adding bullets:

  1. Create an enemy node in the node tree
  2. Create an enemy script and associate it with the enemy node
  3. Make enemy nodes into PerFab
  4. In the main logic preparation of enemy object pool, enemy creation, destruction methods
createEnemy1() {
    // Create enemy 1 method
    let enemy1 = null
    if (this.enemy1Pool.size() > 0) {
        enemy1 = this.enemy1Pool.get()
    } else {
        enemy1 = cc.instantiate(this.pre_enemy_1)
    }
    enemy1.parent = this.node
    enemy1.setPosition(cc.v2(0.590))}enemy1Killed(enemy1){
    this.enemy1Pool.put(enemy1)
}

removeEnemy1s() {
    let children = this.node.children
    for (let i = children.length - 1; i >= 0; i--) {
        let enemy1 = children[i].getComponent("enemy1")
        if (enemy1) {
            this.bulletKilled(children[i])
        }
    }
}
Copy the code

When you set enemy movement in the enemy script, it looks something like this:

Outside the chain picture archiving failure, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – jujgCh49-1647963420444) (s21.aconvert.com/convert/p3r…)”

The bullet collided with the enemy aircraft

When we had enemy planes, we allowed our planes to fire bullets that could hit them.

First we add collision components for enemy planes and bullets (remember to add groups for bullets and enemy planes) :

Then go to project Settings where enemy planes and bullets can collide:

Next edit the frame animation of the dead enemy plane (and edit an animation of the normal enemy plane) :

Then start the collision in the main logic:

// Enable the collision detection system. If it is not enabled, it cannot be detected
cc.director.getCollisionManager().enabled = true;
Copy the code

After the collision is enabled, write a method for the bullet to handle the collision:

onCollisionEnter(other, self) {
    if (self.tag == 1) {
        // Ordinary bullets hit ordinary enemy planes
        Singleton.instance.bulletKilled(this.node)
    }
    if (other.tag == 2) {// Hit a normal enemy aircraft
        let enemy = other.getComponent("enemy_1")
        if(enemy && ! enemy.isDie){ enemy.hit() } } }Copy the code

Added processing logic to enemy aircraft after being hit:

hit(){
    // After hitting, the status is set to death
    this.isDie = true
    // Play frame animation
    let anim = this.getComponent(cc.Animation)
    anim.play('enemy_1_die')}over(){
    // Put the enemy plane back into the object pool after the animation plays, and wait for the next time it appears
    Singleton.instance.enemy1Killed(this.node)
}
Copy the code

Remember to set a random value for the enemy’s birth coordinates

/ /...
enemy1.parent = this.node
let randomX = 295 - 590 * Math.random()
enemy1.setPosition(cc.v2(randomX, 590))
/ /...
Copy the code

The result looks like this:

Well, the last set to this first, efforts to update the next set……

Reference: video link address