LegendTD
我的项目地址: http://codeape.site:16666
源码: https://github.com/ApeWhoLovesCode/LegendTD
根本介绍
开发技术: Vue3
+ Canvas
+ Ts
这是一款反对 pc端
和 挪动端
的网页塔防小游戏。
其余性能:
- 抉择关卡
- 抉择塔防
- 排行榜
要是整个我的项目都放到这篇文章来解说的话会比较复杂,我这里简略复现几个小demo来作为展现。
具体看源码可能会更清晰。
实现技术分享
圆形滚动组件
这个组件我也是依据之前实现的 react
版本改写成 vue
版本的,并依据我的项目须要进行了一些欠缺。从我的项目中也能够看到我用在了好几个中央。
因为篇幅问题,具体能够看源码: https://github.com/ApeWhoLovesCode/LegendTD/tree/master/src/components/scrollCircle
感兴趣的话也能够看这篇文章:https://juejin.cn/post/7174959812084498488
悬浮球
这个也是依据之前的一个 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)}
使敌人挪动
这里就是每次触发都判断敌人以后的方向,对 x
或 y
进行增减即可。
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; }}
这时就能产生大抵如下的成果
发射子弹
- 触发子弹射击的防抖函数
const shootFun = throttle(() => { shootBullet()})function throttle(fn) { let timer = null; return () => { if(timer) return timer = setTimeout(() => { fn() clearTimeout(timer) timer = null }, 500); }}
- 发射子弹的函数
依据敌人和塔防的核心,而后计算间隔,并得出接下来子弹 x
和 y
应该减少和缩小的值即可。
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 })}
- 挪动子弹
遍历子弹数组,如果子弹达到了该达到的间隔就革除该子弹,否则持续向前挪动。(想进一步欠缺的话,能够在遍历的时候,从新计算子弹 x
和 y
应该挪动的值)
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()}
最初就实现了大抵如下成果了。
塔防局部子弹成果
缩放子弹 | 旋转子弹 | 继续变粗的火焰柱 |
---|---|---|
具体代码放到码上掘金了,有须要能够自提
缩放子弹
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端定义了一个 size
为 50px
的一个基准。当来到了挪动端我这里是依据之前定义好的一个 canvas
宽高和手机的宽高来转化成一个我须要的 size
大小。
// canvas 的默认宽高 {w: 1050, h: 600}const {w, h} = gameConfigState.defaultCanvasconst 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
写起来和改起来都很麻烦。还有就是 vite
和 pinia
应用起来是真的香。
总体来说,整个我的项目开发下来,对 vue3
的一些应用根本理解把握, canvas
的应用相熟了不少,js
基本功也晋升不少。
从 react 到 vue3 的应用体验。
这不是什么比照文章,这里就简略说下我在这个我的项目中的开发领会。
vue3
中jsx
组件对ts
的反对不太敌对。不论是props
属性的类型定义,还是emits
事件的定义。- 还有就是
props
不能用解构写法,因为vue
中不是每次render
都能从新触发props
的解构的,所以就失落了响应式。
不过也可能是我写得不熟吧。为此我也看了 element-plus
的源码,它们也是采纳 jsx
写法,我改写起来感觉确实没 react
优雅。
不过 vue
中也有应用起来令我感觉比 react
难受的中央
比方就是 state
的批改,间接批改就完了,不必搞什么 setState
,而后这个 state
扭转后,也不必搞什么 useEffect
。 这点在我我的项目中应用起来我感觉尤为要害。
阐明
第一次在思否这边发文,这是我在掘金写的一篇文章,有不分明的中央能够看原文
https://juejin.cn/post/7214517573584601144