Make a snake game from scratch, just know vue

I am participating in the Nuggets Community Game Creative Contest Team Competition. For details, please see: Game Creative Contribution Contest

Today, I will share with you a small game of greedy snake. It is html + css + jsimplemented in pure vue + uniCloudform. Since the technology stack is very shallow, I hope the big guys don't spray it. Without further ado, let's start straight.

The effect diagram is as follows

renderings.jpg

Initialize the project

This project is uni-appdevelopment based, so it's best to use it HBuilderas your editor of choice.

New Project

Open HBuilder, create a new project, name the project snake_eat_worm(snake eats bugs), I gave it a tall name: Snake Fighting Group Worm. Full marks for the name, give yourself a chicken leg. Select the default template, check Enable uniCloud(Alibaba Cloud is used by default), and click Create.

New project.jpgThis HBuilderwill automatically generate the project directory:

project directory.png

We run the project directly:

run project.png

Seeing the following interface indicates that the project is running successfully

run successfully.jpg

Game resource preparation

put resources in a static/imagesdirectory

game resources.jpg

map

Open the pages/indexpage, let's modify it and add our own things:

<template>
    <view class="content">
        贪吃蛇~
    </view>
</template>
复制代码

Modify the home page.jpg

ok, the page is already under our control, we are going to officially start it.

draw the ground

We set the ground to be a 10 * 10 grid, and use a div to draw a 10 * 10 grid?

Initialize the grid.png

Young hero! Wait, just a div is enough! If you don't believe me, see:

<template>
    <view class="content">
        <view class="game-field">
            <view class="block"></view>
            <view class="block"></view>
            <view class="block"></view>
            <view class="block"></view>
            <!-- 此处省略 100 - 4 个 -->
        </view>
    </view>
</template>
<style>
    .content {
        height: 100vh;
        width: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: space-between;
        padding: 0;
    }
    .game-field {
        display: flex;
        flex-wrap: wrap;
    }
    .block {
        width: 10vw;
        height: 10vw;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: rgb(232, 235, 178);
        background-repeat: no-repeat;
        background-position: center;
        background-size: cover;
        box-sizing: border-box;
        outline: 2upx solid;
    }
</style>
复制代码

I believe that the friends who only know divs should be more familiar with the rendering of the above repeated divs than I am:

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" v-for="x in 100" :key="x"></view>
        </view>
    </view>
</template>
复制代码

xx.jpg

Well done, now there is a lump of yellow land in our game interface, let's see how to put a few bugs on the ground.

drawing bugs

Before drawing, let's think about how to use div to draw different patterns (snakes and bugs or land) in different grids. We might as well think of these 100 grids as an array. Each grid corresponds to the subscripts 0-99 of the array. We initialize the array to 100 zeros, and we use the number 0 to represent the land. Use the number 1 for the bug and the number 2 for the snake. We slightly modified the above code:

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" v-for="(x,i) in blocks" :key="i">{{x}}</view>
        </view>
    </view>
</template>
<script>
    export default {
        data() {
            return {
                blocks: new Array(100).fill(0), // 格子
                worms: [6, 33, 87] // 初始化三只虫子,数字代表它所处的格子的下标
            }
        },
        onLoad() {
            this.worms.forEach(x=> {
                this.blocks[x] = 1;
            })
            this.$forceUpdate();
        }
    }
</script>
复制代码

array.jpg

可以看到在黄色土地里有3个格子的数字是1,其他是0。这样就好办了,我们只需把数字是1的格子的背景图变成一只小虫子就好了:

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" 
                :style="{'background-image': bg(x)}" 
                v-for="(x, i) in blocks" :key="i">
                {{x}}
            </view>
        </view>
    </view>
