Article · 2025-07-21

WebGL 3D Tank Battle Game Development Practice: Modern WebGL Technology Application

Core Technology Stack

System Architecture Design

WebGL 3D Tank Game
├── Rendering Engine
│   ├── Shader Manager
│   ├── Texture System
│   ├── Model Loader
│   └── Scene Renderer
├── Physics Engine
│   ├── Collision Detection
│   ├── Rigid Body Dynamics
│   └── Spatial Partitioning
├── AI System
│   ├── Pathfinding
│   ├── Behavior Tree
│   └── Decision System
├── Audio System
├── User Interface
└── Game Loop

Rendering Engine Implementation

WebGL Rendering Pipeline

class RenderEngine {
    constructor(canvas) {
        this.canvas = canvas;
        this.gl = canvas.getContext('webgl2');
        this.shaderPrograms = new Map();
        this.textures = new Map();
        this.models = new Map();
        
        this.initWebGL();
        this.loadShaders();
        this.setupBuffers();
    }

    initWebGL() {
        const gl = this.gl;
        
        // Enable depth testing
        gl.enable(gl.DEPTH_TEST);
        gl.depthFunc(gl.LEQUAL);
        
        // Enable back-face culling
        gl.enable(gl.CULL_FACE);
        gl.cullFace(gl.BACK);
        
        // Set viewport
        gl.viewport(0, 0, this.canvas.width, this.canvas.height);
        
        // Set clear color
        gl.clearColor(0.2, 0.3, 0.3, 1.0);
    }

    // Compile shader
    compileShader(source, type) {
        const gl = this.gl;
        const shader = gl.createShader(type);
        
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
            gl.deleteShader(shader);
            return null;
        }
        
        return shader;
    }

    // Create shader program
    createShaderProgram(vertexSource, fragmentSource) {
        const gl = this.gl;
        const vertexShader = this.compileShader(vertexSource, gl.VERTEX_SHADER);
        const fragmentShader = this.compileShader(fragmentSource, gl.FRAGMENT_SHADER);
        
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error('Shader program linking error:', gl.getProgramInfoLog(program));
            return null;
        }
        
        return program;
    }

    // Render scene
    render(scene, camera) {
        const gl = this.gl;
        
        // Clear buffers
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        
        // Calculate view and projection matrices
        const viewMatrix = camera.getViewMatrix();
        const projMatrix = camera.getProjectionMatrix();
        
        // Render all game objects
        scene.objects.forEach(obj => {
            this.renderObject(obj, viewMatrix, projMatrix);
        });
    }

    renderObject(object, viewMatrix, projMatrix) {
        const gl = this.gl;
        const program = this.shaderPrograms.get(object.material.shader);
        
        gl.useProgram(program);
        
        // Set matrix uniforms
        const modelMatrix = object.getModelMatrix();
        const mvpMatrix = mat4.multiply(projMatrix, viewMatrix, modelMatrix);
        
        gl.uniformMatrix4fv(gl.getUniformLocation(program, 'u_mvpMatrix'), false, mvpMatrix);
        gl.uniformMatrix4fv(gl.getUniformLocation(program, 'u_modelMatrix'), false, modelMatrix);
        
        // Bind textures
        if (object.material.diffuseTexture) {
            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, object.material.diffuseTexture);
            gl.uniform1i(gl.getUniformLocation(program, 'u_diffuseTexture'), 0);
        }
        
        // Bind vertex data and draw
        this.bindVertexData(object.mesh);
        gl.drawElements(gl.TRIANGLES, object.mesh.indices.length, gl.UNSIGNED_SHORT, 0);
    }
}

Shader System

// Vertex Shader
#version 300 es
precision highp float;

in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;

uniform mat4 u_mvpMatrix;
uniform mat4 u_modelMatrix;
uniform mat4 u_normalMatrix;

out vec3 v_worldPos;
out vec3 v_normal;
out vec2 v_texCoord;

void main() {
    vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0);
    v_worldPos = worldPos.xyz;
    
    v_normal = normalize((u_normalMatrix * vec4(a_normal, 0.0)).xyz);
    v_texCoord = a_texCoord;
    
    gl_Position = u_mvpMatrix * vec4(a_position, 1.0);
}
// Fragment Shader
#version 300 es
precision highp float;

in vec3 v_worldPos;
in vec3 v_normal;
in vec2 v_texCoord;

uniform sampler2D u_diffuseTexture;
uniform vec3 u_lightPos;
uniform vec3 u_lightColor;
uniform vec3 u_viewPos;

out vec4 fragColor;

