今回は目覚まし時計を線路プレートの上で移動させます。
動作確認は こちらから
移動させるときの考え方はC# WinndowsFormsで作成したチクタクバンバンのようなゲームをつくるとほとんど同じです。
まず目覚まし時計がどのプレートの上にいるかを記憶しておきます。そしてプレートから別のプレートまで移動する時間を決めておけば目覚まし時計がどのプレートのどの位置にいるかを特定することができます。
最初に目覚まし時計がプレート上をどこからどこへ移動するかを表す列挙体を定義します。
Contents
DirectOfMove列挙体
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | enum DirectOfMove {     NorthToWest,     NorthToSouth,     NorthToEast,     WestToNorth,     WestToSouth,     WestToEast,     SouthToNorth,     SouthToWest,     SouthToEast,     EastToNorth,     EastToWest,     EastToSouth,     None, } | 
AlarmClockクラス
次にAlarmClockクラスを作成します。まずプロパティとコンストラクタを示します。
初期化
コンストラクタの第一引数は時計を表示させるでCOLLADA ファイル (.dae)から読み取って生成されたものです。第二引数は最初はどのプレートの上にあるかを示すもので、ここから初期状態における移動方向を求めます。
| 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 | class AlarmClock {     Model = null;     // 目覚まし時計はどのプレートの上にあるか?     TrackPlate: TrackPlate = null;     // 目覚まし時計はプレートの上でどの方向からどの方向へ移動するか?     DirectOfMove: DirectOfMove = DirectOfMove.NorthToSouth;     // 目覚まし時計が新しいプレートの上に移動してから何回移動したか?     ElapsedTimeAfterEntering: number = 0;     // ElapsedTimeAfterEnteringの最大値     static ElapsedTimeAfterEnteringMax: number = 120;     constructor(model, trackPlate: TrackPlate) {         this.TrackPlate = trackPlate;         this.Model = model;         this.Model.position.x = trackPlate.Group.position.x;         this.Model.position.z = trackPlate.Group.position.z;         // 最初にどのプレートの上にあるかを調べて初期状態における移動方向を調べる         if (trackPlate.PlateType == PlateType.NS || trackPlate.PlateType == PlateType.NSWE)             this.DirectOfMove = DirectOfMove.NorthToSouth;         if (trackPlate.PlateType == PlateType.NW || trackPlate.PlateType == PlateType.NWSE)             this.DirectOfMove = DirectOfMove.NorthToWest;         if (trackPlate.PlateType == PlateType.NE || trackPlate.PlateType == PlateType.NESW)             this.DirectOfMove = DirectOfMove.NorthToEast;         if (trackPlate.PlateType == PlateType.SE)             this.DirectOfMove = DirectOfMove.EastToSouth;         if (trackPlate.PlateType == PlateType.SW)             this.DirectOfMove = DirectOfMove.WestToSouth;         if (trackPlate.PlateType == PlateType.WE)             this.DirectOfMove = DirectOfMove.WestToEast;         // 南向き(こちらに文字盤が向く方向)にセット         // ゲーム開始と同時に適切な方向に向くようになる         this.Model.rotation.z = Math.PI / 2 * 3;         scene.add(model);     } } | 
目覚まし時計を移動させる処理
目覚まし時計を移動させる関数を示します。目覚まし時計がどの方向に進行しようとしているかを調べて適切な関数を呼びます。
| 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 | class AlarmClock {     Move() {         this.ElapsedTimeAfterEntering++;         if (this.DirectOfMove == DirectOfMove.NorthToSouth)             this.MoveNorthToSouth();         else if (this.DirectOfMove == DirectOfMove.WestToSouth)             this.MoveWestToSouth();         else if (this.DirectOfMove == DirectOfMove.EastToSouth)             this.MoveEastToSouth();         else if (this.DirectOfMove == DirectOfMove.NorthToWest)             this.MoveNorthToWest();         else if (this.DirectOfMove == DirectOfMove.EastToWest)             this.MoveEastToWest();         else if (this.DirectOfMove == DirectOfMove.SouthToWest)             this.MoveSouthToWest();         else if (this.DirectOfMove == DirectOfMove.SouthToNorth)             this.MoveSouthToNorth();         else if (this.DirectOfMove == DirectOfMove.WestToNorth)             this.MoveWestToNorth();         else if (this.DirectOfMove == DirectOfMove.EastToNorth)             this.MoveEastToNorth();         else if (this.DirectOfMove == DirectOfMove.NorthToEast)             this.MoveNorthToEast();         else if (this.DirectOfMove == DirectOfMove.WestToEast)             this.MoveWestToEast();         else if (this.DirectOfMove == DirectOfMove.SouthToEast)             this.MoveSouthToEast();     } } | 
まっすぐに移動させる
これは目覚まし時計が西から東に移動するときの処理です。目覚まし時計がプレート内に入ってから移動した回数とプレートが存在する座標から目覚まし時計の位置を求めています。また進行方向に目覚まし時計の正面が向くようにrotation.zの値を調整しています。西から東とか北から南とまっすぐ進行する場合はそれほど難しくありません。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | class AlarmClock {     MoveWestToEast() {         let count = this.ElapsedTimeAfterEntering - AlarmClock.ElapsedTimeAfterEnteringMax / 2;         this.Model.position.x = this.TrackPlate.Group.position.x;         this.Model.position.z =             this.TrackPlate.Group.position.z + TrackPlate.Size * count / AlarmClock.ElapsedTimeAfterEnteringMax;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingEast(); // プレートの外へ出たときの処理(後述)         this.Model.rotation.z = 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 25 26 27 28 29 30 31 32 33 34 35 36 37 | class AlarmClock {     MoveEastToWest() {         let count = this.ElapsedTimeAfterEntering - AlarmClock.ElapsedTimeAfterEnteringMax / 2;         this.Model.position.x = this.TrackPlate.Group.position.x;         this.Model.position.z =             this.TrackPlate.Group.position.z - TrackPlate.Size * count / AlarmClock.ElapsedTimeAfterEnteringMax;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingWest();         this.Model.rotation.z = Math.PI;     }     MoveNorthToSouth() {         let count = this.ElapsedTimeAfterEntering - AlarmClock.ElapsedTimeAfterEnteringMax / 2;         this.Model.position.z = this.TrackPlate.Group.position.z;         this.Model.position.x =             this.TrackPlate.Group.position.x - TrackPlate.Size * count / AlarmClock.ElapsedTimeAfterEnteringMax;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingSouth();         this.Model.rotation.z = - Math.PI / 2;     }     MoveSouthToNorth() {         let count = this.ElapsedTimeAfterEntering - AlarmClock.ElapsedTimeAfterEnteringMax / 2;         this.Model.position.z = this.TrackPlate.Group.position.z;         this.Model.position.x =             this.TrackPlate.Group.position.x + TrackPlate.Size * count / AlarmClock.ElapsedTimeAfterEnteringMax;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingNorth();         this.Model.rotation.z = Math.PI / 2;     } } | 
カーブを描いて移動させる
次にプレートの上でカーブする移動の場合を考えます。これは北から東に移動するときの処理です。三角関数を使えばうまくできます。
| 1 2 3 4 5 6 7 8 9 10 11 12 | class AlarmClock {     MoveNorthToEast() {         let rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax;         let rad = Math.PI + rad0 * this.ElapsedTimeAfterEntering;         this.Model.position.z = this.TrackPlate.Group.position.z + TrackPlate.Size / 2 * Math.cos(rad) + TrackPlate.Size / 2;         this.Model.position.x = this.TrackPlate.Group.position.x + TrackPlate.Size / 2 * Math.sin(rad) + TrackPlate.Size / 2;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingEast();         this.Model.rotation.z = rad + Math.PI / 2;     } } | 
同様に他の方向へ移動する場合の処理も示します。
| 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 | class AlarmClock {     MoveNorthToWest() {         let radius = TrackPlate.Size / 2;         let rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax;         let rad = - rad0 * this.ElapsedTimeAfterEntering;         this.Model.position.z = this.TrackPlate.Group.position.z + radius * Math.cos(rad) - radius;         this.Model.position.x = this.TrackPlate.Group.position.x + radius * Math.sin(rad) + radius;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingWest();         this.Model.rotation.z = rad - Math.PI / 2;     }     MoveSouthToEast() {         let radius = TrackPlate.Size / 2;         let rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax;         let rad = Math.PI - rad0 * this.ElapsedTimeAfterEntering;         this.Model.position.z = this.TrackPlate.Group.position.z + radius * Math.cos(rad) + radius;         this.Model.position.x = this.TrackPlate.Group.position.x + radius * Math.sin(rad) - radius;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingEast();         this.Model.rotation.z = rad - Math.PI / 2;     }     MoveSouthToWest() {         let radius = TrackPlate.Size / 2;         let rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax;         let rad = rad0 * this.ElapsedTimeAfterEntering;         this.Model.position.z = this.TrackPlate.Group.position.z + radius * Math.cos(rad) - radius;         this.Model.position.x = this.TrackPlate.Group.position.x + radius * Math.sin(rad) - radius;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingWest();         this.Model.rotation.z = rad + Math.PI / 2;     }     MoveEastToNorth() {         let radius = TrackPlate.Size / 2;         let rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax;         let rad = Math.PI / 2 * 3 - rad0 * this.ElapsedTimeAfterEntering;         this.Model.position.z = this.TrackPlate.Group.position.z + radius * Math.cos(rad) + radius;         this.Model.position.x = this.TrackPlate.Group.position.x + radius * Math.sin(rad) + radius;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingNorth();         this.Model.rotation.z = rad - Math.PI / 2;     }     MoveEastToSouth() {         let radius = TrackPlate.Size / 2;         let rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax;         let rad = Math.PI / 2 + rad0 * this.ElapsedTimeAfterEntering;         this.Model.position.z = this.TrackPlate.Group.position.z + radius * Math.cos(rad) + radius;         this.Model.position.x = this.TrackPlate.Group.position.x + radius * Math.sin(rad) - radius;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingSouth();         this.Model.rotation.z = rad + Math.PI / 2;     }     MoveWestToNorth() {         let radius = TrackPlate.Size / 2;         let rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax;         let rad = Math.PI / 2 * 3 + rad0 * this.ElapsedTimeAfterEntering;         this.Model.position.z = this.TrackPlate.Group.position.z + radius * Math.cos(rad) - radius;         this.Model.position.x = this.TrackPlate.Group.position.x + radius * Math.sin(rad) + radius;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingNorth();         this.Model.rotation.z = rad + Math.PI / 2;     }     MoveWestToSouth() {         let radius = TrackPlate.Size / 2;         let rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax;         let rad = Math.PI / 2 - rad0 * this.ElapsedTimeAfterEntering;         this.Model.position.z = this.TrackPlate.Group.position.z + radius * Math.cos(rad) - radius;         this.Model.position.x = this.TrackPlate.Group.position.x + radius * Math.sin(rad) - radius;         if (this.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax)             this.LeavingSouth();         this.Model.rotation.z = rad - Math.PI / 2;     } } | 
別のプレートに移動する処理
次に現在のプレートから別のプレートに移動する処理を考えます。まず進行方向からみて隣にあるプレートが存在するかどうかを調べます。存在する場合、移動可能なプレートかを調べます。移動可能であれば新しくAlarmClock.TrackPlateにそのプレートをセットし、どの方向に移動するかをAlarmClock.DirectOfMoveにセットします。
次のプレートが存在しない場合、存在するが移動可能でない場合はゲームオーバーです。このゲームは残機制ではなく一発ゲームオーバーとします。
これは北からプレートの外に出たときの処理をする関数です。
| 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 | class AlarmClock {     LeavingNorth() {         // 北側から出たので、その上(北)にある線路プレートを取得する         let nextPlate = TrackPlate.GetNorthPlate(this.TrackPlate);         // nextPlate == nullならゲームオーバー。         if (nextPlate != null) {             this.TrackPlate = nextPlate;             this.ElapsedTimeAfterEntering = 0;             // nextPlateのPlateTypeから次はどの方向に移動するかを調べる             if (nextPlate.PlateType == PlateType.NS || nextPlate.PlateType == PlateType.NSWE)                 this.DirectOfMove = DirectOfMove.SouthToNorth;             else if (nextPlate.PlateType == PlateType.SW || nextPlate.PlateType == PlateType.NESW)                 this.DirectOfMove = DirectOfMove.SouthToWest;             else if (nextPlate.PlateType == PlateType.SE || nextPlate.PlateType == PlateType.NWSE)                 this.DirectOfMove = DirectOfMove.SouthToEast;             else                 this.GameOver();                 // どの方向にも移動できない場合はゲームオーバー。         }         else             this.GameOver();     } } | 
同様に南、東、西から外へ出た場合についても示します。
| 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 | class AlarmClock {     LeavingSouth() {         let nextPlate = TrackPlate.GetSouthPlate(this.TrackPlate);         if (nextPlate != null) {             this.TrackPlate = nextPlate;             this.ElapsedTimeAfterEntering = 0;             if (nextPlate.PlateType == PlateType.NS || nextPlate.PlateType == PlateType.NSWE)                 this.DirectOfMove = DirectOfMove.NorthToSouth;             else if (nextPlate.PlateType == PlateType.NE || nextPlate.PlateType == PlateType.NESW)                 this.DirectOfMove = DirectOfMove.NorthToEast;             else if (nextPlate.PlateType == PlateType.NW || nextPlate.PlateType == PlateType.NWSE)                 this.DirectOfMove = DirectOfMove.NorthToWest;             else                 this.GameOver();         }         else             this.GameOver();     }     LeavingEast() {         let nextPlate = TrackPlate.GetEastPlate(this.TrackPlate);         if (nextPlate != null) {             this.TrackPlate = nextPlate;             this.ElapsedTimeAfterEntering = 0;             if (nextPlate.PlateType == PlateType.NW || nextPlate.PlateType == PlateType.NWSE)                 this.DirectOfMove = DirectOfMove.WestToNorth;             else if (nextPlate.PlateType == PlateType.WE || nextPlate.PlateType == PlateType.NSWE)                 this.DirectOfMove = DirectOfMove.WestToEast;             else if (nextPlate.PlateType == PlateType.SW || nextPlate.PlateType == PlateType.NESW)                 this.DirectOfMove = DirectOfMove.WestToSouth;             else                 this.GameOver();         }         else             this.GameOver();     }     LeavingWest() {         let nextPlate = TrackPlate.GetWestPlate(this.TrackPlate);         if (nextPlate != null) {             this.TrackPlate = nextPlate;             this.ElapsedTimeAfterEntering = 0;             if (nextPlate.PlateType == PlateType.NE || nextPlate.PlateType == PlateType.NESW)                 this.DirectOfMove = DirectOfMove.EastToNorth;             else if (nextPlate.PlateType == PlateType.WE || nextPlate.PlateType == PlateType.NSWE)                 this.DirectOfMove = DirectOfMove.EastToWest;             else if (nextPlate.PlateType == PlateType.SE || nextPlate.PlateType == PlateType.NWSE)                 this.DirectOfMove = DirectOfMove.EastToSouth;             else                 this.GameOver();         }         else             this.GameOver();     } } | 
ゲームオーバー時の処理
ゲームオーバーになったときの処理を示します。AlarmClock.DirectOfMoveにDirectOfMove.Noneをセットします。これでMove関数が実行されても目覚まし時計は動かなくなります。
| 1 2 3 4 5 | class AlarmClock {     GameOver() {         this.DirectOfMove = DirectOfMove.None;     } } | 
動作確認は こちらから
