在進行遊戲時,清楚的玩家操作說明及互動是很重要的遊戲要素。因此,像一般的連連看遊戲,都會在玩家選擇圖示之後,把玩家所選擇的圖示做效果,提示玩家已經選擇了某個符號。並且在玩家選擇了第二個符號且連線成功時,會顯示所經過的路徑,並畫出一條線來消除這兩個符號,如下圖:
在這一篇裡,我們就要實做這樣的功能。
這邊是官網使用Graphics的一個使用範例:Graphics
這是API說明:http://pixijs.download/release/docs/PIXI.Graphics.html
下面是畫線的一個簡單範例:
var app = new PIXI.Application(800, 600, { antialias: true });
document.body.appendChild(app.view);
var graphics = new PIXI.Graphics();
// set a line style
graphics.lineStyle(4, 0xffd900, 1);
// draw a shape
graphics.moveTo(50,50);
graphics.lineTo(250, 50);
graphics.lineTo(250, 250);
graphics.endFill();
app.stage.addChild(graphics);
成果如下:
而這是畫矩型的一個簡單範例:
var app = new PIXI.Application(800, 600, { antialias: true });
document.body.appendChild(app.view);
var graphics = new PIXI.Graphics();
// draw a rounded rectangle
graphics.lineStyle(2, 0xFF00FF, 1);
graphics.beginFill(0xFF00BB, 0);
graphics.drawRoundedRect(150, 450, 300, 100, 1);
graphics.endFill();
app.stage.addChild(graphics);
成果如下:
在過去,我們在產生方塊是直接new一個Sprite並加入場景,而現在方塊要能夠有被選取、取消選取的功能,因此我們將方塊拉出成為一個獨立的類別GameIcon
。
其內容如下:
import Sprite = PIXI.Sprite;
import { Loader } from "../core/Loader";
export class GameIcon extends Sprite{
constructor(id,x,y) {
super();
this.texture = Loader.resources['Icon'].textures['icon_' + id];
this.name = 'icon_' + x + "_" + y;//方便可以從父層更容易的取出這個方塊
this.width = this.height = 45;
this.x = (this.width + 20) * x + 22.5;
this.y = (this.width + 6) * y + 22.5;
this.anchor.set(0.5);//縮放時可以以中間為中心點
this.buttonMode = true;
this.interactive = true;
}
//選擇時,繪製邊框,顏色為紅色
select = ()=>{
let gt = new PIXI.Graphics();
gt.lineStyle(3,0xFF0000,1);
gt.drawRect(-3-22.5,-3-22.5,51,51);
gt.endFill();
this.addChild(gt);
}
//取消選擇時,將邊框拿掉
unSelect = ()=>{
this.removeChildren();
}
}
接著我們在GameBoard.ts裡,撰寫兩個方法iconSelected
及iconUnSelected
如下:
iconSelected = (point:Point)=>{
//根據在GameIcon設定的name來取得正確位置上的方塊
let icon = this.getChildByName('icon_'+point.x+"_"+point.y) as GameIcon;
icon.select();
};
iconUnSelected = (point:Point)=>{
let icon = this.getChildByName('icon_'+point.x+"_"+point.y) as GameIcon;
icon.unSelect();
};
然後改寫GameBoard.ts
裡的createIcon方法
createIcon = (id, x, y)=>{
let icon = new GameIcon(id,x,y);//id為要顯示的圖片編號,x,y為位置
this.addChild(icon);
let iconClickHandler = ()=>{
if (this.selected) {
let selectCorrect = false;
this.select2 = new Point(x, y);
this.iconSelected(this.select2);//將方塊加上紅框
setTimeout(()=>{//為了避免第二個方塊都還沒有繪製到邊框就被取消掉,因此在此增加setTimeout
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.clearIcon(this.select1);
this.clearIcon(this.select2);
eventEmitter.emit(GameFlowEvent.LinkedLineSuccess);
selectCorrect = true;
//判斷還有沒有路走
if(board.gameRoundEnd()){
alert("恭喜完成遊戲!");
this.createNewGame();
}else if(board.getFirstExistPath() == null){
this.reloadTimes--;
board.rearrangeBoard();
}
}
}
}
if(selectCorrect){
SoundMgr.play('Sound_select_crrect');
}else{
SoundMgr.play('Sound_select_error');
//不能消除,取消紅框
this.iconUnSelected(this.select1);
this.iconUnSelected(this.select2);
}
this.selected = false;
},0);
} 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);
}
當成功消除兩個方塊時,應該要有剛剛連線的路徑,這樣使用者才能夠確定連線的方式是正確的,現在我們要為遊戲加上這個功能。
我希望能夠在盤面的上方加上一層圖層,能夠繪製剛剛成功消除的方塊的連線路徑。之前我們在[6 – 遊戲邏輯] 連線消除程式撰寫的地方,所撰寫的Path
類別若呼叫canLinkInLine()
結果反回為true的話,同時亦會把所經的路徑的點塞入path_Detail
這個陣列裡面。
所以這個類別的主要職責,應該是要能夠把輸入的path裡的路徑畫出來。為了要使這個圖層能更方便的在各個地方被取用,我使用了singleton方法來建立這個物件,這樣所有的類別都可以利用LinkedLine.instance
來取得這個元件惟一的實體。
下面為LinkedLine.ts
的資料
import Container = PIXI.Container;
import Point = PIXI.Point;
import { Path } from "../core/Path";
export class LinkedLine extends Container {
constructor() {
super();
this.x = 175;
this.y = 20;
}
//將這個類別設定為singleton類別
private static _instance:LinkedLine;
public static get instance():LinkedLine{
if(this._instance == null){
this._instance = new LinkedLine();
}
return this._instance;
}
//輸入一個path物件,藉由paths.path_Detail來畫出連線
public drawPath(paths:Path){
this.removeChildren();
let point = paths.path_Detail.pop();//取出第一個點
let gt = new PIXI.Graphics();
gt.lineStyle(5, 0xff0000);
let start = this.getPositionFromPoint(point);
gt.moveTo(start.x,start.y);//先移到第一個點的位置
do{
point = paths.path_Detail.pop();//取出後面的點
let line = this.getPositionFromPoint(point);
gt.lineTo(line.x,line.y);//繪製連線
}while(paths.path_Detail.length > 0);
this.addChild(gt);
//設定連線會在500毫秒後自動消失
setTimeout(()=>{this.removeChildren();},500);
}
//把遊戲盤面的x,y系統轉化為畫面上實際的坐標系統
public getPositionFromPoint(point:Point){
let x = (45 + 20) * point.x + 22.5;
let y = (45 + 6) * point.y + 22.5;
if(y < 0){
y = -5;
}
if(y > 502){
y = 510;
}
return new Point(x, y);
}
}
接著在GameBoard.ts裡連線成功時加上這行來繪製連線
LinkedLine.instance.drawPath(path);
並在GameScene.ts裡加上LinkedLine元件
application.stage.addChild(LinkedLine.instance);
線上demo:http://claire-chang.com/ironman2018/1105/
今日成果下載:ironman20181105