</template>
<script>
    import worm from "../../static/images/worm.png";
    export default {
        data() {
            return {
                blocks: new Array(100).fill(0), // 格子
                worms: [6, 33, 87] // 初始化三只虫子,数字代表它所处的格子的下标
            }
        },
        onLoad() {
            // 将虫子的格子赋值为1
            this.worms.forEach(x=> {
                this.blocks[x] = 1;
            })
        },
        methods: {
            bg(type) {
                let bg = "";
                switch (type) {
                    case 0: // 地板
                        bg = "unset";
                        break;
                    case 1: // 虫子
                        bg = `url(${worm})`;
                        break;
                    case 2: // 蛇
                        // TODO
                }
                return bg;
            }
        },
    }
</script>
复制代码

1649768365(1).jpg

至此,虫子的绘制就完成了,接下来我们开始绘制蛇,蛇的绘制可是有点难度哦,各位看官做好准备了吗,只会div的朋友,恐怕你真的要告辞了,哈哈啊哈

绘制蛇

从游戏资源中,我们可以看到我们的蛇是有头有尾的,咱们先不管,咱们先把蛇的身子怼上去,去头去尾拿去炖汤~

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" 
                :style="{'background-image': bg(x)}" 
                v-for="(x, i) in blocks" :key="i">
                {{x}}
            </view>
        </view>
    </view>
</template>
<script>
    import worm from "../../static/images/worm.png";
    import snakeBody from "../../static/images/snake_body.png";
    export default {
        data() {
            return {
                blocks: new Array(100).fill(0), // 格子
                worms: [6, 33, 87], // 初始化三只虫子,数字代表它所处的格子的下标
                snakes: [0, 1, 2, 3] // 我们的蛇默认在左上角的位置,占据4个格子
            }
        },
        onLoad() {
            // 将虫子的格子赋值为1
            this.worms.forEach(x=> {
                this.blocks[x] = 1;
            })
            // 同理将蛇的格子赋值为2
            this.snakes.forEach((x) => {
                this.blocks[x] = 2;
            });
            this.$forceUpdate();
        },
        methods: {
            bg(type) {
                let bg = "";
                switch (type) {
                    case 0: // 地板
                        bg = "unset";
                        break;
                    case 1: // 虫子
                        bg = `url(${worm})`;
                        break;
                    case 2: // 蛇
                        bg = `url(${snakeBody})`;
                        break;
                }
                return bg;
            }
        },
    }
</script>
复制代码

1649768552(1).jpg

哈哈,现在我们可以看到一个无头无尾蛇,拿去炖汤。其实啊,完整的蛇更美味呢,接下来我们把蛇头蛇尾补上。这里我们需要记录当前格子的下标,因为我们定义蛇的数组是记录了蛇所处格子的下标的:

绘制蛇头蛇尾

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" 
                :style="{'background-image': bg(x, i)}" 
                v-for="(x, i) in blocks" :key="i">
                {{x}}
            </view>
        </view>
    </view>
</template>
<script>
    import worm from "../../static/images/worm.png";
    import snakeBody from "../../static/images/snake_body.png";
    import snakeHead from "../../static/images/snake_head.png";
    import snakeTail from "../../static/images/snake_tail.png";
    export default {
        data() {
            return {
                blocks: new Array(100).fill(0), // 格子
                worms: [6, 33, 87], // 初始化三只虫子,数字代表它所处的格子的下标
                snakes: [0, 1, 2, 3] // 我们的蛇默认在左上角的位置,占据4个格子
            }
        },
        onLoad() {
            // 将虫子的格子赋值为1
            this.worms.forEach(x=> {
                this.blocks[x] = 1;
            })
            // 同理将蛇的格子赋值为2
            this.snakes.forEach((x) => {
                this.blocks[x] = 2;
            });
            this.$forceUpdate();
        },
        methods: {
            bg(type, index) {
                let bg = "";
                switch (type) {
                    case 0: // 地板
                        bg = "unset";
                        break;
                    case 1: // 虫子
                        bg = `url(${worm})`;
                        break;
                    case 2: // 蛇
                        // 蛇的数组最后一个就是蛇头了
                        let head = this.snakes[this.snakes.length - 1];
                        // 同理第一个就是蛇尾
                        let tail = this.snakes[0];
                        if (index === head) {
                            bg = `url(${snakeHead})`;
                        } else if (index === tail) {
                            bg = `url(${snakeTail})`;
                        } else {
                            bg = `url(${snakeBody})`;
                        }
                        break;
                }
                return bg;
            }
        },
    }
