traktorrr/index.html
2024-09-04 16:09:41 +03:00

483 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PlaSim</title>
<style>
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<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;">
<label>
<input type="radio" name="controlType" value="keyboard" checked> Клавіатура
</label>
<label>
<input type="radio" name="controlType" value="gamepad"> Геймпад
</label>
</div>
<canvas id="gameCanvas"></canvas>
<script>
let controlType = 'keyboard';
document.querySelectorAll('input[name="controlType"]').forEach((elem) => {
elem.addEventListener("change", function(event) {
controlType = event.target.value;
});
});
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let canvasWidth, canvasHeight;
let tractor, leftTrail, rightTrail, obstacles, mudPattern;
function resizeCanvas() {
canvasWidth = window.innerWidth;
canvasHeight = window.innerHeight;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
createMudTexture();
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
let lMut = false;
let rMut = false;
function createMudTexture() {
const textureCanvas = document.createElement('canvas');
textureCanvas.width = 200;
textureCanvas.height = 200;
const textureCtx = textureCanvas.getContext('2d');
textureCtx.fillStyle = '#8B4513';
textureCtx.fillRect(0, 0, 200, 200);
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.beginPath();
textureCtx.arc(Math.random() * 200, Math.random() * 200, Math.random() * 2, 0, Math.PI * 2);
textureCtx.fill();
}
mudPattern = ctx.createPattern(textureCanvas, 'repeat');
}
function initGame() {
tractor = {
x: canvasWidth / 2,
y: canvasHeight / 2,
width: 60,
height: 80,
angle: 0,
leftTrack: 0,
rightTrack: 0,
speed: 1.5,
turnSpeed: 0.008,
friction: 0.92,
acceleration: 0.2,
braking: 0.4,
leftReverse: false,
rightReverse: false,
leftTrackOffset: 0,
rightTrackOffset: 0,
leftBrake: false,
rightBrake: false
};
leftTrail = [];
rightTrail = [];
obstacles = [];
for (let i = 0; i < 10; i++) {
obstacles.push({
x: Math.random() * canvasWidth,
y: Math.random() * canvasHeight,
radius: 15
});
}
createMudTexture();
}
initGame();
const maxTrailLength = 500;
const trailLifetime = 5000;
const trailInterval = 5;
let frameCount = 0;
function drawTractor() {
ctx.save();
ctx.translate(tractor.x, tractor.y);
ctx.rotate(tractor.angle);
ctx.fillStyle = 'green';
ctx.fillRect(-tractor.width / 2, -tractor.height / 2, tractor.width, tractor.height);
const trackHeight = tractor.height;
const trackWidth = 7;
const segmentHeight = 7;
ctx.globalAlpha = 0.8;
ctx.fillStyle = '#333';
for (let i = 0; i < trackHeight / segmentHeight; i++) {
const alpha = Math.max(0.5, 1 - Math.abs((tractor.leftTrackOffset + i * segmentHeight) % 20) / 10);
ctx.globalAlpha = alpha;
ctx.fillRect(
-tractor.width / 2 - trackWidth,
-tractor.height / 2 + i * segmentHeight,
trackWidth,
segmentHeight - 2
);
}
ctx.fillStyle = '#333';
for (let i = 0; i < trackHeight / segmentHeight; i++) {
const alpha = Math.max(0.5, 1 - Math.abs((tractor.rightTrackOffset + i * segmentHeight) % 20) / 10);
ctx.globalAlpha = alpha;
ctx.fillRect(
tractor.width / 2,
-tractor.height / 2 + i * segmentHeight,
trackWidth,
segmentHeight - 2
);
}
ctx.globalAlpha = 1;
ctx.fillStyle = 'lightblue';
ctx.fillRect(-tractor.width / 4, -tractor.height / 4, tractor.width / 2, tractor.height / 2);
ctx.beginPath();
ctx.moveTo(0, -tractor.height / 2 - 20);
ctx.lineTo(10, -tractor.height / 2 - 10);
ctx.lineTo(-10, -tractor.height / 2 - 10);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.fill();
ctx.restore();
}
function drawObstacles() {
ctx.fillStyle = 'orange';
for (let obstacle of obstacles) {
ctx.beginPath();
ctx.arc(obstacle.x, obstacle.y, obstacle.radius, 0, Math.PI * 2);
ctx.fill();
}
}
function drawMud() {
ctx.fillStyle = mudPattern;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
}
function drawTrail() {
const currentTime = Date.now();
ctx.lineWidth = 3;
ctx.strokeStyle = 'rgba(60, 30, 15, 0.5)';
for (let trail of [leftTrail, rightTrail]) {
ctx.beginPath();
for (let i = 0; i < trail.length; i++) {
const alpha = Math.max(0, 1 - (currentTime - trail[i].time) / trailLifetime);
if (alpha > 0) {
ctx.globalAlpha = alpha;
if (i === 0) {
ctx.moveTo(trail[i].x, trail[i].y);
} else {
ctx.lineTo(trail[i].x, trail[i].y);
}
}
}
ctx.stroke();
}
ctx.globalAlpha = 1;
const cutoffTime = currentTime - trailLifetime;
leftTrail = leftTrail.filter(point => point.time > cutoffTime);
rightTrail = rightTrail.filter(point => point.time > cutoffTime);
}
function updateTractor() {
const leftSpeed = tractor.leftTrack * (tractor.leftReverse ? -1 : 1);
const rightSpeed = tractor.rightTrack * (tractor.rightReverse ? -1 : 1);
const forwardSpeed = (leftSpeed + rightSpeed) / 2;
const turn = (rightSpeed - leftSpeed) * tractor.turnSpeed;
const newX = tractor.x + Math.sin(tractor.angle) * forwardSpeed;
const newY = tractor.y - Math.cos(tractor.angle) * forwardSpeed;
let collision = false;
for (let obstacle of obstacles) {
const dx = newX - obstacle.x;
const dy = newY - obstacle.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < tractor.width / 2 + obstacle.radius + 5) {
collision = true;
break;
}
}
if (!collision) {
tractor.x = newX;
tractor.y = newY;
tractor.angle += turn;
}
tractor.leftTrack *= tractor.friction;
tractor.rightTrack *= tractor.friction;
tractor.leftTrackOffset += leftSpeed;
tractor.rightTrackOffset += rightSpeed;
tractor.leftTrackOffset = tractor.leftTrackOffset % 20;
tractor.rightTrackOffset = tractor.rightTrackOffset % 20;
if ((tractor.leftBrake === false && tractor.rightBrake === true) || (tractor.leftBrake === true && tractor.rightBrake === false)) {
tractor.turnSpeed = 0.015;
} else {
tractor.turnSpeed = 0.008;
}
if (frameCount % trailInterval === 0) {
const currentTime = Date.now();
const leftTrackPos = {
x: tractor.x + Math.sin(tractor.angle + Math.PI/2) * tractor.width/2 +
Math.sin(tractor.angle) * (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 = {
x: tractor.x + Math.sin(tractor.angle - Math.PI/2) * tractor.width/2 +
Math.sin(tractor.angle) * (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);
rightTrail.push(rightTrackPos);
if (leftTrail.length > maxTrailLength) leftTrail.shift();
if (rightTrail.length > maxTrailLength) rightTrail.shift();
}
tractor.x = Math.max(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));
}
const keys = {};
document.addEventListener('keydown', (event) => {
keys[event.key] = true;
if (event.key === 'r' || event.key === 'R') {
initGame();
}
});
document.addEventListener('keyup', (event) => {
keys[event.key] = false;
if (event.key === 'q' || event.key === 'Q') {
lMut = false;
}
if (event.key === 'e' || event.key === 'E') {
rMut = false;
}
});
function handleKeyboardInput() {
if (!keys['ArrowDown']) {
tractor.leftBrake = false;
}
if (!keys['s']) {
tractor.rightBrake = false;
}
if (keys['ArrowUp']) {
tractor.leftBrake = false;
tractor.leftTrack = Math.min(tractor.leftTrack + tractor.acceleration, tractor.speed);
} else if (keys['ArrowDown']) {
tractor.leftBrake = true;
tractor.leftTrack = Math.max(tractor.leftTrack - tractor.braking, 0);
}
if (keys['w']) {
tractor.rightBrake = false;
tractor.rightTrack = Math.min(tractor.rightTrack + tractor.acceleration, tractor.speed);
} else if (keys['s']) {
tractor.rightBrake = true;
tractor.rightTrack = Math.max(tractor.rightTrack - tractor.braking, 0);
}
if (keys['q']) {
if (lMut == false) {
tractor.leftReverse = !tractor.leftReverse;
tractor.leftTrack = 0;
lMut = true;
}
}
if (keys['e']) {
if (rMut == false) {
tractor.rightReverse = !tractor.rightReverse;
tractor.rightTrack = 0;
rMut = true;
}
}
}
function handleInput() {
if (controlType === 'gamepad') {
handleGamepadInput();
} else {
handleKeyboardInput();
}
}
function drawStatus() {
ctx.font = '16px Arial';
ctx.fillStyle = 'white';
ctx.fillText(`Права вісь: ${tractor.leftReverse ? 'Реверс' : 'Вперед'} (${tractor.leftTrack.toFixed(2)})`, 10, 30);
ctx.fillText(`Ліва вісь: ${tractor.rightReverse ? 'Реверс' : 'Вперед'} (${tractor.rightTrack.toFixed(2)})`, 10, 60);
}
function drawInstructions() {
ctx.font = '14px Arial';
ctx.fillStyle = 'white';
ctx.fillText('Керування:', 10, canvasHeight - 180);
ctx.fillText('W - рух лівої гусениці', 10, canvasHeight - 160);
ctx.fillText('S - гальмування лівої гусениці', 10, canvasHeight - 140);
ctx.fillText('↑ - рух правої гусениці', 10, canvasHeight - 120);
ctx.fillText('↓ - гальмування правої гусениці', 10, canvasHeight - 100);
ctx.fillText('Q - реверс лівої гусениці', 10, canvasHeight - 80);
ctx.fillText('E - реверс правої гусениці', 10, canvasHeight - 60);
ctx.fillText('R - рестарт гри', 10, canvasHeight - 40);
ctx.fillText('Джойстик: Ліва вісь - ліва гусениця, Права вісь - права гусениця', 10, canvasHeight - 20);
}
let lastTime = 0;
let fps = 0;
function gameLoop(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
fps = 1000 / deltaTime;
handleInput();
updateTractor();
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
drawMud();
drawTrail();
drawObstacles();
drawTractor();
drawStatus();
drawInstructions();
ctx.font = '16px Arial';
ctx.fillStyle = 'white';
ctx.fillText(`FPS: ${fps.toFixed(2)}`, 10, 90);
frameCount++;
requestAnimationFrame(gameLoop);
}
let gamepad = null;
function updateGamepadState() {
const gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []);
gamepad = gamepads[0];
}
function handleGamepadInput() {
if (!gamepad) return;
if (!gamepad.buttons[5].pressed) {
lMut = false;
}
if (!gamepad.buttons[4].pressed) {
rMut = false;
}
const leftAxisY = gamepad.axes[3];
const rightAxisY = gamepad.axes[1];
const deadzone = 0.1;
if (Math.abs(leftAxisY) > deadzone) {
if (leftAxisY < 0) {
tractor.leftTrack = Math.min(tractor.leftTrack - leftAxisY * tractor.acceleration, tractor.speed);
tractor.leftBrake = false;
} else {
tractor.leftTrack = Math.max(tractor.leftTrack - tractor.braking, 0);
tractor.leftBrake = true;
}
} else {
tractor.leftTrack = 0;
tractor.leftBrake = false;
}
if (Math.abs(rightAxisY) > deadzone) {
if (rightAxisY < 0) {
tractor.rightTrack = Math.min(tractor.rightTrack - rightAxisY * tractor.acceleration, tractor.speed);
tractor.rightBrake = false;
} else {
tractor.rightTrack = Math.max(tractor.rightTrack - tractor.braking, 0);
tractor.rightBrake = true;
}
} else {
tractor.rightTrack = 0;
tractor.rightBrake = false;
}
if (gamepad.buttons[9].pressed) {
initGame();
}
if (gamepad.buttons[5].pressed && !lMut) {
tractor.leftReverse = !tractor.leftReverse;
tractor.leftTrack = 0;
lMut = true;
}
if (gamepad.buttons[4].pressed && !rMut) {
tractor.rightReverse = !tractor.rightReverse;
tractor.rightTrack = 0;
rMut = true;
}
}
window.addEventListener("gamepadconnected", function(e) {
console.log("Gamepad connected:", e.gamepad.id);
gamepad = e.gamepad;
});
window.addEventListener("gamepaddisconnected", function(e) {
console.log("Gamepad disconnected:", e.gamepad.id);
gamepad = null;
});
requestAnimationFrame(gameLoop);
setInterval(updateGamepadState, 100);
</script>
</body>
</html>