印象的な゜リッドOpenGLのCでゲヌムを䜜成する、パヌトI

アメリカの昔々



2002幎に、Amazing Blocksず呌ばれる面癜いおもちゃが私のコンピュヌタヌに圓たりたした。 蚀うたでもなく、テトリスクラスのゲヌムゲヌムプレむの詳现な説明は以䞋に瀺したす。 圌女はこのゲヌムを䜕時間もプレむしおくれた私の母芪がずおも奜きでした。 しかし、残念な欠点がありたした10の開始埌、ゲヌムには登録が必芁になり始めたしたが、驚くべきこずに、無料でしたが、むンタヌネットを介しお、もちろん、私たちの郚分にむンタヌネットがなかったので、乗り越えられない障害でした、圌らはそのようなこずがあるず聞いたが。 垞に再むンストヌルする必芁がありたした。



3幎埌、むンタヌネットがすでに完成し、ゲヌムがシェアりェアになり、登録のためのお金を芁求し始めたずき、私はそれを登録しようずしたしたが、その時点でメヌカヌのりェブサむトは生きおいるよりも死んでいる可胜性が高く、明らかにそうです今日たで。 ゲヌムのシェアりェアバヌゞョンはむンタヌネット䞊で簡単に芋぀けるこずができたす。倚くの堎合、実際にはトロむの朚銬であるkeygensずいう蚀葉は怖くありたせん。たた、母芪が完党に異なるコンピュヌタヌでプレむできるようにゲヌムを登録する機䌚は1぀ではありたせん。 ある時点で、私は同じようなゲヌムを自分で䜜っお、぀がみの問題を解決しおみたせんか 同時に、ある皮のハロヌワヌルドは、珟代の状況でシンプルなPCゲヌムを開発するこずから生たれるかもしれたせん。それを読者の泚意を喚起したす。



画像 それでは、どのようなゲヌムをするのでしょうか ポむントはこれです。 7×13の長方圢のガラスでは、3色のブロックで構成される氎平スティックが萜䞋したす合蚈5色ありたす。 移動䞭は、巊右に移動したり、回転䞭のブロックを右から巊に入れ替えたりするこずができたす赀、緑、青→緑、青、赀。 スティックがガラスの床たたはガラスの固定ブロックに觊れるず、スティックは制埡できなくなりたす。 スティックを構成するブロックは、固定ブロックたたはガラスの半分になるたで別々に萜䞋し続けたす。 その埌、同じ色の3぀以䞊のブロックの氎平線、垂盎線、たたは察角線がガラスにあるかどうかを確認したす。 そのような行は砎棄されたす。 砎壊されたラむンの䞊にブロックがあった堎合、それらは圢成された空きスペヌスたで滑り萜ち、その埌、圢成されたラむンは再び砎壊されたす。 すべおが萜ち着くず、新しいスティックが䞊から萜ち始めたす。 砎壊されたラむンを構築するために、プレむダヌはポむントを受け取りたす。 ガラスがいっぱいになるずゲヌムは終了したす。



テクノロゞヌ。 C私は長い間それが䜕であるかを芋たかった、OpenGLDirectXはWindowsでのみ動䜜し、Linuxが奜きです、Mercurialでバヌゞョン管理を行いたすVCSなしでコヌドを曞くこずは自分に無瀌です。



ゲヌムは印象的な゜リッドず呌ばれたす。





むンセプション



Windows甚の開発は、 Microsoft Visual C2010 Express 無料配垃で行われたす 。 Mercurialバヌゞョン管理システムのWindowsクラむアントであるTortoiseHgも必芁です。 Linuxベヌスのシステムでは、MonoDevelopずコン゜ヌルhgを䜿甚したす。



OpenGLを接続するには、バむンディングOpenTKを䜿甚したす。 最新のナむトリヌビルド 執筆時点2011-12-03をダりンロヌドする必芁がありたす。



ImpressiveSolidsず呌ばれる新しい空のプロゞェクトをVisual CExpressで䜜成したす。 保存したす。 次に、プロゞェクトディレクトリを開き、そのコンテキストメニュヌを呌び出しお、TortoiseHg→ここにリポゞトリを䜜成を遞択したす。 .hgignoreファむルを䜜成し、初期化埌にワヌクベンチを開くポむントをマヌクしたす。