void main() {
    // Sample texture
    vec3 texColor = texture(u_diffuseTexture, v_texCoord).rgb;
    
    // Calculate lighting
    vec3 lightDir = normalize(u_lightPos - v_worldPos);
    vec3 normal = normalize(v_normal);
    
    // Ambient light
    float ambientStrength = 0.3;
    vec3 ambient = ambientStrength * u_lightColor;
    
    // Diffuse lighting
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = diff * u_lightColor;
    
    // Specular reflection
    float specularStrength = 0.8;
    vec3 viewDir = normalize(u_viewPos - v_worldPos);
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 64.0);
    vec3 specular = specularStrength * spec * u_lightColor;
    
    vec3 result = (ambient + diffuse + specular) * texColor;
    fragColor = vec4(result, 1.0);
}

Physics System Design

Collision Detection System

class PhysicsEngine {
    constructor() {
        this.bodies = [];
        this.gravity = { x: 0, y: -9.81, z: 0 };
        this.spatialGrid = new SpatialGrid(50); // 50x50 grid
    }

    // Collision detection
    checkCollisions() {
        // Use spatial partitioning to optimize collision detection
        const potentialPairs = this.spatialGrid.getPotentialCollisions();
        
        potentialPairs.forEach(pair => {
            const [bodyA, bodyB] = pair;
            
            if (this.detectCollision(bodyA, bodyB)) {
                this.resolveCollision(bodyA, bodyB);
            }
        });
    }

    // AABB collision detection
    detectAABBCollision(boxA, boxB) {
        return (
            boxA.min.x <= boxB.max.x && boxA.max.x >= boxB.min.x &&
            boxA.min.y <= boxB.max.y && boxA.max.y >= boxB.min.y &&
            boxA.min.z <= boxB.max.z && boxA.max.z >= boxB.min.z
        );
    }

    // Sphere collision detection
    detectSphereCollision(sphereA, sphereB) {
        const distance = vec3.distance(sphereA.center, sphereB.center);
        return distance <= (sphereA.radius + sphereB.radius);
    }

    // Collision response
    resolveCollision(bodyA, bodyB) {
        // Calculate collision normal
        const normal = vec3.normalize(vec3.subtract(bodyB.position, bodyA.position));
        
        // Calculate relative velocity
        const relativeVelocity = vec3.subtract(bodyB.velocity, bodyA.velocity);
        const velocityAlongNormal = vec3.dot(relativeVelocity, normal);
        
        // If objects are separating, don't resolve collision
        if (velocityAlongNormal > 0) return;
        
        // Calculate restitution coefficient
        const restitution = Math.min(bodyA.restitution, bodyB.restitution);
        
        // Calculate impulse
        const impulse = -(1 + restitution) * velocityAlongNormal;
        const impulseVector = vec3.scale(normal, impulse);
        
        // Apply impulse
        bodyA.velocity = vec3.subtract(bodyA.velocity, vec3.scale(impulseVector, 1 / bodyA.mass));
        bodyB.velocity = vec3.add(bodyB.velocity, vec3.scale(impulseVector, 1 / bodyB.mass));
    }

    // Physics update
    update(deltaTime) {
        this.bodies.forEach(body => {
            // Apply gravity
            if (!body.isStatic) {
                body.velocity = vec3.add(body.velocity, vec3.scale(this.gravity, deltaTime));
            }
            
            // Update position
            body.position = vec3.add(body.position, vec3.scale(body.velocity, deltaTime));
            
            // Update bounding box
            body.updateBoundingBox();
        });
        
        // Check collisions
        this.checkCollisions();
    }
}

AI System Implementation

Enemy Tank AI

class TankAI {
    constructor(tank) {
        this.tank = tank;
        this.state = 'patrol';
        this.target = null;
        this.pathfinder = new Pathfinder();
        this.behaviorTree = new BehaviorTree();
        this.setupBehaviorTree();
    }

    setupBehaviorTree() {
        // Build behavior tree
        const root = new Selector([
            new Sequence([
                new Condition(() => this.hasTarget()),
                new Selector([
                    new Action(() => this.attack()),
                    new Action(() => this.chase())
                ])
            ]),
            new Action(() => this.patrol())
        ]);
        
        this.behaviorTree.setRoot(root);
    }

    update(deltaTime, gameState) {
        // Update perception system
        this.updatePerception(gameState);
        
        // Execute behavior tree
        this.behaviorTree.execute(deltaTime);
        
        // Update tank controls
        this.updateTankControls(deltaTime);
    }

    updatePerception(gameState) {
        const playerTank = gameState.playerTank;
        const distance = vec3.distance(this.tank.position, playerTank.position);
        
        // Vision detection
        if (distance <= this.tank.viewDistance) {
            // Check if within field of view
            const dirToPlayer = vec3.normalize(vec3.subtract(playerTank.position, this.tank.position));
            const angle = vec3.angle(this.tank.forward, dirToPlayer);
            
            if (angle <= this.tank.viewAngle) {
                // Ray casting for obstruction
                if (!this.isObstructed(this.tank.position, playerTank.position, gameState.obstacles)) {
                    this.target = playerTank;
                }
            }
        }
    }

