前端实现带商店和设置的贪吃蛇大作战

目录

前言 

功能介绍

游戏截图

主界面

商店-皮肤 

商店-道具 

 查看快捷键

地雷设置 

代码

代码结构

​编辑

主要代码

总结


前言 

❤️欢迎来到我的博客,今天,我要向大家介绍一款前端游戏项目——带商店和可设置的贪吃蛇大作战。此项目总计耗时一天完成,主要是帮我那不争气的表弟写的大一的期末项目。现在免费分享给大家。

功能介绍

此游戏的玩法是控制蛇吃食物并躲避地雷,地雷会随着时间增加越来越多,吃到食物则可加分并减少场上的一颗地雷。

分数可以在商店购买物品和属性。在游玩时不要光顾着吃食物哦,如果你撞到了地雷则游戏直接结束,不过,如果你在商店购买了“宝珠”则可以抵消一次死亡。

如果觉得难度有点大可以在游戏中按下ESC键可,进到设置界面中关闭地雷功能

游戏用到到语言和框架:

  • vue
  • js+ts
  • html
  • css

游戏截图

主界面

商店-皮肤 

商店-道具 

 查看快捷键

地雷设置 

代码

代码结构

主要代码

// index.vue
<template>
    <div class="game">
        <div class="game_header">
            <div class="game_shop" @click="isShowStore = true">商店</div>
            <div class="game_hear_font">贪吃蛇大作战</div>
            <div class="game_score">得分:{{ scoreNum }} 分</div>
            <div class="game_speed">速度:{{ level }} 级</div>
            <div class="game_deposits">宝珠:{{ deposits }} 颗</div>
        </div>
        <div id="game_body" class="game_body">
            <div class="game_pause font_css" v-show="isPause">暂停中,按空格键继续</div>
            <div class="game_over" v-show="gameOver">游戏失败,按Enter键重新开始</div>
            <div class="game_init font_css" v-show="isInit">按任意方向键开始游戏</div>
            <div class="snake" v-for="(item, index) in snakeNums" :key="index">
                <div v-if="index === 0" style="z-index: 2; position: absolute">
                    <img
                        v-show="currentDirection === 'ArrowUp' || currentDirection === ''"
                        style="height: 20px; width: 20px"
                        src="@/assets/img/up.png"
                        alt=""
                    />
                    <img
                        v-show="currentDirection === 'ArrowDown'"
                        style="height: 20px; width: 20px"
                        src="@/assets/img/down.png"
                        alt=""
                    />
                    <img
                        v-show="currentDirection === 'ArrowLeft'"
                        style="height: 20px; width: 20px"
                        src="@/assets/img/left.png"
                        alt=""
                    />
                    <img
                        v-show="currentDirection === 'ArrowRight'"
                        style="height: 20px; width: 20px"
                        src="@/assets/img/right.png"
                        alt=""
                    />
                </div>
                <div v-else style="z-index: 1; position: absolute">
                    <img style="height: 20px; width: 20px" src="@/assets/img/body.png" alt="" />
                </div>
            </div>
        </div>
        <game-setting
            :visible="isShowSetting"
            @clickCheck="isCheck = !isCheck"
            @close="isShowSetting = false"
            :is-check="isCheck"
        />
        <game-store
            :visible="isShowStore"
            @close="isShowStore = false"
            @purchase="handlePurchase"
            :scoreNum="scoreNum"
            @setColor="setColor"
        />
    </div>
</template>

<script lang="ts" setup>
import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue';
import GameSetting from '@/views/componets/GameSetting.vue';
import GameStore from '@/views/componets/GameStore.vue';
import message from '@/components/Message/message';

