Vue3+Ts实现贪吃蛇小游戏

前言

前段时间学习了vue3和typescript,跟着B站李立超老师的视频做了一下这个贪吃蛇的小游戏,简单的记录一下。

编写食物类
class Food {
  element: HTMLElement;
  //每个食物的分值 1-3的随机数
  score: number;
  constructor() {
    this.element = document.getElementById("food")!;
    this.score = 1;
  }
  // 获取食物的位置
  get X() {
    return this.element.offsetLeft;
  }
  get Y() {
    return this.element.offsetTop;
  }
  // 获取食物的得分
  get Score() {
    return this.score;
  }
  // 随机生成食物
  change(): void {
    let top = Math.round(Math.random() * 29) * 10;
    let left = Math.round(Math.random() * 29) * 10;
    this.element.style.left = left + "px";
    this.element.style.top = top + "px";
    this.score = Math.round(Math.random() * 3);
  }
}
编写蛇类
class Snake {
  element: HTMLElement;
  // 蛇头元素 蛇这个div中的第一个子元素
  head: HTMLElement;
  // 包括蛇头 整个蛇 HTMLCollection会实时更新
  bodys: HTMLCollection;
  constructor() {
    this.element = document.getElementById("snake")!;
    // 断言数据类型 获取snake的第一个div子元素
    this.head = document.querySelector("#snake > div") as HTMLElement;
    //head属于body的一部分
    this.bodys = document.getElementById("snake")!.getElementsByTagName("div");
  }
  get X() {
    return this.head.offsetLeft;
  }
  get Y() {
    return this.head.offsetTop;
  }
  set X(value: number) {
    
    if (this.X == value) {
      return;
    }
    if (value < 0 || value > 290) {
      throw new Error("蛇撞墙了x");
    }
    // 判断是否发生了调头
    
    if (this.bodys[1] && (this.bodys[1] as HTMLElement).offsetLeft === value) {
      // 如果发生调头 让蛇继续向反方向走
      if (value > this.X) {
        value = this.X - 10;
      } else {
        value = this.X + 10;
      }
    }
    this.move();
    this.head.style.left = value + "px";
    this.checkHeadBody();
  }
  set Y(value: number) {
    if (this.Y == value) {
      return;
    }
    if (value < 0 || value > 290) {
      throw new Error("蛇撞墙了Y");
    }
    // 判断是否发生了调头
    if (this.bodys[1] && (this.bodys[1] as HTMLElement).offsetTop === value) {
      // 如果发生调头 让蛇继续向反方向走
      if (value > this.Y) {
        value = this.Y - 10;
      } else {
        value = this.Y + 10;
      }
    }
    this.move();
    this.head.style.top = value + "px";
    this.checkHeadBody();
  }
  move() {
    for (let i = this.bodys.length - 1; i > 0; i--) {
      // 获取前边身体位置 类型断言
      let X = (this.bodys[i - 1] as HTMLElement).offsetLeft;
      let Y = (this.bodys[i - 1] as HTMLElement).offsetTop;

      // 身体改了
      (this.bodys[i] as HTMLElement).style.left = X + "px";
      (this.bodys[i] as HTMLElement).style.top = Y + "px";
    }
  }
  addBody() {
    body.push({});
  }
  checkHeadBody() {
    //   获取所有身体 检查是否和蛇头坐标重叠
    for (let i = 1; i < this.bodys.length; i++) {
      let bd = this.bodys[i] as HTMLElement;
      if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
        throw new Error("撞到自己了");
      }
    }
  }
}
编写游戏控制类
class Controller {
  snake: Snake;
  food: Food;
  direction: string = "";
  isAlive: boolean = true;
  constructor() {
    this.snake = new Snake();
    this.food = new Food();
    this.food.change();
    this.init();

  }
  init(): void {
    document.addEventListener<"keydown">(
      "keydown",
      this.keydownHandler.bind(this)
    );
    this.run();
  }
  keydownHandler(event: KeyboardEvent): void {
    this.direction = event.key;
  }
  run(): void {
    let X = this.snake.X;
    let Y = this.snake.Y;
    switch (this.direction) {
      case "ArrowUp":
      case "Up":
        Y -= 10;
        break;
      case "ArrowDown":
      case "Down":
        Y += 10;
        break;
      case "ArrowLeft":
      case "Left":
        X -= 10;
        break;
      case "ArrowRight":
      case "Right":
        X += 10;
        break;
    }
    this.checkEat(X, Y);
    try {
      this.snake.X = X;
      this.snake.Y = Y;
    } catch (error) {
      throw new Error("游戏结束");
    }

    this.isAlive &&
      setTimeout(() => {
        this.run();
      }, 100 - (panel.level - 1) * 30);
  }
  checkEat(X: number, Y: number): void {
    if (this.food.X === X && this.food.Y === Y) {
      changeScore(this.food.Score);
      this.food.change();
      this.snake.addBody();
    }
  }
}
编写计分板函数
let panel: { score: number; level: number };
panel = reactive({
  score: 0,
  level: 1,
});
let levelUp: () => void;
levelUp = function () {
  if (panel.level <= 10) {
    panel.level++;
  }
};

