前回作成したプログラムではコースアウトをしてもなにも起きませんでした。今回はクラッシュしたときの処理を考えます。クラッシュすると下の動画のようになります。
コースアウトの判定
まずコースアウトしたかどうかの判定ですが、車のX、Z座標がコース内から外(境界線も含む)になったときにそのように判定します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// 車のX、Z座標を求める int x = (int)Math.Round(MyCar.X / ExpansionRate); int z = (int)Math.Round(MyCar.Z / ExpansionRate); Point point = new Point(x, z); // 車のX、Z座標がコース内から外に出た // 車の座標がコース内かどうかはCourse.IsPointInsideで判定する // 第二引数がfalseなら境界線はコース外であると判定する if (!Course.IsPointInside(point, false)) { // クラッシュの演出をする } |
クラッシュの演出をどのようにするかですが、車を不規則に回転させながら吹っ飛ばすことにします。すると更新処理をするUpdateメソッドだけでなく、カメラの座標も変えるSetSightメソッドも考えなければなりません。というのは前回のプログラムではカメラは車の真後ろを付いてくるだけでした。これを変えないといけません。
車の状態を示す列挙体
最初に車の状態を示す列挙体をつくります。車の状態として考えられるのは
通常走行
クラッシュしたとき
です。ほかにもゴールしたときも考えないといけないかもしれませんが、これはそのときに考えます。
1 2 3 4 5 |
enum CarStatus { Nomal, Crash, } |
クラッシュ時の車の動きを変える
次にUpdateメソッドを変更します。
通常時とクラッシュ時で車の動きがかわるのでUpdateメソッドを変更します。CarStatusによってUpdateCarStatusNomalメソッドとUpdateCarStatusCrashメソッドを使い分けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class GameManager { void Update() { UpdateCount++; if (LeftKeyDown) MyCar.Rotate += 2; else if (RightKeyDown) MyCar.Rotate -= 2; MyCar.Update(); // ここから下を変更した if (CarStatus == CarStatus.Nomal) UpdateCarStatusNomal(); else if (CarStatus == CarStatus.Crash) UpdateCarStatusCrash(); Form1.label1.Text = String.Format("速度 {0} Km/h", Math.Round(MyCar.Speed, 2) * 100); } } |
通常時の更新処理をするUpdateCarStatusNomalメソッドを示します。クラッシュすることなく移動した場合はその座標を記憶しておきます。クラッシュから回復するときに車を配置する座標を計算するときに必要だからです。クラッシュしたらクラッシュした座標も記憶させます。
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 |
public class GameManager { // 通常移動できたときの自車の座標を記憶する float CarPointX = 0; float CarPointY = 0; float CarPointZ = 0; // 通常移動できた自車の速さを記憶する float CarSpeeed = 0; // クラッシュしたらクラッシュしたときの車の座標を記憶しておく float CrashCarPointX = 0; float CrashCarPointZ = 0; // クラッシュしたときのカメラの座標 float CrashEyePointX = 0; float CrashEyePointZ = 0; // クラッシュしたときに車が飛ばされる速度 float VecXToBlownAway = 0; float VecZToBlownAway = 0; // 車が飛ばされる動作を繰り返した回数 int BlownAwayCount = 0; void UpdateCarStatusNomal() { int x = (int)Math.Round(MyCar.X / ExpansionRate); int z = (int)Math.Round(MyCar.Z / ExpansionRate); Point point = new Point(x, z); if (Course.IsPointInside(point, false)) { // 通常移動ができたのでその座標を記憶する CarPointX = MyCar.X; CarPointY = MyCar.Y; CarPointZ = MyCar.Z; CarSpeeed = MyCar.Speed; } else { // クラッシュした。速度は0に CarStatus = CarStatus.Crash; MyCar.Speed = 0f; // フィールド変数に必要な値を記憶させる CrashCarPointX = MyCar.X; CrashCarPointZ = MyCar.Z; CrashEyePointX = Eye.X; CrashEyePointZ = Eye.Z; // 車は逆方向に飛ばされる VecXToBlownAway = -MyCar.VecX; VecZToBlownAway = -MyCar.VecZ; VecXToBlownAway = -MyCar.VecX / CarSpeeed; VecZToBlownAway = -MyCar.VecZ / CarSpeeed; } } } |
これはCarクラスにおける描画処理です。クラッシュした車は不規則に回転しながら画面外に飛んでいくので描画処理をGameManager.CarStatusの状態で変える必要があります。またつねに車の方向が同じなのは不自然なのでハンドルを切っているときは方向転換しているかのように車体の向きを変えて描画させます。
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 Car: Charctor { public override void Draw() { GL.PushMatrix(); { if (GameManager.CarStatus == CarStatus.Nomal) { GL.Translate(X, Y, Z); GL.Rotate(Rotate + 90, 0, 1, 0); // ハンドルを切っている感じを出す if (GameManager.LeftKeyDown) GL.Rotate(3, 0, 1, 0); if (GameManager.RightKeyDown) GL.Rotate(-3, 0, 1, 0); } if (GameManager.CarStatus == CarStatus.Crash) { // クラッシュした感じを出すために車をXYZ軸で回転させる GL.Translate(X, Y, Z); GL.Rotate(Rotate + 90, 0, 1, 0); GL.Rotate(Rotate, 1, 0, 0); GL.Rotate(Rotate, 0, 0, 1); } if (CarData != null) DrawCar(CarData); } GL.PopMatrix(); } } |
クラッシュから回復する
クラッシュしたときの更新処理を示します。車は衝突したときの速度と反対方向に飛ばされます。2秒間この処理を繰り返したらゲームを続けられるように車をもとの状態に戻します。
そのために直前に正常移動できていた車の座標からもっとも近くにあるコースの境界線上の点を求めます。コースの境界線は内側と外側があります。それぞれのクラッシュ前の車の座標にもっとも近い2点の中点が車の復活場所です。また車の方向も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 |
public class GameManager { void UpdateCarStatusCrash() { // クラッシュしたら回転しながら衝突したときの速度と反対方向+上方に飛ばされる MyCar.Rotate += 30; MyCar.Y += 0.3f; MyCar.X += VecXToBlownAway / 3; MyCar.Z += VecZToBlownAway / 3; BlownAwayCount++; // 2秒後にゲームを続けられるように車をもとの状態に戻す if (BlownAwayCount > 120) { // CarStatusをもとに戻す CarStatus = CarStatus.Nomal; BlownAwayCount = 0; // 車の復活位置を求める // クラッシュ直前の位置からもっとも近い2点を求める PointF[] points = Course.GetNeerestExpandedBorderPoints(new PointF(CarPointX, CarPointZ)); // 両者の中点が復活場所になる int pointX = (int)((points[0].X + points[1].X) / 2); int pointY = (int)((points[0].Y + points[1].Y) / 2); Point newPoint = new Point(pointX, pointY); // 復活した車の向きを求める double rad = Math.Atan2(points[1].Y - points[0].Y, points[1].X - points[0].X); int newRotate = 90 - (int)(rad * 180 / Math.PI); // 求めた値をCarオブジェクトのフィールド変数に格納する MyCar.X = newPoint.X; MyCar.Y = CarPointY; MyCar.Z = newPoint.Y; MyCar.Rotate = newRotate; MyCar.Speed = 0; } } } |
車の状態でカメラの座標を変える
更新処理だけでなくカメラの座標もCarStatusで変えなければなりません。通常時はMyCarを真後ろから追いかけてきましたが、クラッシュ時はクラッシュした場所にとどまらなければなりません。
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 |
public class GameManager { void SetSight() { if(CarStatus == CarStatus.Nomal) SetSightNomal(); if (CarStatus == CarStatus.Crash) SetSightCrash(); // 法線の自動調節 GL.Enable(EnableCap.Normalize); Form1.Text = MyCar.X.ToString() + " " + MyCar.Z.ToString(); } void SetSightNomal() { double rad = MyCar.Rotate * Math.PI / 180; float x = -(float)(5.6 * Math.Cos(rad)) + MyCar.X; float z = (float)(5.6 * Math.Sin(rad)) + MyCar.Z; // 視界の設定 Eye = new Vector3(x, 3, z); Target = new Vector3(MyCar.X, 1.8f, MyCar.Z); Matrix4 look = Matrix4.LookAt(Eye, Target, Vector3.UnitY); GL.LoadMatrix(ref look); } void SetSightCrash() { // 視界の設定 Eye = new Vector3(CrashEyePointX, 3, CrashEyePointZ); Target = new Vector3(CrashCarPointX, 1.8f, CrashCarPointZ); Matrix4 look = Matrix4.LookAt(Eye, Target, Vector3.UnitY); GL.LoadMatrix(ref look); } } |