实战
项目目录
├── images // 图片
├── js
│ ├── libs // 第三方库
│ └── states // phaser 场景
├── game.js // 游戏主入口
├── game.json
└── project.config.json
1.引入 Phaser,libs目录下的p2.js pixi.js、 phaser-split.js 和weapp-adapter.js,在game.js里引入
require('./js/libs/weapp-adapter')
window.p2 = require('./js/libs/p2')
window.PIXI = require('./js/libs/pixi')
window.Phaser = require('./js/libs/phaser-split')
2.编辑 game.js 创建游戏 ->
// 定义全局常量
window.WIDTH = 750 // 游戏宽度
window.SCALE = WIDTH / canvas.width // 游戏宽度/ canvas 宽度
window.HEIGHT = canvas.height * SCALE // 游戏高度
// go: Global Object 用于在 state 之间共享数据和方法
window.go = {
game: null, // 游戏实例
userInfo: null, // 玩家信息
opponentInfo: null, // 对手信息
common: null, // 公共函数
server: null, // 与服务器的交互
launchRoomId: null, // 进入主菜单时需要加入的房间 id
battle: null, // 对战状态
}
// 初始化游戏
const config = {
width: WIDTH, // 游戏世界宽度
height: HEIGHT, // 游戏世界高度
renderer: Phaser.CANVAS, // 渲染器,这里我们使用 canvas
canvas: canvas // 将游戏绘制在 adapter 为我们创建的 canvas 上
}
const game = new Phaser.Game(config) // 创建游戏
// 全局对象中保存一个 game 的引用
go.game = game
// 注册游戏场景
game.state.add('start', require('./js/states/start')) // 添加 start 游戏场景
game.state.start('start')
- 引入 adapter 之后会自动创建一个 canvas ,我们的游戏要使用它来绘制才能正常显示。
- 我们的视觉稿是按 750 的宽度设计的,这里我们按照宽度适应屏幕。
- window.go 对象用来在 state 之间共享数据和方法
- 创建场景 add,跳转场景页面 start
3.下载资源
preload() {
// 配置画面缩放
this.scale.pageAlignHorizontally = true
this.scale.pageAlignVertically = true
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL
// 预加载资源
this.load.image('bg_menu', 'images/bg_menu.png')
this.load.image('bg_playing', 'images/bg_playing.png')
this.load.image('bg_rank', 'images/bg_rank.png')
this.load.image('bg_waiting', 'images/bg_waiting.png')
this.load.image('avatar', 'images/avatar.png')
this.load.image('avatar_unknow', 'images/avatar_unknow.png')
this.load.image('btn', 'images/btn_menu.png')
this.load.image('o', 'images/o.png')
this.load.image('x', 'images/x.png')
this.load.image('row', 'images/rank_row.png')
this.load.image('avatars', 'images/result_avatars.png')
this.load.image('win', 'images/result_win.png')
this.load.image('lose', 'images/result_lose.png')
this.load.image('draw', 'images/result_draw.png')
this.load.image('bunting', 'images/bunting.png')
}
4.生命周期里来处理游戏场景,跳转其他场景等
this.game.add.image(0, 0, 'bg_menu');
// 添加“开始游戏”按钮
const startBtn = addStartBtn((userInfo) => {
// 销毁开始按钮
startBtn.destroy()
// 将玩家信息存入 global object
go.userInfo = userInfo
// 预加载玩家头像,微信头像为空则不加载
if (go.userInfo.avatarUrl !== '') {
this.load.image(go.userInfo.avatarUrl, go.userInfo.avatarUrl)
// 在 preload 生命周期函数以外进行的资源加载必须手动开始加载
this.load.start()
go.game.state.start('menu')
}
5.游戏逻辑部分从created开始看起,基础差,需要自行了解下js游戏逻辑
// 超级人工智能对弈策略。。。
const AI_STRAGETAGE = [
[1, 1],
[0, 0], [0, 2], [2, 0], [2, 2],
[0, 1], [1, 0], [1, 2], [2, 1],
]
let cd // 倒计时
let board // 棋盘
let currentPlayer // 当前玩家
let intervalId // 倒计时定时器 Id ,用于清理倒计时定时器
let lastTimestamp // 用于计算倒计时
let renderCD // 渲染倒计时
let setPiece // 落子
// 游戏结束
function over(result) {
// 清理倒计时定时器
clearInterval(intervalId)
// 调用 go.common.showResult 显示结果层
go.common.showResult({
result,
// start 场景中,我们把玩家的基本信息存到了 go.userInfo 中
meName: go.userInfo.nickName,
// 新注册的微信用户头像地址为空字符串,遇到这种情况,我们提供一个默认头像
meAvatar: go.userInfo.avatarUrl || 'avatar_unknow',
opponentName: '电脑',
opponentAvatar: 'avatar_unknow',
// 结果层 UI 中有一个“回到首页”按钮,这里可以设置它的点击回调
callback: () => {
// 点击后回到主菜单场景
go.game.state.start('menu')
}
})
}
/**
* 落子,并返回游戏是否结束
*/
function placePiece(row, col) {
// 玩家落子
board[row][col] = currentPlayer
setPiece(row, col, currentPlayer)
// 检查游戏结果
if (checkOver()) return true
// 双方换手
currentPlayer = 1 - currentPlayer
return false
}
/**
* 重设游戏
*/
function reset() {
// 重设棋盘,0 是自己, 1是对手,-1是空
board = [
[-1, -1, -1],
[-1, -1, -1],
[-1, -1, -1],
]
// 随机选择先手玩家
currentPlayer = Math.round(Math.random())
// 倒计时(每人 60 秒)
cd = [60000, 60000]
lastTimestamp = Date.now()
intervalId = setInterval(() => {
// 定时更新倒计时
const current = Date.now()
const delta = current - lastTimestamp
lastTimestamp = current
cd[currentPlayer] = cd[currentPlayer] - delta
renderCD(cd[0], cd[1])
// 时间到,当前执子玩家判负
cd[0] <= 0 && over('lose')
cd[1] <= 0 && over('win')
}, 500)
}
/**
* 检查游戏结果
*/
function checkOver() {
// 调用 go.common.checkWin 判断是否形成胜局
if (go.common.checkWin(board)) {
// 若形成胜局且当前玩家执子,则获胜
if (currentPlayer === 0) over('win')
// 否则失败
else over('lose')
return true
// 调用 go.common.checkDraw 判断是否形成平局
} else if (go.common.checkDraw(board)) {
over('draw')
return true
}
return false
}
class Practice extends Phaser.State {
create() {
// 画背景
this.add.image(0, 0, 'bg_playing')
// 重设游戏
reset()
// 调用 go.common.addBattleInfo 绘制游戏信息
// 该函数会绘制游戏信息,并返回一个用于更新倒计时的函数
renderCD = go.common.addBattleInfo({
meAvatar: go.userInfo.avatarUrl || 'avatar_unknow',
meName: go.userInfo.nickName,
opponentAvatar: 'avatar_unknow',
opponentName: '电脑',
})
// 传入玩家及对手的倒计时,进行更新
renderCD(cd[0], cd[1])
// 调用 go.common.addPieces 画棋盘
// 该函数接受一个函数作为棋子被点击后的回调函数,传入 row col 值
// 并返回一个用于落子的函数
setPiece = go.common.addPieces((row, col) => {
// 判断有没有轮到玩家落子
if (currentPlayer !== 0) return
// 玩家落子
const isOver = placePiece(row, col)
if (isOver) return
// 电脑从落子列表里找一个空的位置
const availableCoord = AI_STRAGETAGE.find(coord => board[coord[0]][coord[1]] === -1)
// 装作在努力计算,给玩家留点面子
setTimeout(() => {
// 落子
const isOver = placePiece(availableCoord[0], availableCoord[1])
// 游戏没有结束的话,通知玩家落子
!isOver && go.common.alertYourTurn()
}, 500 + 1000 * Math.random())
})
// 若随机到电脑先下,固定下中间
if (currentPlayer === 1) {
placePiece(1, 1)
} else {
go.common.alertYourTurn()
}
}
}
module.exports = Practice