</script>
复制代码

1649768716(1).jpg

至此,蛇的绘制完成了,但是看着好难受啊,蛇的头身子尾巴都没对上呢

让它看着正常

将图片顺时针旋转90度:

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" 
                :style="{'background-image': bg(x, i),transform: rotate(90deg)}" 
                v-for="(x, i) in blocks" :key="i">
                {{x}}
            </view>
        </view>
    </view>
</template>
复制代码

1649768861(1).jpg

欧耶,看着正常了!emmmm...貌似哪里不对劲,啊呀,虫子怎么倒了~,加个判断,是蛇才旋转90度:

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" 
                :style="{'background-image': bg(x, i),
                transform: x === 2 ? 'rotate(90deg)': 'rotate(0)'}" 
                v-for="(x, i) in blocks" :key="i">
                {{x}}
            </view>
        </view>
    </view>
</template>
复制代码

1649769186(1).jpg

嗯,正常了!

开始游戏

蛇和虫子都画完了,接下来我们应该考虑的是如何让蛇动起来

让蛇动起来

要让蛇动,肯定要加个定时器了。我们刚开始是在onLoad的时候渲染一次界面,要让蛇动起来,我们需要将绘制蛇的方法封装起来,这样才能在定时器中循环执行:

<script>
    // ...
    export default {
        // ...
        data() {
            return: {
                // ...
                timer: null // 定时器
            }
        },
        onLoad() {
            // ...
            this.paint();
            this.timer = setInterval(() => {
                this.toWards(); // 每秒向前走一格
            }, 1000);
        },
        methods: {
            // ...
            paint() {
                this.worms.forEach((x) => {
                    this.blocks[x] = 1;
                });
                this.snakes.forEach((x) => {
                    this.blocks[x] = 2;
                });
                this.$forceUpdate();
            },
            toWards() {
                // 头部下标
                let head = this.snakes[this.snakes.length - 1];
                // 尾部下标
                let tail = this.snakes[0];
                // 向右走一格
                let next = head + 1; 
                // 那么下一格就应该加入蛇的数组中
                this.snakes.push(next); 
                // 保存下一格是什么类型方便一会判断蛇的数组是否需要弹出尾部元素
                let nextType = this.blocks[next]; 
                // 蛇头经过的位置必然变成蛇的格子,于是赋值为2
                this.blocks[next] = 2;
                // 如果是空白格,则蛇的长度应该保持不变,需要弹出蛇的尾部下标
                if (nextType === 0) {
                    this.snakes.shift();
                } else {
                    // 如果是虫子格,则虫子数组会过滤掉被吃的虫子
                    this.worms = this.worms.filter((x) => x !== next);
                }
                // 蛇尾部经过后一定是土地格子了,好好思考下
                this.blocks[tail] = 0;
                // 绘制
                this.paint();
            }
        },
    }
</script>
复制代码

1649770928(1).jpg

蛇是动起来了,但是好像有点呆,不会拐弯,并且我们发现它会穿过边界,这不是我们想要的效果。我们需要在界面上添加四个按钮,用来操控我们可爱的蛇蛇,绑定上各自的事件:

绘制上下左右键

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" 
                :style="{'background-image': bg(x, i),transform: rotate(90deg)}" 
                v-for="(x, i) in blocks" :key="i">
                {{x}}
            </view>
        </view>
        <view class="action-field">
            <button @click="bindUp">上</button>
            <view class="flex">
                <button @click="bindLeft">左</button>
                <button @click="bindRight">右</button>
            </view>
            <button @click="bindDown">下</button>
        </view>
    </view>
</template>
<script>
    // ...
    export default {
        // ...
        methods: {
            bindUp() {
                console.log('up')
            },
            bindDown() {
                console.log('down')
            },
            bindLeft() {
                console.log('left')
            },
            bindRight() {
                console.log('right')
            },
        },
    }
