使用TypeScript(TS) + Vue3+ Vite实现贪吃蛇项目

这篇博客介绍了如何利用TypeScript、Vue3和Vite框架构建一个贪吃蛇游戏。游戏包含食物随机刷新、分数累加和难度升级功能。随着分数增加,蛇的移动速度会加快,最高难度为10级。文章详细展示了项目的创建过程,目录结构,以及关键组件如Game、ScorePanel、Snake和Food的实现细节。
摘要由CSDN通过智能技术生成

贪吃蛇练习

使用TypeScript + Vue3+ Vite实现贪吃蛇的例子;

效果展示:
每次刷新,食物会随机刷新在屏幕的位置,当分数到10后,等级会加1,随后移动的速度会增加,10级为最高难度。
在这里插入图片描述

创建项目

npm init vite-app snakeGame

项目依赖

npm i -D less less-loader ts-loader typescript
{
  "name": "snakeGame",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "vue": "^3.0.4"
  },
  "devDependencies": {
    "@vue/compiler-sfc": "^3.0.4",
    "less": "^4.1.2",
    "less-loader": "^10.2.0",
    "ts-loader": "^9.2.6",
    "typescript": "^4.5.5",
    "vite": "^1.0.0-rc.13"
  }
}
// package.json

项目目录结构

image-20220216161148395

App.vue

<template>
<Game></Game>
</template>

<script>
import Game from './components/Game.vue'
export default {
  name: 'App',
  components:{
    Game
  }
}
</script>

index.css

*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

main.js

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

createApp(App).mount('#app')

components

Game.vue
<template>
  <div id="main">
    <div id="state">
      <div id="snake">
        <div id="body"></div>
      </div>
      <div id="food">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
      </div>
    </div>
    <div id="scope">
      <span id="score">SCORE:<span id="score2">0</span></span>
      <span id="level">LEVEL:<span id="level2">0</span></span>
    </div>
  </div>
</template>

<script lang="ts">
import useGame from '../modules/GameControl'
import {defineComponent, reactive} from 'vue'
interface student {
  name:string,
  age:number,
  gender:string
}
export default defineComponent({
  mounted(){
    // class Food {
    //   // 设置初始属性
    //       element:HTMLElement
    //       // 获取食物类
    //       constructor(){
    //         this.element = document.getElementById('food') 
    //       }
    //     // 定义getter方法
    //       get x (){
    //         return this.element.offsetLeft
    //       }

    //       get y (){
    //         return this.element.offsetTop
    //       }
    //       change (){
    //         this.element.style.top = Math.round(Math.random() * 290)+'px'
    //         this.element.style.left = Math.round(Math.random() * 290)+'px'
    //       }
    //     }

    //   // 测试代码
    //   // const snake = new Food()
    //   // console.log(snake);
    //   // console.log(snake.y);
    //   // snake.change()  更换初始点位置
      useGame()
  },
  setup(){
    
    return {
    }
  }
})
</script>

<style scoped lang="less">

@bg:#b7d4a8;

#main{
  width: 360px;
  height: 420px;
  margin: 100px auto;
  border: 10px solid #000;
  border-radius: 40px;
  background-color: @bg;
  display: flex;
  align-items: center;
  justify-content: space-around;
  flex-direction: column;
  #state{
    width: 304px;
    height: 304px;
    border: 2px solid #000;
    position: relative;
    #snake{
      &>#body{
        width: 10px;
        height: 10px;
        border: 1px solid @bg;
        background-color: #000;
        position: absolute;
      }
    }
    #food{
      width: 10px;
      height: 10px;
      display: flex;
      flex-flow: wrap;
      justify-content: space-between;
      align-content: space-between;
      position: absolute;
      top: 100px;
      left: 200px;
      div{
        width: 4px;
        height: 4px;
        background-color: #000;
      }
    }
  }
  #scope{
    width: 304px;
    font:20px bold Courier;
    display: flex;
    justify-content: space-between;
    align-items: center;
    #score{

    }
    #level{

    }
  }
}
</style>

modules

Food.ts
// 定义食物类Food
class Food{
    // 定义的一个属性表示食物所对应的元素
    element: HTMLElement;

    constructor(){
        // 获取页面种的food元素并将其赋值给element
        this.element = document.getElementById('food')!;
    }

    // 定义一个获取食物X轴坐标的方法
    get X(){
        return this.element.offsetLeft;
    }

    // 定义一个获取食物Y轴坐标的方法
    get Y(){
        return this.element.offsetTop;
    }

