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的顯示元件,因此也都可以再在裡面放其他的顯示元件。
下面是一個簡單的使用範例:
1 2 |
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
下面是一個簡單的使用範例:
1 2 3 4 5 6 7 |
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.
下面是一個簡單的使用範例:
1 2 3 4 5 6 7 |
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內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
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; } } |
新增Board.ts
,內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
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; } } |
新增Direction.ts
,內容如下:
1 2 3 4 5 6 |
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
,要放置所有的圖示。
1 2 3 4 |
import Container = PIXI.Container; export class GameBoard extends Container{ } |
然後在GameScene.ts
加入這個元件:
1 2 |
//加入連連看牌面 application.stage.addChild(new GameBoard()); |
實際撰寫GameBoard的邏輯
完整的程式碼內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
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/