const snakeNums = ref(10);
const scoreNum = ref(0);
const timer: any = ref();
const currentDirection = ref(''); // 前进方向
const isPause = ref(false); // 暂停标识
const gameOver = ref(false); // 游戏失败标识
const isInit = ref(true); // 初始话标识
const isShowSetting = ref(false); // 显示设置框
const isShowStore = ref(false); // 显示游戏商店
const isCheck = ref(true); //是否开启地雷
const reloadPage: any = inject('reloadPage'); // 获取重置页面的方法
function clickKeyBoard(event) {
    // 只有按下方向键、空格和Enter键才进行后面的操作,不然在移动中如果连续按下其他无关按键也会进行判断,占用资源,导致移动变卡顿
    if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Enter', ' ', 'Escape', 'p'].includes(event.key)) {
        return;
    }

    if (event.key === 'Escape') {
        // 商店弹框打开时,不能打开设置
        if (isShowStore.value) {
            return;
        }
        isShowSetting.value = true;
        if (!isInit.value && !gameOver.value) {
            isPause.value = true;
            if (timer.value) {
                clearTimeout(timer.value);
            }
        }
        return;
    }

    if (event.key === 'p') {
        // 设置弹框打开时,不能打开商店
        if (isShowSetting.value) {
            return;
        }
        isShowStore.value = !isShowStore.value;
        if (!isInit.value && !gameOver.value) {
            isPause.value = true;
            if (timer.value) {
                clearTimeout(timer.value);
            }
        }
        return;
    }

    // 游戏未开始或者游戏失败时,暂停无效
    if (event.key === ' ' && !isInit.value && !gameOver.value) {
        isPause.value = !isPause.value;
        gameOver.value = false;
        if (timer.value) {
            clearTimeout(timer.value);
        }
        if (isPause.value) {
            return;
        }
    }
    // 如果按的是方向键,存储前进的方向,为了点击继续后仍然按照原来的方向移动,同时退出初始化状态
    if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key) && !isPause.value && !gameOver.value) {
        currentDirection.value = event.key;
        isInit.value = false;
    }
    // 游戏没有暂停、没有失败时按方向键才有用
    if (
        ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(currentDirection.value) &&
        !isPause.value &&
        !gameOver.value
    ) {
        handleMover();
    }

    // 当游戏失败时,按Enter键重新开始游戏
    if (gameOver.value && event.key === 'Enter') {
        reloadPage();
        return;
    }
}

// 移动相关逻辑
const speed = ref(100);
function handleMover() {
    if (timer.value) {
        clearTimeout(timer.value);
    }
    timer.value = setInterval(() => {
        let snakes: any = document.getElementsByClassName('snake');
        let lodSnakes = [];
        for (let i = 0; i < snakes.length; i++) {
            lodSnakes.push({
                style: {
                    top: snakes[i].style.top,
                    left: snakes[i].style.left,
                    visibility: snakes[i].style.visibility
                }
            });
        }
        // 向上移动
        if (currentDirection.value === 'ArrowUp') {
            // 碰到上边界,游戏失败
            if (Number(lodSnakes[0].style.top.replace('px', '')) - 20 < 0) {
                clearTimeout(timer.value);
                gameOver.value = true;
                return;
            }
            //
            for (let i = 0; i < snakes.length; i++) {
                if (i === 0) {
                    snakes[0].style.top = Number(lodSnakes[0].style.top.replace('px', '')) - 20 + 'px';
                } else {
                    snakes[i].style.top = lodSnakes[i - 1].style.top;
                    snakes[i].style.left = lodSnakes[i - 1].style.left;
                }
            }
            // 向下移动
        } else if (currentDirection.value === 'ArrowDown') {
            // 碰到下边界,游戏失败
            if (Number(lodSnakes[0].style.top.replace('px', '')) + 20 > 780) {
                clearTimeout(timer.value);
                gameOver.value = true;
                return;
            }
            for (let i = 0; i < snakes.length; i++) {
                if (i === 0) {
                    snakes[0].style.top = Number(lodSnakes[0].style.top.replace('px', '')) + 20 + 'px';
                } else {
                    snakes[i].style.top = lodSnakes[i - 1].style.top;
                    snakes[i].style.left = lodSnakes[i - 1].style.left;
                }
            }
            // 向左移动
        } else if (currentDirection.value === 'ArrowLeft') {
            // 碰到左边界,游戏失败
            if (Number(lodSnakes[0].style.left.replace('px', '')) - 20 < 0) {
                clearTimeout(timer.value);
                gameOver.value = true;
                return;
            }
            for (let i = 0; i < snakes.length; i++) {
                if (i === 0) {
                    snakes[0].style.left = Number(lodSnakes[0].style.left.replace('px', '')) - 20 + 'px';
                } else {
                    snakes[i].style.top = lodSnakes[i - 1].style.top;
                    snakes[i].style.left = lodSnakes[i - 1].style.left;
                }
            }
            // 向右移动
        } else if (currentDirection.value === 'ArrowRight') {
            // 碰到右边界,游戏失败
            if (Number(lodSnakes[0].style.left.replace('px', '')) + 20 > 780) {
                clearTimeout(timer.value);
                gameOver.value = true;
                return;
            }
            for (let i = 0; i < snakes.length; i++) {
                if (i === 0) {
                    snakes[0].style.left = Number(lodSnakes[0].style.left.replace('px', '')) + 20 + 'px';
                } else {
                    snakes[i].style.top = JSON.parse(JSON.stringify(lodSnakes[i - 1].style.top));
                    snakes[i].style.left = JSON.parse(JSON.stringify(lodSnakes[i - 1].style.left));
                }
            }
        }
    }, speed.value);
}

