| Cara membuat game Square Jump dengan menggunakan HTML5 Canvas |
Kode Game HTML5:
<canvas id="gameCanvas"></canvas>
<div onclick="closeGame()" style="color: #333333; cursor: pointer; font-size: 30px; font-weight: bold; position: absolute; right: 20px; top: 10px; z-index: 10000;">×</div>
<div id="uiOverlay" style="font-family: Arial; left: 20px; pointer-events: none; position: absolute; top: 10px;">
<div id="scoreText">Skor: 0</div>
<div id="speedText" style="color: #666666; font-size: 12px;">Speed: 100%</div>
</div>
</div>
<div style="padding: 20px; text-align: center;">
<button onclick="openGame()" style="background: rgb(231, 76, 60); border-radius: 10px; border: none; color: white; cursor: pointer; font-size: 18px; padding: 15px 30px;">Mainkan Game (Fullscreen)</button>
</div>
<script>
const container = document.getElementById("gameContainer");
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
// Tambahkan animationId di sini
let player, obstacles, score, gameActive, frame, gameSpeed, lastSpeedUpdate, animationId;
function initGame() {
// PERBAIKAN: Hentikan loop lama sebelum memulai yang baru
if (animationId) {
cancelAnimationFrame(animationId);
}
// Setup Canvas
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
player = {
x: 50,
y: canvas.height - 80,
w: 40,
h: 40,
dy: 0,
jumpForce: canvas.height * 0.025,
gravity: canvas.height * 0.0012,
grounded: false
};
obstacles = [];
score = 0;
gameActive = true;
frame = 0;
gameSpeed = 5;
lastSpeedUpdate = Date.now();
document.getElementById("scoreText").innerText = "Skor: 0";
document.getElementById("speedText").innerText = "Speed: 100%";
loop();
}
function update() {
if (!gameActive) return;
// Logika Kenaikan Kecepatan
let now = Date.now();
if (now - lastSpeedUpdate > 15000) {
gameSpeed *= 1.1;
lastSpeedUpdate = now;
document.getElementById("speedText").innerText = "Speed: " + Math.round((gameSpeed/5)*100) + "%";
}
// Player Gravity
if (!player.grounded) {
player.dy += player.gravity;
player.y += player.dy;
}
if (player.y + player.h >= canvas.height - 40) {
player.y = canvas.height - 40 - player.h;
player.grounded = true;
}
// Spawn Rintangan
let spawnRate = Math.max(60, 120 - Math.floor(gameSpeed * 2));
if (frame % spawnRate === 0) {
obstacles.push({ x: canvas.width, y: canvas.height - 80, w: 30, h: 40 });
}
// Gerakkan Rintangan
for (let i = obstacles.length - 1; i >= 0; i--) {
let obs = obstacles[i];
obs.x -= gameSpeed;
// Cek Tabrakan
if (player.x < obs.x + obs.w && player.x + player.w > obs.x &&
player.y < obs.y + obs.h && player.y + player.h > obs.y) {
gameActive = false;
alert("Game Over! Skor Anda: " + score);
initGame(); // Restart
return; // Keluar dari update agar tidak double run
}
// Hapus rintangan yang lewat
if (obs.x + obs.w < 0) {
obstacles.splice(i, 1);
score++;
document.getElementById("scoreText").innerText = "Skor: " + score;
}
}
frame++;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Tanah
ctx.fillStyle = "#333";
ctx.fillRect(0, canvas.height - 40, canvas.width, 40);
// Pemain
ctx.fillStyle = "#e74c3c";
ctx.fillRect(player.x, player.y, player.w, player.h);
// Rintangan
ctx.fillStyle = "#2c3e50";
obstacles.forEach(obs => ctx.fillRect(obs.x, obs.y, obs.w, obs.h));
}
function loop() {
if (!gameActive) return;
update();
draw();
// Simpan ID loop ke variabel animationId
animationId = requestAnimationFrame(loop);
}
function jump() {
if (player.grounded) {
player.dy = -player.jumpForce;
player.grounded = false;
}
}
function openGame() {
container.style.display = "block";
if (container.requestFullscreen) container.requestFullscreen();
else if (container.webkitRequestFullscreen) container.webkitRequestFullscreen();
if (screen.orientation && screen.orientation.lock) {
screen.orientation.lock("landscape").catch(e => console.log("Rotate manual!"));
}
initGame();
}
function closeGame() {
gameActive = false;
if (animationId) cancelAnimationFrame(animationId);
container.style.display = "none";
if (document.fullscreenElement && document.exitFullscreen) {
document.exitFullscreen();
}
}
// Event Listeners
window.addEventListener("keydown", (e) => { if(e.code === "Space") jump(); });
container.addEventListener("touchstart", (e) => { e.preventDefault(); jump(); }, {passive: false});
container.addEventListener("mousedown", jump);
</script>
Mari kita bedah kode tersebut bagian demi bagian. Bayangkan kode ini seperti sebuah mesin: ada kerangka, bahan bakar, dan sistem kontrolnya.
Berikut adalah penjelasan lengkap alur kodenya:
1. Deklarasi Variabel (Otak Permainan)
let player, obstacles, score, gameActive, frame, gameSpeed, lastSpeedUpdate, animationId;
Di sini kita menyiapkan "wadah" untuk menyimpan data.
player: Data posisi kotak merah.obstacles: Daftar kotak musuh yang muncul.gameSpeed: Seberapa cepat musuh bergerak.animationId: (Penting) Ini adalah nomor identitas unik untuk setiap putaran animasi. Kita butuh ini untuk menghentikan "mesin" lama sebelum menyalakan yang baru.
2. Fungsi initGame() (Persiapan Awal)
Fungsi ini dipanggil setiap kali game dimulai atau di-restart.
Pembersihan:
cancelAnimationFrame(animationId)bertugas mematikan sisa-sisa animasi dari game sebelumnya agar tidak menumpuk (biang kerok bug kecepatan).Reset Data: Skor dikembalikan ke 0, musuh dikosongkan, dan kecepatan (
gameSpeed) diatur ulang ke angka 5.Player: Membuat objek pemain dengan gravitasi dan kekuatan lompat yang disesuaikan dengan tinggi layar (
canvas.height).
3. Fungsi update() (Logika Gerakan)
Ini adalah bagian yang menghitung apa yang terjadi di setiap detik.
Kenaikan Kecepatan: Mengecek waktu. Jika sudah lewat 15 detik,
gameSpeeddikalikan 1.1 (naik 10%).Gravitasi: Jika pemain tidak menyentuh tanah (
!player.grounded), koordinat Y-nya ditambah terus agar jatuh ke bawah.Spawn Musuh: Menggunakan variabel
frame. Setiap kelipatan jumlah frame tertentu (misal tiap 100 frame), satu musuh baru ditambahkan ke dalam daftarobstacles.Deteksi Tabrakan: Mengecek apakah posisi kotak pemain bertabrakan dengan kotak musuh. Jika "IYA", maka
gameActivejadifalse(mati) dan memanggilinitGame()untuk mengulang.
4. Fungsi draw() (Visualisasi)
Setelah hitungan di update() selesai, fungsi ini bertugas menggambar hasilnya ke layar.
ctx.clearRect: Menghapus layar lama agar tidak meninggalkan jejak (seperti menghapus papan tulis).ctx.fillRect: Menggambar kotak-kotak (tanah, pemain, dan musuh) dengan warna yang sudah ditentukan.
5. Fungsi loop() (Jantung Permainan)
function loop() {
if (!gameActive) return;
update();
draw();
animationId = requestAnimationFrame(loop);
}
Ini adalah mesin utama. Fungsi ini memanggil dirinya sendiri terus-menerus (sekitar 60 kali per detik).
Cek apakah game masih aktif.
Hitung posisi baru (
update).Gambar ke layar (
draw).Minta browser menjalankan
looplagi (requestAnimationFrame).
6. Fungsi jump(), openGame(), & closeGame() (Kontrol)
jump(): Memberi sentakan ke atas (dy = -jumpForce) hanya jika pemain sedang di tanah.openGame(): Menampilkan kontainer game, mengaktifkan mode Fullscreen, dan memaksa layar jadi Landscape (mendatar) agar nyaman dimainkan di HP.closeGame(): Menghentikan semua animasi, menyembunyikan layar game, dan keluar dari mode Fullscreen.
7. Event Listeners (Sensor Input)
Bagian paling bawah menghubungkan gerakan manusia ke dalam game:
keydown: Mendeteksi tombol Spasi di keyboard.touchstart: Mendeteksi sentuhan jari di layar HP.mousedown: Mendeteksi klik mouse.
Ringkasan Alur Kerja:
Klik Tombol ->
openGame()->initGame().initGame()nyalakanloop().loop()lari 60x per detik menjalankanupdate()dandraw().Nabrak ->
alert->initGame()(semua di-reset dari nol).
Apakah ada bagian spesifik dari logika matematika di dalam update() (seperti cara menghitung tabrakan) yang ingin kamu pelajari lebih dalam?