Unityを使わずにフリスビーを犬に届けよ!を作ってみるの続きです。今回はPlayerクラスを定義します。
フリスビーを犬に届けよ!の元ネタ
Unishar-ユニシャー【Unityでのゲーム開発を手助けするメディア】
Playerクラスを定義する
1 2 3 4 5 6 |
namespace Frisbee { public class Player { } } |
以降は名前空間を省略して以下のように書きます。
1 2 3 |
public class Player { } |
各種プロパティ
各種プロパティを示します。
ConnectionIdプロパティはAspNetCore.SignalRでサーバーサイドに接続したときに付与されるIDを取得したり設定します。
1 2 3 4 5 6 7 8 |
public class Player { public string ConnectionId { private set; get; } } |
X、Y、VX、VY、AngleプロパティはフリスビーのXY座標、XY方向の速度、左右の傾きの角度を設定したり取得するためのものです。
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 |
public class Player { public double X { private set; get; } public double Y { private set; get; } public double VX { private set; get; } public double VY { private set; get; } public double Angle { private set; get; } } |
Nameプロパティはプレイヤーの名前を設定したり取得するためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Player { string _name = ""; public string Name { set { if (value == "") _name = "名無しさん"; else _name = value; } get { return _name; } } } |
IsDeadプロパティとIsGameOverプロパティはプレイヤーが死亡状態であるかどうか?ゲームオーバー状態であるかどうかを設定したり取得するためのものです。プレイヤー死亡時でなくてもステージクリア時から次のステージが開始されるまでのあいだなどフリスビーを非表示にさせたい場合もIsDeadプロパティがtrueになる場合があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Player { public bool IsDead { private set; get; } public bool IsGameOver { private set; get; } } |
IsXXXKeyDownプロパティはプレイヤーによって方向キーがおされているかどうかを設定したり取得するためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class Player { public bool IsUpKeyDown { set; get; } public bool IsLeftKeyDown { set; get; } public bool IsRightKeyDown { set; get; } } |
コンストラクタと初期化
コンストラクタを示します。
FrisbeeGameクラスのメソッドを呼び出せるように_gameにFrisbeeGameクラスへの参照を格納しています。またスタートしてからゴールするまでの時間を計測したいのでフィールド変数StartTimeも定義しています。スタートボタンを押すまではゲームが開始されないので初期の状態ではIsGameOverプロパティとIsDeadプロパティはtrueにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Player { FrisbeeGame _game; DateTime StartTime = new DateTime(); public Player(FrisbeeGame game) { _game = game; Name = ""; ConnectionId = ""; IsGameOver = true; IsDead = true; } } |
SetConnectionIdメソッドはAspNetCore.SignalRでサーバーサイドに接続したときに付与されるIDをConnectionIdプロパティにセットします。
1 2 3 4 5 6 7 |
public class Player { public void SetConnectionId(string connectionId) { ConnectionId = connectionId; } } |
ゲーム開始時の処理を示します。先述のStartTimeに現在時刻を格納します。
1 2 3 4 5 6 7 8 9 |
public class Player { public void GameStart() { IsGameOver = false; IsDead = false; StartTime = DateTime.Now; } } |
フリスビーをスタート地点に配置するための処理を示します。ミス時にフリスビーをスタート地点に戻せるようにフィールド変数にも引数を格納します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Player { int _startX = 0; int _startY = 0; public void SetStartPosition(int x, int y) { _startX = x; _startY = y; X = x; Y = y; } } |
当たり判定
フリスビーの角にあたる部分の座標を取得します。フリスビーはゲーム中に傾くことがありますが、4つの角の座標のうち、ひとつでも障害物の内部に入ってしまったら障害物にぶつかったことになり、ミスと判断されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Player { public List<Position> GetFrisbeeCorners() { double r = FrisbeeGame.PLAYER_PADIUS; double rad = Math.Atan2(FrisbeeGame.PLAYER_THICKNESS / 2, FrisbeeGame.PLAYER_PADIUS); List<Position> vs = new List<Position>(); vs.Add(new Position((int)(X + r * Math.Cos(Angle + rad)), (int)(Y + r * Math.Sin(Angle + rad)))); vs.Add(new Position((int)(X + r * Math.Cos(Angle - rad)), (int)(Y + r * Math.Sin(Angle - rad)))); vs.Add(new Position((int)(X + r * Math.Cos(Angle + Math.PI + rad)), (int)(Y + r * Math.Sin(Angle + Math.PI + rad)))); vs.Add(new Position((int)(X + r * Math.Cos(Angle + Math.PI - rad)), (int)(Y + r * Math.Sin(Angle + Math.PI - rad)))); return vs; } } |
IsMissメソッドは上記のGetFrisbeeCornersメソッドで4点の座標を取得し、障害物のどれかの内部に存在するかを調べます。
IsStartメソッドはフリスビーの4点がスタート地点にある台の内部にあるかどうかを調べます。フリスビーは重力の影響をうけて下に落下していきますが、スタート地点にあるときはそれ以上下に落下しない(スタート地点の台にめり込まない)ようにしています。
IsGoalメソッドはゴール判定のメソッドですが、フリスビーの4点すべてがゴール領域に入らないとtrueを返さないようにしています。
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 |
public class Player { bool IsMiss() { List<Position> positions = GetFrisbeeCorners(); foreach (Obstacle obstacle in _game.Obstacles) { foreach (Position position in positions) { if (obstacle.IsInside(position.X, position.Y)) return true; } } return false; } bool IsStart() { List<Position> positions = GetFrisbeeCorners(); foreach (Position position in positions) { if (_game.Start.IsInside(position.X, position.Y)) return true; } return false; } bool IsGoal() { List<Position> positions = GetFrisbeeCorners(); foreach (Position position in positions) { if (!_game.Goal.IsInside(position.X, position.Y)) return false; } return true; } } |
プレイ時間の計測
GetElapsedTimeメソッドはゲームが開始されてからの経過時間を返します。
1 2 3 4 5 6 7 |
public class Player { public TimeSpan GetElapsedTime() { return DateTime.Now - StartTime; } } |
更新処理
更新時の処理を示します。
まずプレイヤー死亡時はなにもしません。もし現在位置がスタート地点の内部であればフリスビーをスタート地点の上側に戻します。ゴールしているのであれば星の描画処理をおこない、StageClearedイベントを発生させます。それからステージクリアしたあとはフリスビーを非表示にさせるために死亡フラグをセットします。
それ以外の時でキーが押されているときはフリスビーの方向を変更したり加速処理をおこないます。そのあと現在のXY方向の速度に応じてフリスビーの座標を変更します。そのあと障害物に当たっていないか調べます。ミス時は一定時間が経過するのを待ってからスタート地点に戻してゲームを続行します。
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 |
public class Player { public event EventHandler? DeadEvent; public event EventHandler? StageCleared; public void Update() { if (IsDead) return; // 現在位置がスタート地点の内部であればフリスビーをスタート地点の上に戻す if (IsStart()) { X = _startX; Y = _startY; VX = 0; VY = 0; Angle = 0; return; } // ゴールしているのであればなにもしない if (IsGoal()) { IsDead = true; // 死亡したわけではないけど非表示にするためにIsDead = trueにする _game.SetStars(_game.Goal.CenterX, _game.Goal.CenterY); StageCleared?.Invoke(this, new EventArgs()); return; } // ↑キーが押されているのであればフリスビーが向いている方向に応じて加速処理をおこなう if (IsUpKeyDown) { VY += Math.Cos(Angle) * 0.015 * 20; VX -= Math.Sin(Angle) * 0.015 * 20; } else { VY -= (float)9.8 * 0.015f; } // ← または → キーが押されているのであればフリスビーの傾斜を変更する // ただし±70度まで if (IsLeftKeyDown) { if (Angle < Math.PI / 180 * 70) Angle += Math.PI / 180 * 2; } if (IsRightKeyDown) { if (Angle > -Math.PI / 180 * 70) Angle -= Math.PI / 180 * 2; } // 現在の速度に応じてXY座標を変更する Y += VY; X += VX; // もしミスをしている場合はIsDead = trueにしてから 爆発の火花とイベントを発生させる // 1.5秒経過してからスタート地点にもどってやり直す if (IsMiss()) { DeadEvent?.Invoke(this, new EventArgs()); _game.SetSparks((int)X, (int)Y); System.Timers.Timer timer = new System.Timers.Timer(); timer.Interval = 1500; timer.Elapsed += RecoverFromMiss; timer.Start(); IsDead = true; void RecoverFromMiss(object? sender, EventArgs e) { System.Timers.Timer? t = (System.Timers.Timer?)sender; t?.Stop(); t?.Dispose(); X = _startX; Y = _startY; // 速度と傾斜もリセット VX = 0; VY = 0; Angle = 0; IsDead = false; } } } } |