This post is on my personal blog shymean.com

Recently, a lot of melons have been posted on weibo, and the game “synthesize big watermelon” is also very popular. It was quite fun to play for a while. Studied the principle, found that the version of the current spread is the version of the magic to change the compilation, the code after compression is not readable, so I decided to implement one.

This project is mainly used for the practice of Cocos Creator. All art materials and audio materials are from www.wesane.com/game/654/.

Thanks to the original author, hats off to every game developer!

All the code and materials for this article are available on Github, as well as online preview addresses

The game logic

The whole game logic is relatively simple, combining the core gameplay of Tetris and elimination games

  • It’s producing a fruit
  • Click on the screen to move the fruit to its X-axis position and free fall
  • Each fruit collides with other fruits, and when two identical fruits collide, they merge and become a higher fruit

There are 11 types of fruit,

The goal of the game is to synthesize the highest fruit: big watermelon! The game ends when the pile of fruit exceeds the top red line

Sort out the core logic that needs to be implemented

  • Produce fruit
  • Fruit falls and collides
  • Fruit eliminates animation effects and upgrade logic

Preparatory work

Basic concept of Cocos Creator

The whole project is implemented using Cocos Creator V2.4.3. It is suggested that students who know it for the first time can go through the official documents first. This paper will not introduce much about the use of Creator (mainly because I am not familiar with HAH).

The game is material

First, you need to prepare art resources. All art materials and audio materials come from www.wesane.com/game/654/.

First, visit the game website, open the Network panel, you can see all the art resources the game depends on, we can download the files we need

Required image resources include

  • 11 fruit maps
  • Each fruit composite effect map is included
    • A picture of a fruit
    • A picture of a round water drop
    • An explosion map
  • When two watermelons are synthesized, they will have the effect of lighting and sprinkling, which will not be realized in the limited time

For audio files, you can select. Mp3 in the Filter bar to quickly Filter the corresponding resources.

  • Fruit cancels when the explosion and the sound of water

Create the game scene and background

Open Cocos Creator and create a new project (you can also import the source code from Github directly).

Then remember to drag the material resource you just downloaded into the resource Manager in the lower right corner.

Create scene and background nodes

After the project is initialized, create a new game Scene in the lower left corner of explorer and name it “Game” as the main Scene of the game

Once created, you can see the scene named Game that you just created in Assets in The Explorer.

Select the Game scene, and you can see the Canvas root node of the scene in the hierarchy manager in the upper left corner. The cocos default Canvas is 960*640 in landscape. Select the root node and adjust the width and height to 640*960 in the property Inspector on the right

To create the background layer, create a new Background node under the Canvas node. Since the entire background is a solid color #FBE79D, use a monochrome Sprite to fill it

Similarly, adjust the width and height of the background node to the size of the entire canvas. Since the default anchor points are 0.5*0.5, the entire canvas will be completely filled at this time.

Now the entire game scene looks something like this

The next step is to design the logical script part of the game

Scenario Script Components

Create a new js script in the assets directory, and use the convention to call it game.js. Creator will generate a template file with the basic cc.class

First associate the script component with the node, select the Canvas root node, add the component in the property inspector on the right, and then select the Game component you just created

Then write the specific code logic and open the game.js file (it is recommended to use vscode or webstrom to open the whole project root directory for editing).

The initial code in there looks something like this

// Game.js
cc.Class({
    extends: cc.Component,

    properties: {},onLoad(){},start(){}})Copy the code

We need to maintain the logic of the entire game here, and then gradually add the code.

Create a fruit

Fruit is a core element throughout the game and is created and destroyed frequently.

Generate a single fruit prefab resource

This dynamically created node can be controlled by Prefab resources,

The easiest way to make a Prefab is to drag the resource from the resource manager to the scene editor, and then drag the node from the hierarchy manager back to the resource manager.

Take the lowest-graded fruit, the grape

We then remove the nodes from the hierarchy manager so that we have a fruit prefab resource that can be dynamically generated using code from the prefab resource in the script component.