    attack() {
        if (!this.target) return false;
        
        // Aim at target
        const dirToTarget = vec3.normalize(vec3.subtract(this.target.position, this.tank.position));
        this.tank.turretRotation = Math.atan2(dirToTarget.x, dirToTarget.z);
        
        // Fire
        if (this.isAimed() && this.tank.canFire()) {
            this.tank.fire();
            return true;
        }
        
        return false;
    }

    chase() {
        if (!this.target) return false;
        
        // Use A* algorithm for path planning
        const path = this.pathfinder.findPath(
            this.tank.position, 
            this.target.position, 
            gameState.obstacles
        );
        
        if (path.length > 1) {
            const nextWaypoint = path[1];
            this.moveTowards(nextWaypoint);
            return true;
        }
        
        return false;
    }

    patrol() {
        // Patrol behavior
        if (!this.patrolTarget || vec3.distance(this.tank.position, this.patrolTarget) < 2.0) {
            this.patrolTarget = this.getRandomPatrolPoint();
        }
        
        this.moveTowards(this.patrolTarget);
        return true;
    }

    moveTowards(target) {
        const direction = vec3.normalize(vec3.subtract(target, this.tank.position));
        
        // Turn towards target
        const targetRotation = Math.atan2(direction.x, direction.z);
        this.tank.rotation = this.lerp(this.tank.rotation, targetRotation, 0.1);
        
        // Move forward
        this.tank.moveForward();
    }
}

Game System Integration

Main Game Loop

class TankWarGame {
    constructor(canvas) {
        this.canvas = canvas;
        this.renderEngine = new RenderEngine(canvas);
        this.physicsEngine = new PhysicsEngine();
        this.audioSystem = new AudioSystem();
        this.inputManager = new InputManager();
        
        this.scene = new Scene();
        this.camera = new Camera();
        this.gameState = 'playing';
        
        this.playerTank = new Tank(TankType.PLAYER);
        this.enemyTanks = [];
        
        this.initGame();
        this.startGameLoop();
    }

    initGame() {
        // Create terrain
        this.createTerrain();
        
        // Create enemy tanks
        this.spawnEnemyTanks(5);
        
        // Set camera follow
        this.camera.setTarget(this.playerTank);
        
        // Load sound effects
        this.audioSystem.loadSounds({
            fire: 'sounds/tank_fire.ogg',
            explosion: 'sounds/explosion.ogg',
            engine: 'sounds/tank_engine.ogg'
        });
    }

    startGameLoop() {
        let lastTime = 0;
        
        const gameLoop = (currentTime) => {
            const deltaTime = (currentTime - lastTime) / 1000.0;
            lastTime = currentTime;
            
            this.update(deltaTime);
            this.render();
            
            requestAnimationFrame(gameLoop);
        };
        
        requestAnimationFrame(gameLoop);
    }

    update(deltaTime) {
        // Update input
        this.inputManager.update();
        
        // Update player tank
        this.updatePlayerTank(deltaTime);
        
        // Update enemy tank AI
        this.enemyTanks.forEach(tank => {
            tank.ai.update(deltaTime, this.getGameState());
        });
        
        // Update physics system
        this.physicsEngine.update(deltaTime);
        
        // Update camera
        this.camera.update(deltaTime);
        
        // Check game over conditions
        this.checkGameOver();
    }

    render() {
        // Render 3D scene
        this.renderEngine.render(this.scene, this.camera);
        
        // Render UI
        this.renderUI();
    }

    renderUI() {
        const ctx = this.canvas.getContext('2d');
        
        // Draw health bar
        this.drawHealthBar(ctx, this.playerTank.health);
        
        // Draw ammo counter
        this.drawAmmoCounter(ctx, this.playerTank.ammo);
        
        // Draw minimap
        this.drawMiniMap(ctx);
    }
}

Performance Optimization Approaches

The implementation employs several standard optimization techniques across rendering, physics, and AI:

Rendering: Frustum culling reduces overdraw by rendering only objects within camera view. LOD systems scale model complexity by distance. Instanced rendering batches identical objects into single draw calls. Texture atlasing consolidates multiple images to reduce state changes.

Physics: Spatial partitioning via grid structures accelerates collision detection. A sleep system prevents static objects from participating in physics calculations. Simplified collision geometries use basic shapes rather than exact model meshes.

AI: Decision caching stores recent AI results to reduce redundant calculation. Frame-distributed updates stagger AI computations across multiple frames, spreading load rather than clustering it into single frames. Hierarchical decision-making combines coarse strategic choices with fine-grained local adjustments.

Future Development Directions

Potential areas for expansion include real-time multiplayer via WebSocket, visual level editing tools, particle effects, WebXR support for virtual reality, and mobile-specific adaptations with touch controls and performance tuning.

© 2026 Yuxu Ge ·