Habe mit Vue3+Canvas ein kleines Tower Defense Spiel entwickelt, das man bei Interesse spielen kann

LegendTD

Projektadresse: codeape.site:16666

github: github.com/ApeWhoLoves…

grundlegende Einführung

Entwicklungstechnologie: Vue3 + Canvas+Ts

Dies ist pc端ein 移动端kleines Web-Tower-Defense-Spiel, das und unterstützt.

Andere Funktionen:

  • Stufe auswählen
  • Wählen Sie Tower Defense
  • Bestenliste

Wenn das ganze Projekt in diesem Artikel erklärt wird, wird es komplizierter, hier gebe ich einfach ein paar kleine Demos zur Demonstration wieder.

Es ist vielleicht klarer, wenn Sie sich den Quellcode ansehen.

Verwirklichen Sie den Technologieaustausch

runde Scroll-Baugruppe

td-scrollCircle-4.gif

Ich habe diese Komponente auch in eine Version umgeschrieben, die auf der zuvor implementierten reactVersion vue, und einige Verbesserungen entsprechend den Anforderungen des Projekts vorgenommen. Aus dem Projekt ist auch ersichtlich, dass ich es an mehreren Stellen eingesetzt habe.

Aus Platzgründen können Sie den Quellcode für Details einsehen: github.com/ApeWhoLoves…

Wenn Sie interessiert sind, können Sie auch diesen Artikel lesen: juejin.cn/post/717495…

schwebende Kugel

td-schwimmender-ball-2.gif

Auch diese ist auf Basis einer reactVorgängerversion vue. Dann habe ich die jsxSchreibmethode . vue3Üben Sie einfach jsxdas Schreiben in .

Aus Platzgründen können Sie den Quellcode für Details einsehen: github.com/ApeWhoLoves…

Wenn Sie interessiert sind, können Sie auch diesen Artikel lesen: juejin.cn/post/718665…

Verwenden Sie Canvas, um einfach die Funktion eines kleinen Tower-Defense-Spiels zu simulieren

requestAnimationFrame zeichnet

借助 requestAnimationFrame 即可不断绘制,效果类似于 setInterval ,不过不同点是前者大致能达到每秒60帧的刷新,而且是稳定的(具体效果还是不同机型会不太相同)。这里就不细说,具体可以看其他专门讲解的文章。

const draw = () => {
  if(timer) cancelAnimationFrame(timer);
  (function go() {
    startDraw()
    timer = requestAnimationFrame(go)
  })()
}
draw()
复制代码

绘画主函数

每一次的绘画都需要先清空之前的画布内容

function startDraw() {
  ctx.clearRect(0, 0, w, h)
  drawTower()
  drawEnemy()
  moveEnemy()
  shootFun()
  moveBullet()
}
复制代码

定义变量

定义好敌人,塔防和子弹的存储对象和数组。

const enemy = {
  x: 50,
  y: 50,
  // xy表示: 1:左 2:下 3:右 4:上
  xy: 3,
  // 速度
  speed: 2,
}
const tower = {
  x: 200,
  y: 200,
  // 子弹速度
  bulletSpeed: 8,
}
const bulletArr = []
复制代码

用来控制敌人转弯的

const xyArr = [
  {x: 350, y: 350},
  {x: 50, y: 350},
  {x: 50, y: 50},
  {x: 350,y: 50},
]
复制代码

绘制塔防和敌人

这里为了简便,直接绘画一个文字作为代表了。

function drawTower() {
  ctx.font = '50px 宋体'
  ctx.fillText('塔', tower.x, tower.y)
}
function drawEnemy() {
  ctx.font = '50px 宋体'
  ctx.fillText('敌', enemy.x, enemy.y)
}
复制代码

使敌人移动

这里就是每次触发都判断敌人当前的方向,对 xy 进行增减即可。

function moveEnemy() {
  const {speed, xy, x, y} = enemy
  for(let i = 0; i < xyArr.length; i++) {
    if(x >= xyArr[i].x && x <= xyArr[i].x + speed && y >= xyArr[i].y && y <= xyArr[i].y + speed) {
      if(i + 1 !== enemy.xy) {
        enemy.xy = i + 1
        break
      }
    }
  }
  switch (enemy.xy) {
    case 1: enemy.x -= speed; break;
    case 2: enemy.y -= speed; break;
    case 3: enemy.x += speed; break;
    case 4: enemy.y += speed; break;
  }
}
复制代码

这时就能产生大致如下的效果

Aufnahme vom 17.03.2023 um 15.23.31.gif

发射子弹

  • 触发子弹射击的防抖函数
const shootFun = throttle(() => {
  shootBullet()
})
function throttle(fn) {
  let timer = null;
  return () => {
    if(timer) return
    timer = setTimeout(() => {
      fn()
      clearTimeout(timer)
      timer = null
    }, 500);
  }
}
复制代码
  • 发射子弹的函数

根据敌人和塔防的中心,然后计算距离,并得出接下来子弹 xy 应该增加和减少的值即可。