</script>
<style>
    /* ... */
    .flex {
        display: flex;
        width: 50%;
        justify-content: space-between;
    }
    .action-field {
        display: flex;
        flex-direction: column;
        width: 100%;
        align-items: center;
    }
</style>
复制代码

emmmm 1649748432(1).png

我们的蛇只会往右走,我们在toWards方法里的next赋值加个判断方向的逻辑:

methods: {
    // ...
    toWards() {
        // ...
        let next;
        switch (this.direction) {
            case "up":
                next = head - 10;
                break;
            case "down":
                next = head + 10;
                break;
            case "left":
                next = head - 1;
                break;
            case "right":
                next = head + 1;
                break;
        }
        // ...
        this.paint();
    },
    bindUp() {
        this.direction = 'up';
    },
    bindDown() {
        this.direction = 'down';
    },
    bindLeft() {
        this.direction = 'left';
    },
    bindRight() {
        this.direction = 'right';
    },
}
复制代码

1649772093(1).jpg

妈耶!蛇头方向不对啊。之前粗暴的固定旋转90度是不对的,这个背景图的旋转需要具体问题具体分析呢,新增一个计算旋转的方法,此方法需要好好思考:

<template>
    <view class="content">
        <view class="game-field">
            <view class="block" 
                :style="{'background-image': bg(x, i),
                transform: `rotate(${calcRotate(x, i)}deg)`}" 
                v-for="(x, i) in blocks" :key="i">
                {{x}}
            </view>
        </view>
        <view class="action-field">
            <button @click="bindUp">上</button>
            <view class="flex">
                <button @click="bindLeft">左</button>
                <button @click="bindRight">右</button>
            </view>
            <button @click="bindDown">下</button>
        </view>
    </view>
</template>
<script>
    // ...
    export default {
        // ...
        methods: {
            // ...
            calcRotate(type, index) {
                let rotate = 0;
                switch (type) {
                    case 0: // 地板
                        rotate = 0;
                        break;
                    case 1: // 虫子
                        rotate = 0;
                        break;
                    case 2: // 蛇
                        let length = this.snakes.length;
                        let head = this.snakes[length - 1];
                        let tail = this.snakes[0];
                        // 尾巴的前一个
                        let tailPre = this.snakes[1];
                        // 身子的前一个
                        let bodyPre = this.snakes[this.snakes.indexOf(index) + 1];
                        if (index === head) {
                            if (this.direction === "right") {
                                rotate = 90;
                            } else if (this.direction === "down") {
                                rotate = 180;
                            } else if (this.direction === "left") {
                                rotate = 270;
                            } else {
                                rotate = 0;
                            }
                        } else if (index === tail) {
                            if (tailPre - 1 === tail) {
                                // 向右走的
                                rotate = 90;
                            } else if (tailPre - 10 === tail) {
                                // 向下走的
                                rotate = 180;
                            } else if (tailPre + 1 === tail) {
                                // 向左走的
                                rotate = 270;
                            } else {
                                // 向上走的
                                rotate = 0;
                            }
                        } else {
                            if (bodyPre - 1 === index) {
                                // 向右走的
                                rotate = 90;
                            } else if (bodyPre - 10 === index) {
                                // 向下走的
                                rotate = 180;
                            } else if (bodyPre + 1 === index) {
                                // 向左走的
                                rotate = 270;
                            } else {
                                // 向上走的
                                rotate = 0;
                            }
                        }
                        break;
                }
                return rotate;
            },
        },
    }
</script>
复制代码

1649772878(1).jpg

看,它变得像那么回事了,我们的蛇现在还不会死,我们需要设置它撞到边界,或者自身会死,也就是游戏结束。

