小时候玩过的飞行游戏,叫什么名字来着,通过点击操作控制煽动翅膀来持续飞行,躲避障碍物,有多远就飞多远吧,现在想起来,其中的实现原理非常简单,感兴趣的话来一起看看,这里给大家讲一讲飞翔的小鸟游戏的开发过程。
创建项目
这里用HBuilderX开发工具来创建一个uniapp项目,
例如项目名填写uniapp_FlapperBird
,依次选择如下图
- 选择新建uni-app项目
- 使用默认模板
- Vue版本选择 3
页面布局
新建好项目,会看到自动创建了一个初始页面文件,
初始页面
文件位置在pages/index/index.vue
,打开看看,
在页面布局中,对应的template
标签里添加一个按钮组件,按钮名叫进入游戏,
然后在script
标签里,添加一个按钮点击方法,实现打开游戏页面,
打开页面的代码很简单,自己能写出来吧,这里就不展开讲,
游戏页面
需要自己创建一个游戏页面,
页面文件在pages/game/game.vue
,打开接着写,
在页面布局中,对应的template
标签里添加,内容如下
<!--pages/game/game.wxml-->
<view class="page">
<canvas id="zs1028_csdn" canvas-id="zs1028_csdn" class="canvas" bindtouchstart="onTouchStart" :disable-scroll="true"/>
</view>
就这么简单,只放一个
canvas
组件即可,组件的一些属性不用说能看懂吧
写好后,要做出运行的游戏页面效果,就像如下图这样
这是在游戏开始时,其它的物品还没有出现
设置横屏
竖屏这样玩是不是视野变窄了点,可以设置横屏的,
把项目根目录下的文件pages.json
打开,
添加一个属性pageOrientation
设置,代码如下
{
//...
"globalStyle": {
//...
"pageOrientation": "landscape"
},
}
不知道这样设置有没有效果,H5版好像不行吧,手机浏览器上是跟随系统横屏的
游戏逻辑
接下来,在script
标签里,写游戏逻辑,
加载模块
开始写初始化代码,先加载游戏模块,添加代码如下
// pages/game/game.js
import ZS1028_CSDN from '../../utils/zs1028_CSDN.js'
const app = getApp()
Page({
//...
/**
* 生命周期函数--监听页面初次渲染完成
*/
async onReady() {
//...这里处理初始化
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
this.game?.destroy()
},
/**
* 以下是通过canvas的触摸事件来调用
*/
onTouchStart(e) {...},
})
导入的一个模块
ZS1028_CSDN
,是一个游戏引擎框架模块,用上它让游戏实现变得简单;
话说这个游戏模块,是通过一层又一层的绘制来实现的,代码总才182行,这么少,这一看很快就读完;
继续往下看,就能大概知道怎样绘制游戏了
初始化画布
游戏大致的思路呢,是要先把画布初始化,然后绘制出来游戏画面,
就在onReady()
方法里写,画布初始化的逻辑,代码如下
const { width, height } = await ZS1028_CSDN.query('#zs1028_csdn')
const canvas = {
width,
height,
getContext() {
return uni.createCanvasContext('zs1028_csdn')
},
//...
}
const game = ZS1028_CSDN.createMiniGameEngine({
canvas,
// isTest: true //游戏测试用途
})
this.game = game
初始化游戏数据
继续写下去,初始化游戏数据,代码如下,
//游戏状态数据
const gameState = {
bottom: 50, //绘制游戏状态的底边间距
timeCount: 0, //计时
scope: 0, //计分
}
//小鸟的数据
const birdData = {
width: 60, //定义小鸟宽度
imgIndex: 0, //图片索引(至少要2张图片,才能绘制动画效果)
climb: 0 //点击后,小鸟飞行上升的高度
}
Object.assign(birdData, {
height: birdData.width / 1.4 //按照图片尺寸计算出小鸟的高度
})
//障碍物的数据
const barrierData = {
width: birdData.width * 1.1, 宽度
list: [] //存放的障碍物列表
}
Object.assign(barrierData, {
minWidth: barrierData.width * 0.86, //计算最小的宽度,用于绘制
distance: barrierData.width * 3 //计算上一个障碍物与下一个的间距
})
绘制游戏状态
接下来,调用游戏对象的绘制过程,
这是绘制游戏标题,显示游戏状态,代码如下
const that = this
//绘制游戏标题层,在屏幕上面显示游戏状态
game.initTopBar({
data: gameState, //把定义的游戏状态数据传入其中
//重置事件
reset() {
Object.assign(this.data, {
timeCount: 0, //计时重置
scope: 0, //计分重置
maxScope: app.getMaxScope() //最高分
})
},
//重绘事件
redraw(data) {
const {
canvas,
context: ctx
} = data
const {
timeCount,
scope,
maxScope,
bottom
} = this.data
//...这里处理更新计时timeCount
//以下是绘制标题
ctx.textAlign = 'center'
ctx.font = `18px sans-serif`
ctx.strokeStyle = '#ffffff'
let text = `⏰x${timeCount}s 🪙x${scope} 🏆x${maxScope}`
ctx.strokeText(text, canvas.width * 0.5, 30)
ctx.fillText(text, canvas.width * 0.5, 30)
}
})
绘制小鸟
接下来,绘制一只飞翔的鸟,在其上层的中间位置绘制,代码如下
//添加游戏前景层,绘制一只鸟
game.addForeObject({
data: birdData,
reset() {
//...这里重置小鸟的数据,放屏幕中间位置
},
redraw(data) {
const {
canvas,
context: ctx
} = data
//调用绘制小鸟的方法
this.drawBird(ctx)
},
methods: {
drawBird(ctx) {
let {
x,
y,
width,
height,
imgIndex,
climb
} = this.data
if (climb > 0) {
//...
y -= 2 //小鸟在上升
} else {
y += 2 //小鸟在滑翔
}
// 判断图片索引,决定让使用那个图片显示小鸟
switch (imgIndex) {
//...
default:
ctx.drawImage('./../../static/tu_03.png', x, y, width, height)
}
// 调用碰撞检测方法,如果碰到了障碍物,游戏就会结束
if (this.crashDetection(y)) {
game.stop()
that.isGameEnd = true
app.setMaxScope(gameState.scope)
//...这里执行弹出游戏结束提示即可
}
// 记录时间变化
let nowTime = Date.now()
// 判断时间差,如果大于300毫秒就替换小鸟图片
if (!this.preTime || (nowTime - this.preTime) > 300) {
//...改变小鸟的图片,实现煽动翅膀动画效果
this.data.imgIndex = (imgIndex + 1) % 2
}
//...最后,记得更新一下小鸟变化后的数据
},
crashDetection(y) {
//...这里实现碰撞检测
for (let i = 0; i < barrierData.list.length; i++) {
let barrier = barrierData.list[i]
//...判断是否碰撞条件
}
return false
}
}
})
要运行的话,就能看到屏幕中间的小鸟在煽动翅膀,
绘制障碍物
接下来,绘制障碍物,要在屏幕最右边开始出现,
然后,障碍物在慢慢接近小鸟,实现代码如下
//添加背景层,绘制一些障碍物,绘制砖块,管道都可以
game.addBgObject({
data: barrierData, //传入障碍物的数据
reset() {
this.data.list.length = 0 //重置就清空列表
this.addBarrier() //这里执行添加一个障碍物方法
},
redraw(data) {
const {
canvas,
context: ctx
} = data
const {
list,
width,
distance
} = this.data
list.forEach((item, index) => {
//障碍物上下对称出现
//先绘制上面的障碍物
if (item.y > 0) this.drawBarrier(ctx, item.x, item.y)
//再计算间距,绘制出下面的障碍物
if (item.y + item.distance < canvas.height) this.drawBarrier(ctx, item.x, item.y + item.distance, true)
item.x -= 1 //向水平方向移动
})
//...绘制其它边框
//有障碍物的话,就处理
if (list.length > 0) {
if (list[0].x + width < 0) {
//超出边界,就移出
list.shift()
//游戏加分
gameState.scope++
} else if (list[list.length - 1].x + width + distance < canvas.width) {
//每隔一段距离,就出现一个
this.addBarrier()
}
}
},
methods: {
drawBarrier(ctx, x, y, isDown = false) {
//...这里实现绘制障碍物即可,isDown是绘制下面的,否则绘制上面
},
addBarrier() {
//...这里实现添加新的障碍物数据
this.data.list.push({...}) //数据属性y和distance是随机变化的
}
}
})
开始游戏
初始化都写好了,最后要保存好数据,代码如下
//将初始化数据存放好
this.gameData = {
gameState,
barrierData,
birdData
}
//调用此方法开始游戏
this.restart()
那游戏开始了吗,看方法restart()
代码是怎么样的
const {
game
} = this
this.isGameEnd = false
game.reset()
这里发现,没有调用
game.run()
方法,因此,游戏还没有运行起来
触摸事件
接下来,处理画布canvas
触摸事件,让游戏与玩家交互,
开始触摸时,改一下小鸟的数据即可,
就通过触摸开始事件来处理,代码如下
onTouchStart(e) {
if (this.isGameEnd) return
const {
birdData
} = this.gameData
birdData.climb = 20 //改变这个属性值,小鸟就会上升的
const {
game
} = this
//如果游戏没有运行,这里执行一下就开始游戏了
if (!game.isRun()) game.run()
},
游戏测试
写到这里,基本上就可以运行测试玩玩了,
看看飞翔的小鸟游戏运行效果吧,运行动图如下
游戏里有用了小鸟图片,图片是可以自己替换的,
替换直升机图片飞行吧,反正游戏体验效果都是一致的;
还没有背景音呢,想要就自己找个音乐文件加上吧
想要项目源码在点这里查看下载,或者直接点这里搜索:飞翔的鸟,在本博客站内请放心下载,感谢支持!
可能手机上看不到,请改用电脑浏览器查看;
如果搜索不到,只能在资源一栏慢慢找了(太多了不好找)