PIXI顯示元件介紹
PixiJS裡較常使用的顯示元件有:PIXI.Container、Sprite、AnimatedSprite
PIXI.Container在官網上的解釋如下:
A Container represents a collection of display objects. It is the base class of all display objects that act as a container for other objects.
簡而言之Container就是一個可以放其他顯示元件的一個容器,像Sprite、AnimatedSprite等也都是繼承自Container的顯示元件,因此也都可以再在裡面放其他的顯示元件。
下面是一個簡單的使用範例:
let container = new PIXI.Container();
container.addChild(sprite);
Sprite是可以放單張的靜態圖檔元件。
The Sprite object is the base for all textured objects that are rendered to the screen
下面是一個簡單的使用範例:
PIXI.loader.add("assets/spritesheet.json").load(setup);
function setup() {
  let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet;
  let sprite = new PIXI.Sprite(sheet.textures["image.png"]);
  ...
}
而AnimatedSprite則可以把多張連續的圖檔播放成連續的動畫。
An AnimatedSprite is a simple way to display an animation depicted by a list of textures.
下面是一個簡單的使用範例:
PIXI.loader.add("assets/spritesheet.json").load(setup);
function setup() {
  let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet;
  animatedSprite = new PIXI.extras.AnimatedSprite(sheet.animations["image_sequence"]);
  ...
}
連連看邏輯程式
之前在第一部份我們完成的程式碼在此:ironman20181022
在這一篇我們要將第一篇所寫完的連線邏輯套入pixiJS版本的連連看遊戲之中。
搬移邏輯部份的檔案
在邏輯程式的部份,Path、Board、Direction都可以直接移來專案內使用。
這些程式碼的邏輯解釋在前幾篇的系列文中都有詳細的說明。
新增Path.ts內容如下:
import Point = PIXI.Point;
import {Board} from "./Board";
import {Direction} from "./Direction";
export class Path {
    public point1:Point;
    public point2: Point;
    readonly board: Board;
    public path_Detail:Array<point>;
    constructor(point1: Point, point2: Point, board: Board) {
        this.point1 = point1;
        this.point2 = point2;
        this.board = board;
    }
    public canLinkInLine(): boolean {
        //從上面消
        let point1UP = this.board.getNearByPointByDirection(this.point1, Direction.UP);
        let point2UP = this.board.getNearByPointByDirection(this.point2, Direction.UP);
        {
            let min = Math.max(point1UP.x,point2UP.x);
            let max = Math.min(this.point1.x, this.point2.x);
            for (var i = max;i>=min;i--){
                if (!this.board.hasMiddleValue(new Point(i, this.point1.y), new Point(i, this.point2.y))){
                    this.path_Detail = [this.point1,new Point(i, this.point1.y),new Point(i, this.point2.y),this.point2];
                    return true;
                }
            }
        }
        //從下面消
        let point1DOWN = this.board.getNearByPointByDirection(this.point1, Direction.DOWN);
        let point2DOWN = this.board.getNearByPointByDirection(this.point2, Direction.DOWN);
        {
            let max = Math.min(point1DOWN.x,point2DOWN.x);
            let min = Math.max(this.point1.x, this.point2.x);
            for (var i = min;i<=max;i++){
                if (!this.board.hasMiddleValue(new Point(i, this.point1.y), new Point(i, this.point2.y))){
                    this.path_Detail = [this.point1,new Point(i, this.point1.y),new Point(i, this.point2.y),this.point2];
                    return true;
                }
            }
        }
        //從左邊消
        let point1LEFT = this.board.getNearByPointByDirection(this.point1, Direction.LEFT);
        let point2LEFT = this.board.getNearByPointByDirection(this.point2, Direction.LEFT);
        {
            let min = Math.max(point1LEFT.y,point2LEFT.y);
            let max = Math.min(this.point1.y, this.point2.y);
            for (var i = max;i>=min;i--) {
                if (!this.board.hasMiddleValue(new Point(this.point1.x, i), new Point(this.point2.x, i))) {
                    this.path_Detail = [this.point1, new Point(this.point1.x, i), new Point(this.point2.x, i), this.point2];
                    return true;
                }
            }
        }
        //從右邊消
        let point1RIGHT = this.board.getNearByPointByDirection(this.point1, Direction.RIGHT);
        let point2RIGHT = this.board.getNearByPointByDirection(this.point2, Direction.RIGHT);
        {
            let max = Math.min(point1RIGHT.y,point2RIGHT.y);
            let min = Math.max(this.point1.y, this.point2.y);
            for (var i = min;i<=max;i++) {
                if (!this.board.hasMiddleValue(new Point(this.point1.x, i), new Point(this.point2.x, i))) {
                    this.path_Detail = [this.point1, new Point(this.point1.x, i), new Point(this.point2.x, i), this.point2];
                    return true;
                }
            }
        }
        //左右連消
        if (this.point1.y != this.point2.y){
            let leftPoint = (this.point1.y < this.point2.y) ? this.point1:this.point2;
            let rightPoint = (this.point1.y >= this.point2.y) ? this.point1:this.point2;
            let leftPointRIGHT = this.board.getNearByPointByDirection(leftPoint, Direction.RIGHT);
            let rightPointLEFT = this.board.getNearByPointByDirection(rightPoint, Direction.LEFT);
            if (leftPointRIGHT.y != leftPoint.y && rightPointLEFT.y != rightPoint.y){
                for (var i = rightPointLEFT.y; i <= leftPointRIGHT.y; i++) {
                    if (!this.board.hasMiddleValue(new Point(leftPoint.x, i), new Point(rightPoint.x, i))) {
                        this.path_Detail = [leftPoint, new Point(leftPoint.x, i), new Point(rightPoint.x, i), rightPoint];
                        return true;
                    }
                }
            }
        }
        //上下連消
        if (this.point1.x != this.point2.x){
            let upPoint = (this.point1.x < this.point2.x) ? this.point1:this.point2;
            let downPoint = (this.point1.x >= this.point2.x) ? this.point1:this.point2;
            let upPointDOWN = this.board.getNearByPointByDirection(upPoint, Direction.DOWN);
            let downPointUP = this.board.getNearByPointByDirection(downPoint, Direction.UP);
            if (upPointDOWN.x != upPoint.x && downPointUP.x != downPoint.x){
                for (var i = downPointUP.x; i <= upPointDOWN.x; i++) {
                    if (!this.board.hasMiddleValue(new Point(i, upPoint.y), new Point(i, downPoint.y))) {
                        this.path_Detail = [upPoint, new Point(i, upPoint.y), new Point(i, downPoint.y), downPoint];
                        return true;
                    }
                }
            }
        }
        return false;
    }
}[/code]
新增<code>Board.ts</code>,內容如下:
[code lang="js"]import {Path} from "./Path";
import Point = PIXI.Point;
import {Direction} from "./Direction";
export class Board {
    public board: Array<array<number>>;
    constructor() {
        let content = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
        //產生初始局面
        let length = 10;
        let data = content.concat(content).concat(content).concat(content).sort((a, b) => (Math.random() > .5) ? 1 : 0);
        this.board = []
        for (var i = 0;i<length;i++){
            this.board.push(data.slice(i*length, (i+1)*length))
        }
    }
    public gameRoundEnd():boolean{
        for (var i =0;i<this.board.length;i++){
            for (var j = 0; j<this.board[i].length;j++){
                if(this.board[i][j] != null){
                    return false;
                }
            }
        }
        return true;
    }
    public getFirstExistPath():Path{
        var searchedValue = [];
        for (var i =0;i<this.board.length;i++){
            for (var j = 0; j<this.board[i].length;j++){
                let value = this.board[i][j];
                if(value!= null && searchedValue.indexOf(value) == -1){
                    searchedValue.push(value);
                    let positionsArr = this.getPositionByValue(value);
                    let permutationsArr = this.getPairNumPermutations(positionsArr.length);
                    for(var k = 0;k<permutationsArr.length;k++){
                        let v = permutationsArr[k];
                        let path = new Path(positionsArr[v[0]], positionsArr[v[1]],this);
                        if(path.canLinkInLine()){
                            return path;
                        }
                    }
                }
            }
        }
        return null;
    }
    private getAllValueInBoard(){
        let values = [];
        for (var i =0;i<this.board.length;i++){
            for (var j = 0; j<this.board[i].length;j++){
                if(this.board[i][j] != null){
                    values.push(this.board[i][j]);
                }
            }
        }
        return values;
    }
    public rearrangeBoard(){
        let values = this.getAllValueInBoard().sort((a, b) => (Math.random() > .5) ? 1 : 0);
        for (var i =0;i<this.board.length;i++){
            for (var j = 0; j<this.board[i].length;j++){
                if(this.board[i][j] != null){
                    this.board[i][j] = values.pop();
                }
            }
        }
    }
    private pairNumPermutations = {};
    /**
     * 取得輸入的index中,2個2個一組的所有可能排列組合
     */
    public getPairNumPermutations(num:number){
        if(this.pairNumPermutations[num] != null){
            return this.pairNumPermutations[num];
        }
        let data = [];
        for(var i = 0; i <num;i++){
            for(var j = 0; j <num;j++){
                if(i != j && i <= j){
                    data.push([i,j]);
                }
            }
        }
        this.pairNumPermutations[num] = data;
        return data;
    }
    public getPositionByValue(value:number):Array<point>{
        let arr = new Array<point>();
        for (var i =0;i<this.board.length;i++){
            for (var j = 0; j<this.board[i].length;j++){
                if (this.board[i][j] == value){
                    arr.push(new Point(i, j));
                }
            }
        }
        return arr;
    }
    public getNearByPointByDirection(point: Point, direction: string): Point {
        let nearByPoint: Point = new Point(point.x, point.y);
        switch (direction) {
            case Direction.UP:
                for (var i = point.x-1; i >= 0; i--) {
                    if (this.board[i][point.y] == null) {
                        nearByPoint.x = i;
                    } else {
                        break;
                    }
                }
                if (nearByPoint.x == 0) {
                    nearByPoint.x = -1;
                }
                break;
            case Direction.DOWN:
                let maxLengthDOWN = this.board.length;
                for (var i = point.x+1; i < maxLengthDOWN; i++) {
                    if (this.board[i][point.y] == null) {
                        nearByPoint.x = i;
                    } else {
                        break;
                    }
                }
                if (nearByPoint.x == maxLengthDOWN - 1) {
                    nearByPoint.x = maxLengthDOWN;
                }
                break;
            case Direction.RIGHT:
                let maxLengthRIGHT = this.board[0].length;
                for (var i = point.y+1; i < maxLengthRIGHT; i++) {
                    if (this.board[point.x][i] == null) {
                        nearByPoint.y = i;
                    } else {
                        break;
                    }
                }
                if (nearByPoint.y == maxLengthRIGHT - 1) {
                    nearByPoint.y = maxLengthRIGHT;
                }
                break;
            case Direction.LEFT:
                for (var i = point.y-1; i >= 0; i--) {
                    if (this.board[point.x][i] == null) {
                        nearByPoint.y = i;
                    } else {
                        break;
                    }
                }
                if (nearByPoint.y == 0) {
                    nearByPoint.y = -1;
                }
                break;
        }
        return nearByPoint;
    }
    public canFindPath(a: Point, b: Point, direction:string): boolean {
        return this.hasMiddleValue(a ,b);
    }
    public hasMiddleValue(a: Point, b: Point): boolean {
        let arr = [];
        if (a.x == b.x) {
            if (a.x == -1 || a.x == this.board.length) return false;
            let max = Math.max(a.y, b.y);
            let min = Math.min(a.y, b.y);
            for (var i = min + 1; i < max; i++) {
                if (this.board[a.x][i] != null) {
                    return true;
                }
            }
            return false;
        } else if (a.y == b.y) {
            if (a.y == -1 || a.y == this.board[0].length) return false;
            let max = Math.max(a.x, b.x);
            let min = Math.min(a.x, b.x);
            for (var i = min + 1; i < max; i++) {
                if (this.board[i][a.y] != null) {
                    return true;
                }
            }
            return false;
        } else {
            return true;
        }
    }
    public hasSameValue(point1: Point, point2: Point): boolean {
        return this.board[point1.x][point1.y] == this.board[point2.x][point2.y];
    }
    public clearPoint(point: Point) {
        this.board[point.x][point.y] = null;
        point = null;
    }
}[/code]
新增<code>Direction.ts</code>,內容如下:
[code lang="js"]export class Direction {
    public static UP: string = "up";
    public static DOWN: string = "down";
    public static RIGHT: string = "right";
    public static LEFT: string = "left";
}
需要另外撰寫的部份
第一部份邏輯的介面我們是使用angularJS來做呈現,而現在的專案則需要使用pixiJS,因此呈現部份的程式碼需要重新撰寫。
首先我們先創立一個檔案名為GameBoard.ts,要放置所有的圖示。
import Container = PIXI.Container;
export class GameBoard extends Container{
}
然後在GameScene.ts加入這個元件:
//加入連連看牌面
application.stage.addChild(new GameBoard());
實際撰寫GameBoard的邏輯
完整的程式碼內容如下:
import Container = PIXI.Container;
import {Board} from "../core/Board";
import {Loader} from "../core/Loader";
import Point = PIXI.Point;
import {Path} from "../core/Path";
import {SoundMgr} from "../core/SoundMgr";
export let board:Board;
export class GameBoard extends Container{
    private select1 = new Point(-1, -1);
    private select2 = new Point(-1, -1);
    private selected = false;
    private msgArr = [];
    private reloadTimes = 3;
    private selectedBorder:PIXI.Graphics;
    constructor() {
        super();
        this.createNewGame();
        this.x = 175;
        this.y = 20;
    }
    createNewGame = ()=>{
        this.removeChildren();
        this.select1 = new Point(-1, -1);
        this.select2 = new Point(-1, -1);
        this.selected = false;
        this.msgArr = [];
        this.reloadTimes = 3;
        board = new Board();
        for (var i =0;i<board.board.length;i++){
            for (var j = 0; j<board.board[i].length;j++){
                this.createIcon(board.board[i][j], i, j);
            }
        }
    };
    clearIcon = (point:Point)=>{
        this.removeChild(this.getChildByName('icon_'+point.x+"_"+point.y));
        board.clearPoint(point);
        this.removeChild(this.selectedBorder);
    };
    IconSelected = (point:Point)=>{
    };
    IconUnSelected = (point:Point)=>{
    };
    createIcon = (id, x, y)=>{
        let icon = PIXI.Sprite.from(Loader.resources['Icon'].textures['icon_' + id]);
        icon.name = 'icon_'+x+"_"+y;
        icon.width = icon.height = 45;
        icon.x = (icon.width + 20) * x + 22.5;
        icon.y = (icon.width + 6) * y + 22.5;
        icon.anchor.set(0.5);
        icon.buttonMode = true;
        icon.interactive = true;
        this.addChild(icon);
        let iconClickHandler = ()=>{
            if (this.selected) {
                let selectCorrect = false;
                this.select2 = new Point(x, y);
                if (board.hasSameValue(this.select1, this.select2)) {
                    if (! (this.select1.x == x && this.select1.y == y) ) {
                        let path = new Path(this.select1, this.select2, board);
                        if(path.canLinkInLine()){
                            this.msgArr.push(path);
                            selectCorrect = true;
                            //判斷還有沒有路走
                            if(board.gameRoundEnd()){
                                alert("恭喜完成遊戲!");
                                this.createNewGame();
                            }else if(board.getFirstExistPath() == null){
                                this.reloadTimes--;
                                board.rearrangeBoard();
                            }
                        }
                    }
                }
                if(selectCorrect){
                    this.clearIcon(this.select1);
                    this.clearIcon(this.select2);
                    SoundMgr.play('Sound_select_crrect');
                }else{
                    SoundMgr.play('Sound_select_error');
                    this.IconUnSelected(this.select1);
                }
                this.selected = false;
            } else {
                this.select1 = new Point(x, y);
                this.IconSelected(this.select1);
                this.selected = true;
                SoundMgr.play('Sound_select_1');
            }
        };
        icon.on("click", iconClickHandler);
        icon.on("tap", iconClickHandler);
    }
}
今日成果
今天終於有個連連看遊戲的樣子了!

程式碼下載:ironman20181103
線上demo:http://claire-chang.com/ironman2018/1103/