267 lines
9.0 KiB
HTML
267 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Pong in WebGL</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
font-family: Arial, sans-serif;
|
|
color: white;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
flex-direction: column;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
}
|
|
#score {
|
|
position: absolute;
|
|
top: 20px;
|
|
font-size: 24px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="score">Player: 0 | Opponent: 0</div>
|
|
<canvas id="gameCanvas"></canvas>
|
|
|
|
<script>
|
|
const canvas = document.getElementById('gameCanvas');
|
|
const gl = canvas.getContext('webgl');
|
|
const scoreDisplay = document.getElementById('score');
|
|
|
|
if (!gl) {
|
|
alert('WebGL not supported');
|
|
throw new Error('WebGL not supported');
|
|
}
|
|
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
|
|
const vertexShaderSource = `
|
|
attribute vec2 a_position;
|
|
uniform vec2 u_resolution;
|
|
|
|
void main() {
|
|
vec2 zeroToOne = a_position / u_resolution;
|
|
vec2 zeroToTwo = zeroToOne * 2.0;
|
|
vec2 clipSpace = zeroToTwo - 1.0;
|
|
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
|
|
}
|
|
`;
|
|
|
|
const fragmentShaderSource = `
|
|
precision mediump float;
|
|
|
|
void main() {
|
|
gl_FragColor = vec4(1, 1, 1, 1);
|
|
}
|
|
`;
|
|
|
|
function createShader(gl, type, source) {
|
|
const shader = gl.createShader(type);
|
|
gl.shaderSource(shader, source);
|
|
gl.compileShader(shader);
|
|
|
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
console.error(gl.getShaderInfoLog(shader));
|
|
gl.deleteShader(shader);
|
|
return null;
|
|
}
|
|
|
|
return shader;
|
|
}
|
|
|
|
function createProgram(gl, vertexShader, fragmentShader) {
|
|
const program = gl.createProgram();
|
|
gl.attachShader(program, vertexShader);
|
|
gl.attachShader(program, fragmentShader);
|
|
gl.linkProgram(program);
|
|
|
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
console.error(gl.getProgramInfoLog(program));
|
|
gl.deleteProgram(program);
|
|
return null;
|
|
}
|
|
|
|
return program;
|
|
}
|
|
|
|
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
|
|
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
|
|
const program = createProgram(gl, vertexShader, fragmentShader);
|
|
|
|
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
|
|
const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');
|
|
|
|
const positionBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
|
|
const paddleWidth = 20;
|
|
const paddleHeight = 100;
|
|
const ballSize = 15;
|
|
|
|
let paddle1Y = canvas.height / 2 - paddleHeight / 2;
|
|
let paddle2Y = canvas.height / 2 - paddleHeight / 2;
|
|
let balls = [{ x: canvas.width / 2, y: canvas.height / 2, speedX: 5, speedY: 5 }];
|
|
let playerScore = 0;
|
|
let opponentScore = 0;
|
|
let speedIncrement = 0.2;
|
|
let powerups = [];
|
|
let bricks = [];
|
|
let opponentInverted = false;
|
|
|
|
function spawnPowerup() {
|
|
powerups.push({
|
|
x: Math.random() * (canvas.width - 50) + 25,
|
|
y: Math.random() * (canvas.height - 50) + 25,
|
|
type: Math.floor(Math.random() * 3) // 0: multi-ball, 1: invert opponent, 2: bricks
|
|
});
|
|
}
|
|
|
|
function spawnBricks() {
|
|
for (let i = 0; i < 5; i++) {
|
|
bricks.push({
|
|
x: canvas.width / 2 - 100 + i * 40,
|
|
y: canvas.height / 2 - 20,
|
|
width: 30,
|
|
height: 10
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
paddle1Y = e.clientY - paddleHeight / 2;
|
|
paddle1Y = Math.max(0, Math.min(canvas.height - paddleHeight, paddle1Y));
|
|
});
|
|
|
|
function drawRect(x, y, width, height) {
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
x, y,
|
|
x + width, y,
|
|
x, y + height,
|
|
x, y + height,
|
|
x + width, y,
|
|
x + width, y + height,
|
|
]), gl.STATIC_DRAW);
|
|
|
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
}
|
|
|
|
function update() {
|
|
// Clear canvas
|
|
gl.clearColor(0, 0, 0, 1);
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
gl.useProgram(program);
|
|
gl.enableVertexAttribArray(positionAttributeLocation);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
|
|
gl.uniform2f(resolutionUniformLocation, canvas.width, canvas.height);
|
|
|
|
// Draw paddles
|
|
drawRect(10, paddle1Y, paddleWidth, paddleHeight);
|
|
drawRect(canvas.width - paddleWidth - 10, paddle2Y, paddleWidth, paddleHeight);
|
|
|
|
// Update and draw balls
|
|
balls.forEach((ball, index) => {
|
|
ball.x += ball.speedX;
|
|
ball.y += ball.speedY;
|
|
|
|
if (ball.y <= 0 || ball.y + ballSize >= canvas.height) {
|
|
ball.speedY *= -1;
|
|
}
|
|
|
|
if (ball.x <= paddleWidth + 10 && ball.y + ballSize >= paddle1Y && ball.y <= paddle1Y + paddleHeight) {
|
|
ball.speedX *= -1;
|
|
ball.speedX += speedIncrement;
|
|
ball.speedY += (Math.random() - 0.5) * 2;
|
|
}
|
|
|
|
if (ball.x + ballSize >= canvas.width - paddleWidth - 10 && ball.y + ballSize >= paddle2Y && ball.y <= paddle2Y + paddleHeight) {
|
|
ball.speedX *= -1;
|
|
ball.speedX -= speedIncrement;
|
|
ball.speedY += (Math.random() - 0.5) * 2;
|
|
}
|
|
|
|
if (ball.x <= 0) {
|
|
opponentScore++;
|
|
balls.splice(index, 1);
|
|
} else if (ball.x + ballSize >= canvas.width) {
|
|
playerScore++;
|
|
balls.splice(index, 1);
|
|
}
|
|
|
|
drawRect(ball.x, ball.y, ballSize, ballSize);
|
|
});
|
|
|
|
if (balls.length === 0) {
|
|
balls.push({ x: canvas.width / 2, y: canvas.height / 2, speedX: 5, speedY: 5 });
|
|
}
|
|
|
|
// Draw and check powerups
|
|
powerups.forEach((powerup, index) => {
|
|
drawRect(powerup.x, powerup.y, 20, 20);
|
|
|
|
balls.forEach((ball) => {
|
|
if (
|
|
ball.x < powerup.x + 20 &&
|
|
ball.x + ballSize > powerup.x &&
|
|
ball.y < powerup.y + 20 &&
|
|
ball.y + ballSize > powerup.y
|
|
) {
|
|
if (powerup.type === 0) {
|
|
balls.push({ x: ball.x, y: ball.y, speedX: -ball.speedX, speedY: -ball.speedY });
|
|
} else if (powerup.type === 1) {
|
|
opponentInverted = !opponentInverted;
|
|
} else if (powerup.type === 2) {
|
|
spawnBricks();
|
|
}
|
|
|
|
powerups.splice(index, 1);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Draw and check bricks
|
|
bricks.forEach((brick, index) => {
|
|
drawRect(brick.x, brick.y, brick.width, brick.height);
|
|
|
|
balls.forEach((ball) => {
|
|
if (
|
|
ball.x < brick.x + brick.width &&
|
|
ball.x + ballSize > brick.x &&
|
|
ball.y < brick.y + brick.height &&
|
|
ball.y + ballSize > brick.y
|
|
) {
|
|
ball.speedY *= -1;
|
|
bricks.splice(index, 1);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Opponent AI
|
|
const targetY = balls[0].y - paddleHeight / 2;
|
|
paddle2Y += (opponentInverted ? -1 : 1) * (targetY - paddle2Y) * 0.1;
|
|
paddle2Y = Math.max(0, Math.min(canvas.height - paddleHeight, paddle2Y));
|
|
|
|
// Update score
|
|
scoreDisplay.textContent = `Player: ${playerScore} | Opponent: ${opponentScore}`;
|
|
|
|
// Spawn powerups occasionally
|
|
if (Math.random() < 0.01) {
|
|
spawnPowerup();
|
|
}
|
|
|
|
requestAnimationFrame(update);
|
|
}
|
|
|
|
update();
|
|
</script>
|
|
</body>
|
|
</html>
|