ゲームの仕様とCircle, Bullet, Foodクラスの定義 クソゲーに魂を!プロジェクト(1)の続きです。今回はPlayerクラスを定義します。
インデントが深くなるので名前空間部分は省略して表記します。
1 2 3 4 5 6 |
namespace FireSnake { public Player(string connectionId) { } } |
1 2 3 |
public class Player { } |
プロパティとフィールド変数
プロパティとフィールド変数を以下のように定義します。プレイヤーの身体を構成するCircleオブジェクトを格納するための配列デックを使っていますが、これは両端キューを実装するで解説しているものと同じものです。
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 |
public class Player { static long _nextPlayerID = 1; // プレイヤーに付与するIDを生成するための整数 static Random _random = new Random(); public string ConnectionId { set; get; } // ASP.NET SignalRで使われる一意の接続ID public long PlayerID { set; get; } // プレイヤーを区別するための一意のID public string PlayerName { private set; get; } // プレイヤーの名前 public string PlayerShortName { private set; get; } // プレイヤーの短い名前(半角16文字) // プレイヤーの身体を構成するCircleオブジェクトを格納するための配列デック public ArrayDeque<Circle> Circles = new ArrayDeque<Circle>(128, 128); public double HeadX { private set; get; } // プレイヤーの頭部の座標 public double HeadY { private set; get; } public double Length { set; get; } // プレイヤーの長さ public int KillCount { set; get; } // 倒したプレイヤーの数 public int TotalScore { set; get; } // 総スコア public int Score { set; get; } // そのステージで獲得した点数 public int JoinInCount { set; get; } // ゲームに参加した回数 public int VictoryCount { set; get; } // 優勝した回数 public int Radius { private set; get; } // Circleオブジェクトの半径 public double Angle { set; get; } // 進行方向(単位はラジアン) public int NpcTurnLeftCount { set; get; } // 0以上であれば更新時に左に旋回する public int NpcTurnRightCount { set; get; } // 右に旋回する public bool IsDead { set; get; } // 死亡フラグ public int DeadCount { set; get; } // 同じステージで何回死亡したか? public int NoHitCheckValue { set; get; } bool IsTurnLeft = false; // ユーザーは左に旋回しようとしている bool IsTurnRight = false; // 右に旋回しようとしている bool EnableAngleByMouse = false; // ユーザーはマウスで操作しようとしている double AngleByMouse = 0; // マウスで設定しようとしている進行方向 long UpdateCount = 0; // 更新回数 int TimeForShot = 0; // 次に弾丸を発射できるようになるまでの更新回数 // NPCの回避行動率(あまり的確に回避行動をされると倒せないので 0 - 100 でばらつきをもたせる) public int Intelligence { set; get; } } |
コンストラクタと初期化
コンストラクタを示します。引数はASP.NET SignalRで使われる一意の接続IDです。これが空文字列の場合、それはNPCです。NPCのときはPlayerIDをつかってNPCの名前をつけます。ユーザーであれば後述するSetPlayerNameメソッドで名前をつけます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Player { public Player(string connectionId) { PlayerID = _nextPlayerID++; ConnectionId = connectionId; // connectionId == "" ならNPCなのでNPCの名前をつける if (connectionId == "") { PlayerName = "NPC " + PlayerID; PlayerShortName = PlayerName; } } } |
Playerオブジェクトを生成したら初期化の処理をおこないます。具体的には初期位置と初期の進行方向を設定し、各フィールド変数とプロパティに適切な初期値を設定します。
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 |
public class Player { public void Init(int x, int y, double angle) { // 変数・フラグ等のリセット Length = Constant.PLAYER_INIT_LENGTH; UpdateCount = 0; IsTurnLeft = false; IsTurnRight = false; NpcTurnLeftCount = 0; NpcTurnRightCount = 0; // 頭部の初期座標と太さを設定する HeadX = x; HeadY = y; Radius = (int)Math.Min(Math.Log10(this.Length * 2) * 4, 16); Angle = angle; // 頭部のCircleオブジェクトを生成して追加する Circles.AddFirst(new Circle(this, x, y)); Angle = 1.0 * _random.Next(62800) / 10000; NoHitCheckValue = Constant.INIT_NO_HITCHECK_VALUE; JoinInCount++; // ゲームへの参加回数 Score = 0; IsDead = false; } } |
SetCenterメソッドはプレイヤーの座標を中央に設定します。
1 2 3 4 5 6 7 8 |
public class Player { public void SetCenter() { HeadX = Game.CenterX; HeadY = Game.CenterY; } } |
プレイヤーに名前をつける
プレイヤーに名前をつける処理を示します。引数を16文字以内に短くしてPlayerNameプロパティに格納しますが、GetShortNameメソッドはさらにそれを半角16~17文字以下にします(全角1文字は半角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 |
public class Player { public void SetPlayerName(string playerName) { PlayerName = playerName.Replace("\t", "").Replace("\r", "").Replace("\n", ""); if (PlayerName.Length > 16) PlayerName = PlayerName.Substring(0, 16); PlayerShortName = GetShortName(PlayerName); } string GetShortName(string str) { string shortName = ""; int count = 0; char[] arr = str.ToArray(); foreach (char c in arr) { if ((int)c < 128) { shortName += c; count += 1; } else { shortName += c; count += 2; } if (count >= 16) break; } return shortName; } } |
方向転換と更新処理
方向転換の処理を示します。方向キーが押下状態が変化したときはマウス操作による方向転換を無効にしてIsTurnLeftまたはIsTurnRightを変更します。これによって後述するUpdateメソッドで回頭処理がおこなわれます。またマウス操作がされたときはマウス操作による方向転換を有効にしてAngleByMouseに進行方向をセットします。この場合もUpdateメソッドで回頭処理がおこなわれます。
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 |
public class Player { public void TurnLeft(bool b) { IsTurnLeft = b; EnableAngleByMouse = false; } public void TurnRight(bool b) { IsTurnRight = b; EnableAngleByMouse = false; } public void TurnByMouse(double angle) { AngleByMouse = angle; EnableAngleByMouse = true; if (AngleByMouse < 0) AngleByMouse += Math.PI * 2; else if (AngleByMouse >= Math.PI * 2) AngleByMouse -= Math.PI * 2; } } |
更新の処理を示します。
IsDead == true であったり Circles.Length == 0 ならなにもしません。そうでない場合はNPCでなければ IsTurnRight == true なら Angle を増加させ、 IsTurnLeft == true なら減少させます。また EnableAngleByMouse == true なら Angle の値を AngleByMouse に近づけます。
NPCのときは NpcTurnRightCount NpcTurnLeftCount が 0 でなければ Angle の値を増減させます。いずれも 0 のときは 1秒毎に左右にクネクネと移動させる処理をおこないます。
そのあとCircles[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 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 |
public class Player { public void Update() { if (IsDead || Circles.Length == 0) return; UpdateCount++; TimeForShot--; if (NoHitCheckValue > 0) NoHitCheckValue--; double oldAngle = Angle; if (this.ConnectionId != "") { if (IsTurnRight) Angle += Constant.TURN_ANGLE_PAR_UPDATE; if (IsTurnLeft) Angle -= Constant.TURN_ANGLE_PAR_UPDATE; if (EnableAngleByMouse) { if (Math.Abs(Angle - AngleByMouse) <= Math.PI) { if (Angle < AngleByMouse) Angle += Constant.TURN_ANGLE_PAR_UPDATE; else Angle -= Constant.TURN_ANGLE_PAR_UPDATE; } else { if (Angle < AngleByMouse) Angle -= Constant.TURN_ANGLE_PAR_UPDATE; else Angle += Constant.TURN_ANGLE_PAR_UPDATE; } } } else { if (NpcTurnRightCount > 0) { NpcTurnRightCount--; Angle += Constant.TURN_ANGLE_PAR_UPDATE; } else if (NpcTurnLeftCount > 0) { NpcTurnLeftCount--; Angle -= Constant.TURN_ANGLE_PAR_UPDATE; } else { if (1 < UpdateCount % 120 && UpdateCount % 120 < 20) this.Angle -= Constant.TURN_ANGLE_PAR_UPDATE; if (61 < UpdateCount % 120 && UpdateCount % 120 < 80) this.Angle += Constant.TURN_ANGLE_PAR_UPDATE; } } int increase = 0; if (oldAngle < Angle) increase = 1; if (oldAngle > Angle) increase = -1; if (Angle < 0) Angle += Math.PI * 2; else if (Angle >= Math.PI * 2) Angle -= Math.PI * 2; Move(); Circle? circle0 = Circles[0]; Circle? circle1 = Circles[1]; if (circle0 != null && circle1 != null) { circle0.RotateCount = circle1.RotateCount + increase; circle0.PlayerLength = (int)Length; circle0.Score = TotalScore; circle0.KillCount = KillCount; } } } |
移動の処理を示します。進行方向から新しい頭部の座標を計算してその位置にCiecleオブジェクトを生成します。そして配列デックの先頭に追加します。このとき配列デックの長さがプレイヤーの長さを超えたらプレイヤーの長さと同じになるまで配列デックの最後尾からCiecleオブジェクトを取り除きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Player { void Move() { if (IsDead || Circles.First == null) return; HeadX += Constant.PLAYER_SPEED * Math.Cos(Angle); HeadY += Constant.PLAYER_SPEED * Math.Sin(Angle); Length = Math.Max(Length, Constant.PLAYER_MIN_LENGTH); Length = Math.Min(Length, 800); Radius = (int)Math.Min(Math.Log10(Length * 2) * 4, 16); Circle head = new Circle(this, HeadX, HeadY); Circles.AddFirst(head); while (Circles.Length > Length) Circles.RemoveLast(); } } |
弾丸の発射
弾丸を発射する処理を示します。
体長が最小値になっているときやTimeForShotが0より大きいときは弾丸は発射できません。またNPCは当たり判定無効時は弾丸を発射しません。
弾丸は自機の前後から自機が向いている方向と逆方向に発射されます。
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 |
public class Player { public bool Shot(List<Bullet> bullets) { if (IsDead || TimeForShot > 0 || Length <= Constant.PLAYER_MIN_LENGTH) return false; if (ConnectionId == "" && NoHitCheckValue > 0) return false; if (this.Circles.Length > 2) { int len = Circles.Length; Circle? first = Circles[0]; if (first == null) return false; bullets.Add(new Bullet(PlayerID, first.X, first.Y, Angle, 1)); Circle? last = Circles.Last; if (last == null) return false; bullets.Add(new Bullet(PlayerID, last.X, last.Y, Angle + Math.PI, 2)); Length = Math.Max(Length - 1, Constant.PLAYER_MIN_LENGTH); if (ConnectionId != "") TimeForShot = Constant.SHOT_INTERVAL; else TimeForShot = Constant.SHOT_INTERVAL * 4 + _random.Next(30); return true; } return false; } } |