This is my HTML code, and my server.js code is below it. I’m looking for any tips on how to make my code better because it’s a bit buggy in terms of actually deleting players, and more importantly a camera system so I can create a more extensive map with checkpoints. I can’t find any good guides online.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Multiplayer Game</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { background-color: #eee; display: block; }
#startScreen {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border: 1px solid #000;
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
}
#iconSelection img {
width: 50px;
height: 50px;
margin: 5px;
cursor: pointer;
border: 2px solid transparent;
}
#iconSelection img.selected {
border: 2px solid blue;
}
</style>
</head>
<body>
<div id="startScreen">
<h2>Enter Your Name</h2>
<input type="text" id="playerNameInput" placeholder="Player Name" />
<h3>Select an Icon</h3>
<div id="iconSelection">
<img src="image1.png" alt="Icon 1" class="icon" data-icon="image1.png">
<img src="image2.webp" alt="Icon 2" class="icon" data-icon="image2.webp">
<img src="image3.png" alt="Icon 3" class="icon" data-icon="image3.png">
<!-- Add more icons as needed -->
</div>
<button id="startGameButton">Start Game</button>
</div>
<canvas id="gameCanvas"></canvas>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const GRASS_HEIGHT = 50; // Height of the grass section
let playerName = "";
let playerIcon = "";
let players = {}; // Store all players keyed by their socket ID
let myPlayerId = null; // Store the current player's ID
let gameStarted = false;
class Player {
constructor(id, x, y, w, h, spritePath, name, opacity = 1) {
this.id = id; // Unique ID for each player
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.speedX = 0;
this.speedY = 0;
this.gravity = 1; // Gravity strength
this.jumpSpeed = -15; // Jump speed
this.sprite = new Image();
this.sprite.src = spritePath;
this.loaded = false;
this.opacity = opacity; // Set opacity for players
this.name = name; // Player's name
this.onObject = false; // Track if the player is on a parkour object
this.sprite.onload = () => {
this.loaded = true;
};
}
draw() {
if (this.loaded) {
ctx.globalAlpha = this.opacity; // Set opacity for drawing
ctx.drawImage(this.sprite, this.x, this.y, this.w, this.h);
ctx.globalAlpha = 1; // Reset opacity back to full
// Draw the player's name above the character
ctx.fillStyle = 'black';
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.fillText(this.name, this.x + this.w / 2, this.y - 10); // Positioning the name above the player
}
}
update(parkourObjects) {
// Check horizontal collisions first
this.x += this.speedX; // Update horizontal position
// Handle collisions with parkour objects when moving horizontally
for (let obj of parkourObjects) {
if (this.collidesWith(obj)) {
if (this.speedX > 0) { // Moving right
this.x = obj.x - this.w; // Move player to the left of the object
} else if (this.speedX < 0) { // Moving left
this.x = obj.x + obj.w; // Move player to the right of the object
}
}
}
// Apply gravity
this.y += this.speedY; // Apply current vertical speed
this.speedY += this.gravity; // Apply gravity to vertical speed
// Ground check
if (this.y + this.h >= canvas.height - GRASS_HEIGHT) {
this.y = canvas.height - this.h - GRASS_HEIGHT; // Position on the grass
this.speedY = 0; // Reset vertical speed
this.onObject = false; // Reset onObject flag
}
// Reset onObject flag before checking collisions
this.onObject = false;
// Check collision with parkour objects
for (let obj of parkourObjects) {
if (this.collidesWith(obj)) {
// Collision response for falling onto an object
if (this.speedY > 0 && this.y + this.h <= obj.y + obj.h) {
this.y = obj.y - this.h; // Position on top of the object
this.speedY = 0; // Reset vertical speed
this.onObject = true; // Set onObject to true
}
// Stop upward movement if colliding with the bottom of the parkour object
if (this.speedY < 0 && this.y <= obj.y + obj.h && this.y + this.h > obj.y) {
this.y = obj.y + obj.h; // Position just below the object
this.speedY = 0; // Stop vertical movement
}
}
}
this.draw();
}
collidesWith(obj) {
return this.x < obj.x + obj.w &&
this.x + this.w > obj.x &&
this.y < obj.y + obj.h &&
this.y + this.h > obj.y;
}
jump() {
// Allow jumping if on the ground or on top of any parkour object
const onGround = this.y + this.h >= canvas.height - GRASS_HEIGHT; // Check if on ground (grass)
// Allow jump if on the ground or on a parkour object
if (onGround || this.onObject) {
this.speedY = this.jumpSpeed; // Set the jump speed
}
}
}
class ParkourObject {
constructor(x, y, w, h, color) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
class Controller {
constructor() {
this.up = false;
this.right = false;
this.left = false;
let keyEvent = (e) => {
if (e.code === "KeyW" || e.code === "ArrowUp") { this.up = e.type === 'keydown'; }
if (e.code === "KeyD" || e.code === "ArrowRight") { this.right = e.type === 'keydown'; }
if (e.code === "KeyA" || e.code === "ArrowLeft") { this.left = e.type === 'keydown'; }
};
addEventListener('keydown', keyEvent);
addEventListener('keyup', keyEvent);
}
}
// Create parkour objects
const parkourObjects = [
new ParkourObject(200, canvas.height - GRASS_HEIGHT - 100, 100, 20, 'brown'), // Parkour box 1
new ParkourObject(400, canvas.height - GRASS_HEIGHT - 50, 100, 20, 'brown'), // Parkour box 2
];
// Initialize controller
const controller1 = new Controller();
// WebSocket connection
// WebSocket connection
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', () => {
console.log('WebSocket connection established');
});
// Listen for messages
socket.onmessage = (event) => {
event.data.text().then((text) => {
const data = JSON.parse(text);
if (data.type === 'positionUpdate') {
// Update player positions
if (!players[data.id]) {
players[data.id] = new Player(data.id, data.x, data.y, 50, 50, data.icon, data.name, 0.5);
} else {
players[data.id].x = data.x;
players[data.id].y = data.y;
}
} else if (data.type === 'playerDisconnected') {
// Remove player when they disconnect
delete players[data.id];
}
}).catch((error) => console.error("Error parsing message:", error));
};
// Handle page unload event to notify server of disconnection
window.addEventListener('beforeunload', () => {
if (myPlayerId) {
socket.send(JSON.stringify({
type: 'disconnect',
id: myPlayerId
}));
}
});
// Function to update positions based on controls
function updatePosition() {
if (myPlayerId) {
const player = players[myPlayerId];
if (controller1.up) {
player.jump();
}
if (controller1.right) {
player.speedX = 5; // Move right
} else if (controller1.left) {
player.speedX = -5; // Move left
} else {
player.speedX = 0; // Stop moving
}
}
}
// Main game loop
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas
ctx.fillStyle = 'green'; // Grass color
ctx.fillRect(0, canvas.height - GRASS_HEIGHT, canvas.width, GRASS_HEIGHT); // Draw grass
// Draw parkour objects
parkourObjects.forEach(obj => obj.draw());
// Update and draw all players
for (const id in players) {
players[id].update(parkourObjects);
}
updatePosition(); // Update my player position
// Send position updates if WebSocket is open
if (socket.readyState === WebSocket.OPEN && myPlayerId) {
socket.send(JSON.stringify({
type: 'positionUpdate',
id: myPlayerId,
x: players[myPlayerId].x,
y: players[myPlayerId].y,
icon: playerIcon,
name: playerName
}));
}
requestAnimationFrame(gameLoop); // Continue the game loop
}
// Start the game once the player enters their name and selects an icon
document.getElementById('startGameButton').addEventListener('click', () => {
playerName = document.getElementById('playerNameInput').value;
playerIcon = document.querySelector('.icon.selected')?.dataset.icon || 'defaultIcon.png'; // Default if not selected
// Initialize player with selected icon
const groundLevel = canvas.height - GRASS_HEIGHT;
myPlayerId = Date.now(); // Generate a unique ID based on timestamp
players[myPlayerId] = new Player(myPlayerId, 50, groundLevel, 50, 50, playerIcon, playerName, 1); // Opaque for this player
// Hide the start screen and start the game
document.getElementById('startScreen').style.display = 'none';
gameLoop(); // Start the game loop
});
// Handle icon selection
document.querySelectorAll('.icon').forEach(icon => {
icon.addEventListener('click', () => {
// Deselect all icons
document.querySelectorAll('.icon').forEach(i => i.classList.remove('selected'));
// Select the clicked icon
icon.classList.add('selected');
});
});
// Resize canvas on window resize
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
</script>
</body>
</html>
server-side code
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
const players = {}; // Object to hold connected players
server.on('connection', (socket) => {
let playerId = Date.now(); // Generate a unique ID for the player
players[playerId] = { socket }; // Store the player's socket
// Notify all players of the new player
for (const id in players) {
if (players[id].socket.readyState === WebSocket.OPEN) {
players[id].socket.send(JSON.stringify({
type: 'playerConnected',
id: playerId,
name: "Player " + playerId, // Placeholder for player name
icon: 'icon1.png' // Placeholder for player icon
}));
}
}
socket.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'positionUpdate') {
// Broadcast updated player position to all players
for (const id in players) {
if (players[id].socket.readyState === WebSocket.OPEN) {
players[id].socket.send(JSON.stringify({
type: 'positionUpdate',
id: data.id,
x: data.x,
y: data.y,
icon: data.icon,
name: data.name
}));
}
}
} else if (data.type === 'disconnect') {
// Handle player disconnection
delete players[data.id]; // Remove player from the players object
// Notify all remaining players about the disconnection
for (const id in players) {
if (players[id].socket.readyState === WebSocket.OPEN) {
players[id].socket.send(JSON.stringify({
type: 'playerDisconnected',
id: data.id
}));
}
}
}
});
socket.on('close', () => {
// Handle cleanup when the socket closes unexpectedly
delete players[playerId];
});
});