[Yugong Series] August 2023 WEBGL Topic - Snake Game

Article directory


foreword

Snake game is a classic video game, players need to control a snake to eat various food, make it longer and longer, and avoid hitting its body or walls. As more and more food is eaten, the speed of the snake will also increase. Players need to constantly adjust the direction of the snake to avoid failure. Snake game can be played on different platforms such as computers, mobile phones, game consoles, etc. It is a simple and interesting casual game.

1. Snake game

Ok, the following is a complete WebGL implementation of the Snake game. In this implementation, we will use the keyboard to control the snake's movement and make the snake grow longer when it eats food.

HTML code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    html,body{
      
      
        height: 100%;
        margin: 0;
        text-align: center;
    }
    canvas{
      
      
        width: 600px;
        height: 600px;
        margin-top: 100px;
        background-color: aqua;
    }
</style>
<body>
    <canvas></canvas>
    <div>
        <button id="start">开始</button>
        <button id="stop">暂停</button>
    </div>

</body>
</html>

JavaScript code:

<script>
    const canvas = document.querySelector('canvas');
    const gl = canvas.getContext('webgl');

    // 顶点着色器源码
    const vertexShaderSource = `
        attribute vec4 aPostion;
        void main(){
            gl_Position = aPostion;
            gl_PointSize = 5.0;
        }
    `

    // 片元着色器源码
    const fragmentShaderSource  = `
        void main(){
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    `

    // 初始化着色器
    function initShader(gl,vertexShaderSource,fragmentShaderSource){
    
    
        // 创建顶点着色器
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        // 创建片元着色器
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        // 为着色器添加源码
        gl.shaderSource(vertexShader, vertexShaderSource);
        gl.shaderSource(fragmentShader, fragmentShaderSource);
        // 编译着色器
        gl.compileShader(vertexShader);
        gl.compileShader(fragmentShader);
        // 创建程序
        const program = gl.createProgram();
        // 为程序添加着色器
        gl.attachShader(program,vertexShader);
        gl.attachShader(program,fragmentShader);
        // 建立联系
        gl.linkProgram(program);
          // 使用程序
        gl.useProgram(program);
        return program;
    }

    const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
    // 获取attribute参数
    const aPostion = gl.getAttribLocation(program,'aPostion');

    // 蛇
    const points = [{
    
    x:0,y:0}];
    // 当前的点
    const point = {
    
    
        isConnect:true
    }

    const ERROR_SIZE = 0.03;
    const BASE_STEP = 0.03;
    // 每步走多少
    let step = BASE_STEP;
    // 方向
    let direction = 'x';
    let timer = null;
    // 生成点
    const createPoint = () => {
    
    
        if(point.isConnect){
    
    
            point.x = Math.random()*2 - 1;
            point.y = Math.random()*2 - 1;
            point.isConnect = false;
        }
    }

    document.onkeydown = e => {
    
    
        switch(e.keyCode){
    
    
            case 37:
                direction = 'x';
                step = -1 * BASE_STEP;
                break;
            case 38:
                direction = 'y';
                step = BASE_STEP;
                break;
            case 39:
                direction = 'x';
                step = BASE_STEP;
                break;
            case 40:
                direction = 'y';
                step = -1 * BASE_STEP;
                break;
        }
    }

    const draw = () => {
    
    
        // 绘制食物
        gl.vertexAttrib4f(aPostion,point.x,point.y,0.0,1.0);
        gl.drawArrays(gl.POINTS,0,1);
        // 蛇需要动,那么就需要改变头的位置,并且后面的位置要为一到上一个位置
        let prex = 0;
        let prey = 0;
        // 绘制蛇
        for(let i = 0; i < points.length ; i++){
    
    
            if(i === 0){
    
    
                // 头部的位置通过位移解决
                prex = points[0].x;
                prey = points[0].y;
                points[0][direction] += step;
            }else{
    
    
                const {
    
     x, y } = points[i];
                points[i].x = prex;
                points[i].y = prey;

                prex = x;
                prey = y;
            }
            // 画蛇
            gl.vertexAttrib4f(aPostion,points[i].x,points[i].y,0.0,1.0);
            gl.drawArrays(gl.POINTS,0,1);
        }
    }

    // 开始
    const start = () => {
    
    
        createPoint();
        // 需要让蛇动起来
        timer = setInterval(()=>{
    
    
            if(points[0].x > 1.0 ||
               points[0].x < -1.0 ||
               points[0].y > 1.0 ||
               points[0].y < -1.0 
            ){
    
    
                // 游戏结束
                alert('游戏结束')
                reast();
                return ;
            }
            // 如果在误差范围内则命中
            if(points[0].x < point.x + ERROR_SIZE &&
               points[0].x > point.x - ERROR_SIZE &&
               points[0].y < point.y + ERROR_SIZE &&
               points[0].y > point.y - ERROR_SIZE 
            ){
    
    
                points.push({
    
    x:point.x,y:point.y});
                point.isConnect = true;
                createPoint();
            }
            // 需要去绘制
            draw()
        },100)
    }

    // 重置
    const reast = () => {
    
    
        timer && clearInterval(timer);
        points = [{
    
    x:0,y:0}];
        point = {
    
    
            isConnect:true
        }
        step = BASE_STEP;
        direction = 'x';
    }
    

    document.querySelector('#start').addEventListener('click',()=>{
    
    
        start();
    })

    document.querySelector('#stop').addEventListener('click',()=>{
    
    
        timer && clearInterval(timer);
    })
</script>

In this implementation, we add a food object and check every update cycle if the snake has eaten the food. If the snake eats the food, we make the snake grow longer and spawn a new food in a new location.

We also add an event listener to change the direction of the snake when an arrow key is pressed.

Guess you like

Origin blog.csdn.net/aa2528877987/article/details/132082925