Created
June 27, 2026 01:54
-
-
Save EncodeTheCode/0b29444a709e393f1db82bcdccc08bd1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Tank Physics System</title> | |
| <style> | |
| body { margin:0; overflow:hidden; background:#111; } | |
| canvas { display:block; } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="game"></canvas> | |
| <script> | |
| const canvas = document.getElementById("game"); | |
| const ctx = canvas.getContext("2d"); | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| /* ========================= | |
| INPUT | |
| ========================= */ | |
| const keys = {}; | |
| const mouse = { x: 0, y: 0 }; | |
| window.addEventListener("keydown", e => keys[e.key.toLowerCase()] = true); | |
| window.addEventListener("keyup", e => keys[e.key.toLowerCase()] = false); | |
| window.addEventListener("mousemove", e => { | |
| mouse.x = e.clientX; | |
| mouse.y = e.clientY; | |
| }); | |
| /* ========================= | |
| ANGLE LERP (important) | |
| ========================= */ | |
| function lerpAngle(a, b, t) { | |
| const diff = Math.atan2(Math.sin(b - a), Math.cos(b - a)); | |
| return a + diff * t; | |
| } | |
| /* ========================= | |
| SPRITE LAYER | |
| ========================= */ | |
| class SpriteLayer { | |
| constructor(src, offsetX = 0, offsetY = 0) { | |
| this.img = new Image(); | |
| this.img.src = src; | |
| this.offsetX = offsetX; | |
| this.offsetY = offsetY; | |
| this.rotation = 0; | |
| this.scale = 1; | |
| this.isTurret = false; | |
| } | |
| draw(ctx, x, y, parentRotation = 0) { | |
| if (!this.img.complete) return; | |
| ctx.save(); | |
| ctx.translate(x, y); | |
| ctx.rotate(parentRotation + this.rotation); | |
| ctx.drawImage( | |
| this.img, | |
| this.offsetX - this.img.width / 2, | |
| this.offsetY - this.img.height / 2, | |
| this.img.width * this.scale, | |
| this.img.height * this.scale | |
| ); | |
| ctx.restore(); | |
| } | |
| } | |
| /* ========================= | |
| GAME OBJECT (TANK) | |
| ========================= */ | |
| class GameObject { | |
| constructor(x, y, playerId = 1) { | |
| this.x = x; | |
| this.y = y; | |
| this.vx = 0; | |
| this.vy = 0; | |
| // Movement tuning | |
| this.acceleration = 0.5; | |
| this.maxSpeed = 6; | |
| this.friction = 0.61; | |
| this.gravity = 0.0; | |
| // Rotation tuning (IMPORTANT) | |
| this.rotation = 0; | |
| this.turnSpeed = 0.01; // body turning speed | |
| this.velocityRotationThreshold = 0.001; | |
| this.health = 100; | |
| this.playerId = playerId; | |
| this.layers = []; | |
| this.turretFollowMouse = false; | |
| } | |
| addLayer(layer) { | |
| this.layers.push(layer); | |
| return layer; | |
| } | |
| update() { | |
| /* ========================= | |
| PLAYER 1 CONTROLS (WASD) | |
| ========================= */ | |
| if (this.playerId === 1) { | |
| if (keys["w"]) this.vy -= this.acceleration; | |
| if (keys["s"]) this.vy += this.acceleration; | |
| if (keys["a"]) this.vx -= this.acceleration; | |
| if (keys["d"]) this.vx += this.acceleration; | |
| // clamp speed | |
| this.vx = Math.max(-this.maxSpeed, Math.min(this.maxSpeed, this.vx)); | |
| this.vy = Math.max(-this.maxSpeed, Math.min(this.maxSpeed, this.vy)); | |
| /* ========================= | |
| TURRET AIM (MOUSE) | |
| ========================= */ | |
| if (this.turretFollowMouse) { | |
| const dx = mouse.x - this.x; | |
| const dy = mouse.y - this.y; | |
| const angle = Math.atan2(dy, dx); | |
| const turret = this.layers.find(l => l.isTurret); | |
| if (turret) turret.rotation = angle; | |
| } | |
| } | |
| /* ========================= | |
| PHYSICS | |
| ========================= */ | |
| this.vx *= this.friction; | |
| this.vy *= this.friction; | |
| this.vy += this.gravity; | |
| this.x += this.vx; | |
| this.y += this.vy; | |
| /* ========================= | |
| BODY ROTATION TOWARD MOVEMENT | |
| ========================= */ | |
| const speed = Math.hypot(this.vx, this.vy); | |
| if (speed > this.velocityRotationThreshold) { | |
| const targetAngle = Math.atan2(this.vy, this.vx); | |
| this.rotation = lerpAngle( | |
| this.rotation, | |
| targetAngle, | |
| this.turnSpeed | |
| ); | |
| } | |
| } | |
| draw(ctx) { | |
| for (let layer of this.layers) { | |
| layer.draw(ctx, this.x, this.y, this.rotation); | |
| } | |
| } | |
| } | |
| /* ========================= | |
| GAME SETUP | |
| ========================= */ | |
| const objects = []; | |
| /* Tank */ | |
| const tank = new GameObject(400, 300, 1); | |
| // body | |
| const body = new SpriteLayer("https://i.imgur.com/4YfKX5c.png"); | |
| tank.addLayer(body); | |
| // turret | |
| const turret = new SpriteLayer("https://i.imgur.com/5cX1S0m.png"); | |
| turret.isTurret = true; | |
| tank.addLayer(turret); | |
| // enable mouse turret | |
| tank.turretFollowMouse = true; | |
| objects.push(tank); | |
| /* ========================= | |
| GAME LOOP | |
| ========================= */ | |
| function loop() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| for (let obj of objects) obj.update(); | |
| for (let obj of objects) obj.draw(ctx); | |
| requestAnimationFrame(loop); | |
| } | |
| loop(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment