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!