用 Vue3+Canvas 开发了个塔防小游戏,感兴趣可以玩一玩

本文主要是介绍用 Vue3+Canvas 开发了个塔防小游戏,感兴趣可以玩一玩,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

LegendTD

项目地址: http://codeape.site:16666

源码: https://github.com/ApeWhoLovesCode/LegendTD

基本介绍

开发技术: Vue3 + Canvas + Ts

这是一款支持 pc端移动端 的网页塔防小游戏。

其他功能:

  • 选择关卡
  • 选择塔防
  • 排行榜

要是整个项目都放到这篇文章来讲解的话会比较复杂,我这里简单复现几个小demo来作为展示。

具体看源码可能会更清晰。

实现技术分享

圆形滚动组件

td-scrollCircle-4.gif

这个组件我也是根据之前实现的 react 版本改写成 vue 版本的,并根据项目需要进行了一些完善。从项目中也可以看到我用在了好几个地方。

由于篇幅问题,具体可以看源码: https://github.com/ApeWhoLovesCode/LegendTD/tree/master/src/components/scrollCircle

感兴趣的话也可以看这篇文章:https://juejin.cn/post/7174959812084498488

悬浮球

td-floating-ball-2.gif

这个也是根据之前的一个 react 的版本写成 vue 的版本的。然后这个组件我继续沿用了 jsx 的写法。就当练习一下 vue3 中的 jsx 写法了。

由于篇幅问题,具体可以看源码: https://github.com/ApeWhoLovesCode/LegendTD/tree/master/src/components/floating-ball

感兴趣的话也可以看这篇文章:https://juejin.cn/post/7186658143563644985

用 canvas 简单模拟一个塔防小游戏功能

jcode

requestAnimationFrame 绘制

借助 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} = enemyfor(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 + 1break}}}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;}
}

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

Kapture 2023-03-17 at 15.23.31.gif
发射子弹
  • 触发子弹射击的防抖函数
const shootFun = throttle(() => {shootBullet()
})
function throttle(fn) {let timer = null;return () => {if(timer) returntimer = 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 / distanceconst addY = tower.bulletSpeed * diff.y / distancebulletArr.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 += addXbulletArr[i].y += addYbulletArr[i].xy += tower.bulletSpeeddrawBullet(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()
}

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

Kapture 2023-03-17 at 15.25.15.gif

塔防部分子弹效果

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

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

缩放子弹

jcode

旋转子弹

jcode

持续变粗的火焰柱

jcode

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 版本:https://juejin.cn/post/7087764218673365023

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

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

从 react 到 vue3 的使用体验。

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

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

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

不过也可能是我写得不熟吧。为此我也看了 element-plus的源码,它们也是采用 jsx 写法,我改写起来感觉的确没 react 优雅。

不过 vue 中也有使用起来令我觉得比 react 舒服的地方

比如就是 state 的修改,直接修改就完了,不用搞什么 setState ,然后这个 state 改变后,也不用搞什么 useEffect 。 这点在我项目中使用起来我感觉尤为关键。

说明

这是我在掘金写的一篇文章,有不清楚的地方可以看原文。

https://juejin.cn/post/7214517573584601144

这篇关于用 Vue3+Canvas 开发了个塔防小游戏,感兴趣可以玩一玩的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/364767

相关文章

vite搭建vue3项目的搭建步骤

《vite搭建vue3项目的搭建步骤》本文主要介绍了vite搭建vue3项目的搭建步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1.确保Nodejs环境2.使用vite-cli工具3.进入项目安装依赖1.确保Nodejs环境

Nginx搭建前端本地预览环境的完整步骤教学

《Nginx搭建前端本地预览环境的完整步骤教学》这篇文章主要为大家详细介绍了Nginx搭建前端本地预览环境的完整步骤教学,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录项目目录结构核心配置文件:nginx.conf脚本化操作:nginx.shnpm 脚本集成总结:对前端的意义很多

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D

基于Python开发Windows自动更新控制工具

《基于Python开发Windows自动更新控制工具》在当今数字化时代,操作系统更新已成为计算机维护的重要组成部分,本文介绍一款基于Python和PyQt5的Windows自动更新控制工具,有需要的可... 目录设计原理与技术实现系统架构概述数学建模工具界面完整代码实现技术深度分析多层级控制理论服务层控制注

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

Vue3视频播放组件 vue3-video-play使用方式

《Vue3视频播放组件vue3-video-play使用方式》vue3-video-play是Vue3的视频播放组件,基于原生video标签开发,支持MP4和HLS流,提供全局/局部引入方式,可监听... 目录一、安装二、全局引入三、局部引入四、基本使用五、事件监听六、播放 HLS 流七、更多功能总结在 v

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

vue监听属性watch的用法及使用场景详解

《vue监听属性watch的用法及使用场景详解》watch是vue中常用的监听器,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作,:本文主要介绍vue监听属性watch的用法及使用场景... 目录1. 监听属性 watch2. 常规用法3. 监听对象和route变化4. 使用场景附Watch 的

前端导出Excel文件出现乱码或文件损坏问题的解决办法

《前端导出Excel文件出现乱码或文件损坏问题的解决办法》在现代网页应用程序中,前端有时需要与后端进行数据交互,包括下载文件,:本文主要介绍前端导出Excel文件出现乱码或文件损坏问题的解决办法,... 目录1. 检查后端返回的数据格式2. 前端正确处理二进制数据方案 1:直接下载(推荐)方案 2:手动构造