Using vite, node.js make Suika Game

ChengKang Tan
5 min readNov 18, 2023

--

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 :

Please remove all the values inside the main.js file.
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!

Reference :

--

--

ChengKang Tan

NCKU_CSIE šŸ’»Master print(" I want to share and record my knowledge through this website.") šŸŒŒ