Développé un petit jeu de tower defense avec Vue3+Canvas, vous pouvez y jouer si vous êtes intéressé

LégendeTD

Adresse du projet : codeape.site:16666

github : github.com/ApeWhoLoves…

introduction de base

Technologie de développement : Vue3 + Canvas+Ts

Il s'agit pc端d'un 移动端petit jeu de défense de tour Web qui prend en charge et .

Autres fonctions :

  • Choisir le niveau
  • choisir la tour de défense
  • classement

Si tout le projet est expliqué dans cet article, ce sera plus compliqué, ici je reproduis simplement quelques petites démos en guise de démonstration.

Cela peut être plus clair si vous regardez le code source.

Réaliser le partage de technologie

assemblage de volutes rondes

td-scrollCircle-4.gif

J'ai également réécrit ce composant dans une version basée sur la reactversion vue, et apporté quelques améliorations en fonction des besoins du projet. Il ressort également du projet que je l'ai utilisé à plusieurs endroits.

En raison de problèmes d'espace, vous pouvez consulter le code source pour plus de détails : github.com/ApeWhoLoves…

Si cela vous intéresse, vous pouvez également lire cet article : juejin.cn/post/717495…

balle en lévitation

td-floating-ball-2.gif

Ceci est également écrit sur la base d'une version reactprécédente vue. Ensuite, j'ai continué à utiliser la méthode jsxd'écriture . vue3Entraînez-vous simplement jsxà écrire en .

En raison de problèmes d'espace, vous pouvez consulter le code source pour plus de détails : github.com/ApeWhoLoves…

Si cela vous intéresse, vous pouvez également lire cet article : juejin.cn/post/718665…

Utilisez la toile pour simuler simplement la fonction d'un petit jeu de défense de tour

requestAnimationFrame dessine

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

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

Capture 2023-03-17 à 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()
}
复制代码

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

Capture 2023-03-17 à 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 的解构的,所以就丢失了响应式。

Mais peut-être que je ne le connais pas. Pour cette raison, j'ai également lu element-plusle code source, ils utilisent également jsxla méthode de formulation, et je pense qu'il n'est pas reactélégant .

Cependant vue, il y a aussi reactdes endroits dans lesquels je me sens plus à l'aise d'utiliser.

Par exemple, statec'est la modification de , il suffit de le modifier directement, pas besoin de faire quoi que ce soit setState, et après ce statechangement , il n'y a plus rien à faire useEffect. Je pense que cela est particulièrement essentiel à utiliser dans mon projet.

Je suppose que tu aimes

Origine juejin.im/post/7214517573584601144
conseillé
Classement