    // 修改食物位置
    change(){
        // 生成一个随机的位置
        // 食物的位置最小是0, 最大是290
        // 蛇移动一次就是一格,一格大小就是10,所以要求 食物的坐标必须是整10

        // Math.round(Math.random() * 290);
       let top = Math.round(Math.random() * 29) * 10;
       let left = Math.round(Math.random() * 29) * 10;
        // Math.floor(Math.random() * 30) * 10;//向下取整

        this.element.style.left = top + 'px';
        this.element.style.top = left + 'px';
    }
}

// 测试代码
// const food = new Food();
// console.log(food.X, food.Y);
// food.change();
// console.log(food.X, food.Y);

export default Food;
GameControl.ts
// 引入其他的类

import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";

// 游戏控制器,控制其他的所有类
function useGame(){
    class GameControl {
        // 定义三个属性
        // 蛇
        snake: Snake;
        // 食物
        food: Food;
        // 记分牌
        scorePanel: ScorePanel;
    
        // 创建一个属性来存储蛇的移动方向(也就是按键的方向)
        direction: string = '';
        // 创建一个属性用来记录游戏是否结束
        isLive = true;
    
        constructor() {
            this.snake = new Snake();
            this.food = new Food();
            this.scorePanel = new ScorePanel(10, 2);
    
            this.init();
        }
    
        // 游戏的初始化方法,调用后游戏即开始
        init() {
            // 绑定键盘按下的事件
            document.addEventListener('keydown', this.keydownHandler.bind(this));
            // 涉及到this和bind知识
    
            // 调用run()方法,使蛇移动
            this.run();
    
        }
    
        /* 
            ArrowUp Up
            ArrowDown Down
            ArrowRight Right
            ArrowLeft Left
        */
        // 创建一个键盘按下的响应函数
        keydownHandler(event: KeyboardEvent) {
            // console.log(this);
            // 需要检查event.key的值是否合法(用户是否按了正确的按键)
            // 修改direction属性
            this.direction = event.key
            // console.log(event.key);
        }
    
        // 创建一个控制蛇移动的方法
        run() {
            /* 
                根据方向(this.direction)来使蛇的位置改变
                向上 top 减少
                向下 top 增加
                向左 left 减少
                向右 left 增加 
            */
            // 获取蛇现在的坐标
            let X = this.snake.X;
            let Y = this.snake.Y;
    
    
            // 根据按键方向修改X值和Y值
            switch (this.direction) {
                case "ArrowUp":
                case "Up":
                    // 向上移动 top 减少
                    Y -= 10; 
                    break;
                case "ArrowDown":
                case "Down":
                    // 向下移动 top 增加
                    Y += 10;
                    break;
                case "ArrowLeft":
                case "Left":
                    // 向左移动 left 减少
                    X -= 10;
                    break;
                case "ArrowRight":
                case "Right":
                    // 向右移动 left 增加
                    X += 10;
                    break;
                
    
            }
    
            // 检查蛇是否吃到了食物
            this.checkEat(X, Y);
            // if (this.checkEat(X, Y)) {
            //     console.log('吃到食物了~~');
            //     // 食物的位置进行重置
            //     this.food.change();
            //     // 分数增加
            //     this.scorePanel.addScore();
            //     // 蛇要增加一节
            //     this.snake.addBody();
            // }
    
            // 修改蛇的X和Y值
            try {
                this.snake.X = X;
                this.snake.Y = Y;
            } catch (e) {
                // 进入到catch, 说明出现了异常,游戏结束,弹出一个提示信息
                alert(e.message+ 'GAME OVER!');
                // 将isLive设置为false
                this.isLive = false;
            }
        
    
            // 开启一个定时调用
            clearTimeout();
            this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);
        }
    
        // 定义一个方法,用来检查蛇是否吃到食物
        checkEat(X: number, Y: number){
            if (X === this.food.X && Y === this.food.Y) {
                console.log('吃到食物了~~');
                // 食物的位置进行重置
                this.food.change();
                // 分数增加
                this.scorePanel.addScore();
                // 蛇要增加一节
                this.snake.addBody();
            } 
        }
    
    
    }
    const gm = new GameControl()
    gm.init()
}

export default useGame;

ScorePanel.ts
// 定义表示记分牌的类
class ScorePanel{
    // score和level用来记录分数和等级
    score = 0;
    level = 1;

    // 分数和等级所在的元素,在构造函数种进行初始化
    scoreEle: HTMLElement;
    levelEle: HTMLElement;

    // 设置一个变量限制等级
    maxLevel: number;
    // 设置一个变量表示多少分时升级
    upScore: number;

    constructor(maxLevel: number = 10, upScore: number = 10){
        this.scoreEle = document.getElementById('score')!;
        this.levelEle = document.getElementById('level')!;
        this.maxLevel = maxLevel;
        this.upScore = upScore;
    }

