ついにこのブログも800回更新を達成しました。パチパチパチ。ところが次に何をつくっていいかわからない。困っていたところ面白そうな動画をみつけました。
以下はネタ元の「フリスビーを犬に届けよ!」の動画です。「フリスビーを犬に届けよ!」はUnityでのゲーム開発を手助けするメディア「Unishar-ユニシャー」のさぎのみや氏作成のゲームのチュートリアルです。
フリスビーを犬に届けよ!の元ネタ
Unishar-ユニシャー【Unityでのゲーム開発を手助けするメディア】
今回はUnityを使わずにフリスビーを犬に届けよ!に似たゲームを作成しました。
それからいつもお世話になっているT.Umezawaさんにも ダメだし レビューしていただきました。
動画ばっかりベタベタ貼ってすみません。ではさっそく作成してみましょう。名前空間はFrisbeeとします。
Contents
Obstacleクラスを定義する
障害物の位置を格納するObstacleクラスを定義します。
1 2 3 4 5 6 |
namespace Frisbee { public class Obstacle { } } |
以降は名前空間を省略して以下のように書きます。
1 2 3 |
public class Obstacle { } |
コンストラクタを示します。
1 2 3 4 5 6 7 8 9 10 |
public class Obstacle { public Obstacle(int centerX, int centerY, int width, int height) { CenterX = centerX; CenterY = centerY; Width = width; Height = height; } } |
ブロックの中心のXY座標となるCenterX、CenterYプロパティを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Obstacle { public int CenterX { private set; get; } public int CenterY { private set; get; } } |
ブロックの幅、高さをWidth、Heightプロパティとして定義します。またブロックの上下左右の座標がすぐにわかるようにLeft、Right、Top、Bottomプロパティも定義します。
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 |
public class Obstacle { public int Width { private set; get; } public int Height { private set; get; } public int Left { get { return CenterX - Width / 2; } } public int Right { get { return CenterX + Width / 2; } } public int Top { get { return CenterY + Height / 2; } } public int Bottom { get { return CenterY - Height / 2; } } } |
IsInsideメソッドは引数で渡された座標がブロックの内部にあるかどうかを返します。
1 2 3 4 5 6 7 8 9 10 |
public class Obstacle { public bool IsInside(int x, int y) { if (Left < x && x < Right && Bottom < y && y < Top) return true; else return false; } } |
FrisbeeGameクラスを定義する
FrisbeeGameクラスを定義します。
1 2 3 4 5 6 |
namespace Frisbee { public class FrisbeeGame { } } |
以降は名前空間を省略して以下のように書きます。
1 2 3 |
public class FrisbeeGame { } |
定数
まず定数について定義している部分を示します。
1 2 3 4 5 6 7 8 9 10 |
public class FrisbeeGame { public const int BLOCK_SIZE = 32; // 障害物のブロックの大きさ public const int UPDATES_PER_SECOND = 24; // 1秒あたりの更新回数 public const int SPARK_LIFE_MAX = UPDATES_PER_SECOND / 2; // ミス時に表示される火花の表示時間 public const int PLAYER_PADIUS = 20; // フリスビーの半径と厚さ public const int PLAYER_THICKNESS = 8; } |
プロパティ
各種プロパティを示します。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 31 32 33 34 35 36 37 |
public class FrisbeeGame { // プレイヤー public Player Player { private set; get; } // 障害物(これにぶつかったらミス) public List<Obstacle> Obstacles { private set; get; } // スタート地点(ブロックで構成されている) public Obstacle Start { private set; get; } // ゴール地点(このなかにプレイヤーが入ればゴール。これも表示されないブロックで構成されている) public Obstacle Goal { private set; get; } // フィールドとして表示すべき横幅の最大はピクセル分か? public int MaxX { private set; get; } } |
コンストラクタと初期化
コンストラクタと初期化の処理を示します。
コンストラクタのなかでPlayerオブジェクトを生成したあとInitメソッドを呼び出して障害物のリストとスタート地点、ゴール地点をつくります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class FrisbeeGame { public FrisbeeGame() { Player = new Player(this); // 「コンストラクタが終了したときにnullであってはならない」という警告が出るので // 警告を消すためになにか入れておく // Initメソッドが実行されたらすべて非nullになるのでなくてもよいと言われればない Obstacles = new List<Obstacle>(); Start = new Obstacle(this, 0, 0, 0, 0); Goal = new Obstacle(this, 0, 0, 0, 0); Init(); } } |
Initメソッドを示します。
ここではリソースからテキストファイルを読み込み、ゲームのためのフィールド(スタート地点とゴール地点と障害物)を生成しています。そのあとフリスビーをスタート地点に設置します。
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 |
public class FrisbeeGame { public void Init() { Obstacles = new List<Obstacle>(); string fieldText = Zero.Properties.Resources.frisbee_stage_1; string[] vs = fieldText.Replace("\r", "").Split('\n'); int colMax = vs[0].Length; vs = vs.Where(s => s.Length == colMax).ToArray(); vs = vs.Reverse().ToArray(); int rowMax = vs.Length; List<Obstacle> starts = new List<Obstacle>(); List<Obstacle> goals = new List<Obstacle>(); for (int row = 0; row < rowMax; row++) { char[] chars = vs[row].ToArray(); for (int col = 0; col < colMax; col++) { if (chars[col] == '#') Obstacles.Add(new Obstacle(this, BLOCK_SIZE * col, BLOCK_SIZE * row, BLOCK_SIZE, BLOCK_SIZE)); if (chars[col] == 'S') starts.Add(new Obstacle(this, BLOCK_SIZE * col, BLOCK_SIZE * row, BLOCK_SIZE, BLOCK_SIZE)); if (chars[col] == 'G') goals.Add(new Obstacle(this, BLOCK_SIZE * col, BLOCK_SIZE * row, BLOCK_SIZE, BLOCK_SIZE)); } } int startCenterX = (int)starts.Average(o => o.CenterX); int startCenterY = (int)starts.Average(o => o.CenterY); int startWidth = (int)starts.Max(o => o.Right) - (int)starts.Min(o => o.Left); int startHeight = (int)starts.Max(o => o.Top) - (int)starts.Min(o => o.Bottom); Start = new Obstacle(this, startCenterX, startCenterY, startWidth, startHeight); int goalCenterX = (int)goals.Average(o => o.CenterX); int goalCenterY = (int)goals.Average(o => o.CenterY); int goalWidth = (int)goals.Max(o => o.Right) - (int)goals.Min(o => o.Left); int goalHeight = (int)goals.Max(o => o.Top) - (int)goals.Min(o => o.Bottom); Goal = new Obstacle(this, goalCenterX, goalCenterY, goalWidth, goalHeight); // フリスビーの中心の初期y座標はスタート地点にあるブロックの上面のy座標+フリスビーの厚さの半分 Player.SetStartPosition(startCenterX, starts.Max(o => o.Top) + PLAYER_THICKNESS / 2); // BLOCK_SIZE * colMaxまで表示する MaxX = BLOCK_SIZE * colMax; } } |
ミス時に火花を発生させる
ミス時に表示される火花を発生させる処理を示します。
最初にSparkクラスを定義します。発生したあと更新処理がおこなわれるごとに座標を変化させるとともにLifeがデクリメントされていきます。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 |
namespace Frisbee { public class Spark { public Spark(int x, int y, int vx, int vy) { Life = FrisbeeGame.SPARK_LIFE_MAX; X = x; Y = y; VelocityX = vx; VelocityY = vy; } public int X { private set; get; } public int Y { private set; get; } int VelocityX { get; } int VelocityY { get; } public int Life { private set; get; } public void Update() { X += VelocityX; Y += VelocityY; Life--; } } } |
火花を発生させる処理とLifeが0になっていないSparkオブジェクトのリストを取得する処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class FrisbeeGame { List<Spark> _sparks = new List<Spark>(); public List<Spark> Sparks { get { return _sparks.Where(spark => spark.Life > 0).ToList(); } } static Random _random = new Random(); public void SetSparks(int x, int y) { for (int i = 0; i < 24; i++) { int r = _random.Next(128); double rad = Math.PI * 2 * r / 128; double v = 8 + _random.Next(8); _sparks.Add(new Spark(x, y, (int)(v * Math.Cos(rad)), (int)(v * Math.Sin(rad)))); } } } |
ステージクリア時の処理
ステージクリア時に星を表示させます。そのための処理を示します。
星はSparkクラスを流用します。乱数を用いずに均等の間隔で放射状にひろがります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class FrisbeeGame { List<Spark> _stars = new List<Spark>(); public List<Spark> Stars { get { return _stars.Where(star => star.Life > 0).ToList(); } } public void SetStars(int x, int y) { for (int i = 0; i < 12; i++) { double rad = Math.PI * 2 * i / 12; _stars.Add(new Spark(x, y, (int)(24 * Math.Cos(rad)), (int)(24 * Math.Sin(rad)))); } } } |
ゲームスタート時と更新処理時の処理を示します。更新処理ではプレイヤーの更新処理と火花と星が存在する場合、その移動処理をおこなっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class FrisbeeGame { public void GameStart() { Player.GameStart(); } public void Update() { Player.Update(); foreach (Spark spark in Sparks) spark.Update(); foreach (Spark star in Stars) star.Update(); } } |