Modify game.js and add a property fruitPrefab of type cc.prefab,

// Game.js
properties: {
    fruitPrefab: {
        default: null.type: cc.Prefab
    },
}
Copy the code

Back to Creator. Select the Canvas node and you can now see and modify the property in the Game Component section of the property inspector. We drag the prefab resource we just created from the resource manager to here. During initialization, cocos is responsible for initializing the corresponding property data

Creating individual fruits

Back in game.js, start writing the real logic: creating a grape

// Game.js
onLoad(){
    let fruit = cc.instantiate(this.fruitPrefab);
    fruit.setPosition(cc.v2(0.400));

    this.node.addChild(fruit);
}
Copy the code

In preview mode, you can see a grape at the top of the screen

Nice, very good start!

Also, since Fruit also contains some specific logic, we can add a Fruit script component to it, although it doesn’t seem useful yet, right

Creating the Fruit script component is similar to creating the Game component above, then select the prefab you just made and edit it again, associating it with the Fruit user script component.

Dynamically maintain a variety of fruits

There are 11 fruits in the game (which can be added or changed), and it would be too tedious to manually generate premade resources for each fruit and then initialize them separately. We need to figure out how to dynamically render multiple fruits.

We need to get the map information for each fruit and then select the map when instantiating the fruit. The easiest way to do this is to maintain a configuration table with the data fields of ID and iconSF for each row

