traktorrr/index.html

483 lines
17 KiB
HTML
Raw Normal View History

2024-09-04 16:09:41 +03:00
<!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>