Using vite, node.js make Suika Game
Today, Iām going to create a Suika game, which is currently trending.
āResultā :
āFirst : Install node.js (LTS Version)
āSecond :
ā project name : xxxxx
ā Select => vanilla
ā Select => javascript
[ If youāve entered the command above, please continue with the following command ]
ā cd project name
ā npm install
ā npm run dev
If youāve reached the homepage, the setup is complete! !
ā Warning ā :
Below, the code is divided into many parts for better understanding.
š„If youād like to see the entire code directly, please check the GitHub link at the bottom. Thank you.
āCoding time :
import { Bodies, Body, Events, Engine, Render, Runner, World} from "matter-js";
import { FRUITS_BASE } from "./fruits";
Here, we are importing the Matter.js physics engine library.
Important !
The fruits images and JSON file can be downloaded from the link below.
[detail for each fruits image]
[Images]
After downloading the JSON file, you will find the following contents inside. Please customize the image paths and names according to your preferences.
Initial + background setting :
function createWorld(delta) {
const engine = Engine.create();
const element = document.createElement('div');
document.body.appendChild(element);
const render = Render.create(
{
element,
engine: engine,
options:{
// Set wireframes True, can see how physcics engine operate.
wireframes: false,
background: "#F7F4C8",
width: 620,
height: 850,
}
}
);
Specify the colors for walls in each direction :
const world = engine.world;
// x coordinate + 15, y coordinate + 395
// wall Size width 30, height 790
const leftWall = Bodies.rectangle(15, 395, 30, 790, {
// let wall don't move anymore.
isStatic: true,
render: {fillStyle: "#E6B143"}
});
const rightWall = Bodies.rectangle(605, 395, 30, 790, {
isStatic: true,
render: {fillStyle: "#E6B143"}
});
const topLine = Bodies.rectangle(310, 150, 620, 2,{
name: "topLine",
isStatic: true,
// A line to check if the fruits are overflowing or not.
isSensor: true,
render: {fillStyle: "#E6B143"}
});
const ground = Bodies.rectangle(310, 820, 620, 60, {
isStatic: true,
render: {fillStyle: "#E6B143"}
});
// add to World, Its like html body part
World.add(engine.world, [leftWall, rightWall, ground, topLine]);
// You can see the wall right now.
Render.run(render);
* Bodies.rectangle(x+?, y+?, width, height)
* isStatic : A function to fix the created blocks in place. If they are not fixed, they will keep falling due to the physics engine.
* isSensor : A line to check if the fruits are overflowing or not.
Initialize :
let currentBody = null;
let currentFruit = null;
let disableAction = false;
let interval = null;
let num_suika = 0;
function adddFruit(){
// random
const index = Math.floor(Math.random() * 5);
// import the images
const fruit = FRUITS_BASE[index];
const body = Bodies.circle(300, 50, fruit.radius, {
index: index,
isSleeping: true,
render: {
sprite: { texture: `${fruit.name}.png` }
},
restitution: 0.2,
});
currentBody = body;
currentFruit = fruit;
World.add(world, body);
}
* index : save fruit index number.
* isSleeping : A function to keep the objects fixed on the screen, preventing them from falling.
* restitution: Elasticity (0~1).
Movint part :
window.onkeydown = (event) => {
if (disableAction) {
return;
}
switch (event.code) {
case "KeyA":
if (interval)
return;
interval = setInterval(() => {
if (currentBody.position.x - currentFruit.radius > 30)
Body.setPosition(currentBody,{
x: currentBody.position.x-1,
y: currentBody.position.y,
});
}, 5)
break;
case "KeyD":
if (interval)
return;
interval = setInterval(() => {
if (currentBody.position.x + currentFruit.radius < 590)
Body.setPosition(currentBody,{
x: currentBody.position.x+1,
y: currentBody.position.y,
});
}, 5)
break;
case "KeyS":
currentBody.isSleeping = false;
disableAction = true;
setTimeout(() => {
adddFruit();
disableAction = false;
}, 1200);
break;
}
}
window.onkeyup = (event) => {
switch(event.code){
case "KeyA":
case "KeyD":
clearInterval(interval);
interval = null;
}
}
- If
disableAction
is true, meaning that the fruits are currently falling, keyboard input is disabled to prevent any response. - To achieve smooth fruit movement,
setInterval
is utilized. To make the fruit visible again. currentBody.isSleeping
is set to false, allowing the fruit to fall.
Collision part :
Events.on(engine, "collisionStart", (event) => {
event.pairs.forEach((collision) => {
if (collision.bodyA.index === collision.bodyB.index) {
// bodyA.index or bodyB.index both OK because it's same fruits.
const index = collision.bodyA.index;
// If the collision involves the final stage, which is the watermelon, the execution is skipped.
if (index === FRUITS_BASE.length - 1) {
return;
}
World.remove(world, [collision.bodyA, collision.bodyB]);
const newFruit = FRUITS_BASE[index + 1];
// render new fruit !
const newBody = Bodies.circle(
collision.collision.supports[0].x,
collision.collision.supports[0].y,
newFruit.radius,
{
render: {
sprite: { texture: `${newFruit.name}.png` },
},
index: index + 1,
}
);
// if concate result was a watermelon
World.add(world, newBody)
if (newBody.index == 10) {
num_suika++;
}
}
End part (continue the code from the previous collision part.) :
if (!disableAction && (collision.bodyA.name === "topLine" || collision.bodyB.name === "topLine"))
{
alert("Game over (Press F5 to Refresh the Page to Restart)");
}
// If there are two watermelons alreadyin your body.
else if (num_suika === 2)
{
alert("You Win (Press F5 to Refresh the Page to Restart)");
}
})
});
// This is done to drop the current fruit and load the next one.
adddFruit();
}
# execute the game
createWorld(1000/60)
Full Code :
Conclusion :
If there are any difficult parts to understand, please leave a comment below. Iāll respond promptly.
Learning a new language is often more efficient when diving into projects right away, rather than starting from scratch. Understanding what is commonly used and where specific syntax is frequently applied is crucial. I appreciate those who share such information, and Iāll also work hard to study.
Thank you to everyone who reads the text, and letās all work hard together!