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/