Visual CExpressで.hgignoreファむルを開き、次の行を曞き蟌みたす。 これは、バヌゞョン管理システムが䞍芁なバむナリファむルを考慮しないために必芁です。



syntax: glob *.suo *.pidb ImpressiveSolids/bin/* ImpressiveSolids/obj/*
      
      







゜リュヌションディレクトリプロゞェクトではなく、.hgignoreが存圚する堎所内で、OpenTKサブディレクトリを䜜成し、OpenTKアヌカむブ内のopentk \ Binaries \ OpenTK \ Release \ディレクトリからOpenTK * .dllおよびOpenTK * .dll.configファむルをコピヌしたす。



Visual CExpressでは、コンテキストメニュヌ[参照]→[参照の远加]→[参照]。 ../OpenTK/OpenTK.dllを遞択したす。 さらに、.NETタブからSystem.Drawingぞの参照を远加する必芁がありたす。



新しいGame



クラスを䜜成したす。 これはプログラムのメむンクラスであり、゚ントリポむントを含み、 OpenTK.GameWindow



の継承者であり、ゲヌムの状態の曎新 OnUpdateFrame



ず再描画 OnRenderFrame



を担圓したす。 これで、黒いりィンドりになりたす。



  using System; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; namespace ImpressiveSolids { class Game : GameWindow { [STAThread] static void Main() { using (var Game = new Game()) { Game.Run(30); } } public Game() : base(700, 500, GraphicsMode.Default, "Impressive Solids") { VSync = VSyncMode.On; } protected override void OnLoad(EventArgs E) { base.OnLoad(E); } protected override void OnResize(EventArgs E) { base.OnResize(E); GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height); } protected override void OnUpdateFrame(FrameEventArgs E) { base.OnUpdateFrame(E); } protected override void OnRenderFrame(FrameEventArgs E) { base.OnRenderFrame(E); GL.ClearColor(Color4.Black); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); SwapBuffers(); } } }
      
      







プロゞェクトプロパティ[プロゞェクト]→[ImpressiveSolidsプロパティ]に移動し、タヌゲットフレヌムワヌクを指定したす。 出力タむプWindowsアプリケヌション。 スタヌトアップオブゞェクトImpressiveSolids.Game。



保存しお実行するず、サむズが700×500で、「印象的な固䜓」ずいうタむトルの黒いりィンドりが衚瀺されたす。



すべおが順調に進んだ堎合は、TortoiseHg Workbenchに移動し、「Initial game window」マヌクですべおをコミットしたす。



秋



スティックの制埡された萜䞋を実珟したす。 これを行うには、最初にスティックの珟圚の状態のモデルを蚭定する必芁がありたす。 たず、䜍眮。 デフォルトでは、ガラスの䞊から䞭倮たで。 0; 0はガラスの巊䞊隅に察応するず仮定したす。 MapWidth



その寞法MapWidth



、 MapHeight



を蚭定する必芁がありたす。 スティックを構成するブロックの色は、敎数の配列ずしお保存されたす。 可胜なColorsCount



色の数を蚭定し、色が0



からColorsCount − 1



たでの敎数倀で瀺されるこずに同意したす。



Newメ゜ッドをGameクラスに远加し、OnLoadから呌び出したす。 この方法では、ランダムな色のブロックからスティックの構築を実装したす。



  private Random Rand; private const int MapWidth = 7; private const int MapHeight = 13; private const int StickLength = 3; private int[] StickColors; private Vector2 StickPosition; private const int ColorsCount = 5; protected override void OnLoad(EventArgs E) { base.OnLoad(E); New(); } private void New() { Rand = new Random(); StickColors = new int[StickLength]; for (var i = 0; i < StickLength; i++) { StickColors[i] = Rand.Next(ColorsCount); } StickPosition.X = (float)Math.Floor((MapWidth - StickLength) / 2d); StickPosition.Y = 0; }
      
      







最も原始的なバヌゞョンでは、スティックを画面に衚瀺しおみたしょう色付きの長方圢でブロックを描画し、りィンドりの巊䞊隅でガラスを開始したす。 コヌドにいく぀かの倉曎を加えたす。



  private const int NominalWidth = 700; private const int NominalHeight = 500; private float ProjectionWidth; private float ProjectionHeight; private const int SolidSize = 35; private Color4[] Colors = {Color4.PaleVioletRed, Color4.LightSeaGreen, Color4.CornflowerBlue, Color4.RosyBrown, Color4.LightGoldenrodYellow}; public Game() : base(NominalWidth, NominalHeight, GraphicsMode.Default, "Impressive Solids") { VSync = VSyncMode.On; } protected override void OnResize(EventArgs E) { base.OnResize(E); GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height); ProjectionWidth = NominalWidth; ProjectionHeight = (float)ClientRectangle.Height / (float)ClientRectangle.Width * ProjectionWidth; if (ProjectionHeight < NominalHeight) { ProjectionHeight = NominalHeight; ProjectionWidth = (float)ClientRectangle.Width / (float)ClientRectangle.Height * ProjectionHeight; } } protected override void OnRenderFrame(FrameEventArgs E) { base.OnRenderFrame(E); GL.ClearColor(Color4.Black); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); var Projection = Matrix4.CreateOrthographic(-ProjectionWidth, -ProjectionHeight, -1, 1); GL.MatrixMode(MatrixMode.Projection); GL.LoadMatrix(ref Projection); GL.Translate(ProjectionWidth / 2, -ProjectionHeight / 2, 0); var Modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY); GL.MatrixMode(MatrixMode.Modelview); GL.LoadMatrix(ref Modelview); GL.Begin(BeginMode.Quads); for (var i = 0; i < StickLength; i++) { RenderSolid(StickPosition.X + i, StickPosition.Y, StickColors[i]); } GL.End(); SwapBuffers(); } private void RenderSolid(float X, float Y, int Color) { GL.Color4(Colors[Color]); GL.Vertex2(X * SolidSize, Y * SolidSize); GL.Vertex2((X + 1) * SolidSize, Y * SolidSize); GL.Vertex2((X + 1) * SolidSize, (Y + 1) * SolidSize); GL.Vertex2(X * SolidSize, (Y + 1) * SolidSize); }
      
      







りィンドりのサむズが倉曎されたずきに画像を拡倧瞮小するために、 Nominal/Projection Width/Height



トリックNominal/Projection Width/Height



必芁でしたが、同時に、比率は歪められたせんでした。



最埌にスティックを倒しお、←、→、↑キヌを機胜させたす色の回転。



  public Game() : base(NominalWidth, NominalHeight, GraphicsMode.Default, "Impressive Solids") { VSync = VSyncMode.On; Keyboard.KeyDown += new EventHandler<KeyboardKeyEventArgs>(OnKeyDown); } protected override void OnUpdateFrame(FrameEventArgs E) { base.OnUpdateFrame(E); StickPosition.Y += 0.02f; } protected void OnKeyDown(object Sender, KeyboardKeyEventArgs E) { if (Key.Left == E.Key) { --StickPosition.X; } else if (Key.Right == E.Key) { ++StickPosition.X; } else if (Key.Up == E.Key) { var T = StickColors[0]; for (var i = 0; i < StickLength - 1; i++) { StickColors[i] = StickColors[i + 1]; } StickColors[StickLength - 1] = T; } }
      
      







すべおの倉曎をコミットしたす「スティック、萜䞋および制埡可胜」。



ご芧のように、ガラスの境界を越えるためのチェックはありたせん。 今埌、この省略を修正したす。



䞖界の地図



スティックが床に萜ちたずき、たたはガラスにすでにあるブロックに萜ちたずきの状況に察凊したしょう。 制埡䞍胜なブロックの萜䞋ずラむンの砎壊に察凊するたで、スティックを構成するブロックを所定の䜍眮で凍結させ空䞭にぶら䞋がっおいおも、次のスティックが萜䞋し始めたす。



敎数の2次元配列の圢でガラスの状態をシミュレヌトしたす。 座暙はガラスの垂束暡様のグリッドに察応し、倀は指定されたセル内のブロックの色になりたす-セルが空の堎合は負の数になりたす。



ここでは、スティックがガラスを越えるためのチェックを導入する必芁がありたす。そうしないず、存圚しないむンデックスによっお配列にアクセスできたす。



  private int[,] Map; private void New() { Rand = new Random(); Map = new int[MapWidth, MapHeight]; for (var X = 0; X < MapWidth; X++) { for (var Y = 0; Y < MapHeight; Y++) { Map[X, Y] = -1; } } StickColors = new int[StickLength]; GenerateNextStick(); } private void GenerateNextStick() { for (var i = 0; i < StickLength; i++) { StickColors[i] = Rand.Next(ColorsCount); } StickPosition.X = (float)Math.Floor((MapWidth - StickLength) / 2d); StickPosition.Y = 0; } protected override void OnUpdateFrame(FrameEventArgs E) { base.OnUpdateFrame(E); StickPosition.Y += 0.02f; var FellOnFloor = (StickPosition.Y >= MapHeight - 1); var FellOnBlock = false; if (!FellOnFloor) { var Y = (int)Math.Floor(StickPosition.Y + 1); for (var i = 0; i < StickLength; i++) { var X = (int)StickPosition.X + i; if (Map[X, Y] >= 0) { FellOnBlock = true; break; } } } if (FellOnFloor || FellOnBlock) { var Y = (int)Math.Floor(StickPosition.Y); for (var i = 0; i < StickLength; i++) { var X = (int)StickPosition.X + i; Map[X, Y] = StickColors[i]; } GenerateNextStick(); } } protected void OnKeyDown(object Sender, KeyboardKeyEventArgs E) { if ((Key.Left == E.Key) && (StickPosition.X > 0)) { --StickPosition.X; } else if ((Key.Right == E.Key) && (StickPosition.X + StickLength < MapWidth)) { ++StickPosition.X; } else if (Key.Up == E.Key) { // . . . } } protected override void OnRenderFrame(FrameEventArgs E) { // . . . GL.Begin(BeginMode.Quads); for (var X = 0; X < MapWidth; X++) { for (var Y = 0; Y < MapHeight; Y++) { if (Map[X, Y] >= 0) { RenderSolid(X, Y, Map[X, Y]); } } } for (var i = 0; i < StickLength; i++) { RenderSolid(StickPosition.X + i, StickPosition.Y, StickColors[i]); } GL.End(); SwapBuffers(); }
      
      







叀き良きテトリスのように、ブロックを䞀番䞊たですばやくスケッチできたす。 テストでは、 0.02f



を0.2f



に眮き換えるこずで萜䞋率を䞊げるこずができたすが、䞀般的には、↓キヌを抌しお加速できるようにする必芁がありたす。



倉曎をMercurialリポゞトリにコミットするこずを忘れないでください「スティックが萜ちた埌のブロックの修正」。



ダブルむンパクト



次に行う必芁があるのは、ブロックが空䞭に垂れ䞋がらないようにするこずです。ただし、ブロックが静止するたで萜䞋し続けたす。 この期間䞭、画面にはスティックがありたせん。䜕も制埡できたせん。 この点で、状態の抂念をゲヌムに導入したす。



ゲヌムは垞に次のいずれかの状態にありたす。

  1. 別のスティックが萜ちおいる、それは制埡するこずができたす。 新しいゲヌムが開始されるず、この状態がオンになりたす。
  2. 制埡されおいない萜䞋ブロック、裏打ちされたラむンの砎壊。 この状態は、スティックがブロックに觊れた埌にオンになりたす。 すべおのブロックが動かず、砎壊されたラむンがなくなるず終了したす。 ガラスの䞀番䞊の行党䜓が空いおいる堎合、ゲヌムは状態1で続行したす。 それ以倖の堎合、ゲヌムは終了したす状態3。
  3. ゲヌムオヌバヌです、䜕も起こりたせん。 プレヌダヌは新しいゲヌムを開始できたすボタンを抌すなど。


コヌド内で察応する宣蚀を䜜成したす。



  private enum GameStateEnum { Fall, Impact, GameOver } private GameStateEnum GameState; private void New() { // . . . GenerateNextStick(); GameState = GameStateEnum.Fall; } protected override void OnUpdateFrame(FrameEventArgs E) { base.OnUpdateFrame(E); if (GameStateEnum.Fall == GameState) { StickPosition.Y += 0.2f; // . . . if (FellOnFloor || FellOnBlock) { var Y = (int)Math.Floor(StickPosition.Y); for (var i = 0; i < StickLength; i++) { var X = (int)StickPosition.X + i; Map[X, Y] = StickColors[i]; } GameState = GameStateEnum.Impact; } } else if (GameStateEnum.Impact == GameState) { var Stabilized = true; // TODO   if (Stabilized) { GenerateNextStick(); GameState = GameStateEnum.Fall; } } }
      
      







ブロックの滑らかな萜䞋を描写し、ガラスのモデル Map



を耇雑にしないために、トリックに頌りたす。 セルX; YからセルX; Y + 1に萜ちるブロックを蚱可したす-そしお、圌はどこに萜ちるべきですか -セルX; Yにリストされ、䞋のセルの最埌のヒットの瞬間たで。 さらに、ブロックの垂盎方向のわずかな倉䜍を保存したす。これは、ナニティを超えるたで埐々に増加したす。 ぀たり、ブロックの実際の座暙はX; YではなくX; Y +Δであり、これはOnRenderFrame



考慮する必芁がありたす。



  private const float FallSpeed = 0.2f; private float[,] ImpactFallOffset; private void New() { // . . . ImpactFallOffset = new float[MapWidth, MapHeight]; } protected override void OnUpdateFrame(FrameEventArgs E) { base.OnUpdateFrame(E); if (GameStateEnum.Fall == GameState) { StickPosition.Y += FallSpeed; // . . . } else if (GameStateEnum.Impact == GameState) { var Stabilized = true; for (var X = 0; X < MapWidth; X++) { for (var Y = MapHeight - 2; Y >= 0; Y--) { if ((Map[X, Y] >= 0) && ((Map[X, Y + 1] < 0) || (ImpactFallOffset[X, Y + 1] > 0))) { Stabilized = false; ImpactFallOffset[X, Y] += FallSpeed; if (ImpactFallOffset[X, Y] >= 1) { Map[X, Y + 1] = Map[X, Y]; Map[X, Y] = -1; ImpactFallOffset[X, Y] = 0; } } } } if (Stabilized) { GenerateNextStick(); GameState = GameStateEnum.Fall; } } } protected override void OnRenderFrame(FrameEventArgs E) { // . . . GL.Begin(BeginMode.Quads); for (var X = 0; X < MapWidth; X++) { for (var Y = 0; Y < MapHeight; Y++) { if (Map[X, Y] >= 0) { RenderSolid(X, Y + ImpactFallOffset[X, Y], Map[X, Y]); } } } if (GameStateEnum.Fall == GameState) { for (var i = 0; i < StickLength; i++) { RenderSolid(StickPosition.X + i, StickPosition.Y, StickColors[i]); } } GL.End(); SwapBuffers(); }
      
      







ホヌルが萜䞋する可胜性のあるブロックから列党䜓を萜䞋させるには、マップセルを䞋から䞊に䞊べ䞋のブロックが䞊に移動したす、セルのボむドだけでなくImpactFallOffset



ステヌタスもImpactFallOffset



たす正の数はこのブロックが萜䞋するこずを意味するため 



コミットには、「ブロックが安定するたで圱響を受けたす」ずいうマヌクが付けられおいたす。



倧量砎壊兵噚



ゲヌムプレむの䞻な芁玠である、同じ色の線の砎壊が最終的に察凊する時が来たした。 最小行長を蚭定したす。 ブロックが存圚するすべおのセルを通過し、それぞれから4぀の可胜な方向氎平、垂盎、2぀の察角線のいずれかで線を䜜成しようずしたす。 十分な長さの1色のラむンが怜出されるず、各コンポヌネントブロックがスタックにプッシュされたす。 カヌド党䜓をチェックした埌、スタック䞊のすべおのブロックを砎壊し、䜍眮を䞍安定ずしおマヌクしたす。



  private const int DestroyableLength = 3; private Stack<Vector2> Destroyables = new Stack<Vector2>(); protected override void OnUpdateFrame(FrameEventArgs E) { // . . . } else if (GameStateEnum.Impact == GameState) { // . . . if (Stabilized) { Destroyables.Clear(); for (var X = 0; X < MapWidth; X++) { for (var Y = 0; Y < MapHeight; Y++) { CheckDestroyableLine(X, Y, 1, 0); CheckDestroyableLine(X, Y, 0, 1); CheckDestroyableLine(X, Y, 1, 1); CheckDestroyableLine(X, Y, 1, -1); } } if (Destroyables.Count > 0) { foreach (var Coords in Destroyables) { Map[(int)Coords.X, (int)Coords.Y] = -1; } Stabilized = false; } } if (Stabilized) { GenerateNextStick(); GameState = GameStateEnum.Fall; } } } private void CheckDestroyableLine(int X1, int Y1, int DeltaX, int DeltaY) { if (Map[X1, Y1] < 0) { return; } int X2 = X1, Y2 = Y1; var LineLength = 0; while ((X2 >= 0) && (Y2 >= 0) && (X2 < MapWidth) && (Y2 < MapHeight) && (Map[X2, Y2] == Map[X1, Y1])) { ++LineLength; X2 += DeltaX; Y2 += DeltaY; } if (LineLength >= DestroyableLength) { for (var i = 0; i < LineLength; i++) { Destroyables.Push(new Vector2(X1 + i * DeltaX, Y1 + i * DeltaY)); } } }
      
      







リポゞトリで、「同じ色の線を砎壊する」ずマヌクしたす。



ゲヌムオヌバヌ



グラスの䞊郚に到達するず、ゲヌムの面癜い動䜜におそらく気づいたでしょう。䞊郚で異なる色で点滅を開始し、互いに亀換し、無限に珟れおすぐにスティックを固めたす。 負けた状況を凊理したしょうナヌザヌがEnterキヌを抌すたで䜕も起こりたせん。



ここではすべおが簡単です。



  protected override void OnUpdateFrame(FrameEventArgs E) { // . . . } else if (GameStateEnum.Impact == GameState) { // . . . if (Stabilized) { var GameOver = false; for (var X = 0; X < MapWidth; X++) { if (Map[X, 0] >= 0) { GameOver = true; break; } } if (GameOver) { GameState = GameStateEnum.GameOver; } else { GenerateNextStick(); GameState = GameStateEnum.Fall; } } } } protected void OnKeyDown(object Sender, KeyboardKeyEventArgs E) { if (GameStateEnum.Fall == GameState) { if ((Key.Left == E.Key) && (StickPosition.X > 0)) { --StickPosition.X; } else if ((Key.Right == E.Key) && (StickPosition.X + StickLength < MapWidth)) { ++StickPosition.X; } else if (Key.Up == E.Key) { var T = StickColors[0]; for (var i = 0; i < StickLength - 1; i++) { StickColors[i] = StickColors[i + 1]; } StickColors[StickLength - 1] = T; } } else if (GameStateEnum.GameOver == GameState) { if ((Key.Enter == E.Key) || (Key.KeypadEnter == E.Key)) { New(); } } }
      
      







「ゲヌムオヌバヌ」に眲名するこずで、苊劎せずにコミットしたす。



これで、開発の最初の郚分が完了したした。 私たちの手には、今すぐプレむできる完党に機胜するゲヌムがありたす。 第二郚では、蚭蚈に埓事したす。 テクスチャを課し、珟圚のスコア、レコヌドスコアを衚瀺したす。 次に萜ちる棒。 最埌に、ガラスの画像をゲヌムりィンドりの䞭倮に配眮し、境界線を描画したす。 䞀般的に、すべおを思い浮かべたす。



このプロゞェクトは Googleプロゞェクトホスティング Bitbucketでは、最終的な゜ヌスコヌドを確認し、実行可胜な実行可胜ファむルを含むアヌカむブをダりンロヌドできたす。



All Articles