function shootBullet() {
  const size = 50
  // 敌人中心
  const ex = enemy.x + size / 2, ey = enemy.y - size / 2
  // 塔防中心,也是子弹初始坐标
  const begin = {x: tower.x + size / 2, y: tower.y - size / 2}
  const diff = {x: ex - begin.x, y: ey - begin.y}
  // 子弹和敌人的距离
  const distance = powAndSqrt(diff.x, diff.y)
  const addX = tower.bulletSpeed * diff.x / distance
  const addY = tower.bulletSpeed * diff.y / distance
  bulletArr.push({
    x: begin.x, y: begin.y, addX, addY, xy: 0, distance
  })
}
复制代码
  • 移动子弹

遍历子弹数组,如果子弹到达了该到达的距离就清除该子弹,否则继续向前移动。(想进一步完善的话,可以在遍历的时候,重新计算子弹 xy 应该移动的值)

function moveBullet() {
  for(let i = bulletArr.length - 1; i >= 0; i--) {
    const {addX, addY, distance} = bulletArr[i]
    if(bulletArr[i].xy >= distance) {
      bulletArr.splice(i, 1)
    } else {
      bulletArr[i].x += addX
      bulletArr[i].y += addY
      bulletArr[i].xy += tower.bulletSpeed
      drawBullet(bulletArr[i])
    }
  }
}
复制代码
  • 画子弹

简单画一个圆

function drawBullet(bullet) {
  ctx.save()
  ctx.beginPath()
  ctx.arc(bullet.x, bullet.y, 5, 0, 2 * Math.PI, false)
  ctx.fillStyle = 'skyblue'
  ctx.fill()
  ctx.restore()
}
复制代码

最后就实现了大致如下效果了。

Aufnahme vom 17.03.2023 um 15.25.15.gif

塔防部分子弹效果

缩放子弹 旋转子弹 持续变粗的火焰柱
td-canvas-scale.gif td-canvas-rotate.gif td-canvas-fire2.gif

具体代码放到码上掘金了,有需要可以自提

缩放子弹

旋转子弹

持续变粗的火焰柱

pc端和移动端的兼容处理

pinia 全局保存一个状态代表当前是pc端还是移动端。

// 判断是移动端还是pc端的方法
function isMobile() {
  return navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)
}
复制代码

我这里的 canvas 在pc端定义了一个 size50px 的一个基准。当来到了移动端我这里是根据之前定义好的一个 canvas 宽高和手机的宽高来转化成一个我需要的 size 大小。

// canvas 的默认宽高 {w: 1050, h: 600}
const {w, h} = gameConfigState.defaultCanvas
const wp = document.documentElement.clientWidth / (h + 80)
const hp = document.documentElement.clientHeight / (w + 80)
const p = Math.floor(Math.min(wp, hp) * 10) / 10
// 将 50px 进行比例转化
gameConfigState.size *= p
复制代码

再通过 style 将这个变量传递到 css 中即可使用了。

<template>
    <div class="game-wrap" :style="{'--size': size + 'px'}"></div>
</template>
<style lang='less' scoped>
.game-wrap {
    @size: var(--size); // 这个就是50px的一个变量了
    .title {
        width: calc(@size * 0.5); // 使用
    }
}
</style>
复制代码

移动端下将游戏区域横屏处理,旋转90度 canvas 画板即可。

@media screen and (orientation: portrait) {
  .game-wrap {
    -webkit-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
    transform: rotate(90deg);
  }
}
复制代码

项目体会和收获

由于 vue2 之前就掌握了,而公司的技术栈是 react ;一直没什么机会接触到 vue3 方面的技术,然后就想着做个 vue3 项目。而现在的这个项目也是根据之前写的 vue2 简陋版本,来进行改写成 vue3 的,同时也做了很多完善。

之前的简陋 vue2 版本:juejin.cn/post/708776…

整个项目下来,在游戏实现方面其实涉及 vue3 的东西不多,主要是 js 的处理;主要是组件的封装和其他一些功能对 vue3 涉及较多。顺带说一句我将之前的 js 改写成 ts 后感觉是真的香,之前 js 写起来和改起来都很麻烦。还有就是 vitepinia 使用起来是真的香。

总体来说,整个项目开发下来,对 vue3 的一些使用基本了解掌握, canvas 的使用熟悉了不少,js 基本功也提升不少。

从 react 到 vue3 的使用体验。

这不是什么对比文章,这里就简单说下我在这个项目中的开发体会。

  1. vue3jsx 组件对 ts 的支持不太友好。不管是 props 属性的类型定义,还是 emits 事件的定义。

  2. 还有就是 props 不能用解构写法,因为 vue 中不是每次 render 都能重新触发 props 的解构的,所以就丢失了响应式。

Aber vielleicht kenne ich mich damit nicht aus. Aus diesem Grund lese ich auch element-plusden Quellcode, sie verwenden auch jsxdie Wording-Methode, und ich empfinde es als nicht reactelegant .

vueAllerdings gibt es auch reactStellen, an denen ich mich wohler fühle .

Zum Beispiel stateist die Änderung von , ändern Sie es einfach direkt, Sie müssen nichts tun setState, und nach dieser stateÄnderung müssen Sie nichts tun useEffect. Ich denke, dass dies in meinem Projekt besonders wichtig ist.

Guess you like

Origin juejin.im/post/7214517573584601144