const FruitItem = cc.Class({
    name: 'FruitItem'.properties: {
        id: 0.// The type of fruit
        iconSF: cc.SpriteFrame // Map resources}});Copy the code

Then add a fruits attribute to the Game script component to hold the configuration information for each fruit, which is of type array, and the element in the array is of type FruitItem that you just created

// Game.js
properties: {
    fruits: {
        default: [].type: FruitItem
    },
}
Copy the code

Going back to the editor, you can see that there is a Fruits property under the Game component property, change its length to 11, and then write the ID of each fruit in turn, while pasting its map resources from the resource editor (manual).

In this way, we only need to pass in the id of the fruit we want to make, and then we can get the corresponding configuration information and dynamically modify the map

This initialization logic should be maintained by the Fruit itself, so in the Fruit component we just created, we expose an init interface

// Fruit.js
properties: {
    id: 0,},// Instances can be called in other components
init(data) {
    this.id = data.id
    // Modify the map resources according to the parameters passed in
    const sp = this.node.getComponent(cc.Sprite)
    sp.spriteFrame = data.iconSF
},
Copy the code

Then modify the code above to initialize the fruit

// Game.js
createOneFruit(num) {
    let fruit = cc.instantiate(this.fruitPrefab);
    // Obtain the configuration information
    const config = this.fruits[num - 1]

    // Get the node's Fruit component and call the instance method
    fruit.getComponent('Fruit').init({
        id: config.id,
        iconSF: config.iconSF
    });
}
Copy the code

In this way, you can create a variety of fruits happily

Listen for click events

Cocos provides a variety of event monitoring, front-end and client students will be familiar with.

The entire Game creates a fruit when the screen is clicked, just by listening for the global click event, and this logic is also in the Game script component

onLoad() {
    // Listen for click events
    this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this)},onTouchStart(){
    this.createOneFruit(1) // Generate fruit
}
Copy the code

The actual game also needs to deal with the details of randomly generated fruit and the last fruit falling on the X-axis of the click, which I won’t go into here.

Physical systems: free-falling bodies colliding with rigid bodies

The above processing of fruit creation logic, in the whole game, fruit can produce falling and elastic collision and other physical effects, using coCOS built-in physics engine, can be very convenient to achieve

If you are not familiar with the CoCOS engine, you can check out the official demo, which is more detailed (at least it is easier to understand than the document).

Enable the physics engine and collision detection

The first is to start the physics engine and set the gravity

const instance = cc.director.getPhysicsManager()
instance.enabled = true
// instance.debugDrawFlags = 4
instance.gravity = cc.v2(0, -960);
Copy the code

You then need to enable collision detection, which is off by default

const collisionManager = cc.director.getCollisionManager();
collisionManager.enabled = true
Copy the code

Then set up the surrounding walls for collisions so that the fruit doesn’t fall down indefinitely

 // Set the collision zone around
let width = this.node.width;
let height = this.node.height;

let node = new cc.Node();

let body = node.addComponent(cc.RigidBody);
body.type = cc.RigidBodyType.Static;

const _addBound = (node, x, y, width, height) = > {
    let collider = node.addComponent(cc.PhysicsBoxCollider);
    collider.offset.x = x;
    collider.offset.y = y;
    collider.size.width = width;
    collider.size.height = height;
}

_addBound(node, 0, -height / 2, width, 1);
_addBound(node, 0, height / 2, width, 1);
_addBound(node, -width / 2.0.1, height);
_addBound(node, width / 2.0.1, height);

node.parent = this.node;
Copy the code

Now we have the physics engine of the game world turned on, and we need to configure the nodes that need to be affected by the engine, namely our fruit.

Fruit rigid body assembly and collision assembly

Go back to Creator, find our fruit Prefab, and add the physical components

First is Rigid Body components

The next component is PhysicsCircleCollider, because our fruits are all round. If there is an irregular polygonal side like a banana, this will add a lot of work

Then you can see the overall effect (remember to add the click event just now, and then control the random generated fruit type).

Perfect!!!!!

Fruit collision callback

Once the addition is complete, you also need to enable the collision property of the rigidbody component Enabled Contact Listener so that it can receive post-collision callbacks

This collision callback is also written in the Fruit script component,

// Fruit.js
onBeginContact(contact, self, other) {
    // A collision between two identical fruits was detected
    if (self.node && other.node) {
        const s = self.node.getComponent('Fruit')
        const o = other.node.getComponent('Fruit')
        if (s && o && s.id === o.id) {
            self.node.emit('sameContact', {self, other}); }}},Copy the code

In order to ensure that the Fruit component functions are single, we notify game. js of the collision between two identical fruits by event, so that we can register the sameContact custom event handling method when initializes the Fruit

// Game.js
createOneFruit(num) {
    let fruit = cc.instantiate(this.fruitPrefab);
    / /... Other initialization logic
     fruit.on('sameContact'.({self, other}) = > {
        // Both nodes will trigger, temporary processing, see if there is any other way to show only once
        other.node.off('sameContact') 
        // Let's deal with the logic of fruit merging
        this.onSameFruitContact({self, other})
     })
}
Copy the code

This allows us to listen and process the elimination upgrade logic when the fruit collids.

Eliminate fruit animation

Animated version

The simple elimination logic is to delete the two nodes, and then generate a higher level of fruit in the original fruit position, without any animation effect

self.node.removeFromParent(false)
other.node.removeFromParent(false)

const {x, y} = other.node // Get the location of the merged fruit
const id = other.getComponent('Fruit').id

const nextId = id + 1
const newFruit = this.createFruitOnPos(x, y, nextId) // Generate a new fruit at the specified location
Copy the code

It looks a little weird, but it’s really fun!

Analysis of the animation

Open the source site and analyze the animation effects through the Performance panel (I won’t record GIF here).

You can see the animations included when compositing

  • The collision fruit moves towards the center of the original fruit
  • The particle effect of the fruit explosion
  • The particle effect of the water drop explosion
  • Zooming animation of a puddle of juice

There are also sounds of explosions and water

Manage explosive material resources

As the whole animation involves a lot of materials, each fruit contains three maps with different colors. Similar to the above FruitItem, we also use prefab and dynamic resources to manage the corresponding materials and animation logic.

Start by defining a JuiceItem that holds the ingredients needed to explode a single fruit

// Game.js
const JuiceItem = cc.Class({
    name: 'JuiceItem'.properties: {
        particle: cc.SpriteFrame, / / fruit
        circle: cc.SpriteFrame, / / water
        slash: cc.SpriteFrame, / / juice}});Copy the code

Then add a juices property to the Game component

// Game.js
properties: {
    juices: {
        default: [].type: JuiceItem
    },
    juicePrefab: {
        default: null.type: cc.Prefab
    },
}
Copy the code

Now it’s time to sell your labor. Drag and drop all the tiles under the juices attribute

Then add an empty prefab resource to mount the script component, the Juice script below, and remember to mount the prefab resource to your Game’s juicePrefab.

Finally, create a Juice component that implements the animation logic for the explosion, exposing the init interface as well

// Juice.js
cc.Class({
    extends: cc.Component,

    properties: {
        particle: {
            default: null.type: cc.SpriteFrame
        },
        circle: {
            default: null.type: cc.SpriteFrame
        },
        slash: {
            default: null.type: cc.SpriteFrame
        }
    },
    // Expose an init interface as well
    init(data) {
        this.particle = data.particle
        this.circle = data.particle
        this.slash = data.slash
    },
    // Animation effects
    showJuice(){}}Copy the code

So, at merge time, we initialize a Juice node and show the explosion

// Game.js
let juice = cc.instantiate(this.juicePrefab);
this.node.addChild(juice);

const config = this.juices[id - 1]
const instance = juice.getComponent('Juice')
instance.init(config)
instance.showJuice(pos, n) // The corresponding explosion logic
Copy the code

Exploding Particle animation

About particle animation, the Internet can find a lot of information, if interested, can also move to the front end of my sorting out the common animation principle.

The main implementation idea of particle animation is: initialize N particles, control their speed size, direction and life cycle, and then control each particle in accordance with the corresponding parameters to execute the animation, all particles together to form the effect of particle animation.

Having said that, getting the animation right can be a bit of a hassle, with all sorts of random parameters to control.

showJuice(pos, width) {
    / / fruit
    for (let i = 0; i < 10; ++i) {
        const node = new cc.Node('Sprite');
        const sp = node.addComponent(cc.Sprite);
        sp.spriteFrame = this.particle;
        node.parent = this.node;
        / /... A bunch of random parameters

        node.position = pos;
        node.runAction(
            cc.sequence(
                / /... Animation logic for various actions
                cc.callFunc(function () {
                    // Remove particles after animation
                    node.active = false
                }, this)))}/ / water
    for (let f = 0; f < 20; f++) {
        // Use the same spriteFrame as the fruit, switch to this.circle
    }
    
    // The juice has only one tile. Use this.slash to show the regular action zooming and transparent animation
},
Copy the code

Source project code using createFruitL this method to deal with the explosion animation, although after the code compression, but can see the corresponding animation parameters logic, if you do not want to adjust the animation parameters, you can learn from

And so you have an explosion that looks something like this, although it’s a little ugly

sound

Sound effects are implemented by playing AudioClip resources directly through CC. audioEngine

Add two AudioClip resources under the Game component to facilitate access by script components

properties: {
    boomAudio: {
        default: null.type: cc.AudioClip
    },
    waterAudio: {
        default: null.type: cc.AudioClip
    }
}
Copy the code

As above, drag two audio resources from the Resource manager under the properties of the Game component in the properties inspector

onSameFruitContact(){
    cc.audioEngine.play(this.boomAudio, false.1);
    cc.audioEngine.play(this.waterAudio, false.1);
}
Copy the code

So you can hear it when it hits.

Build a package

Once the entire game has been developed, you can choose to build and publish it, package it as a Web-mobile version, and deploy it on a server for others to happily play with

summary

I do not know not to write to the last, seemingly!! The work is done!!

Although there are still many details to achieve, such as adding scores, after the synthesis of watermelon and other functions, interested students can clone to try to modify it. All the code and materials for this article are available on Github, as well as online preview addresses

It took me Saturday afternoon and one night to finish this game. I was not familiar with Cocos Creator, so I spent some time to look up documents and materials, and even to watch some teaching videos on THE B website. However, the sense of achievement and satisfaction of the harvest is still very big, but also serious writing a game.

Finally, I would especially like to thank my daughter-in-law for her help in testing and raising new requirements. Don’t say, I still have to add a click on the fruit to directly eliminate the function!