从零开始做一个贪吃蛇游戏(用纯html + css + js实现,主要技术栈:vue + uniCloud)

今天给大家分享一个贪吃蛇的小游戏,用纯html + css + js实现,主要技术栈:vue + uniCloud,希望能对还未涉及游戏开发的小伙伴有所启发。由于技术栈很浅,希望大佬勿喷。话不多说,直接开撸。

效果图如下

初始化项目

本项目是基于uni-app开发的,所以最好使用HBuilder作为你的编辑器首选。

新建项目

打开HBuilder,新建项目,给项目命名snake_eat_worm(蛇吃虫子),我给它起了个高大上的名字:蛇战群蠕。名字满分,给自己加个鸡腿。选择默认模板,勾选启用uniCloud(默认使用阿里云),点击创建。

 这样HBuilder就会自动生成项目目录:

我们直接运行项目:

看到以下界面说明项目运行成功了

游戏资源准备

将资源放在static/images目录下

绘制地图

打开pages/index页面,我们将它修改一下,加上自己的东西:

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

ok,页面已经在我们的掌控之中,咱们要正式开搞了。

绘制地面

我们设定地面是一个10 * 10 的格子,用div画一个10 * 10 的格子?

少侠!等等,只会div就够了!不信你看:

<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>
复制代码

相信只会div的小伙伴,应该对于上面的重复div的渲染比我还熟吧:

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

干的漂亮,现在有一坨黄色的土地在我们的游戏界面中了,接下来我们看看怎么在地上放几只虫子。

绘制虫子

在绘制之前,我们思考下用div如何在不同的格子里画上不同的图案(蛇和虫子或者是土地)。我们不妨把这100个格子看成一个数组。每个格子对应数组的下标0-99。我们把数组初始化为100个0,我们用数字0代表土地。用数字1代表虫子,用数字2代表蛇。我们将上面的代码稍加改动:

<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>
复制代码

可以看到在黄色土地里有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>
复制代码

至此,虫子的绘制就完成了,接下来我们开始绘制蛇,蛇的绘制可是有点难度哦,各位看官做好准备了吗,只会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>
复制代码

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

绘制蛇头蛇尾

<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>
复制代码

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

让它看着正常

将图片顺时针旋转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>
复制代码

欧耶,看着正常了!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>
复制代码

嗯,正常了!

开始游戏

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

让蛇动起来

要让蛇动,肯定要加个定时器了。我们刚开始是在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>
复制代码

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

绘制上下左右键

<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 

我们的蛇只会往右走,我们在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';
    },
}
复制代码

妈耶!蛇头方向不对啊。之前粗暴的固定旋转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>
复制代码

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

<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>
复制代码

撞到上边缘,挂了!

随机生成虫子

目前我们的虫子是写死了三只,实际上我们的虫子是随机产生的,并且吃完一只会产生另外一只,虫子不会在蛇的位置上出现:

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;
    },
}
复制代码

细节优化

去除格子的边框和格子里的数字:

到目前为止,贪吃蛇的基本功能就完成了,恭喜只会div的同学也学废了。如果还没学废,可以在码上掘金在线体验,后面还会陆续更新,欢迎点赞关注

待办功能

  •  可上下左右移动

  •  撞到四边游戏结束

  •  撞到自己游戏结束

  •  吃到虫子蛇会边长

  •  吃到虫子会随机生成一只虫子

  •  计算当前蛇有多长

  •  新增会爆炸的虫子

  •  虫子爆炸后土地被污染

  •  撞到污染的土地游戏结束

  •  添加用户系统

  •  添加排行榜

猜你喜欢

转载自blog.csdn.net/l1063951462/article/details/124241899