はじめに
H5ゲームはUnityと比べてコーディングが難しいです。そこで、よく使う機能をまとめたEgret Engineのテンプレートを作成しました。
GitHubにも挙げておきますので、よかったら使ってください。
Egret Template
https://github.com/squmari/EgretTemplate
Egret EngineをできるだけUnityのように使えるコード作成しました。
以下のコードを参照しているので、こちらもごらんください。
参照したコード
準備
1、srcフォルダのMain以外を削除する。
新規プロジェクトを生成すると、いろんなスクリプトが入っていると思います。
Main以外は使わなくても大丈夫ですので消しましょう。
2、Resource/Assetフォルダ内にある画像を削除する。
Button等の画像が入っていますが、デフォルトで使うことはほとんどないと思います。200KB近くあるので消してしまいましょう。
Resource/eui_skinフォルダも同様です。
簡単に使えるUnityライクなテンプレートの作成
Main.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 |
class Main extends eui.UILayer { public constructor() { super(); this.once(egret.Event.ADDED_TO_STAGE, this.addToStage, this); } private addToStage() { GameObject.initial( this.stage ); Util.init(this); Game.init(); egret.startTick(this.tickLoop, this); } tickLoop(timeStamp:number):boolean{ GameObject.update(); return false; } } class Game{ public static height: number; public static width: number; static init() { this.height = egret.MainContext.instance.stage.stageHeight; this.width = egret.MainContext.instance.stage.stageWidth; /* new メソッドを記入*/ new Background(); new Score(); } } class Background extends GameObject{ constructor() { super(); this.shape = new egret.Shape(); this.shape.graphics.beginFill(Util.color(255,255,255)); this.shape.graphics.drawRect(0, 0, Game.width, Game.height); this.shape.graphics.endFill(); GameObject.display.addChild(this.shape); } updateContent() {} } class CreateWorld extends PhysicsObject{ static I : CreateWorld = null; constructor() { super(); CreateWorld.I = this; CreateWorld.world.on("beginContact", this.collision, this); } createWorld(){ CreateWorld.world = new p2.World(); CreateWorld.world.sleepMode = p2.World.BODY_SLEEPING; CreateWorld.world.gravity = [0, 9.8]; } static worldBegin(dt : number) :boolean{ CreateWorld.world.step(1/60, dt/1000, 10); return false; } //コリジョンイベントはここにまとめる private collision(evt : any){ } addDestroyMethod(){CreateWorld.world.clear();} updateContent(){} } |
Util.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 |
// ゲームで便利に使えるUtilityクラス class Util{ public static height: number; public static width: number; public static ui:eui.UILayer; static init( eui:eui.UILayer ) { this.height = eui.stage.stageHeight; this.width = eui.stage.stageWidth; this.ui = eui; } static random(min:number, max:number):number { return min + Math.random() * (max - min); } static randomInt(min:number, max:number):number { return Math.floor( min + Math.random() * (max+0.999 - min) ); } static clamp(value:number, min:number, max:number):number { if( value < min ) value = min; if( value > max ) value = max; return value; } //rgbを16進数へ変換 static color( r:number, g:number, b:number):number { //小数点の切り捨て let r16 = r.toFixed(0); let g16 = g.toFixed(0); let b16 = b.toFixed(0); //16進数へ変換 r16 = r.toString(16); g16 = g.toString(16); b16 = b.toString(16); //r = 0だと r16 =0なので00にするために'00'加算 r16 = ('00' + r16).slice(-2); g16 = ('00' + g16).slice(-2); b16 = ('00' + b16).slice(-2); //色コードへ変換 let code :number = parseInt(("0x" +r16 + g16 + b16), 16) ; return code; } static myText(x:number, y:number, text:string, size:number, ratio:number, color:number, bold:boolean): egret.TextField { let tf :egret.TextField = new egret.TextField(); tf.x = x; tf.y = y; tf.text = text; tf.bold = bold; tf.size = size; tf.scaleX = ratio; tf.scaleY = ratio; tf.textColor = color; return tf; } static myStrokeText(x:number, y:number, text:string, size:number, ratio:number, color:number, font:string, stColor:number, stSize:number): egret.TextField { let tf :egret.TextField = new egret.TextField(); tf.x = x; tf.y = y; tf.scaleX = ratio; tf.scaleY = ratio; tf.textFlow = <Array<egret.ITextElement>>[ {text: text, style: { "textColor": color, "size": size, "fontFamily": font, "strokeColor": stColor, "stroke": stSize, } } ]; return tf; } } |
GameObject.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 |
// UnityのGameObjectライクなタスク管理クラス // update()に毎フレームの処理を書く // オブジェクトを破棄するときはdestroy()を呼ぶ // 破棄のときに後処理が必要なら、addDestroyMethod()に記述 // 生成時の初期化はUnityと違い、constructor()を使う(引数を渡せる) // シーンを切り替えたい場合は transitにシーンロード関数を設定(全オブジェクトを破棄してからtransitを実行) abstract class GameObject { public shape:egret.Shape = null; public static objects: GameObject[] = []; public static display: egret.DisplayObjectContainer; public static transit:()=>void; protected destroyFlag : boolean = false; constructor() { GameObject.objects.push(this); } static initial(displayObjectContainer: egret.DisplayObjectContainer){ GameObject.objects = []; GameObject.display = displayObjectContainer; } abstract updateContent() : void; static update(){ //繰り返しメソッド GameObject.objects.forEach(obj => obj.updateContent()); //destroyFlagがtrueならshapeを削除 GameObject.objects = GameObject.objects.filter( obj =>{ if( obj.destroyFlag ) obj.delete(); return ( !obj.destroyFlag ); } ); if( GameObject.transit ) { GameObject.allDestroy(); GameObject.transit(); GameObject.transit = null; } } static allDestroy(){ GameObject.objects = GameObject.objects.filter( obj => { obj.destroy(); obj.delete(); return false; }); } //オブジェクトを削除 destroy() { this.destroyFlag = true; } //shapeの削除など、destroy後に後処理が必要なら記述 addDestroyMethod(){} private delete(){ this.addDestroyMethod(); if( this.shape ){ GameObject.display.removeChild(this.shape); this.shape = null; } } } abstract class PhysicsObject extends GameObject{ protected body : p2.Body = null; protected bodyShape : p2.Circle | p2.Box = null; protected static world : p2.World = null; constructor(){ super(); } abstract updateContent() : void; addDestroyMethod(){ CreateWorld.world.removeBody(this.body); } } |
Score.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 |
class Score extends GameObject{ static I:Score = null; // singleton instance score:number = 0; bestScore:number = 0; text:egret.TextField = null; textBest:egret.TextField = null; textColor : number = 0x00FF3B; constructor() { super(); this.textColor = Util.color(0,255,0); Score.I = this; this.score = 0; this.text = Util.myText(0, 0, "SCORE : 0", 100, 0.5, this.textColor, true); GameObject.display.addChild( this.text ); let bestScore = window.localStorage.getItem("bestScore"); // string if( bestScore == null ){ bestScore = "0"; window.localStorage.setItem("bestScore", bestScore); } this.bestScore = parseInt( bestScore ); this.textBest = Util.myText(0, 50, "BEST : " + bestScore, 100, 0.5, this.textColor, true); GameObject.display.addChild( this.textBest ); } onDestroy() { GameObject.display.removeChild( this.text ); this.text = null; GameObject.display.removeChild( this.textBest ); this.textBest = null; } updateContent() { this.text.text = "SCORE : " + this.score.toFixed(); if( this.bestScore < this.score ){ this.bestScore = this.score; this.textBest.text = "BEST : " + this.score.toFixed(); } } addScore(){ this.score += 1; } } |
GameOver.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 |
class GameOver extends GameObject{ textGameOver:egret.TextField = null; textScore:egret.TextField = null; textColor : number = 0x00FF3B; constructor() { super(); this.textGameOver = Util.myText(Game.width/2, Game.height/2 - 50, "GAME OVER", 100, 0.5, this.textColor, true); this.textGameOver.anchorOffsetX = this.textGameOver.width/2; this.textGameOver.anchorOffsetY = this.textGameOver.height/2; GameObject.display.addChild( this.textGameOver ); this.textScore = Util.myText(Game.width/2, Game.height/2 + 50, "SCORE : " + Score.I.score, 100, 0.5, this.textColor, true); this.textScore.anchorOffsetX = this.textScore.width/2; this.textScore.anchorOffsetY = this.textScore.height/2; GameObject.display.addChild( this.textScore ); if( Score.I.score >= Score.I.bestScore ){ window.localStorage.setItem("bestScore", Score.I.score.toFixed() ); // string } GameObject.display.once(egret.TouchEvent.TOUCH_TAP, (e: egret.TouchEvent) => this.tap(e), this); } onDestroy() { GameObject.display.removeChild( this.textGameOver ); this.textGameOver = null; GameObject.display.removeChild( this.textScore ); this.textScore = null; } updateContent() { GameObject.display.addChild( this.textGameOver ); GameObject.display.addChild( this.textScore ); } tap(e:egret.TouchEvent){ GameObject.transit = Game.init; this.destroy(); } } |
Ball.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 |
class Ball extends GameObject{ static I:Ball = null; // singleton instance private radius :number =null; constructor(x : number, y:number, radius:number) { super(); //Ball.I = this; this.setShape(x, y, radius); } setShape(x: number, y:number, radius: number){ if( this.shape ){ GameObject.display.removeChild(this.shape); } this.shape = new egret.Shape(); this.shape.x = x; this.shape.y = y; this.shape.graphics.beginFill(0xff0000); this.shape.graphics.drawCircle(0, 0, radius); this.shape.graphics.endFill(); GameObject.display.addChild(this.shape); } updateContent(){ } } abstract class PhysicsBall extends PhysicsObject{ //static I:PhysicsBall = null; // singleton instance private radius :number =null; constructor(x : number, y:number, radius:number) { super(); //PhysicsBall.I = this; this.setBody(x, y, radius); this.setShape(x, y, radius); } private setBody(x: number, y:number, radius: number){ this.body = new p2.Body({mass : 1, position:[x,y]}); this.bodyShape = new p2.Circle({ radius : radius, fixedRotation:true }); this.body.addShape(this.bodyShape); CreateWorld.world.addBody(this.body); } private setShape(x: number, y:number, radius: number){ if( this.shape ){ GameObject.display.removeChild(this.shape); } this.shape = new egret.Shape(); this.shape.x = x; this.shape.y = y; this.shape.graphics.beginFill(0xff0000); this.shape.graphics.drawCircle(0, 0, radius); this.shape.graphics.endFill(); GameObject.display.addChild(this.shape); } } class MyBall extends Ball{ constructor(x : number, y:number, radius:number) { super(x, y, radius); Ball.I = this; } } |
Box.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 |
class Box extends GameObject{ constructor(x : number, y : number, width : number, height : number, color:number) { super(); this.setShape(x, y, width, height, color); } setShape(x : number, y : number, width : number, height : number, color:number){ if( this.shape ){ GameObject.display.removeChild(this.shape); } this.shape = new egret.Shape(); this.shape.x = x; this.shape.y = y; this.shape.graphics.beginFill(color); this.shape.graphics.drawRect(0, 0, width , height); this.shape.graphics.endFill(); GameObject.display.addChild(this.shape); } updateContent(){}; } abstract class PhysicsBox extends PhysicsObject{ protected width :number; protected height :number; protected x : number; protected y : number; protected color : number; constructor(x : number, y : number, width : number, height : number, color:number) { super(); this.x = x; this.y = y; this.width = width ; this.height =height; this.color = color; this.setShape(this.width, this.height); } private setBody(x : number, y : number, width : number, height : number){ this.body = new p2.Body({mass : 1, position:[x,y], type:p2.Body.STATIC}); this.bodyShape = new p2.Box({ width : width, height: height }); this.body.addShape(this.bodyShape); CreateWorld.world.addBody(this.body); } setShape(width: number, height : number){ if( this.shape ){ GameObject.display.removeChild(this.shape); } this.shape = new egret.Shape(); this.shape.anchorOffsetX += width/2; this.shape.anchorOffsetY += height/2; this.shape.x = this.body.position[0]; this.shape.y = this.body.position[1]; this.shape.graphics.beginFill(this.color); this.shape.graphics.drawRect(0, 0, width , height); this.shape.graphics.endFill(); GameObject.display.addChild(this.shape); } } class MyBox extends Box{ constructor(x : number, y : number, width : number, height : number, color:number) { super(x, y, width, height, color); } } |
使い方
オブジェクトのインスタンス化
Main.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Game{ public static height: number; public static width: number; static init() { this.height = egret.MainContext.instance.stage.stageHeight; this.width = egret.MainContext.instance.stage.stageWidth; /* new メソッドを記入*/ new Background(); new Score(); } } |
Main.tsのGameクラスのinit()内にnewしてください。init()はゲームをリセットしたり、リトライするときに使用します。
オブジェクトの削除
GameObject.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 |
abstract class GameObject { public shape:egret.Shape = null; public static objects: GameObject[] = []; public static display: egret.DisplayObjectContainer; public static transit:()=>void; protected destroyFlag : boolean = false; constructor() { GameObject.objects.push(this); } static initial(displayObjectContainer: egret.DisplayObjectContainer){ GameObject.objects = []; GameObject.display = displayObjectContainer; } abstract updateContent() : void; static update(){ //繰り返しメソッド GameObject.objects.forEach(obj => obj.updateContent()); //destroyFlagがtrueならshapeを削除 GameObject.objects = GameObject.objects.filter( obj =>{ if( obj.destroyFlag ) obj.delete(); return ( !obj.destroyFlag ); } ); if( GameObject.transit ) { GameObject.allDestroy(); GameObject.transit(); GameObject.transit = null; } } static allDestroy(){ GameObject.objects = GameObject.objects.filter( obj => { obj.destroy(); obj.delete(); return false; }); } //オブジェクトを削除 destroy() { this.destroyFlag = true; } //shapeの削除など、destroy後に後処理が必要なら記述 addDestroyMethod(){} private delete(){ this.addDestroyMethod(); if( this.shape ){ GameObject.display.removeChild(this.shape); this.shape = null; } } } |
BallもBoxもGameObjectクラスを継承しております。
オブジェクトを削除するときは destroy()メソッドを使用してください。
インスタンス化されたオブジェクトはstatic objectsにpushされてますので、destroyFlagがtrueになると、オブジェクトが削除されます。
なお、GameObjectはabstruct(抽象メソッド)ですので、継承した場合はupdateContent(){}をクラス内に追加してください。
このupdateContent(){}に記入されたメソッドはUnityのupdate()のように繰り返されます。
メソッドの連続実行
Main.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Main extends eui.UILayer { public constructor() { super(); this.once(egret.Event.ADDED_TO_STAGE, this.addToStage, this); } private addToStage() { GameObject.initial( this.stage ); Util.init(this); Game.init(); egret.startTick(this.tickLoop, this); } tickLoop(timeStamp:number):boolean{ GameObject.update(); return false; } } |
GameObject.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static update(){ //繰り返しメソッド GameObject.objects.forEach(obj => obj.updateContent()); //destroyFlagがtrueならshapeを削除 GameObject.objects = GameObject.objects.filter( obj =>{ if( obj.destroyFlag ) obj.delete(); return ( !obj.destroyFlag ); } ); if( GameObject.transit ) { GameObject.allDestroy(); GameObject.transit(); GameObject.transit = null; } } |
Unityのupdate関数のように、常にメソッドを連続実行したい場合は、BallやBox内のupdateContent(){}に関数を記載してください。
GameObject.update()がMainでtickLoopされて、フレームレート60で繰り返されます。
GameObjectを継承した場合は、updateContent(){}を追加しないとエラーになりますので、繰り返しをしない場合でも空メソッドとして追記してください。
シーンの切り替え
GameOver.ts
1 2 3 4 |
tap(e:egret.TouchEvent){ GameObject.transit = Game.init; this.destroy(); } |
GameObject.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static update(){ //繰り返しメソッド GameObject.objects.forEach(obj => obj.updateContent()); //destroyFlagがtrueならshapeを削除 GameObject.objects = GameObject.objects.filter( obj =>{ if( obj.destroyFlag ) obj.delete(); return ( !obj.destroyFlag ); } ); if( GameObject.transit ) { GameObject.allDestroy(); GameObject.transit(); GameObject.transit = null; } } |
シーンを切り替えるときはGameObject.transit = 関数; のように利用します。
allDestroyでGameObject系を全て削除した後、transitに代入した関数が実行されます。
リトライするときはinit()を代入するといいでしょう。
HTML5 Egret Engine 入門へ戻る