前言
Egret 九宫格拼图小游戏。 项目地址:九宫格拼图小游戏
项目介绍
一个由“Egret、TweenMax”实现的简单九宫格拼图小游戏。点击空白滑块相邻的滑块进行移动。 适合Egret初学者。大佬请关闭!后续会进行实现思路优化~
项目启动
- egret run 启动项目/热更新 egret run -a
- egret publish 项目发布
未解决问题
- 九宫格随机打乱会有50%的概率出现无法还原的情况。
项目整体代码的实现逻辑
- 遍历数组创建Bitmap,作为拼图每一个滑块儿,滑块儿位置根据数组索引计算确定。
- 通过判断滑块儿是否和白色滑块相邻,确认滑块是否可移动
- 移动方向判断(参考代码)
- 拼图成功判断:遍历存放滑块儿的数组,所有滑块儿位置全部正确时,即拼图成功
主要代码实现
- 创建puzzle类,用做拼图界面
class PuzzleScene extends egret.DisplayObjectContainer {
// 源数据
private dataSource: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7];
// 创建滑块及设置滑块集合
private mapSource: Array<number>;
// 滑块儿集合
private sprites: Array<any> = [];
// 滑块儿坐标集合
private positions: Array<any> = [];
// 舞台宽度
private _width: number;
// 舞台高度
private _height: number;
// 拼图名称
private _mapName: string;
// 是否开始
private _start: boolean = false;
// 开始按钮
private startContainer: egret.DisplayObjectContainer;
// 游戏成功容器
private resultContainer: egret.DisplayObjectContainer;
// 上一次随机的数据
private oldIndex: number;
// 拼图数据, 随机打乱数组会出现不可复原情况。暂时绕过,先自定义几个打乱后的情况。
private arr: Array<any> = [
[1,3,2,4,6,5,0,7],
[1,2,5,4,3,7,0,6],
[1,5,3,0,4,6,7,2],
[1,6,3,7,0,2,4,5],
[2,5,3,6,1,7,0,4],
[2,6,5,3,4,7,1,0],
[3,0,2,1,4,7,5,6],
[3,1,5,2,7,6,4,0],
[4,1,5,2,6,3,0,7],
[4,2,7,1,6,0,5,3],
[5,4,3,1,2,7,0,6],
[5,4,3,7,0,6,1,2],
[6,4,5,7,3,0,2,1],
[6,0,3,1,4,7,5,2],
[7,1,3,6,0,2,4,5],
[7,1,3,0,4,2,6,5]
];
public constructor() {
super()
this._width = GameUtil.getStageWidth();
this._height = GameUtil.getStageHeight();
this._mapName = 'cat';
// 初始化画面
this.InitMap();
}
}
/**
* 注:GameUtil.getStageWidth()、GameUtil.getStageHeight()获取舞台宽度、高度像素值
*/
public static getStageHeight(): number {
return egret.MainContext.instance.stage.stageHeight
}
public static getStageWidth(): number {
return egret.MainContext.instance.stage.stageWidth
}
- 初始化画面
/**
* 初始化地图
*/
private InitMap() {
this.mapSource = DataUtil.cloneDeep(this.dataSource).concat([8]);
this.InitView();
}
/**
* 初始化视图
*/
private InitView() {
// 创建拼图容器
const container: egret.DisplayObjectContainer = new egret.DisplayObjectContainer();
container.width = this._width;
container.height = this._width;
container.x = 0;
container.y = 50;
this.addChild(container);
// 绘制开始按钮
this.drawStartBtn();
// 生成card并渲染
this.drawMap(container);
// 绘制游戏结果弹窗
this.drawResultToast();
}
- 绘制开始按钮
/**
* 绘制开始按钮
*/
private drawStartBtn() {
// 创建开始按钮容器
this.startContainer = new egret.DisplayObjectContainer();
// 设置开始按钮容器宽高
this.startContainer.width = this._width;
this.startContainer.height = this._height;
// 创建开始按钮
const startBtn = new egret.Shape();
// 绘制圆角矩形
GameUtil.drawRoundRect(startBtn, 0x4e6ef2, 200, 50, 10, [0, 1, 2, 3] );
// 坐标
startBtn.x = (this._width - startBtn.width) / 2;
startBtn.y = this._width + 150;
// 创建TextField
const btnText = new egret.TextField();
// 设置文本
btnText.text = '开始';
// 文本颜色
btnText.textColor = 0xffffff;
// 文本大小
btnText.size = 24;
// 坐标
btnText.x = (this._width - btnText.width) / 2;
btnText.y = this._width + 150 + (startBtn.height - btnText.height) / 2;
// 将元素添加进容器
this.startContainer.addChild(startBtn);
this.startContainer.addChild(btnText);
// 将容器添加至舞台
this.addChild(this.startContainer);
// 开始按钮点击事件
GameUtil.tap(startBtn, () => {
this.startGame();
}, this);
}
/**
* GameUtil.tap设置容器可点击并添加点击事件
*/
public static tap(bitmap: egret.DisplayObject, callback, thisObject) {
bitmap.touchEnabled = true
bitmap.addEventListener(egret.TouchEvent.TOUCH_TAP, callback, thisObject)
}
- 绘制游戏结果
/**
* 绘制游戏结果弹窗
*/
private drawResultToast() {
this.resultContainer = new egret.DisplayObjectContainer();
this.resultContainer.width = this._width;
this.resultContainer.height = this._height;
// 创建结果文本
const text: egret.TextField = new egret.TextField();
text.text = '拼图成功';
text.textAlign = 'center';
text.textColor = 0xffffff;
text.size = 24;
const textWidth = text.textWidth;
text.x = (this._width - textWidth) / 2;
text.y = 400;
// 绘制半透明窗体
const shapeBg = new egret.Shape()
shapeBg.graphics.beginFill(0x000000, .7)
shapeBg.graphics.drawRect(0, 0, this._width, this._height);
shapeBg.graphics.endFill();
// 绘制再玩一次按钮
const againBtn: egret.Shape = new egret.Shape();
GameUtil.drawRoundRect(againBtn, 0x4e6ef2, 200, 50, 10, [0, 1, 2, 3]);
againBtn.x = (this._width - againBtn.width) / 2;
againBtn.y = 500;
// 绘制再玩一次文本
const againText: egret.TextField = new egret.TextField();
againText.text = '再玩一次';
againText.textColor = 0xffffff;
againText.size = 24;
againText.x = (this._width - againText.width) / 2;
againText.y = 500 + (againBtn.height - againText.height) / 2;
// 向容器添加元素
this.resultContainer.addChild(shapeBg);
this.resultContainer.addChild(text);
this.resultContainer.addChild(againBtn);
this.resultContainer.addChild(againText);
// 增加再玩一次事件
GameUtil.tap(againBtn, () => {
this.againGame();
}, this);
// 添加容器
this.addChild(this.resultContainer);
// 隐藏容器
this.resultContainer.visible = false;
}
- 开始游戏
/**
* 开始游戏
*/
private startGame() {
if(!this._start) {
// 根据最大值与最小值获取随机数
const index = this.createIndexByRandom(0, this.arr.length);
this.oldIndex = index;
const newMap = DataUtil.cloneDeep(this.arr[index]).concat([8]);
this.setCardPosition(newMap);
this.sprites[this.sprites.length - 1].alpha = 0;
setTimeout(() => {
this._start = true;
// 隐藏开始按钮
this.startContainer.visible = false;
}, 300);
}
}
/**
* 获取随机数,保证和上次随机的结果不同
*/
private createIndexByRandom(min, max) {
let index: number;
index = DataUtil.random(min, max);
if(index === this.oldIndex) {
index = this.createIndexByRandom(min, max);
}
return index
}
- 再玩一次
/**
* 再玩一次
*/
private againGame() {
// 恢复卡片位置
const newMap = DataUtil.cloneDeep(this.dataSource).concat([8]);
// 设置每个卡片的位置
this.setCardPosition(newMap);
// 隐藏游戏结果
this.resultContainer.visible = false;
// 显示开始按钮
this.startContainer.visible = true;
this._start = false;
}
- 设置每个卡片的位置
/**
* 设置每个卡片的位置
* @param newMap 打乱顺序后的数组
*/
private setCardPosition(newMap: Array<any>) {
for (let i = newMap.length - 1; i >= 0 ; i--) {
for(let j = this.sprites.length - 1; j >= 0 ; j--) {
const nameNum = parseInt(this.sprites[j].name);
if(nameNum === newMap[i]) {
// 设置每一行数量
const rowNum = 3;
// 计算商值,设置y轴位置
const quot: number = Math.floor(i / rowNum);
// 计算每行位置索引
const col: number = i - (quot * rowNum);
// 改变每张卡片的位置
egret.Tween.get(this.sprites[j], { loop: false }).to({x: this.sprites[j].width * col, y: this.sprites[j].width * quot}, 200, egret.Ease.sineIn);
}
}
}
}
- 渲染位图卡片
/**
* 渲染位图卡片
* @param number
*/
private drawMap(parent: egret.DisplayObjectContainer) {
for(let i = 0; i< this.mapSource.length; i++ ) {
// 位图命名
const name = this._mapName + (this.mapSource[i] + 1);
// 设置每一行数量
const rowNum = 3;
// 计算商值,设置y轴位置
const quot: number = Math.floor(i / rowNum);
// 计算每行位置索引
const col: number = i - (quot * rowNum);
// 计算宽度
const width = this._width / rowNum;
// 创建位图
const square: egret.Bitmap = ResUtil.createBitmap(name, 'png');
// 设置宽高
square.width = width;
square.height = width;
// 设置y轴位置
square.y = width * quot;
// 设置x轴位置
square.x = width * col;
// 将位图的name属性设置为正确的索引项
square.name = this.mapSource[i].toString()
// 将元素添加进数组
this.sprites.push(square);
// 将位置信息添加进数组
this.positions.push({
x: square.x,
y: square.y
})
// 添加元素到舞台上
parent.addChild(square);
GameUtil.tap(square, () => {
this.cardTap(i)
}, this)
}
}
/**
* ResUtil.createBitmap(name, 'png')可替换为
*/
const square: egret.Bitmap = new egret.Bitmap();
const texture: egret.Texture = RES.getRes(name + '_png');
square.texture = texture;
- 移动滑块儿
/**
* 移动卡片
* @param currentCard 当前点击卡片
* @param lastCard 空白卡片
* @param currentPosition 当前点击卡片位置
* @param lastPosition 空白卡片位置
*/
private moveCard(currentCard, lastCard, currentPosition, lastPosition) {
egret.Tween.get(currentCard, { loop: false }).to({x: lastPosition.x, y: lastPosition.y}, 100, egret.Ease.sineIn)
egret.Tween.get(lastCard, { loop: false }).to({x: currentPosition.x, y: currentPosition.y}, 100, egret.Ease.sineIn)
egret.setTimeout(() => {
// 判断拼图是否完成
const success = this.checkSuccess()
if(success) {
this.sprites[this.sprites.length - 1].alpha = 1;
this.resultContainer.visible = true;
}
}, this, 100)
}
- 判断拼图是否完成
/**
* 检查拼图是否完成
*/
private checkSuccess() {
let status = true;
for(let i = this.sprites.length - 1; i>= 0 ; i--) {
if( this.sprites[i].x !== this.positions[this.sprites[i].name].x || this.sprites[i].y !== this.positions[this.sprites[i].name].y ) {
return false
}
}
return status
}
- 滑块儿点击,判断是否可移动
/**
* 位图点击
* @param i 当前点击索引
*/
private cardTap(i: any) {
if (this._start) {
const currentCard = this.sprites[i];
// 空白卡片
const lastCard = this.sprites[this.sprites.length - 1];
// 空白卡片位置
const lastPosition = {
x: lastCard.x,
y: lastCard.y
}
// 当前点击卡片位置
const currentPosition = {
x: currentCard.x,
y: currentCard.y
}
const cardWidth = currentCard.width;
const cardHeight = currentCard.height;
// 判断当前点击卡片是否可移动
// 向右移: 当前点击卡片, y相同, x相差一个卡片宽度, 且当前卡片x小于空白卡片x
// 向下移: 当前点击卡片, x相同, y相差一个卡片高度, 且当前卡片y小于空白卡片y
// 向左移: 当点点击卡片, y相同, y相差一个卡片宽度,且当前卡片x大于空白卡片x
// 向上移: 当前点击卡片, x相同, y相差一个卡片高度, 且当前卡片y大于空白卡片y
if(currentPosition.y === lastPosition.y && (currentPosition.x + cardWidth) === lastPosition.x && currentPosition.x < lastPosition.x) {
console.log('当前卡片向右移')
this.moveCard(currentCard, lastCard, currentPosition, lastPosition)
} else if ( currentPosition.x === lastPosition.x && (currentPosition.y + cardHeight) === lastPosition.y && currentPosition.y < lastPosition.y) {
console.log('当前卡片向下移')
this.moveCard(currentCard, lastCard, currentPosition, lastPosition)
} else if (currentPosition.y === lastPosition.y && (lastPosition.x + cardWidth) === currentPosition.x && currentPosition.x > lastPosition.x ) {
console.log('当前卡片向左移')
this.moveCard(currentCard, lastCard, currentPosition, lastPosition)
} else if (currentPosition.x === lastPosition.x && (lastPosition.y + cardHeight) === currentPosition.y && currentPosition.y > lastPosition.y ) {
console.log('当前卡片向上移')
this.moveCard(currentCard, lastCard, currentPosition, lastPosition)
}
}
}
结束语
一个简单的九宫格拼图完成。实现逻辑比较直白。后续会进行迭代,优化一下整理的逻辑。