摘要:這個類可以大大減少后期的代碼量,降低整體的耦合度。關鍵代碼是把位圖按照區域進行分割,顯示對象的滾動矩形范圍。
這次給大家帶來的是通過Egret實現密室逃生小游戲的教程。該游戲包括人物狀態機、MVC設計模式和單例模式,該游戲在1.5s內通過玩家點擊操作尋找安全點,方可進入下一關,關卡無限,分數無限。下面是具體的模塊介紹和代碼實現。
該游戲主要內容包括
**開始游戲場景
游戲場景
游戲結束結算場景
全局常量類
人物狀態機類**
游戲源碼素材下載:https://github.com/shenysun/R...
創建全局常量類
在所有舞臺搭建之前先寫一個全局的靜態方法類,取名為GameConst。這個類里面的方法和常量可以供全局使用,例如舞臺寬高、通過名字獲取位圖、通過名字獲取紋理集精靈等等。這個類可以大大減少后期的代碼量,降低整體的耦合度。
/**常用常量類 */ class GameConst { /**舞臺寬度 */ public static StageW:number; /**舞臺高度 */ public static StageH:number; /**根據名字創建位圖 */ public static CreateBitmapByName(name:string):egret.Bitmap { let texture:egret.Texture = RES.getRes(name); let bitmap:egret.Bitmap = new egret.Bitmap(texture); return bitmap; } /** * 根據name關鍵字創建一個Bitmap對象。此name 是根據TexturePacker 組合成的一張位圖 */ public static createBitmapFromSheet(name:string, sheetName:string):egret.Bitmap { let texture:egret.Texture = RES.getRes(`${sheetName}_json.${name}`); let result:egret.Bitmap = new egret.Bitmap(texture); return result; } public static getTextureFromSheet(name:string, sheetName:string):egret.Texture { let result:egret.Texture = RES.getRes(`${sheetName}_json.${name}`); return result; } /**移除子類方法 */ public static removeChild(child:egret.DisplayObject) { if(child && child.parent) { if((child.parent).removeElement) { ( child.parent).removeElement( (child)); } else { child.parent.removeChild(child); } } } }
如果游戲中設置圖片錨點較多也可以在這個類里面加一個設置錨點的方法,傳入對象,橫軸錨點和縱軸錨點坐標三個參數。
開始場景
開始頁面比較簡潔,有一個LOGO和兩個按鈕分別是開始游戲,更多游戲。
/**游戲開始場景 */ class StartGameLayer extends egret.Sprite { /**開始按鈕 */ private startBtn:MyButton; /**更多按鈕 */ private moreBtn:MyButton; /**LOGO */ private titleImage:egret.Bitmap; public constructor() { super(); this.init(); } private init():void { /**添加游戲LOGO */ this.titleImage = GameConst.createBitmapFromSheet("logo_mishitaosheng", "ui"); this.titleImage.x = 51; this.titleImage.y = 161; this.addChild(this.titleImage); //開始按鈕設置 this.startBtn = new MyButton("btn_y", "btn_kaishi"); this.addChild(this.startBtn); this.startBtn.x = (GameConst.StageW - this.startBtn.width) / 2; this.startBtn.y = GameConst.StageH / 2 - 75; this.startBtn.setClick(this.onStartGameClick); //更多按鈕設置 this.moreBtn = new MyButton("btn_b", "btn_gengduo"); this.moreBtn.x = (GameConst.StageW - this.startBtn.width) / 2; this.moreBtn.y =GameConst.StageH / 2 + 75; this.addChild(this.moreBtn); this.moreBtn.setClick(this.onMoreBtnClick); //文本 let tex:egret.TextField = new egret.TextField(); tex.width = GameConst.StageW; tex.textAlign = egret.HorizontalAlign.CENTER; tex.strokeColor = 0x403e3e; tex.stroke = 1; tex.bold = true; tex.y = GameConst.StageH / 2 + 250; tex.text = "Powered By ShenYSun"; this.addChild(tex); } private onStartGameClick() { GameControl.Instance.onGameScenesHandler(); } private onMoreBtnClick() { console.log("更多游戲"); platform.GetInfo(); } }
點擊startBtn按鈕執行GameControl類的切換場景方法。
場景控制類
/**游戲管理 */ class GameControl extends egret.Sprite { private static _instance:GameControl; public static get Instance() { if(!GameControl._instance) { GameControl._instance = new GameControl(); } return GameControl._instance; } /**當前場景 */ private currentStage:egret.DisplayObjectContainer; //開始游戲 private startGame:StartGameLayer; /**游戲場景 */ private gameScenes:GameScenesLayer; /**結束場景 */ private overScenes:GameOverLayer; /**背景 */ private bgImg:egret.Bitmap; public constructor() { super(); this.startGame = new StartGameLayer(); this.gameScenes = new GameScenesLayer(); this.overScenes = new GameOverLayer(); } public setStageHandler(stage:egret.DisplayObjectContainer):void { /**設置當前場景的背景 */ this.currentStage = stage; this.bgImg = GameConst.CreateBitmapByName("bg_jpg"); this.bgImg.width = GameConst.StageW; this.bgImg.height = GameConst.StageH; //把背景添加到當期場景 this.currentStage.addChild(this.bgImg); } /**開始游戲的場景 */ public startGameHandler():void { if(this.gameScenes && this.gameScenes.parent) { GameConst.removeChild(this.gameScenes); } if(this.gameScenes && this.overScenes.parent) { GameConst.removeChild(this.overScenes); } this.currentStage.addChild(this.startGame); GameApp.xia.visible = true; } /**游戲場景 */ public onGameScenesHandler():void { if(this.startGame && this.startGame.parent) { GameConst.removeChild(this.startGame); } if(this.overScenes && this.overScenes.parent) { GameConst.removeChild(this.overScenes); } this.currentStage.addChild(this.gameScenes); GameApp.xia.visible = false; } /**游戲結束場景 */ public showGameOverSceneHandler():void{ if(this.startGame && this.startGame.parent){ GameConst.removeChild(this.startGame) } if(this.gameScenes && this.gameScenes.parent){ GameConst.removeChild(this.gameScenes) } this.currentStage.addChild(this.overScenes); GameApp.xia.visible = true; } public getGameOverDisplay():GameOverLayer { return this.overScenes; } }
場景切換貫穿游戲全局,封裝成類方便調用,以及后期擴展只需要加上新場景類的實例,便可以切換自如。
自定義按鈕類
不難發現上面的開始游戲界面的按鈕是MyButton類型,在MyButton類的構造函數中傳入背景圖和顯示文字,創建出一個按鈕。此類有一個設置點擊事件的方法,按鈕調用此公開方法傳入觸發事件即可設置點擊事件。
/**自定義按鈕類 */ class MyButton extends egret.Sprite { private _bg:egret.Bitmap; private title:egret.Bitmap; private onClick:Function; public constructor(bgName:string, titleName:string) { super(); this._bg = GameConst.createBitmapFromSheet(bgName, "ui"); this.addChild(this._bg); this.title = GameConst.createBitmapFromSheet(titleName, "ui"); this.title.x = (this._bg.width - this.title.width) >> 1; this.title.y = (this._bg.height - this.title.height) >> 1; this.addChild(this.title); } //設置點擊觸發事件 public setClick(func:Function):void { this.touchEnabled = true; this.addEventListener(egret.TouchEvent.TOUCH_TAP, this.onClickEvent, this); this.onClick = func; } //點擊觸發的事件 private onClickEvent() { this.onClick(); } public setTitle(title:string):void { this.title = GameConst.CreateBitmapByName(title); } public get bg() { return this._bg; } public set bg(bg:egret.Bitmap) { this._bg = bg; } }
自定義特殊數字類
一般游戲中的分數、時間等數字組成的UI為了美觀都會使用位圖文本,但是當游戲邏輯跑起來需要不斷的刷新游戲的分數,每次改變分數的時候都要從紋理集里面調用對應位圖,在時間上是一個大大的浪費,所以創建一個特殊字符類SpecialNumber,讓這個類替我們實現轉換特殊字符。
具體代碼如下:
/**特殊字符數字類 */ class SpecialNumber extends egret.DisplayObjectContainer { public constructor() { super(); } public gap:number = 0; /**設置顯示的字符串 */ public setData(str:string):void { this.clear(); if(str == "" || str == null) { return; } //把所有數字每一個都存進數組中 let chars:Array= str.split(""); let w:number = 0; //所有的長度 let length:number = chars.length; for(let i:number = 0; i < length; i++) { try { let image:egret.Bitmap = GameConst.createBitmapFromSheet(chars[i], "ui"); if(image) { image.x = w; w += image.width + this.gap; this.addChild(image); } } catch (error) { console.log(error); } } this.anchorOffsetX = this.width / 2; } public clear() { while(this.numChildren) { this.removeChildAt(0); } } }
在體驗過游戲的時候會發現任務會根據不一樣的墻體高度擺不一樣的poss,這才poss全是來自于幀動畫紋理集,只需要把對應一套的動畫解析出來人物就會跳起舞來。下面是人物狀態類。
狀態機類
人物共有五個狀態,其中一個是默認狀態state為跳舞狀態STAGE1,還有設置當前狀態的方法setState
/**角色動作類 */ class Role extends egret.Sprite{ //狀態 public static STATE1:number = 0; public static STATE2:number = 1; public static STATE3:number = 2; public static STATE4:number = 3; public static STATE5:number = 4; /**人物狀態集合 */ public static FRAMES:Array= [ ["0020003", "0020004", "0020005", "0020006","0020007"], ["0020008"], ["0020009", "0020010"], ["0020011", "0020012"], ["xue0001", "xue0002", "xue0003", "xue0004", "xue0005"] ] //身體 private Body:egret.Bitmap; private state:number; private currFrames:Array ; private currFramesIndex:number = 0; private runFlag:number; private isLoop:boolean; public constructor() { super(); this.Body = new egret.Bitmap; //人物初始狀態 this.Body = GameConst.createBitmapFromSheet("Role.FRAMES[0][0]", "Sprites"); //設置錨點 this.Body.anchorOffsetX = this.Body.width * 0.5; this.addChild(this.Body); } /**設置狀態 */ public setState(state:number) :void { this.state = state; //死亡狀態 if(this.state == Role.STATE5) { this.isLoop = false; this.Body.anchorOffsetY = this.Body.height * 0; }else{ this.isLoop = true; this.Body.anchorOffsetY = this.Body.height * 1; } if(this.state == Role.STATE3 || this.state == Role.STATE4){ this.currFrames = []; if(Math.random() > 0.5){ this.currFrames.push(Role.FRAMES[this.state][0]); }else{ this.currFrames.push(Role.FRAMES[this.state][3]); } }else{ this.currFrames = Role.FRAMES[this.state]; } this.currFramesIndex = 0; this.setBody(); } private setBody() { this.Body.texture = GameConst.getTextureFromSheet(this.currFrames[this.currFramesIndex], "Sprites"); this.Body.anchorOffsetX = this.Body.width * 0.5; if(this.state == Role.STATE5){ this.isLoop = false; this.Body.anchorOffsetY = this.Body.height * 0; }else{ this.isLoop = true; this.Body.anchorOffsetY = this.Body.height * 1; } } public run():boolean{ this.runFlag ++; if(this.runFlag > 4){ this.runFlag = 0; } if(this.runFlag != 0){ return; } var gotoFrameIndex:number = this.currFramesIndex + 1; if(gotoFrameIndex == this.currFrames.length){ if(this.isLoop){ gotoFrameIndex = 0; }else{ gotoFrameIndex = this.currFramesIndex; } } if(gotoFrameIndex != this.currFramesIndex){ this.currFramesIndex = gotoFrameIndex; this.setBody(); } return false; } public play():void{ egret.startTick(this.run,this); this.runFlag = 0; } public stop():void{ egret.stopTick(this.run,this); } }
游戲場景
一切工作準備就緒,下面就是本文的重點-----游戲場景的搭建以及邏輯的實現。先看一下游戲內的主要內容
首先是藍色的游戲背景,和開始游戲界面背景如出一轍不用更換,在場景管理的時候注意背景保留一下繼續使用。
其次分數、關卡、上背景圖等等這些只需要調用常量類的獲取紋理集圖片的方法調整位置即可實現。
最后重點介紹一下內容:
墻體生成和運動
人物運動和狀態切換
分數和關卡數改變并記錄最高分數
下面是重要代碼片段
墻體生成和運動
墻體分別包括上半部分和下半部分
/**上部分墻體容器 */ private topContianer:egret.Sprite; /**下部分墻體容器 */ private bottomContianer:egret.Sprite;
容器內又包含了上下部分的墻體圖片,上下邊界線
/**上下墻體填充圖 */ private topSprite:egret.Sprite; private bottomSprite:egret.Sprite; /**上下邊界線 */ private topLine:egret.Shape; private bottomLine:egret.Shape;
把填充圖和邊界線加到容器內(以上邊界為例)
this.topContianer = new egret.Sprite(); this.addChild(this.topContianer); this.topSprite = new egret.Sprite(); this.topContianer.addChild(this.topSprite); this.topContianer.addChild(this.topLine);
定義一個top和bottom范圍區間,隨機在舞臺范圍內取值。
let min:number = 150; let flag:boolean = false; let len:number = 8; let w:number = GameConst.StageW / len; for(let i:number = 0; i < len; i++) { var h:number = min + Math.floor(Math.random() * 8) * 10; this.bottomRects.push(new egret.Rectangle(i * w, GameConst.StageH - h, w, h)); h = GameConst.StageH - h; if (Math.random() < 0.2 || (!flag && i == len - 1)) { var index:number = Math.floor(Math.random() * this.spaceArr.length); h -= this.spaceArr[index]; flag = true; } this.topRects.push(new egret.Rectangle(i * w, 0, w, h)); }
這是隨機取區域已經完成,不過都是理想的區域,并沒有填充實際上的圖片,下面寫一個方法通過區域來填充背景墻。
private fullFront(bgSptite:egret.Sprite, rects:Array, isBottom:boolean = false):void { bgSptite.cacheAsBitmap = false; this.clearBg(bgSptite); var len:number = rects.length; for (var i:number = 0; i < len; i++) { var rec:egret.Rectangle = rects[i]; var bitmap:egret.Bitmap; if (this.bgBitmaps.length) { bitmap = this.bgBitmaps.pop(); } else { bitmap = new egret.Bitmap(); bitmap.texture = this.bg; } bitmap.scrollRect = rec; bitmap.x = rec.x; bitmap.y = rec.y; bgSptite.addChild(bitmap); } }
關鍵代碼bitmap.scrollRect = rec是把位圖按照區域進行分割,顯示對象的滾動矩形范圍。顯示對象被裁切為矩形定義的大小,當您更改 scrollRect 對象的 x 和 y 屬性時,它會在矩形內滾動。
上下背景位圖填充完畢,下面可是畫上下邊界線,同樣是寫了一個方法(以上邊界為例),如下:
private drawLine():void { var lineH:number = 10; this.topLine.graphics.clear(); this.topLine.graphics.lineStyle(lineH, 0x33E7FE); this.bottomLine.graphics.clear(); this.bottomLine.graphics.lineStyle(lineH, 0x33E7FE); this.drawTopLine(lineH / 2); this.drawBottomLine(lineH / 2); this.topLine.graphics.endFill(); this.bottomLine.graphics.endFill(); } private drawTopLine(lineH:number):void { var len:number = this.topRects.length; for (var i:number = 0; i < len; i++) { var rec:egret.Rectangle = this.topRects[i]; if (i == 0) { this.topLine.graphics.moveTo(rec.x, rec.height); this.topLine.graphics.lineTo(rec.x + rec.width, rec.height); } else { this.topLine.graphics.lineTo(rec.x, rec.height); this.topLine.graphics.lineTo(rec.x + rec.width, rec.height); } } }
此時,背景填充完畢,但墻體還不能運動。前面this.topContianer.y = -200;把上部分墻體的的縱軸設置在-200的位置,等到游戲開始執行Tween動畫,使this.topContianer.y = 0,為了有更好的效果游戲開始延遲1.5s再調用墻體運動,Tween動畫如下:
let self = this; setTimeout(function() { // self.shakeRun(); //上面的模塊往下運動 egret.Tween.get(this.topContianer).to({"y":0}, 100).call(function():void { self.landOver(); }) }, 1500);
人物運動和狀態切換
人物運動:給舞臺添加點擊事件,判斷點擊位置并移動。
/**點擊事件 */ private onClick(e:egret.TouchEvent):void { let len:number = this.bottomRects.length; for(let i:number = 0; i < len; i++) { let rec:egret.Rectangle = this.bottomRects[i]; if(e.stageX > rec.x && e.stageX < rec.x + rec.width) { this.setRolePos(i); break; } } }
操作角色所在位置全部是根據上面定義的人物所在位置下標rolePosIndex的相對位置來決定的。
private setRolePos(index:number, offY:number = 17, offX:number = 0, isInit:boolean = false):void { if (!isInit) { //人物每次移動一個格子 if (this.rolePosIndex > index) { index = this.rolePosIndex - 1; } else if (this.rolePosIndex < index) { index = this.rolePosIndex + 1; } } this.rolePosIndex = index; var rec:egret.Rectangle = this.bottomRects[index]; //一次只移動一格 this.role.x = rec.x + rec.width / 2 + offX; this.role.y = rec.y + offY; }
狀態切換:
墻體運動完畢之后,通過人物所在位置下標找到上半部分墻體和下半部分墻體對應的位置的差值,并根據差值判斷人物是否存活,如果存活應該表現出什么狀態。
獲取人物所在位置上下墻體的距離:
privategetSpace():number{ lettop:egret.Rectangle=this.topRects[this.rolePosIndex]; letbottom:egret.Rectangle=this.bottomRects[this.rolePosIndex]; returnGameConst.StageH-top.height-bottom.height; }
根據返回的距離差值判斷人物的狀態:
privatecheckState() { letspace:number=this.getSpace(); if(space==0) { this.role.setState(Role.STATE5); } elseif(space==this.spaceArr[2]) { this.role.setState(Role.STATE4); } elseif(space==this.spaceArr[0]) { this.role.setState(Role.STATE3); } elseif(space==this.spaceArr[1]) { this.role.setState(Role.STATE2); } if(space==0) { this.setRolePos(this.rolePosIndex, -10, 4); } }
根據返回的距離判斷游戲狀態,若返回值為0,游戲結束;不為0,進入下一關:
/**檢驗這關結束主角是否存活 */ privatecheckResult() { letspace:number=this.getSpace(); letself=this; if(space==0) { this.dieNum++; if(this.dieNum==1) { this.role.stop(); setTimeout(function() { //游戲結束 GameControl.Instance.getGameOverDisplay().setGameOverDataHandler(self.score, self.curretMaxScore); GameControl.Instance.showGameOverSceneHandler(); }, 500); return; } } //進入下一關 else{ this.curretLevel++; this.score+=10; if(this.score>this.curretMaxScore) { this.curretMaxScore=this.score; } //刷新成績 this.refreshScore(); } setTimeout(function() { self.refurbish() }, 1000); }
分數和關卡
接著上一步此時如果人物存活進入下一關,那么就要刷新游戲成績和關卡數,并檢驗此時是否為最高成績:
/**刷新成績數據 */ privaterefreshScore() { this.LvNum.setData(this.curretLevel.toString()); this.recodeNum.setData(this.score.toString()); }
游戲進入下一關卡:
/**刷新游戲關卡 */ privaterefreshPoint() { this.initData(); this.start(); }
游戲結算界面
游戲結算界面效果圖
和開始界面差不多,有所不同的是需要從游戲場景中傳入本局分數和做高分數,在這個頁面寫一個公開的setGameOverDataHandler方法,游戲結束是調用此方法傳入數值。
/**游戲結束頁面分數最高分數 */ publicsetGameOverDataHandler(score:number=0, maxScore:number=0):void{ this.scoreNum.setData(score.toString()); this.maxScore.setData(maxScore.toString()); }
擴展
墻體晃動效果
游戲場景內當墻體要下落的時候墻體會晃動一下,晃動墻體不僅提醒玩家這個墻體即將下落,同時也增加了這個游戲的可玩性,下面是控制墻體晃動的Shake類。
/**墻體晃動 */ classShake{ privateinitY:number; privateshakeNum:number; privateoverFunc:Function; privateobj:egret.DisplayObject; privatenum:number; privateflag:number; ? publicrun(obj:egret.DisplayObject, shakeNum:number, overFunc:Function=null) { this.obj=obj; this.initY=obj.y; this.shakeNum=shakeNum; this.overFunc=overFunc; egret.startTick(this.loop, this); this.num=0; this.flag=0; } privateloop():boolean{ if(this.flag==0) { if(this.obj.y<=this.initY) { this.obj.y+=5; } else{ this.obj.y-=5; } if(this.obj.y==this.initY) { this.num++; if(this.num==this.shakeNum) { egret.stopTick(this.loop, this); if(this.overFunc) { this.overFunc(); } } } } this.flag++; if(this.flag==2) { this.flag=0; } returnfalse; } }
小結
本文通過對一個簡單小游戲進行模塊化的分析,并介紹了模塊化的好處,降低這些模塊之間的耦合度,后期如果要加新功能對舊的代碼無需進行大的修改。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/98106.html
摘要:今天我們分享的菜鳥文檔將介紹微信小游戲好友排行榜的制作過程,包括創建項目并發布微信開發者平臺添加小游戲打開開放域功能主域和開放域通訊,以及與原生的布局。 寫在前面:隨著越來越多的新人開始接觸白鷺引擎,創作屬于自己的游戲。考慮到初學者會遇到一些實際操作問題,我們近期整理推出菜鳥系列技術文檔,以便更好的讓這些開發者們快速上手,Egret大神們可以自動忽略此類內容。 今天我們分享的菜鳥文檔將...
摘要:對的要求基本沒有,都能繪制出來,但是動畫制作方式存在不同,可能某些幀不能完全繪制出來。目前對是有要求的本身必須是個多幀,如果只作為容器嵌套其他子項的做法將不會被繪制。點擊播放按鈕可以預覽動畫,默認幀率為。 Texture Merger 可將零散紋理拼合為整圖,同時也可以解析SWF、GIF動畫,制作Egret位圖文本,導出可供Egret使用的配置文件,其紋理集制作功能在小游戲開發中可以起...
摘要:為避免加載資源時游戲黑屏,導致玩家誤認為游戲非正常運行,界面起到至關重要的作用。今天就為大家帶來用制作頁面及分步加載資源的教程。在中測試分步加載資源,原有的頁面上加上一個按鈕,添加加載資源事件。 我們都知道,當游戲越做越大,資源越來越多的時候,加載資源會造成大量時間的浪費。為避免加載資源時游戲黑屏,導致玩家誤認為游戲非正常運行,Loading界面起到至關重要的作用。今天就為大家帶來用E...
閱讀 2326·2021-11-17 09:33
閱讀 852·2021-10-13 09:40
閱讀 582·2019-08-30 15:54
閱讀 788·2019-08-29 15:38
閱讀 2423·2019-08-28 18:15
閱讀 2481·2019-08-26 13:38
閱讀 1848·2019-08-26 13:36
閱讀 2137·2019-08-26 11:36