Update index.html

This commit is contained in:
assada 2024-09-04 19:47:31 +03:00
parent 1667dc1088
commit 20c1ddd378

View File

@ -1,11 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="uk"> <html lang="uk">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PlaSim</title> <title>PlaSim</title>
<style> <style>
body, html { body,
html {
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 100%; width: 100%;
@ -16,29 +17,50 @@
display: block; display: block;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="controlSwitch" style="position: absolute; font-family: Arial, Helvetica, sans-serif; top: 10px; right: 10px; background: rgba(0,0,0,0.5); color: aliceblue; padding: 10px; border-radius: 5px;"> <div
id="controlSwitch"
style="
position: absolute;
font-family: Arial, Helvetica, sans-serif;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.5);
color: aliceblue;
padding: 10px;
border-radius: 5px;
"
>
<label> <label>
<input type="radio" name="controlType" value="keyboard" checked> Клавіатура <input type="radio" name="controlType" value="keyboard" checked />
Клавіатура
</label> </label>
<label> <label>
<input type="radio" name="controlType" value="gamepad"> Геймпад <input type="radio" name="controlType" value="gamepad" /> Геймпад
</label> </label>
</div> </div>
<canvas id="gameCanvas"></canvas> <canvas id="gameCanvas"></canvas>
<script> <script>
let controlType = 'keyboard'; let controlType = "keyboard";
let bullets = [];
let explosionMarks = [];
document.querySelectorAll('input[name="controlType"]').forEach((elem) => { document.querySelectorAll('input[name="controlType"]').forEach((elem) => {
elem.addEventListener("change", function(event) { elem.addEventListener("change", function (event) {
controlType = event.target.value; controlType = event.target.value;
}); });
}); });
const wallTexture = new Image();
wallTexture.src = "wall.png";
const canvas = document.getElementById('gameCanvas'); const obstacleTexture = new Image();
const ctx = canvas.getContext('2d'); obstacleTexture.src = "obs.png";
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
let canvasWidth, canvasHeight; let canvasWidth, canvasHeight;
let tractor, leftTrail, rightTrail, obstacles, mudPattern; let tractor, leftTrail, rightTrail, obstacles, mudPattern;
@ -50,32 +72,262 @@
canvas.height = canvasHeight; canvas.height = canvasHeight;
createMudTexture(); createMudTexture();
} }
window.addEventListener('resize', resizeCanvas); window.addEventListener("resize", resizeCanvas);
resizeCanvas(); resizeCanvas();
let lastFireTime = 0;
const fireInterval = 100;
let lMut = false; let lMut = false;
let rMut = false; let rMut = false;
let explosions = [];
let audioContext;
let isSoundInitialized = false;
let engineOscillator;
let engineGainNode;
function initializeSound() {
if (!audioContext) {
audioContext = new (window.AudioContext ||
window.webkitAudioContext)();
isSoundInitialized = true;
initializeEngineSound();
}
}
function initializeEngineSound() {
if (!isSoundInitialized) return;
engineOscillator = audioContext.createOscillator();
engineGainNode = audioContext.createGain();
engineOscillator.type = "triangle";
engineOscillator.frequency.setValueAtTime(10, audioContext.currentTime);
engineGainNode.gain.setValueAtTime(0, audioContext.currentTime);
engineOscillator.connect(engineGainNode);
engineGainNode.connect(audioContext.destination);
engineOscillator.start();
}
function updateEngineSound() {
if (!engineOscillator) return;
const speed = Math.sqrt(
tractor.leftTrack * tractor.leftTrack +
tractor.rightTrack * tractor.rightTrack
);
const maxSpeed = tractor.speed * 2;
const minFreq = 20;
const maxFreq = 60;
const frequency = minFreq + (maxFreq - minFreq) * (speed / maxSpeed);
engineOscillator.frequency.setTargetAtTime(
frequency,
audioContext.currentTime,
0.1
);
const volume = speed / maxSpeed;
engineGainNode.gain.setTargetAtTime(
volume * 0.8,
audioContext.currentTime,
0.1
);
}
function playShootSound() {
if (!isSoundInitialized) return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = "square";
oscillator.frequency.setValueAtTime(150, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(
0.01,
audioContext.currentTime + 0.2
);
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.01,
audioContext.currentTime + 0.2
);
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.1);
}
function playExplosionSound() {
if (!isSoundInitialized) return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = "sawtooth";
oscillator.frequency.setValueAtTime(100, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(
0.01,
audioContext.currentTime + 0.5
);
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.01,
audioContext.currentTime + 0.5
);
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.5);
}
function stopEngineSound() {
if (engineOscillator) {
engineOscillator.stop();
engineOscillator = null;
}
}
function fireBullet() {
const currentTime = Date.now();
if (currentTime - lastFireTime < fireInterval) {
return;
}
const bulletSpeed = Math.random() * 5 + 5;
const bulletSize = 10;
bullets.push({
x: tractor.x + Math.sin(tractor.angle) * (tractor.height / 2),
y: tractor.y - Math.cos(tractor.angle) * (tractor.height / 2),
angle: tractor.angle,
speed: bulletSpeed,
size: bulletSize,
lifetime: 1000,
});
playShootSound();
lastFireTime = currentTime;
}
function updateAndDrawExplosions() {
explosions = explosions.filter((particle) => {
particle.x += particle.vx;
particle.y += particle.vy;
particle.lifetime--;
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
return particle.lifetime > 0;
});
}
function updateBullets() {
bullets = bullets.filter((bullet) => {
if (bullet.lifetime <= 0) {
createExplosion(bullet.x, bullet.y);
playExplosionSound();
return false;
}
bullet.x += Math.sin(bullet.angle) * bullet.speed;
bullet.y -= Math.cos(bullet.angle) * bullet.speed;
bullet.lifetime -= 25;
return (
bullet.x >= 0 &&
bullet.x <= canvasWidth &&
bullet.y >= 0 &&
bullet.y <= canvasHeight
);
});
}
function createExplosion(x, y) {
const particleCount = 10;
for (let i = 0; i < particleCount; i++) {
const angle = Math.random() * Math.PI * 1.2;
const speed = Math.random() * 2 + 1;
explosions.push({
x: x,
y: y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
radius: Math.random() * 3 + 1,
color: `hsl(${Math.random() * 60 + 15}, 100%, 50%)`,
lifetime: 10,
});
}
explosionMarks.push({
x: x,
y: y,
radius: Math.random() * 5 + 5,
color: `rgba(${Math.floor(Math.random() * 56) + 50}, ${
Math.floor(Math.random() * 56) + 50
}, ${Math.floor(Math.random() * 56) + 50}, 0.7)`,
});
}
function drawExplosionMarks() {
for (let mark of explosionMarks) {
ctx.beginPath();
ctx.arc(mark.x, mark.y, mark.radius, 0, Math.PI * 2);
ctx.fillStyle = mark.color;
ctx.fill();
}
}
function drawBullets() {
ctx.fillStyle = "#505070";
for (let bullet of bullets) {
ctx.fillRect(
bullet.x - bullet.size / 2,
bullet.y - bullet.size / 2,
bullet.size,
bullet.size
);
}
}
function createMudTexture() { function createMudTexture() {
const textureCanvas = document.createElement('canvas'); const textureCanvas = document.createElement("canvas");
textureCanvas.width = 200; textureCanvas.width = 500;
textureCanvas.height = 200; textureCanvas.height = 500;
const textureCtx = textureCanvas.getContext('2d'); const textureCtx = textureCanvas.getContext("2d");
textureCtx.fillStyle = '#8B4513'; textureCtx.fillStyle = "#8dff6c";
textureCtx.fillRect(0, 0, 200, 200); textureCtx.fillRect(0, 0, 500, 500);
for (let i = 0; i < 1000; i++) { for (let i = 0; i < 1000; i++) {
textureCtx.fillStyle = `rgba(${Math.random() * 50 + 100}, ${Math.random() * 30 + 50}, ${Math.random() * 20}, ${Math.random() * 0.5})`; textureCtx.fillStyle = `rgba(${Math.random() * 20 + 20}, ${
Math.random() * 90 + 100
}, ${Math.random() * 20}, ${Math.random() * 0.5})`;
textureCtx.beginPath(); textureCtx.beginPath();
textureCtx.arc(Math.random() * 200, Math.random() * 200, Math.random() * 2, 0, Math.PI * 2); textureCtx.arc(
Math.random() * 500,
Math.random() * 500,
Math.random() * 2,
0,
Math.PI * 20
);
textureCtx.fill(); textureCtx.fill();
} }
mudPattern = ctx.createPattern(textureCanvas, 'repeat'); mudPattern = ctx.createPattern(textureCanvas, "repeat");
} }
function initGame() { function initGame() {
tileSize = 64;
tractor = { tractor = {
x: canvasWidth / 2, x: canvasWidth / 2,
y: canvasHeight / 2, y: canvasHeight / 2,
@ -94,29 +346,34 @@
leftTrackOffset: 0, leftTrackOffset: 0,
rightTrackOffset: 0, rightTrackOffset: 0,
leftBrake: false, leftBrake: false,
rightBrake: false rightBrake: false,
}; };
leftTrail = []; leftTrail = [];
explosionMarks = [];
rightTrail = []; rightTrail = [];
obstacles = []; obstacles = [];
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
obstacles.push({ obstacles.push({
x: Math.random() * canvasWidth, x: Math.random() * (canvasWidth - tileSize * 2 - 32) + tileSize,
y: Math.random() * canvasHeight, y: Math.random() * (canvasHeight - tileSize * 2 - 46) + tileSize,
radius: 15 width: 32,
height: 46,
}); });
} }
stopEngineSound();
initializeEngineSound();
createMudTexture(); createMudTexture();
} }
initGame(); initGame();
const maxTrailLength = 500; const maxTrailLength = 5000;
const trailLifetime = 5000; const trailLifetime = 50000;
const trailInterval = 5; const trailInterval = 4;
let frameCount = 0; let frameCount = 0;
@ -125,8 +382,13 @@
ctx.translate(tractor.x, tractor.y); ctx.translate(tractor.x, tractor.y);
ctx.rotate(tractor.angle); ctx.rotate(tractor.angle);
ctx.fillStyle = 'green'; ctx.fillStyle = "#333";
ctx.fillRect(-tractor.width / 2, -tractor.height / 2, tractor.width, tractor.height); ctx.fillRect(
-tractor.width / 2,
-tractor.height / 2,
tractor.width,
tractor.height
);
const trackHeight = tractor.height; const trackHeight = tractor.height;
const trackWidth = 7; const trackWidth = 7;
@ -134,9 +396,13 @@
ctx.globalAlpha = 0.8; ctx.globalAlpha = 0.8;
ctx.fillStyle = '#333'; ctx.fillStyle = "#333";
for (let i = 0; i < trackHeight / segmentHeight; i++) { for (let i = 0; i < trackHeight / segmentHeight; i++) {
const alpha = Math.max(0.5, 1 - Math.abs((tractor.leftTrackOffset + i * segmentHeight) % 20) / 10); const alpha = Math.max(
0.5,
1 -
Math.abs((tractor.rightTrackOffset + i * segmentHeight) % 20) / 10
);
ctx.globalAlpha = alpha; ctx.globalAlpha = alpha;
ctx.fillRect( ctx.fillRect(
-tractor.width / 2 - trackWidth, -tractor.width / 2 - trackWidth,
@ -146,9 +412,13 @@
); );
} }
ctx.fillStyle = '#333'; ctx.fillStyle = "#333";
for (let i = 0; i < trackHeight / segmentHeight; i++) { for (let i = 0; i < trackHeight / segmentHeight; i++) {
const alpha = Math.max(0.5, 1 - Math.abs((tractor.rightTrackOffset + i * segmentHeight) % 20) / 10); const alpha = Math.max(
0.5,
1 -
Math.abs((tractor.leftTrackOffset + i * segmentHeight) % 20) / 10
);
ctx.globalAlpha = alpha; ctx.globalAlpha = alpha;
ctx.fillRect( ctx.fillRect(
tractor.width / 2, tractor.width / 2,
@ -160,26 +430,54 @@
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
ctx.fillStyle = 'lightblue'; ctx.fillStyle = "lightblue";
ctx.fillRect(-tractor.width / 4, -tractor.height / 4, tractor.width / 2, tractor.height / 2); ctx.fillRect(
-tractor.width / 4,
-tractor.height / 4,
tractor.width / 2,
tractor.height / 2
);
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(0, -tractor.height / 2 - 20); ctx.moveTo(0, -tractor.height / 2 - 20);
ctx.lineTo(10, -tractor.height / 2 - 10); ctx.lineTo(10, -tractor.height / 2 - 10);
ctx.lineTo(-10, -tractor.height / 2 - 10); ctx.lineTo(-10, -tractor.height / 2 - 10);
ctx.closePath(); ctx.closePath();
ctx.fillStyle = 'red'; ctx.fillStyle = "red";
ctx.fill(); ctx.fill();
ctx.restore(); ctx.restore();
} }
const shadowCanvas = document.createElement("canvas");
shadowCanvas.width = 32;
shadowCanvas.height = 46;
const shadowCtx = shadowCanvas.getContext("2d");
obstacleTexture.onload = function () {
shadowCtx.drawImage(obstacleTexture, 0, 0);
};
function drawObstacles() { function drawObstacles() {
ctx.fillStyle = 'orange'; const t = Date.now() * 0.002;
for (let obstacle of obstacles) { for (let obstacle of obstacles) {
ctx.beginPath(); const yOffset = Math.sin(t + obstacle.x * 0.1) * 2;
ctx.arc(obstacle.x, obstacle.y, obstacle.radius, 0, Math.PI * 2); ctx.save();
ctx.fill();
ctx.shadowColor = "rgba(0, 0, 0, 0.3)";
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.drawImage(
obstacleTexture,
obstacle.x,
obstacle.y - yOffset,
obstacle.width,
obstacle.height
);
ctx.restore();
} }
} }
@ -191,12 +489,15 @@
function drawTrail() { function drawTrail() {
const currentTime = Date.now(); const currentTime = Date.now();
ctx.lineWidth = 3; ctx.lineWidth = 10;
ctx.strokeStyle = 'rgba(60, 30, 15, 0.5)'; ctx.strokeStyle = "rgba(60, 30, 15, 0.5)";
for (let trail of [leftTrail, rightTrail]) { for (let trail of [leftTrail, rightTrail]) {
ctx.beginPath(); ctx.beginPath();
for (let i = 0; i < trail.length; i++) { for (let i = 0; i < trail.length; i++) {
const alpha = Math.max(0, 1 - (currentTime - trail[i].time) / trailLifetime); const alpha = Math.max(
0,
1 - (currentTime - trail[i].time) / trailLifetime
);
if (alpha > 0) { if (alpha > 0) {
ctx.globalAlpha = alpha; ctx.globalAlpha = alpha;
if (i === 0) { if (i === 0) {
@ -211,8 +512,8 @@
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
const cutoffTime = currentTime - trailLifetime; const cutoffTime = currentTime - trailLifetime;
leftTrail = leftTrail.filter(point => point.time > cutoffTime); leftTrail = leftTrail.filter((point) => point.time > cutoffTime);
rightTrail = rightTrail.filter(point => point.time > cutoffTime); rightTrail = rightTrail.filter((point) => point.time > cutoffTime);
} }
function updateTractor() { function updateTractor() {
@ -226,10 +527,12 @@
let collision = false; let collision = false;
for (let obstacle of obstacles) { for (let obstacle of obstacles) {
const dx = newX - obstacle.x; if (
const dy = newY - obstacle.y; newX - tractor.width / 2 < obstacle.x + obstacle.width &&
const distance = Math.sqrt(dx * dx + dy * dy); newX + tractor.width / 2 > obstacle.x &&
if (distance < tractor.width / 2 + obstacle.radius + 5) { newY - tractor.height / 2 < obstacle.y + obstacle.height &&
newY + tractor.height / 2 > obstacle.y
) {
collision = true; collision = true;
break; break;
} }
@ -239,6 +542,9 @@
tractor.x = newX; tractor.x = newX;
tractor.y = newY; tractor.y = newY;
tractor.angle += turn; tractor.angle += turn;
} else {
tractor.leftTrack = 0;
tractor.rightTrack = 0;
} }
tractor.leftTrack *= tractor.friction; tractor.leftTrack *= tractor.friction;
@ -250,7 +556,10 @@
tractor.leftTrackOffset = tractor.leftTrackOffset % 20; tractor.leftTrackOffset = tractor.leftTrackOffset % 20;
tractor.rightTrackOffset = tractor.rightTrackOffset % 20; tractor.rightTrackOffset = tractor.rightTrackOffset % 20;
if ((tractor.leftBrake === false && tractor.rightBrake === true) || (tractor.leftBrake === true && tractor.rightBrake === false)) { if (
(tractor.leftBrake === false && tractor.rightBrake === true) ||
(tractor.leftBrake === true && tractor.rightBrake === false)
) {
tractor.turnSpeed = 0.015; tractor.turnSpeed = 0.015;
} else { } else {
tractor.turnSpeed = 0.008; tractor.turnSpeed = 0.008;
@ -259,18 +568,38 @@
if (frameCount % trailInterval === 0) { if (frameCount % trailInterval === 0) {
const currentTime = Date.now(); const currentTime = Date.now();
const leftTrackPos = { const leftTrackPos = {
x: tractor.x + Math.sin(tractor.angle + Math.PI/2) * tractor.width/2 + x:
Math.sin(tractor.angle) * (tractor.leftReverse ? tractor.height/2 : -tractor.height/2), tractor.x +
y: tractor.y - Math.cos(tractor.angle + Math.PI/2) * tractor.width/2 - (Math.sin(tractor.angle + Math.PI / 2) * tractor.width) / 2 +
Math.cos(tractor.angle) * (tractor.leftReverse ? tractor.height/2 : -tractor.height/2), Math.sin(tractor.angle) *
time: currentTime (tractor.leftReverse
? tractor.height / 2
: -tractor.height / 2),
y:
tractor.y -
(Math.cos(tractor.angle + Math.PI / 2) * tractor.width) / 2 -
Math.cos(tractor.angle) *
(tractor.leftReverse
? tractor.height / 2
: -tractor.height / 2),
time: currentTime,
}; };
const rightTrackPos = { const rightTrackPos = {
x: tractor.x + Math.sin(tractor.angle - Math.PI/2) * tractor.width/2 + x:
Math.sin(tractor.angle) * (tractor.rightReverse ? tractor.height/2 : -tractor.height/2), tractor.x +
y: tractor.y - Math.cos(tractor.angle - Math.PI/2) * tractor.width/2 - (Math.sin(tractor.angle - Math.PI / 2) * tractor.width) / 2 +
Math.cos(tractor.angle) * (tractor.rightReverse ? tractor.height/2 : -tractor.height/2), Math.sin(tractor.angle) *
time: currentTime (tractor.rightReverse
? tractor.height / 2
: -tractor.height / 2),
y:
tractor.y -
(Math.cos(tractor.angle - Math.PI / 2) * tractor.width) / 2 -
Math.cos(tractor.angle) *
(tractor.rightReverse
? tractor.height / 2
: -tractor.height / 2),
time: currentTime,
}; };
leftTrail.push(leftTrackPos); leftTrail.push(leftTrackPos);
@ -279,62 +608,82 @@
if (rightTrail.length > maxTrailLength) rightTrail.shift(); if (rightTrail.length > maxTrailLength) rightTrail.shift();
} }
tractor.x = Math.max(tractor.width / 2, Math.min(canvasWidth - tractor.width / 2, tractor.x)); tractor.x = Math.max(
tractor.y = Math.max(tractor.height / 2, Math.min(canvasHeight - tractor.height / 2, tractor.y)); tractor.width / 2,
Math.min(canvasWidth - tractor.width / 2, tractor.x)
);
tractor.y = Math.max(
tractor.height / 2,
Math.min(canvasHeight - tractor.height / 2, tractor.y)
);
updateEngineSound();
} }
const keys = {}; const keys = {};
document.addEventListener('keydown', (event) => { document.addEventListener("keydown", (event) => {
keys[event.key] = true; keys[event.key] = true;
if (event.key === 'r' || event.key === 'R') { if (event.key === "r" || event.key === "R") {
initGame(); initGame();
} }
if (event.key === " ") {
fireBullet();
}
}); });
document.addEventListener('keyup', (event) => { document.addEventListener("keyup", (event) => {
keys[event.key] = false; keys[event.key] = false;
if (event.key === 'q' || event.key === 'Q') { if (event.key === "e" || event.key === "E") {
lMut = false; lMut = false;
} }
if (event.key === 'e' || event.key === 'E') { if (event.key === "q" || event.key === "Q") {
rMut = false; rMut = false;
} }
}); });
function handleKeyboardInput() { function handleKeyboardInput() {
if (!keys['ArrowDown']) { if (!keys["ArrowDown"]) {
tractor.leftBrake = false; tractor.leftBrake = false;
} }
if (!keys['s']) { if (!keys["s"]) {
tractor.rightBrake = false; tractor.rightBrake = false;
} }
if (keys["ArrowUp"]) {
if (keys['ArrowUp']) {
tractor.leftBrake = false; tractor.leftBrake = false;
tractor.leftTrack = Math.min(tractor.leftTrack + tractor.acceleration, tractor.speed); tractor.leftTrack = Math.min(
} else if (keys['ArrowDown']) { tractor.leftTrack + tractor.acceleration,
tractor.speed
);
} else if (keys["ArrowDown"]) {
tractor.leftBrake = true; tractor.leftBrake = true;
tractor.leftTrack = Math.max(tractor.leftTrack - tractor.braking, 0); tractor.leftTrack = Math.max(tractor.leftTrack - tractor.braking, 0);
} }
if (keys['w']) { if (keys["w"]) {
tractor.rightBrake = false; tractor.rightBrake = false;
tractor.rightTrack = Math.min(tractor.rightTrack + tractor.acceleration, tractor.speed); tractor.rightTrack = Math.min(
} else if (keys['s']) { tractor.rightTrack + tractor.acceleration,
tractor.speed
);
} else if (keys["s"]) {
tractor.rightBrake = true; tractor.rightBrake = true;
tractor.rightTrack = Math.max(tractor.rightTrack - tractor.braking, 0); tractor.rightTrack = Math.max(
tractor.rightTrack - tractor.braking,
0
);
} }
if (keys['q']) { if (keys["e"]) {
if (lMut == false) { if (lMut == false) {
tractor.leftReverse = !tractor.leftReverse; tractor.leftReverse = !tractor.leftReverse;
tractor.leftTrack = 0; tractor.leftTrack = 0;
lMut = true; lMut = true;
} }
} }
if (keys['e']) { if (keys["q"]) {
if (rMut == false) { if (rMut == false) {
tractor.rightReverse = !tractor.rightReverse; tractor.rightReverse = !tractor.rightReverse;
tractor.rightTrack = 0; tractor.rightTrack = 0;
@ -344,7 +693,7 @@
} }
function handleInput() { function handleInput() {
if (controlType === 'gamepad') { if (controlType === "gamepad") {
handleGamepadInput(); handleGamepadInput();
} else { } else {
handleKeyboardInput(); handleKeyboardInput();
@ -352,24 +701,78 @@
} }
function drawStatus() { function drawStatus() {
ctx.font = '16px Arial'; ctx.font = "16px Arial";
ctx.fillStyle = 'white'; ctx.fillStyle = "white";
ctx.fillText(`Права вісь: ${tractor.leftReverse ? 'Реверс' : 'Вперед'} (${tractor.leftTrack.toFixed(2)})`, 10, 30); ctx.fillText(
ctx.fillText(`Ліва вісь: ${tractor.rightReverse ? 'Реверс' : 'Вперед'} (${tractor.rightTrack.toFixed(2)})`, 10, 60); `Права вісь: ${
tractor.leftReverse ? "Реверс" : "Вперед"
} (${tractor.leftTrack.toFixed(2)})`,
10,
30
);
ctx.fillText(
`Ліва вісь: ${
tractor.rightReverse ? "Реверс" : "Вперед"
} (${tractor.rightTrack.toFixed(2)})`,
10,
60
);
} }
function drawInstructions() { function drawInstructions() {
ctx.font = '14px Arial'; ctx.font = "14px Arial";
ctx.fillStyle = 'white'; ctx.fillStyle = "white";
ctx.fillText('Керування:', 10, canvasHeight - 180); ctx.fillText("Керування:", 10, canvasHeight - 180);
ctx.fillText('W - рух лівої гусениці', 10, canvasHeight - 160); ctx.fillText("W - рух лівої гусениці", 10, canvasHeight - 160);
ctx.fillText('S - гальмування лівої гусениці', 10, canvasHeight - 140); ctx.fillText("S - гальмування лівої гусениці", 10, canvasHeight - 140);
ctx.fillText('↑ - рух правої гусениці', 10, canvasHeight - 120); ctx.fillText("↑ - рух правої гусениці", 10, canvasHeight - 120);
ctx.fillText('↓ - гальмування правої гусениці', 10, canvasHeight - 100); ctx.fillText("↓ - гальмування правої гусениці", 10, canvasHeight - 100);
ctx.fillText('Q - реверс лівої гусениці', 10, canvasHeight - 80); ctx.fillText("Q - реверс лівої гусениці", 10, canvasHeight - 80);
ctx.fillText('E - реверс правої гусениці', 10, canvasHeight - 60); ctx.fillText("E - реверс правої гусениці", 10, canvasHeight - 60);
ctx.fillText('R - рестарт гри', 10, canvasHeight - 40); ctx.fillText("R - рестарт гри", 10, canvasHeight - 40);
ctx.fillText('Джойстик: Ліва вісь - ліва гусениця, Права вісь - права гусениця', 10, canvasHeight - 20); ctx.fillText("Пробіл - стріляти", 10, canvasHeight - 200);
ctx.fillText(
"Джойстик: Ліва вісь - ліва гусениця, Права вісь - права гусениця",
10,
canvasHeight - 20
);
}
function drawFrame() {
const tileSize = 64;
for (let x = 0; x < canvasWidth; x += tileSize) {
ctx.drawImage(wallTexture, x, 0, tileSize, tileSize);
ctx.drawImage(
wallTexture,
x,
canvasHeight - tileSize,
tileSize,
tileSize
);
}
ctx.save();
ctx.translate(0, 0);
ctx.rotate(Math.PI / 2);
for (let y = 0; y < canvasHeight; y += tileSize) {
ctx.drawImage(wallTexture, y, -tileSize, tileSize, tileSize);
}
ctx.restore();
ctx.save();
ctx.translate(canvasWidth, 0);
ctx.rotate(Math.PI / 2);
for (let y = 0; y < canvasHeight; y += tileSize) {
ctx.drawImage(wallTexture, y, 0, tileSize, tileSize);
}
ctx.restore();
}
function limitExplosionMarks(maxMarks = 50) {
if (explosionMarks.length > maxMarks) {
explosionMarks = explosionMarks.slice(-maxMarks);
}
} }
let lastTime = 0; let lastTime = 0;
@ -382,28 +785,38 @@
handleInput(); handleInput();
updateTractor(); updateTractor();
updateBullets();
ctx.clearRect(0, 0, canvasWidth, canvasHeight); ctx.clearRect(0, 0, canvasWidth, canvasHeight);
drawMud(); drawMud();
drawTrail(); drawTrail();
drawExplosionMarks();
drawFrame();
drawObstacles(); drawObstacles();
drawTractor(); drawTractor();
drawStatus(); drawStatus();
drawInstructions(); drawInstructions();
drawBullets();
updateAndDrawExplosions();
ctx.font = '16px Arial'; ctx.font = "16px Arial";
ctx.fillStyle = 'white'; ctx.fillStyle = "white";
ctx.fillText(`FPS: ${fps.toFixed(2)}`, 10, 90); ctx.fillText(`FPS: ${fps.toFixed(2)}`, 10, 90);
frameCount++; frameCount++;
limitExplosionMarks();
requestAnimationFrame(gameLoop); requestAnimationFrame(gameLoop);
} }
let gamepad = null; let gamepad = null;
function updateGamepadState() { function updateGamepadState() {
const gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []); const gamepads = navigator.getGamepads
? navigator.getGamepads()
: navigator.webkitGetGamepads
? navigator.webkitGetGamepads()
: [];
gamepad = gamepads[0]; gamepad = gamepads[0];
} }
@ -424,27 +837,39 @@
if (Math.abs(leftAxisY) > deadzone) { if (Math.abs(leftAxisY) > deadzone) {
if (leftAxisY < 0) { if (leftAxisY < 0) {
tractor.leftTrack = Math.min(tractor.leftTrack - leftAxisY * tractor.acceleration, tractor.speed); tractor.leftTrack = Math.min(
tractor.leftTrack - leftAxisY * tractor.acceleration,
tractor.speed
);
tractor.leftBrake = false; tractor.leftBrake = false;
} else { } else {
tractor.leftTrack = Math.max(tractor.leftTrack - tractor.braking, 0); tractor.leftTrack = Math.max(
tractor.leftTrack - tractor.braking,
0
);
tractor.leftBrake = true; tractor.leftBrake = true;
} }
} else { } else {
tractor.leftTrack = 0; //tractor.leftTrack = 0;
tractor.leftBrake = false; tractor.leftBrake = false;
} }
if (Math.abs(rightAxisY) > deadzone) { if (Math.abs(rightAxisY) > deadzone) {
if (rightAxisY < 0) { if (rightAxisY < 0) {
tractor.rightTrack = Math.min(tractor.rightTrack - rightAxisY * tractor.acceleration, tractor.speed); tractor.rightTrack = Math.min(
tractor.rightTrack - rightAxisY * tractor.acceleration,
tractor.speed
);
tractor.rightBrake = false; tractor.rightBrake = false;
} else { } else {
tractor.rightTrack = Math.max(tractor.rightTrack - tractor.braking, 0); tractor.rightTrack = Math.max(
tractor.rightTrack - tractor.braking,
0
);
tractor.rightBrake = true; tractor.rightBrake = true;
} }
} else { } else {
tractor.rightTrack = 0; //tractor.rightTrack = 0;
tractor.rightBrake = false; tractor.rightBrake = false;
} }
@ -462,21 +887,65 @@
tractor.rightTrack = 0; tractor.rightTrack = 0;
rMut = true; rMut = true;
} }
if (gamepad.buttons[7].pressed) {
fireBullet();
}
} }
window.addEventListener("gamepadconnected", function(e) { window.addEventListener("gamepadconnected", function (e) {
console.log("Gamepad connected:", e.gamepad.id); console.log("Gamepad connected:", e.gamepad.id);
gamepad = e.gamepad; gamepad = e.gamepad;
}); });
window.addEventListener("gamepaddisconnected", function(e) { window.addEventListener("gamepaddisconnected", function (e) {
console.log("Gamepad disconnected:", e.gamepad.id); console.log("Gamepad disconnected:", e.gamepad.id);
gamepad = null; gamepad = null;
}); });
requestAnimationFrame(gameLoop); requestAnimationFrame(gameLoop);
function stopEngineSound() {
if (engineOscillator) {
engineOscillator.stop();
engineOscillator = null;
}
}
document.addEventListener("click", function initAudio() {
if (!isSoundInitialized) {
initializeSound();
document.removeEventListener("click", initAudio);
}
});
setInterval(updateGamepadState, 100); setInterval(updateGamepadState, 100);
function showAudioPrompt() {
const promptDiv = document.createElement("div");
promptDiv.style.position = "absolute";
promptDiv.style.top = "50%";
promptDiv.style.left = "50%";
promptDiv.style.transform = "translate(-50%, -50%)";
promptDiv.style.background = "rgba(0, 0, 0, 0.7)";
promptDiv.style.color = "white";
promptDiv.style.fontFamily = "Arial, sans-serif";
promptDiv.style.padding = "20px";
promptDiv.style.borderRadius = "10px";
promptDiv.style.fontSize = "20px";
promptDiv.style.textAlign = "center";
promptDiv.innerHTML = "Клікніть будь-де для запуску звуку";
document.body.appendChild(promptDiv);
function removePrompt() {
document.body.removeChild(promptDiv);
document.removeEventListener("click", removePrompt);
}
document.addEventListener("click", removePrompt);
}
showAudioPrompt();
</script> </script>
</body> </body>
</html> </html>