    // 设置加分的方法
    addScore(){
        // 使分数自增
        // this.score++;
        // this.scoreEle.innerHTML = this.score + '';
        this.scoreEle.innerHTML = ++this.score + '';
        // 判断分数是多少
        if (this.score % this.upScore === 0) {
            this.levelUp();
            
        }
    }

    // 提升等级的方法
    levelUp(){
        if (this.level < this.maxLevel) {
            this.levelEle.innerHTML = ++this.level + '';
        }
        
    }
}


// 测试代码
// const scorePanel = new ScorePanel(100, 2);
// scorePanel.addScore();
// scorePanel.addScore();
// scorePanel.addScore();

// for (let i = 0; i < 200; i++) {
//     scorePanel.addScore();
    
// }

export default ScorePanel;
Snake.ts
class Snake{
    // 表示蛇头的元素
    head: HTMLElement;

    // 蛇的身体(包括蛇头)
    bodies: HTMLCollection;

    // 获取蛇的容器
    element: HTMLElement;

    constructor(){
        this.element = this.element = document.getElementById('snake')!;
        this.head = document.querySelector('#snake > div') as HTMLElement;
        // document.querySelectorAll('#snake > div');// nodeList
        this.bodies = this.element.getElementsByTagName('div');
    }

    // 获取蛇的坐标(蛇头坐标)
    get X(){
        return this.head.offsetLeft;
    }

    // 获取蛇的Y轴坐标
    get Y(){
        return this.head.offsetTop
    }

    // 设置蛇头的坐标
    set X(value:number){

        // 如果新值和旧值相同,则直接返回不再修改
        if (this.X === value) {
            return;
        }
        // X值的合法范围0-290之间
        if (value <0 || value > 290 ) {
            // 进入判断说明蛇撞墙了
            throw new Error("蛇撞墙了~~");
        }
        // 修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            // console.log('水平方向发生了掉头');
            // 如果发生了掉头,让蛇向方向继续移动
            if (value > this.X) {
                // 如果新值value大于旧值X, 则说明蛇在向右走,此时发生掉头,应该使蛇继续向左走
                value = this.X - 10;
            }else {
                // 向左走
                value = this.X + 10;
            }
        }
        // 移动身体
        this.moveBody();

        this.head.style.left = value + 'px';
         // 检查有没有撞自己
        this.checkHeadBody();
    }

    set Y(value: number){
        // 如果新值和旧值相同,则直接返回不再修改
        if (this.Y === value) {
            return;
        }
        // Y值的合法范围0-290之间
        if (value <0 || value > 290 ) {
            // 进入判断说明蛇撞墙了
            throw new Error("蛇撞墙了~~");
        }
        
        // 修改Y时,是在修改水平坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            // console.log('垂直方向发生了掉头');
            // 如果发生了掉头,让蛇向方向继续移动
            if (value > this.Y) {
                // 如果新值value大于旧值Y, 则说明蛇在向右走,此时发生掉头,应该使蛇继续向左走
                value = this.Y - 10;
            }else {
                // 向左走
                value = this.Y + 10;
            }
        }
        // 移动身体
        this.moveBody();

        this.head.style.top = value + 'px';
        // 检查有没有撞自己
        this.checkHeadBody();
    }

    // 蛇增加身体的方法
    addBody(){
        // 向element中添加一个div
        this.element.insertAdjacentHTML("beforeend", "<div></div>")//添加到结束标签前
    }

    // 添加一个蛇身体移动的方法
    moveBody(){
        /* 
            从后往前改
            将后面的身体设置为前面身体的位置
                举例子:
                    第4节 = 第3节的位置
                    第3节 = 第2节的位置
                    第2节 = 蛇头的位置
        */
        //遍历获取所有的身体
        for(let i = this.bodies.length-1; i > 0; i--){
            // 获取前面身体的位置
            let X = (this.bodies[i-1] as HTMLElement).offsetLeft;
            let Y = (this.bodies[i-1] as HTMLElement).offsetTop;
            // 将这个值设置到当前身体上
            (this.bodies[i] as HTMLElement).style.left = X + 'px';
            (this.bodies[i] as HTMLElement).style.top = Y + 'px';

        }
    }

    // 检查蛇头是否撞到身体的方法
    checkHeadBody(){
        // 获取所有的身体,检查是否和蛇头的坐标发生重叠
        for (let i = 1; i < this.bodies.length; i++) {
            let bd = this.bodies[i] as HTMLElement;
            if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
                // 进入判断说明蛇头撞到了身体,游戏结束
                throw new Error("撞到自己了~~");
                
            }
            
        }
    }

}

export default Snake;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值