使用 Pixi.js 插件实现探险者小游戏(二)
使用 Pixi.js 插件实现探险者小游戏(一)中我们学习了如何创建精灵图,这节我们要让精灵图动起来。
精灵图布局
游戏画面如下图所示,我们要生成一个围墙,探险者、恶魔、宝物都在这个围墙里面。探险者可以上下左右移动,恶魔只能上下移动,宝物是不动的。探险者与宝物被恶魔群隔开,探险者需要移动到宝物处才能获得游戏的胜利。

1. 使用砖块生成围墙
loader
.add("images/textureMap.json")
.load(intLoad)
function intLoad() {
const textures = _loader.resources['images/textureMap.json'].textures
// 添加纹理墙
const walls = addWall(textures['1.png'])
walls.forEach(wall => {
_stage.addChild(wall)
})
}
function addWall(texture) {
const walls = []
// 生成第一堵墙
for (let i = 0; i < 15; i++) {
let wall = new _Sprite(texture)
wall.height = 40
wall.width = 40
wall.x = i * 40
wall.y = 0
walls.push(wall)
}
// 生成第二堵墙
for (let i = 1; i < 15; i++) {
let wall = new _Sprite(texture)
wall.height = 40
wall.width = 40
wall.x = 0
wall.y = 40 * i
walls.push(wall)
}
// 生成第三堵墙
for (let i = 1; i < 15; i++) {
let wall = new _Sprite(texture)
wall.height = 40
wall.width = 40
wall.x = 40 * i
wall.y = 560
walls.push(wall)
}
// 生成第四堵墙
for (let i = 1; i < 14; i++) {
let wall = new _Sprite(texture)
wall.height = 40
wall.width = 40
wall.x = 560
wall.y = 40 * i
walls.push(wall)
}
return walls
}
2. 添加探险者
探险者是可以运动的,监听的是键盘的方向键。探险者的运动只能在围墙内部进行。使用 Pixi 的 ticker 创建一个循环函数,这被称为游戏循环(game loop)。放入app.ticker中的回调函数可以被执行60次每秒。精灵的上下左右移动速度可以使用精灵的 vx、vy 属性。水平方向上 vx=0 表示水平方向不动,vx=-1 表示每次更新向左移动 1px,vx=1 表示每次更新向右移动 1px;同理,vy=0 表示垂直方向不动,vy=-1 表示每次更新向上移动 1px,vy=1 表示每次更新向下移动 1px。
loader
.add("images/textureMap.json")
.load(intLoad)
const wallWidth = 40 // 墙的宽度
function intLoad() {
const textures = _loader.resources['images/textureMap.json'].textures
// 添加纹理墙
const walls = addWall(textures['1.png'])
walls.forEach(wall => {
_stage.addChild(wall)
})
// 添加探索者
let explorer = addExplorer(textures['2.png'])
_stage.addChild(explorer)
app.ticker.add(() => explorerMove(explorer, wallWidth))
}
// 添加探索者
function addExplorer(texture) {
let explorer = new _Sprite(texture)
explorer.height = 40
explorer.width = 40
explorer.x = 60
// 垂直居中
explorer.y = _stage.height / 2 - explorer.height / 2
explorer.vx = 0 // x轴速度,0为不动,-1为向左,1为向右
explorer.vy = 0 // y轴速度,0为不动,-1为向上,1为向下
return explorerEvent(explorer)
}
// 给探险者添加键盘事件
function explorerEvent(explorer) {
// 为探索者绑定键盘事件
const left = keyboard('ArrowLeft')
const right = keyboard('ArrowRight')
const up = keyboard('ArrowUp')
const down = keyboard('ArrowDown')
const wallWidth = 40 // 墙的宽度
// 按下左箭头键
left.press = () => {
// 右箭头键按下时,左箭头键不生效
if (right.isDown) return
if (explorer.x <= wallWidth) {
// 左移到墙的左侧时,左箭头键不生效
explorer.vx = 0
explorer.x = wallWidth
return
}
explorer.vx = -5
explorer.vy = 0
}
// 松开左箭头键
left.release = () => {
// 如果右箭头键没有按下,左箭头松开时精灵x轴不动
if (!right.isDown && explorer.vy === 0) {
explorer.vx = 0
}
}
// 按下右箭头键
right.press = () => {
// 左箭头键按下时,右箭头键不生效
if (left.isDown) return
if (explorer.x >= _stage.width - explorer.width - wallWidth) {
// 右移到墙的右侧时,右箭头键不生效
explorer.vx = 0
explorer.x = _stage.width - explorer.width - wallWidth
return
}
explorer.vx = 5
explorer.vy = 0
}
// 松开右箭头键
right.release = () => {
// 如果左箭头键没有按下,右箭头松开时精灵x轴不动
if (!left.isDown && explorer.vy === 0) {
explorer.vx = 0
}
}
// 按下上箭头键
up.press = () => {
// 下箭头键按下时,上箭头键不生效
if (down.isDown) return
if (explorer.y <= wallWidth) {
explorer.vy = 0
explorer.y = wallWidth
return
}
explorer.vx = 0
explorer.vy = -5
}
// 松开上箭头键
up.release = () => {
// 如果下箭头键没有按下,上箭头松开时精灵y轴不动
if (!down.isDown && explorer.vx === 0) {
explorer.vy = 0
}
}
// 按下下箭头键
down.press = () => {
// 上箭头键按下时,下箭头键不生效
if (up.isDown) return
if (explorer.y >= _stage.height - explorer.height - wallWidth) {
explorer.vy = 0
explorer.y = _stage.height - explorer.height - wallWidth
return
}
explorer.vx = 0
explorer.vy = 5
}
// 松开下箭头键
down.release = () => {
// 如果上箭头键没有按下,下箭头松开时精灵y轴不动
if (!up.isDown && explorer.vx === 0) {
explorer.vy = 0
}
}
return explorer
}
// 键盘
function keyboard(value) {
let key = {}
key.value = value
key.isDown = false
key.isUp = true
key.press = undefined
key.release = undefined
// 键盘按下
key.downHandler = event => {
if (event.key === key.value) {
if (key.isUp && key.press) key.press()
key.isDown = true
key.isUp = false
event.preventDefault()
}
}
// 键盘弹起
key.upHandler = event => {
if (event.key === key.value) {
if (key.isDown && key.release) key.release()
key.isDown = false
key.isUp = true
event.preventDefault()
}
}
// 订阅事件
const downListener = key.downHandler.bind(key)
const upListener = key.upHandler.bind(key)
window.addEventListener('keydown', downListener, false)
window.addEventListener('keyup', upListener, false)
// 取消订阅事件
key.unsubscribe = () => {
window.removeEventListener('keydown', downListener)
window.removeEventListener('keyup', upListener)
}
return key
}
function explorerMove(sprite, wallWidth) {
sprite.x += sprite.vx
if (sprite.x <= wallWidth) {
sprite.x = wallWidth
}
if (sprite.x >= _stage.width - sprite.width - wallWidth) {
sprite.x = _stage.width - sprite.width - wallWidth
}
sprite.y += sprite.vy
if (sprite.y <= wallWidth) {
sprite.y = wallWidth
}
if (sprite.y >= _stage.height - sprite.height - wallWidth) {
sprite.y = _stage.height - sprite.height - wallWidth
}
}
3. 添加宝物
loader
.add("images/textureMap.json")
.load(intLoad)
const wallWidth = 40 // 墙的宽度
function intLoad() {
const textures = _loader.resources['images/textureMap.json'].textures
// 添加纹理墙
const walls = addWall(textures['1.png'])
walls.forEach(wall => {
_stage.addChild(wall)
})
// 添加探索者
let explorer = addExplorer(textures['2.png'])
_stage.addChild(explorer)
app.ticker.add(() => explorerMove(explorer, wallWidth))
// 添加宝物
const treasure = addTreasure(textures['4.png'])
_stage.addChild(treasure)
}
// 添加宝物
function addTreasure(texture) {
let treasure = new _Sprite(texture)
treasure.height = 40
treasure.width = 40
treasure.x = _stage.width - treasure.width - wallWidth
// 垂直居中
treasure.y = _stage.height / 2 - treasure.height / 2
return treasure
}
4. 添加拦截者
阻拦者有很多,每个阻拦者垂直方向上的位置是随机的。
loader
.add("images/textureMap.json")
.load(intLoad)
const wallWidth = 40 // 墙的宽度
function intLoad() {
const textures = _loader.resources['images/textureMap.json'].textures
// 添加纹理墙
const walls = addWall(textures['1.png'])
walls.forEach(wall => {
_stage.addChild(wall)
})
// 添加探索者
let explorer = addExplorer(textures['2.png'])
_stage.addChild(explorer)
app.ticker.add(() => explorerMove(explorer, wallWidth))
// 添加宝物
const treasure = addTreasure(textures['4.png'])
_stage.addChild(treasure)
// 添加拦截者
const interceptors = addInterceptor(textures['3.png'], 8)
interceptors.forEach(interceptor => {
_stage.addChild(interceptor)
// 拦截者运动
app.ticker.add(() => interceptorMove(interceptor, _stage, wallWidth))
})
}
// 添加拦截者
function addInterceptor(texture, n) {
const interceptors = []
// 生成8个拦截者
const xOffset = 120 // x偏移量
const count = n // 拦截者数量
const spacing = 40 // 水平间隔
for (let i = 0; i < count; i++) {
let interceptor = new _Sprite(texture)
interceptor.height = 40
interceptor.width = 40
interceptor.x = xOffset + i * spacing
// 随机生成y坐标
interceptor.y = randomInt(40, _stage.height - interceptor.height - 40)
interceptor.vy = Math.round(Math.random()) ? 1 : -1 // y轴速度,0为不动,-1为向上,1为向下
interceptors.push(interceptor)
}
return interceptors
}
function interceptorMove(sprite, _stage, wallWidth) {
sprite.y += sprite.vy
if (sprite.y <= wallWidth) {
sprite.y = wallWidth
sprite.vy = 1
}
if (sprite.y >= _stage.height - sprite.height - wallWidth) {
sprite.y = _stage.height - sprite.height - wallWidth
sprite.vy = -1
}
}
// 随机数min~max
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
5. 添加碰撞检测
现在按键盘方向键时探险者可以移动起来了,拦截者们也上下移动起来了,现在还有两个碰撞检测没有添加。探险者与宝物碰撞检测,检测到碰撞后游戏胜利;探险者与阻拦者之间的碰撞检测,检测到碰撞后,游戏失败。
现在对上面的代码进行改造。检测碰撞的函数hitTestRectangle不再提供,请下载源码查看。
loader
.add("images/textureMap.json")
.load(intLoad)
const wallWidth = 40 // 墙的宽度
let gameState = 'playing' // 游戏状态:playing、win、lose
let hitInterceptor = false // 是否碰撞拦截者
let hitTreasure = false // 是否碰撞宝物
let stateTips // 游戏状态提示
function intLoad() {
const textures = _loader.resources['images/textureMap.json'].textures
// 添加纹理墙
const walls = addWall(textures['1.png'])
walls.forEach(wall => {
_stage.addChild(wall)
})
// 添加探索者
let explorer = addExplorer(textures['2.png'])
_stage.addChild(explorer)
// 添加宝物
const treasure = addTreasure(textures['4.png'])
_stage.addChild(treasure)
// 每次更新都要检测探险者与宝物是否碰撞
app.ticker.add(() => updateAdnHitTest1(explorer, treasure, _stage, wallWidth))
// 添加拦截者
const interceptors = addInterceptor(textures['3.png'], 8)
interceptors.forEach(interceptor => {
_stage.addChild(interceptor)
// 每次更新检测每个拦截者与探险者是否碰撞
app.ticker.add(() => updateAdnHitTest2(explorer, interceptor, _stage, wallWidth))
// 添加文字
stateTips = addText(gameState, 60, 6)
stateTips.style.fill = 0x000000
_stage.addChild(stateTips)
})
}
// 检测探险者与宝物是否碰撞
function updateAdnHitTest1(explorer, treasure, _stage, wallWidth) {
explorerMove(explorer, wallWidth)
// 检测碰撞宝物
hitTreasure = hitTestRectangle(explorer, treasure)
if (hitTreasure) {
gameState = 'win'
stateTips.text = 'You Win'
stateTips.style.fill = 0x00ff00
// 停止游戏
app.ticker.stop()
// 停止探索者的移动
explorer.vx = 0
explorer.vy = 0
}
}
// 刷新拦截者的位置及检测碰撞
function updateAdnHitTest2(explorer, interceptor, _stage, wallWidth) {
interceptorMove(interceptor, _stage, wallWidth)
// 探险者与拦截者进行碰撞检测
hitInterceptor = hitTestRectangle(explorer, interceptor)
if (hitInterceptor) {
gameState = 'lose'
stateTips.text = 'Game Over'
stateTips.style.fill = 0xff0000
// 停止游戏
app.ticker.stop()
// 停止探索者的移动及置灰
explorer.vx = 0
explorer.vy = 0
explorer.alpha = 0.6
}
}
到此,简单的探宝游戏就开发完成了,游戏很简单,没有难度级别和关卡设置,后续会完善上这些功能。如果觉得有收获就点赞收藏下吧或者 github 上给个星星吧。
源码地址:https://github.com/zhench0515/explorer-game

浙公网安备 33010602011771号