用微信小游戏实现龙舟大战-打粽子
Posted 沉默着忍受
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用微信小游戏实现龙舟大战-打粽子相关的知识,希望对你有一定的参考价值。
用微信小游戏实现龙舟大战-打粽子
端午节来啦!各位c粉有没有吃粽子啊!
前言
端午节来啦!今天沉默带大家来做个关于端午节的小游戏,我的设计思路是用龙舟打粽子,类似于飞机大战,只不过我们的场景是在河中。源码在文章后获取哟!
提示:以下是本篇文章正文内容,下面案例可供参考
一、体验视频
下面是小游戏的开发效果视频:
龙舟大战
二、开发流程
1.素材收集
龙舟大战,我们需要一张龙舟的图片和粽子的图片,这里我们还需要河面的背景图片。值得注意的是,龙舟打粽子还需要子弹的图片,为了体现端午节的元素,我们将子弹设定为粽子,当子弹接触到前方的粽子时,前方粽子爆炸特效也需要通过图片生成。具体图片素材如下图:
2.游戏逻辑实现
2.1 定义游戏开发基础类
- 相关的代码说明请看注解
import Sprite from './sprite'
import DataBus from '../databus'
const databus = new DataBus()
const __ =
timer: Symbol('timer'),
/**
* 简易的帧动画类实现
*/
export default class Animation extends Sprite
constructor(imgSrc, width, height)
super(imgSrc, width, height)
// 当前动画是否播放中
this.isPlaying = false
// 动画是否需要循环播放
this.loop = false
// 每一帧的时间间隔
this.interval = 1000 / 60
// 帧定时器
this[__.timer] = null
// 当前播放的帧
this.index = -1
// 总帧数
this.count = 0
// 帧图片集合
this.imgList = []
/**
* 推入到全局动画池里面
* 便于全局绘图的时候遍历和绘制当前动画帧
*/
databus.animations.push(this)
/**
* 初始化帧动画的所有帧
* 为了简单,只支持一个帧动画
*/
initFrames(imgList)
imgList.forEach((imgSrc) =>
const img = new Image()
img.src = imgSrc
this.imgList.push(img)
)
this.count = imgList.length
// 将播放中的帧绘制到canvas上
aniRender(ctx)
ctx.drawImage(
this.imgList[this.index],
this.x,
this.y,
this.width * 1.2,
this.height * 1.2
)
// 播放预定的帧动画
playAnimation(index = 0, loop = false)
// 动画播放的时候精灵图不再展示,播放帧动画的具体帧
this.visible = false
this.isPlaying = true
this.loop = loop
this.index = index
if (this.interval > 0 && this.count)
this[__.timer] = setInterval(
this.frameLoop.bind(this),
this.interval
)
// 停止帧动画播放
stop()
this.isPlaying = false
if (this[__.timer]) clearInterval(this[__.timer])
// 帧遍历
frameLoop()
this.index++
if (this.index > this.count - 1)
if (this.loop)
this.index = 0
else
this.index--
this.stop()
2.2 帧动画的简易实现
const __ =
poolDic: Symbol('poolDic')
/**
* 简易的对象池实现
* 用于对象的存贮和重复使用
* 可以有效减少对象创建开销和避免频繁的垃圾回收
* 提高游戏性能
*/
export default class Pool
constructor()
this[__.poolDic] =
/**
* 根据对象标识符
* 获取对应的对象池
*/
getPoolBySign(name)
return this[__.poolDic][name] || (this[__.poolDic][name] = [])
/**
* 根据传入的对象标识符,查询对象池
* 对象池为空创建新的类,否则从对象池中取
*/
getItemByClass(name, className)
const pool = this.getPoolBySign(name)
const result = (pool.length
? pool.shift()
: new className())
return result
/**
* 将对象回收到对象池
* 方便后续继续使用
*/
recover(name, instance)
this.getPoolBySign(name).push(instance)
2.3 游戏基本元素精灵类
(粽子.子弹.击中特效)
/**
* 游戏基础的精灵类
*/
export default class Sprite
constructor(imgSrc = '', width = 0, height = 0, x = 0, y = 0)
this.img = new Image()
this.img.src = imgSrc
this.width = width
this.height = height
this.x = x
this.y = y
this.visible = true
/**
* 将精灵图绘制在canvas上
*/
drawToCanvas(ctx)
if (!this.visible) return
ctx.drawImage(
this.img,
this.x,
this.y,
this.width,
this.height
)
/**
* 简单的碰撞检测定义:
* 另一个精灵的中心点处于本精灵所在的矩形内即可
* @paramSprite sp: Sptite的实例
*/
isCollideWith(sp)
const spX = sp.x + sp.width / 2
const spY = sp.y + sp.height / 2
if (!this.visible || !sp.visible) return false
return !!(spX >= this.x
&& spX <= this.x + this.width
&& spY >= this.y
&& spY <= this.y + this.height)
2.4 粽子类实现过程
import Animation from '../base/animation'
import DataBus from '../databus'
const ENEMY_IMG_SRC = 'images/enemy.png'
const ENEMY_WIDTH = 60
const ENEMY_HEIGHT = 60
const __ =
speed: Symbol('speed')
const databus = new DataBus()
function rnd(start, end)
return Math.floor(Math.random() * (end - start) + start)
export default class Enemy extends Animation
constructor()
super(ENEMY_IMG_SRC, ENEMY_WIDTH, ENEMY_HEIGHT)
this.initExplosionAnimation()
init(speed)
this.x = rnd(0, window.innerWidth - ENEMY_WIDTH)
this.y = -this.height
this[__.speed] = speed
this.visible = true
// 预定义爆炸的帧动画
initExplosionAnimation()
const frames = []
const EXPLO_IMG_PREFIX = 'images/explosion'
const EXPLO_FRAME_COUNT = 19
for (let i = 0; i < EXPLO_FRAME_COUNT; i++)
frames.push(`$EXPLO_IMG_PREFIX + (i + 1).png`)
this.initFrames(frames)
// 每一帧更新子弹位置
update()
this.y += this[__.speed]
// 对象回收
if (this.y > window.innerHeight + this.height) databus.removeEnemey(this)
2.5 粽子子弹类实现
import Sprite from '../base/sprite'
import DataBus from '../databus'
const BULLET_IMG_SRC = 'images/bullet.png'
const BULLET_WIDTH = 16
const BULLET_HEIGHT = 30
const __ =
speed: Symbol('speed')
const databus = new DataBus()
export default class Bullet extends Sprite
constructor()
super(BULLET_IMG_SRC, BULLET_WIDTH, BULLET_HEIGHT)
init(x, y, speed)
this.x = x
this.y = y
this[__.speed] = speed
this.visible = true
// 每一帧更新子弹位置
update()
this.y -= this[__.speed]
// 超出屏幕外回收自身
if (this.y < -this.height) databus.removeBullets(this)
2.6 玩家类(龙舟)
import Sprite from '../base/sprite'
import Bullet from './bullet'
import DataBus from '../databus'
const screenWidth = window.innerWidth
const screenHeight = window.innerHeight
// 玩家相关常量设置
const PLAYER_IMG_SRC = 'images/hero.png'
const PLAYER_WIDTH = 80
const PLAYER_HEIGHT = 80
const databus = new DataBus()
export default class Player extends Sprite
constructor()
super(PLAYER_IMG_SRC, PLAYER_WIDTH, PLAYER_HEIGHT)
// 玩家默认处于屏幕底部居中位置
this.x = screenWidth / 2 - this.width / 2
this.y = screenHeight - this.height - 30
// 用于在手指移动的时候标识手指是否已经在龙舟上了
this.touched = false
this.bullets = []
// 初始化事件监听
this.initEvent()
/**
* 当手指触摸屏幕的时候
* 判断手指是否在龙舟上
* @param Number x: 手指的X轴坐标
* @param Number y: 手指的Y轴坐标
* @return Boolean: 用于标识手指是否在龙舟上的布尔值
*/
checkIsFingerOnAir(x, y)
const deviation = 30
return !!(x >= this.x - deviation
&& y >= this.y - deviation
&& x <= this.x + this.width + deviation
&& y <= this.y + this.height + deviation)
/**
* 根据手指的位置设置龙舟的位置
* 保证手指处于龙舟中间
* 同时限定龙舟的活动范围限制在屏幕中
*/
setAirPosAcrossFingerPosZ(x, y)
let disX = x - this.width / 2
let disY = y - this.height / 2
if (disX < 0) disX = 0
else if (disX > screenWidth - this.width) disX = screenWidth - this.width
if (disY <= 0) disY = 0
else if (disY > screenHeight - this.height) disY = screenHeight - this.height
this.x = disX
this.y = disY
/**
* 玩家响应手指的触摸事件
* 改变龙舟的位置
*/
initEvent()
canvas.addEventListener('touchstart', ((e) =>
e.preventDefault()
const x = e.touches[0].clientX
const y = e.touches[0].clientY
//
if (this.checkIsFingerOnAir(x, y))
this.touched = true
this.setAirPosAcrossFingerPosZ(x, y)
))
canvas.addEventListener('touchmove', ((e) =>
e.preventDefault()
const x = e.touches[0].clientX
const y = e.touches[0].clientY
if (this.touched) this.setAirPosAcrossFingerPosZ(x, y)
))
canvas.addEventListener('touchend', ((e) =>
e.preventDefault()
this.touched = false
))
/**
* 玩家射击操作
* 射击时机由外部决定
*/
shoot()
const bullet = databus.pool.getItemByClass('bullet', Bullet)
bullet.init(
this.x + this.width / 2 - bullet.width / 2,
this.y - 10,
10
)
databus.bullets.push(bullet)
2.7 背景类(河面)
import Sprite from '../base/sprite'
const screenWidth = window.innerWidth
const screenHeight = window.innerHeight
const BG_IMG_SRC = 'images/bg.jpg'
const BG_WIDTH = 512
const BG_HEIGHT = 512
/**
* 游戏背景类
* 提供update和render函数实现无限滚动的背景功能
*/
export default class BackGround extends Sprite
constructor(ctx)
super(BG_IMG_SRC, BG_WIDTH, BG_HEIGHT)
this.top = 0
this.render(ctx)
update()
this.top += 2
if (this.top >= screenHeight) this.top = 0
/**
* 背景图重绘函数
* 绘制两张图片,两张图片大小和屏幕一致
* 第一张漏出高度为top部分,其余的隐藏在屏幕上面
* 第二张补全除了top高度之外的部分,其余的隐藏在屏幕下面
*/
render(ctx)
ctx.drawImage(
this.img,
0,
0,
this.width,
this.height,
0,
-screenHeight + this.top,
screenWidth,
screenHeight
)
ctx.drawImage(
this.img,
0,
0,
this.width,
this.height,
0,
this.top,
screenWidth,
screenHeight
)
2.8 展示分数和结算界面实现
const screenWidth = window.innerWidth
const screenHeight = window.innerHeight
const atlas = new Image()
atlas.src = 'images/Common.png'
export default class GameInfo
renderGameScore(ctx, score)
ctx.fillStyle = '#ffffff'
ctx.font = '20px Arial'
ctx.fillText(
score,
10,
30
)
renderGameOver(ctx, score)
ctx.drawImage(atlas, 0, 0, 119, 108, screenWidth / 以上是关于用微信小游戏实现龙舟大战-打粽子的主要内容,如果未能解决你的问题,请参考以下文章