// 添加食物
function addFood(number) {
    let body: any = document.getElementById('game_body');
    for (let i = 0; i < number; i++) {
        let food = document.createElement('div');
        food.className = 'food';
        food.style.height = '20px';
        food.style.width = '20px';
        food.style.position = 'absolute';
        food.style.borderRadius = '50%';
        food.style.backgroundColor = '#f6d77e';
        food.style.left = 20 * Math.floor(Math.random() * 40) + 'px';
        food.style.top = 20 * Math.floor(Math.random() * 40) + 'px';
        body.appendChild(food);
    }
}

// 添加地雷
function addMine(number) {
    let body: any = document.getElementById('game_body');
    for (let i = 0; i < number; i++) {
        let mine = document.createElement('div');
        mine.className = 'mine';
        mine.style.height = '20px';
        mine.style.width = '20px';
        mine.style.position = 'absolute';
        mine.style.borderRadius = '50%';
        mine.style.backgroundColor = 'black';
        mine.style.left = 20 * Math.floor(Math.random() * 40) + 'px';
        mine.style.top = 20 * Math.floor(Math.random() * 40) + 'px';
        body.appendChild(mine);
    }
}

// 监听地雷勾选框
watch(
    () => isCheck.value,
    (val) => {
        let mines: any = document.getElementsByClassName('mine');
        for (let i = 0; i < mines.length; i++) {
            if (val) {
                mines[i].style.display = '';
            } else {
                // 隐藏地雷
                mines[i].style.display = 'none';
            }
        }
    }
);

function handlePurchase(item) {
    if (item === '宝珠') {
        scoreNum.value -= 200;
        deposits.value++;
    } else if (item === '速度') {
        if (speed.value === 25) {
            message({ type: 'warn', text: '已达到速度上限制' });
            return;
        }
        scoreNum.value -= 100;
        speed.value /= 2;
    }
}

const deposits = ref(0);
const level = computed(() => {
    if (speed.value === 100) {
        return 1;
    }
    if (speed.value === 50) {
        return 2;
    } else {
        return 3;
    }
});

function setColor(type, color) {
    if (type === 'food') {
        let foods: any = document.getElementsByClassName('food');
        for (let i = 0; i < foods.length; i++) {
            foods[i].style.backgroundColor = color;
        }
    } else {
        let mines: any = document.getElementsByClassName('mine');
        for (let i = 0; i < mines.length; i++) {
            mines[i].style.backgroundColor = color;
        }
    }
}

const startListener = ref();
const mineListener = ref();
onMounted(() => {
    window.addEventListener('keyup', clickKeyBoard);
    let snakes: any = document.getElementsByClassName('snake');
    for (let i = 0; i < snakes.length; i++) {
        snakes[i].style.left = 100 + 'px';
        snakes[i].style.top = 100 + i * 20 + 'px';
        snakes[i].style.visibility = 'unset';
    }
    addFood(5);
    addMine(2);
    // 监听间隔需要小于或等于蛇移动的间隔
    startListener.value = setInterval(() => {
        if (isInit.value === false && isPause.value === false && gameOver.value === false) {
            let body = document.getElementById('game_body');
            let foods: any = document.getElementsByClassName('food');
            let mines: any = document.getElementsByClassName('mine');
            // 开启地雷时,如果蛇头碰撞到地雷游戏结束
            if (isCheck.value) {
                for (let i = 0; i < mines.length; i++) {
                    if (mines[i].style.left === snakes[0].style.left && mines[i].style.top === snakes[0].style.top) {
                        if (deposits.value > 0) {
                            deposits.value--;
                        } else {
                            clearTimeout(timer.value);
                            gameOver.value = true;
                        }
                        return;
                    }
                }
            }

            for (let i = 0; i < foods.length; i++) {
                if (foods[i].style.left === snakes[0].style.left && foods[i].style.top === snakes[0].style.top) {
                    let snakes: any = document.getElementsByClassName('snake');
                    // 添加的身体先隐藏,否则会因为渲染导致页面闪一下
                    snakes[snakes.length - 1].style.visibility = 'unset';
                    body.removeChild(foods[i]);
                    // 每吃掉一个食物,则减少一个地雷
                    if (mines.length > 0) {
                        body.removeChild(mines[mines.length - 1]);
                    }
                    scoreNum.value += 10;
                }
            }
        }
    }, 100);

    // 监听间隔需要小于或等于蛇移动的间隔
    startListener.value = setInterval(() => {
        if (isInit.value === false && isPause.value === false && gameOver.value === false) {
            let foods: any = document.getElementsByClassName('food');
            if (foods.length < 10) {
                addFood(5);
            }
        }
    }, 5000);

    // 监听间隔需要小于或等于蛇移动的间隔
    mineListener.value = setInterval(() => {
        if (isInit.value === false && isPause.value === false && gameOver.value === false) {
            addMine(3);
        }
    }, 5000);
});

