Online Game Development Course(5)


Building the Backend Server

Hello! Welcome to the fifth installment of our online Snake game development tutorial. Today, we will explore setting up a server using Express.js and implementing RESTful APIs.


1. Setting Up the Express.js Server

First, a brief introduction: Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.

Let’s start by setting up a basic Express.js server.

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const path = require('path');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

const PORT = process.env.PORT || 3000;

app.use(express.static(path.join(__dirname, 'public')));

server.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

2. Modularizing Game Logic

Separate the game logic into a module for better management.

// gameLogic.js
const GRID_SIZE = 20;
const CANVAS_SIZE = 400;

class Game {
    constructor() {
        this.players = [];
        this.foods = [];
        this.gridSize = GRID_SIZE;
        this.canvasSize = CANVAS_SIZE;
    }

    addPlayer(id) {
        // Logic to add a player
    }

    removePlayer(id) {
        // Logic to remove a player
    }

    movePlayer(id, direction) {
        // Logic to move a player
    }

    update() {
        // Logic to update the game state
    }

    // Additional methods as needed...
}

module.exports = Game;

3. Handling Socket.io Events

Manage Socket.io events on the server.

const Game = require('./gameLogic');

const games = new Map();

io.on('connection', (socket) => {
    console.log('A user connected');

    socket.on('createRoom', () => {
        const roomCode = generateRoomCode();
        games.set(roomCode, new Game());
        socket.join(roomCode);
        socket.emit('roomCreated', roomCode);
    });

    socket.on('joinRoom', (roomCode) => {
        if (games.has(roomCode)) {
            socket.join(roomCode);
            const game = games.get(roomCode);
            game.addPlayer(socket.id);
            socket.emit('joinedRoom', roomCode);
            io.to(roomCode).emit('playerJoined', game.players.length);
        } else {
            socket.emit('roomNotFound');
        }
    });

    socket.on('startGame', (roomCode) => {
        if (games.has(roomCode)) {
            const game = games.get(roomCode);
            game.start();
            io.to(roomCode).emit('gameStarted', game.getState());
            startGameLoop(roomCode);
        }
    });

    socket.on('changeDirection', ({ roomCode, direction }) => {
        if (games.has(roomCode)) {
            const game = games.get(roomCode);
            game.movePlayer(socket.id, direction);
        }
    });

    socket.on('disconnect', () => {
        console.log('A user disconnected');
        // Logic to remove a player and update game state
    });
});

4. Implementing the Game Loop

Run a game loop on the server to periodically update the game state.

function startGameLoop(roomCode) {
    const game = games.get(roomCode);
    const interval = setInterval(() => {
        game.update();
        const gameState = game.getState();
        io.to(roomCode).emit('gameState', gameState);

        if (game.isOver()) {
            clearInterval(interval);
            io.to(roomCode).emit('gameOver', game.getWinners());
            games.delete(roomCode);
        }
    }, 100); // Update every 100ms
}

5. Implementing RESTful APIs

Create simple RESTful APIs to retrieve game-related information.

app.get('/api/rooms', (req, res) => {
    const roomList = Array.from(games.keys());
    res.json(roomList);
});

app.get('/api/room/:roomCode', (req, res) => {
    const roomCode = req.params.roomCode;
    if (games.has(roomCode)) {
        const game = games.get(roomCode);
        res.json(game.getState());
    } else {
        res.status(404).json({ error: 'Room not found' });
    }
});

6. Error Handling

Handle potential errors on the server appropriately.

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ error: 'Something went wrong!' });
});

process.on('uncaughtException', (err) => {
    console.error('Uncaught Exception:', err);
    process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('Unhandled Rejection at:', promise, 'reason:', reason);
    process.exit(1);
});

7. Using Environment Variables

Use environment variables for security and flexibility.

require('dotenv').config();

const PORT = process.env.PORT || 3000;
const MAX_PLAYERS = process.env.MAX_PLAYERS || 4;

Conclusion

In this post, we explored building a backend server using Express.js. We covered modularizing game logic, handling real-time communication with Socket.io, implementing RESTful APIs, and error handling.

This backend structure ensures the stability and scalability of the game while providing an efficient communication channel with the client.

In the next post, we will discuss game optimization and bug fixes. We will cover performance improvement techniques and how to address common game development bugs. Thank you for following along!

Previous Related Posts


Leave a Reply

Your email address will not be published. Required fields are marked *