Desenvolvi um pequeno jogo de defesa de torre com Vue3+Canvas, você pode jogá-lo se estiver interessado

LegendTD

Endereço do projeto: codeape.site:16666

github: github.com/ApeWhoLoves…

introdução básica

Tecnologia de desenvolvimento: Vue3 + Canvas+Ts

Este é pc端um 移动端pequeno jogo de defesa de torre na web que suporta e.

Outras funções:

  • selecione o nível
  • escolha a defesa da torre
  • Entre os melhores

Se todo o projeto for explicado neste artigo, será mais complicado, aqui apenas reproduzo algumas pequenas demos como demonstração.

Pode ser mais claro se você olhar o código-fonte.

Realize o compartilhamento de tecnologia

montagem de rolagem redonda

td-scrollCircle-4.gif

Também reescrevi este componente em uma versão baseada na reactversão vuee fiz algumas melhorias de acordo com as necessidades do projeto. Também pode ser visto no projeto que usei em vários lugares.

Devido a problemas de espaço, você pode ver o código-fonte para obter detalhes: github.com/ApeWhoLoves…

Se você estiver interessado, também pode ler este artigo: juejin.cn/post/717495…

bola levitando

td-floating-ball-2.gif

Isso também foi escrito com base em uma versão reactanterior vue. Então continuei a usar o método jsxde escrita . Apenas vue3pratique jsxa escrita em .

Devido a problemas de espaço, você pode ver o código-fonte para obter detalhes: github.com/ApeWhoLoves…

Se você estiver interessado, também pode ler este artigo: juejin.cn/post/718665…

Use a tela para simular simplesmente a função de um pequeno jogo de defesa de torre

requestAnimationFrame desenha

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

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

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

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

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

Mas talvez eu não esteja familiarizado com isso. Por esse motivo, também leio element-pluso código-fonte, eles também usam jsxo método de redação e acho que não é reactelegante .

No entanto vue, também existem reactlugares em que me sinto mais confortável para usar.

Por exemplo, stateé a modificação de , basta modificá-lo diretamente, não precisa fazer nada setState, e após essa statealteração , não há necessidade de fazer nada useEffect. Eu sinto que isso é especialmente crítico para usar no meu projeto.

Acho que você gosta

Origin juejin.im/post/7214517573584601144
Recomendado
Clasificación