onUnmounted(() => {
    window.removeEventListener('keyup', clickKeyBoard);
    if (timer.value) {
        clearTimeout(timer.value);
    }
    clearTimeout(startListener.value);
    clearTimeout(mineListener.value);
});
</script>

<style scoped lang="less">
.game {
    border: 2px solid sandybrown;
    border-radius: 1%;
    height: 900px;
    width: 800px;
    background-color: lavender;
    position: relative;
}

.game_header {
    position: relative;
    height: 100px;
    width: 100%;
    border-bottom: 2px solid sandybrown;
    display: flex;
    align-items: center;
    justify-content: center;

    .game_shop {
        position: absolute;
        left: 20px;
        height: 30px;
        width: 50px;
        text-align: center;
        line-height: 30px;
        top: 50%;
        color: white;
        cursor: pointer;
        background-color: yellowgreen;
        border-radius: 4px;
        border: 1px solid yellow;
        &:hover {
            color: saddlebrown;
        }
    }
    .game_hear_font {
        font-size: 25px;
        color: rebeccapurple;
    }

    .game_score {
        position: absolute;
        right: 100px;
        top: 20%;
        width: 120px;
    }
    .game_speed {
        position: absolute;
        right: 100px;
        top: 45%;
        width: 120px;
    }
    .game_deposits {
        position: absolute;
        right: 100px;
        top: 70%;
        width: 120px;
    }
}

.game_body {
    position: relative;
    height: 800px;
    width: 100%;
    border-radius: 0 0 1% 1%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.game_init {
    font-size: 30px;
    position: absolute;
    color: white;
    z-index: 999;
}

@keyframes stream {
    //匀速流动
    0% {
        background-position: 0 0;
    }
    100% {
        background-position: -100% 0;
    }
}

.font_css {
    background-image: -webkit-linear-gradient(
        left,
        green,
        yellow,
        pink,
        blue,
        red 25%,
        green 35%,
        blue 50%,
        yellow 60%,
        red 75%,
        pink 85%,
        blue 100%
    ); // 括号内可添加多种颜色,多种百分比
    -webkit-text-fill-color: transparent; // 颜色填充 透明
    -webkit-background-clip: text; // 背景颜色绘制区域
    animation: stream 5s infinite linear; //流动 15秒 循环 直线
    background-size: 200% 100%;
}

.game_pause {
    font-size: 30px;
    position: absolute;
    z-index: 999;
    color: white;
}

.game_over {
    font-size: 30px;
    position: absolute;
    z-index: 999;
    background-image: -webkit-linear-gradient(
        left,
        green,
        yellow,
        pink,
        blue,
        red 25%,
        green 35%,
        blue 50%,
        yellow 60%,
        red 75%,
        pink 85%,
        blue 100%
    ); // 括号内可添加多种颜色,多种百分比
    -webkit-text-fill-color: transparent; // 颜色填充 透明
    -webkit-background-clip: text; // 背景颜色绘制区域
    animation: cartoon 2s infinite linear;
    background-size: 200% 100%;
}

@keyframes cartoon {
    0% {
        transform: rotate(0deg);
        background-position: 0 0;
    }
    25% {
        transform: rotate(6deg);
    }
    50% {
        transform: rotate(0deg);
    }
    75% {
        transform: rotate(-6deg);
    }
    100% {
        transform: rotate(0deg);
        background-position: -100% 0;
    }
}

.snake {
    position: absolute;
    visibility: hidden;
}

.food {
    position: absolute;
    height: 20px;
    width: 20px;
    border-radius: 50%;
    background-color: #f6d77e;
}
</style>

总结

贪吃蛇这个游戏的难度对于刚开始学习前端的小伙伴是刚刚好的,这个我的这个版本还有很多功能可以完善。比如

  • 商店里的皮肤中可以加上蛇的皮肤。
  • 道具你也可以按自己的想法加一些好玩的功能。
  • 可以加入多人对战。
  • 加入人机。

希望这个游戏可以帮助到大家,感谢观看❤️,日后会推出更多的前端小游戏,请大家敬请期待哦🙏~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马可家的菠萝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值