Egret入门——九宫格拼图

661 阅读5分钟

前言

Egret 九宫格拼图小游戏。 项目地址:九宫格拼图小游戏

项目介绍

一个由“Egret、TweenMax”实现的简单九宫格拼图小游戏。点击空白滑块相邻的滑块进行移动。 适合Egret初学者。大佬请关闭!后续会进行实现思路优化~

项目启动

  1. egret run 启动项目/热更新 egret run -a
  2. egret publish 项目发布

未解决问题

  1. 九宫格随机打乱会有50%的概率出现无法还原的情况。

项目整体代码的实现逻辑

  1. 遍历数组创建Bitmap,作为拼图每一个滑块儿,滑块儿位置根据数组索引计算确定。
  2. 通过判断滑块儿是否和白色滑块相邻,确认滑块是否可移动
  3. 移动方向判断(参考代码)
  4. 拼图成功判断:遍历存放滑块儿的数组,所有滑块儿位置全部正确时,即拼图成功

主要代码实现

  1. 创建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
}

  1. 初始化画面
/**
* 初始化地图
*/
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();
}

  1. 绘制开始按钮
/**
* 绘制开始按钮
*/
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)
}
  1. 绘制游戏结果
/**
* 绘制游戏结果弹窗
*/
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;
}
  1. 开始游戏
/**
* 开始游戏
*/
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
}
  1. 再玩一次
/**
* 再玩一次
*/
private againGame() {
    // 恢复卡片位置
    const newMap =  DataUtil.cloneDeep(this.dataSource).concat([8]);
    // 设置每个卡片的位置
    this.setCardPosition(newMap);
    // 隐藏游戏结果
    this.resultContainer.visible = false;
    // 显示开始按钮
    this.startContainer.visible = true;
    this._start = false;
}
  1. 设置每个卡片的位置
/**
* 设置每个卡片的位置
* @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);
            }
        }
    }
}
  1. 渲染位图卡片
/**
* 渲染位图卡片
* @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;
  1. 移动滑块儿
/**
* 移动卡片
* @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)
}
  1. 判断拼图是否完成
/**
* 检查拼图是否完成
*/
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
}
  1. 滑块儿点击,判断是否可移动
/**
* 位图点击
* @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)
        }
    }
}

结束语

一个简单的九宫格拼图完成。实现逻辑比较直白。后续会进行迭代,优化一下整理的逻辑。