<script>
    // ...
    export default {
        // ...
        methods: {
            // ...
            toWards() {
                if (this.snakes.length === 100) {
                    alert("你赢了!");
                    clearInterval(this.timer);
                    return;
                }
                let head = this.snakes[this.snakes.length - 1];
                let tail = this.snakes[0];
                let next;
                switch (this.direction) {
                    case "up":
                        next = head - 10;
                        break;
                    case "down":
                        next = head + 10;
                        break;
                    case "left":
                        next = head - 1;
                        break;
                    case "right":
                        next = head + 1;
                        break;
                }
                let gameover = this.checkGame(next);
                if (gameover) {
                    console.log("游戏结束");
                    clearInterval(this.timer);
                } else {
                    // 游戏没结束
                    this.snakes.push(next);
                    let nextType = this.blocks[next];
                    this.blocks[next] = 2;
                    // 如果是空白格
                    if (nextType === 0) {
                        this.snakes.shift();
                    } else {
                        // 如果是虫子格
                        this.worms = this.worms.filter((x) => x !== next);
                    }
                    this.blocks[tail] = 0;
                    this.paint();
                }
            },
            checkGame(next) {
                let gameover = false;
                let isSnake = this.snakes.indexOf(next) > -1;
                if (isSnake) {
                    gameover = true;
                }
                switch (this.direction) {
                    case "up":
                        if (next < 0) {
                            gameover = true;
                        }
                        break;
                    case "down":
                        if (next >= 100) {
                            gameover = true;
                        }
                        break;
                    case "left":
                        if (next % 10 === 9) {
                            gameover = true;
                        }
                        break;
                    case "right":
                        if (next % 10 === 0) {
                            gameover = true;
                        }
                        break;
                }
                return gameover;
            },
        },
    }
</script>
复制代码

1649773393(1).jpg

撞到上边缘,挂了!

随机生成虫子

At present, we have written three dead bugs. In fact, our bugs are randomly generated, and after eating one, the other will be generated, and the bug will not appear in the position of the snake:

methods: {
    // ... 
     toWards() {
        if (this.snakes.length === 100) {
            alert("你赢了!");
            clearInterval(this.timer);
            return;
        }
        let head = this.snakes[this.snakes.length - 1];
        let tail = this.snakes[0];
        let next;
        switch (this.direction) {
            case "up":
                next = head - 10;
                break;
            case "down":
                next = head + 10;
                break;
            case "left":
                next = head - 1;
                break;
            case "right":
                next = head + 1;
                break;
        }
        let gameover = this.checkGame(next);
        if (gameover) {
            console.log("游戏结束");
            clearInterval(this.timer);
        } else {
            // 游戏没结束
            this.snakes.push(next);
            let nextType = this.blocks[next];
            this.blocks[next] = 2;
            // 如果是空白格
            if (nextType === 0) {
                this.snakes.shift();
            } else {
                // 如果是虫子格
                this.worms = this.worms.filter((x) => x !== next);
                let nextWorm = this.createWorm();
                this.worms.push(nextWorm);
            }
            this.blocks[tail] = 0;
            this.paint();
        }
    },
    // 生成下一只虫子
    createWorm() {
        let blocks = Array.from({length: 100}, (v, k) => k);
        let restBlocks = blocks.filter(x => this.snakes.indexOf(x) < 0);
        let worm = restBlocks[Math.floor(Math.random() * restBlocks.length)];
        return worm;
    },
}
复制代码

Detail optimization

Remove the borders of the grid and the numbers in the grid:

1649773690(1).jpg

So far, the basic functions of Snake have been completed, and congratulations to the students who only know div. There will be updates in the future, please like and follow

To-do function

  • Can move up, down, left and right
  • Hit four sides game over
  • Bump yourself game over
  • Snakes will grow sideways after eating bugs
  • Eating a bug will generate a random bug
  • Calculate how long the current snake is
  • Added exploding bugs
  • Land polluted after bug blast
  • Hit the polluted land game over
  • add user system
  • Add leaderboard

Special thanks

@大 handsome old ape @Sophora . Thanks to Mr. Dashuai for leading the team, providing material support and technical support, and thanks to the help of the teammates.

Source code address , comments are welcome, if you feel good, click star. Bye bye Jianghu~

Guess you like

Origin juejin.im/post/7085727363547283469