let changeScore: (a: number) => void;
changeScore = function (a: number) {
  panel.score += a;
  if (panel.score % 10 == 0) {
    levelUp();
  }
};
启动

启动必须在DOM渲染完成之后再启动即Mounted,否则无法获取到DOM元素

onMounted(() => {
  const controller = new Controller();
});
页面
<template>
  <div class="main-container">
    <div class="body-wrapper">
      <div id="snake">
        <div v-for="(item, index) in body" :key="index"></div>
      </div>
      <div id="food">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
      </div>
    </div>
    <div class="footer-panel">
      <div>
        SCORE: <span>{{ panel.score }}</span>
      </div>
      <div>
        level: <span>{{ panel.level }}</span>
      </div>
    </div>
  </div>
</template>
<style lang="scss" scoped>
.main-container {
  height: 420px;
  width: 360px;
  background-color: #b7d4a8;
  border: 10px solid #000;
  border-radius: 20px;
  margin: 100px auto;
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: space-around;
  .body-wrapper {
    height: 304px;
    width: 304px;
    border: 2px solid #000;
    position: relative;
    #snake {
      & > div {
        height: 10px;
        width: 10px;
        background: #000;
        position: absolute;
      }
    }
    #food {
      height: 10px;
      width: 10px;
      position: absolute;
      display: flex;
      flex-wrap: wrap;
      top: 0;
      left: 0;
      justify-content: space-between;
      align-content: space-between;
      & > div {
        background: #000;
        width: 4px;
        height: 4px;
      }
    }
  }
  .footer-panel {
    font-size: 20px;
    font-weight: bold;
    display: flex;
    justify-content: space-between;
    font-family: "Courier New", Courier, monospace;
    width: 300px;
  }
}
</style>

效果

请添加图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
首先,我们需要安装Vue 3和TypeScript的相关依赖。可以通过以下命令进行安装: ``` npm install vue@next vue-router@next @vue/compiler-sfc@next typescript ``` 然后,在Vue 3项目中创建一个小球组件,该组件将接收起始坐标、目标坐标和抛物线高度作为属性。代码如下: ```html <template> <div class="ball" :style="{ top: y + 'px', left: x + 'px' }"></div> </template> <script lang="ts"> import { defineComponent, ref, watchEffect } from 'vue'; export default defineComponent({ props: { startX: { type: Number, required: true, }, startY: { type: Number, required: true, }, endX: { type: Number, required: true, }, endY: { type: Number, required: true, }, height: { type: Number, required: true, }, }, setup(props) { const x = ref(props.startX); const y = ref(props.startY); const deltaX = props.endX - props.startX; const deltaY = props.endY - props.startY; const duration = Math.sqrt(deltaX ** 2 + deltaY ** 2) * 10; const startTime = Date.now(); watchEffect(() => { const currentTime = Date.now() - startTime; const t = Math.min(1, currentTime / duration); x.value = props.startX + deltaX * t; y.value = props.startY + deltaY * t - ((4 * props.height * t * (1 - t)) / 1); }); return { x, y, }; }, }); </script> <style> .ball { position: absolute; width: 20px; height: 20px; border-radius: 50%; background-color: red; } </style> ``` 在该组件中,我们使用了Vue 3的Composition API来管理状态。我们定义了起始坐标和目标坐标作为必需的属性,并且使用`watchEffect`函数来监听这些属性的变化。在`watchEffect`回调函数中,我们计算小球的当前坐标,并将其设置为`x`和`y`变量的值。 我们使用以下公式计算小球的抛物线运动: ``` x = startX + deltaX * t y = startY + deltaY * t - ((4 * height * t * (1 - t)) / 1) ``` 其中,`startX`和`startY`是小球的起始坐标,`deltaX`和`deltaY`是小球的运动距离,`height`是小球的抛物线高度,`t`是小球的运动时间占总运动时间的比例。 最后,我们将小球的位置设置为`x`和`y`的值,并在样式中使用绝对定位来使其在页面上正确显示。 在父组件中,我们可以使用该组件并传递起始坐标、目标坐标和抛物线高度属性。例如: ```html <template> <div class="container"> <ball :startX="100" :startY="100" :endX="500" :endY="500" :height="100" /> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import Ball from './Ball.vue'; export default defineComponent({ components: { Ball, }, }); </script> <style> .container { position: relative; width: 600px; height: 600px; border: 1px solid black; } </style> ``` 这将在容器中创建一个小球,小球将从`(100, 100)`的位置开始,向`(500, 500)`的位置移动,并在高度为100的抛物线上运动。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值