OpenGLチュートリアル:3D テトリス
目次
- はじめに
- 3Dテトリス開発の魅力
- 本記事の概要
- 開発環境の準備
- 必要なライブラリ
- 開発環境のセットアップ
- 3Dテトリスの基本構造
- ブロックの表現
- テトリスフィールドの設計
- カメラワーク
- OpenGLによる描画
- 頂点バッファオブジェクト(VBO)と頂点配列オブジェクト(VAO)
- シェーダーの作成(頂点シェーダー、フラグメントシェーダー)
- ブロックの描画処理
- ゲームロジックの実装
- ブロックの生成
- ブロックの移動と回転
- 衝突判定
- ラインの消去とスコアリング
- コード実装(サンプル)
- メインループ
- 描画関連処理
- ゲームロジック関連処理
- 実行結果と今後の展望
- 動作デモ
- 改善点と今後の機能追加
- まとめ
1. はじめに
3Dテトリス開発の魅力
テトリスは、シンプルながらも奥深いパズルゲームとして長年愛されています。それを3D空間で表現することで、新たなゲーム体験とプログラミングの挑戦が生まれます。OpenGLを用いることで、美しい3Dグラフィックスとスムーズなアニメーションを実現し、より没入感のあるテトリスを作り上げることができます。
本記事の概要
本記事では、OpenGLを使って3Dテトリスを開発する基本的な手順とコード例を紹介します。3Dグラフィックスの基礎からゲームロジックの実装まで、ステップバイステップで解説していきます。
2. 開発環境の準備
必要なライブラリ
OpenGLの基本的な機能に加えて、以下のライブラリを使用します。
開発環境のセットアップ
お使いのOSに合わせて、上記のライブラリをインストールしてください。具体的なインストール方法は各ライブラリの公式サイトなどを参照してください。
3. 3Dテトリスの基本構造
ブロックの表現
3Dテトリスでは、各ブロックは複数の立方体(キューブ)で構成されます。それぞれのキューブは、OpenGLのプリミティブである三角形の組み合わせで描画されます。
テトリスフィールドの設計
テトリスフィールドは、3次元の格子状の空間として表現します。各格子点にブロックが存在するかどうかを管理します。
カメラワーク
3D空間での視点を操作するために、カメラを導入します。ユーザーが視点を移動させたり、固定された視点からゲームをプレイできるようにします。
4. OpenGLによる描画
頂点バッファオブジェクト(VBO)と頂点配列オブジェクト(VAO)
OpenGLでオブジェクトを描画するためには、その形状を定義する頂点データをGPUに送る必要があります。VBOは頂点データを格納するバッファ、VAOはVBOと頂点属性の設定をまとめたオブジェクトです。
シェーダーの作成(頂点シェーダー、フラグメントシェーダー)
シェーダーはGPU上で実行されるプログラムで、頂点の変換やピクセルの色決定を行います。
ブロックの描画処理
各ブロックを構成するキューブの頂点データをVBOに格納し、VAOを使って描画します。モデル変換行列を適用することで、各キューブを適切な位置に配置します。
import glfw from OpenGL.GL import * import numpy as np # キューブの頂点データ vertices = np.array([ [-0.5, -0.5, -0.5], [ 0.5, -0.5, -0.5], [ 0.5, 0.5, -0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, 0.5], [ 0.5, -0.5, 0.5], [ 0.5, 0.5, 0.5], [-0.5, 0.5, 0.5] ], dtype=np.float32) # キューブの面(インデックス) indices = np.array([ 0, 1, 2, 2, 3, 0, # 背面 1, 5, 6, 6, 2, 1, # 右面 5, 4, 7, 7, 6, 5, # 前面 4, 0, 3, 3, 7, 4, # 左面 3, 2, 6, 6, 7, 3, # 上面 4, 5, 1, 1, 0, 4 # 下面 ], dtype=np.uint32) def draw_cube(): glBindVertexArray(VAO) glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None) glBindVertexArray(0)
5. ゲームロジックの実装
ブロックの生成
ランダムにテトリスのブロック(I, O, T, S, Z, J, L型など)を生成し、フィールドの上部中央に配置します。
ブロックの移動と回転
ユーザーの入力に応じて、ブロックを左右に移動させたり、回転させたりする処理を実装します。
衝突判定
ブロックが他のブロックやフィールドの壁、底面と衝突していないかを常にチェックします。衝突した場合、それ以上移動できないように制御します。
ラインの消去とスコアリング
横一列にブロックが揃った場合、そのラインを消去し、スコアを加算します。上のブロックを落下させる処理も必要です。
# サンプル:ブロックの移動(左右) block_position = np.array([0.0, 10.0, 0.0], dtype=np.float32) move_speed = 0.1 def move_block_left(): block_position[0] -= move_speed def move_block_right(): block_position[0] += move_speed # サンプル:簡単な衝突判定(床との衝突) field_bottom = -5.0 def check_collision(): if block_position[1] <= field_bottom: return True return False
6. コード実装(サンプル)
ここでは、基本的なOpenGLの初期化、描画ループ、そして簡単なブロックの描画と移動のサンプルコードを示します。完全なゲームロジックは複雑になるため、主要な部分に焦点を当てています。
import glfw from OpenGL.GL import * import numpy as np from pyrr import Matrix44 # 画面サイズ screen_width = 800 screen_height = 600 # キューブの頂点データとインデックス(再掲) vertices = np.array(...) indices = np.array(...) # シェーダーソースコード vertex_shader_source = """ #version 330 core layout (location = 0) in vec3 aPos; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { gl_Position = projection * view * model * vec4(aPos, 1.0); } """ fragment_shader_source = """ #version 330 core out vec4 FragColor; uniform vec4 color; void main() { FragColor = color; } """ # OpenGLの初期化 def initialize_opengl(): if not glfw.init(): return None glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) window = glfw.create_window(screen_width, screen_height, "3D Tetris", None, None) if not window: glfw.terminate() return None glfw.make_context_current(window) glfw.set_framebuffer_size_callback(window, framebuffer_size_callback) glfw.set_key_callback(window, key_callback) return window def framebuffer_size_callback(window, width, height): glViewport(0, 0, width, height) # キー入力コールバック関数 def key_callback(window, key, scancode, action, mods): if key == glfw.KEY_ESCAPE and action == glfw.PRESS: glfw.set_window_should_close(window, True) if key == glfw.KEY_LEFT and action == glfw.REPEAT: move_block_left() if key == glfw.KEY_RIGHT and action == glfw.REPEAT: move_block_right() # シェーダーのコンパイル def compile_shader(shader_type, source): shader = glCreateShader(shader_type) glShaderSource(shader, source) glCompileShader(shader) # エラーチェック success = glGetShaderiv(shader, GL_COMPILE_STATUS) if not success: info_log = glGetShaderInfoLog(shader).decode("utf-8") print(f"Shader compilation error: {info_log}") return shader # シェーダープログラムの作成 def create_shader_program(vertex_shader, fragment_shader): program = glCreateProgram() glAttachShader(program, vertex_shader) glAttachShader(program, fragment_shader) glLinkProgram(program) # エラーチェック success = glGetProgramiv(program, GL_LINK_STATUS) if not success: info_log = glGetProgramInfoLog(program).decode("utf-8") print(f"Shader linking error: {info_log}") glDeleteShader(vertex_shader) glDeleteShader(fragment_shader) return program # メインループ def main(): window = initialize_opengl() if not window: return vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source) fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source) shader_program = create_shader_program(vertex_shader, fragment_shader) # VBOとVAOの作成と設定 VBO = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, VBO) glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW) EBO = glGenBuffers(1) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO) glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW) VAO = glGenVertexArrays(1) glBindVertexArray(VAO) glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * 4, ctypes.c_void_p(0)) glEnableVertexAttribArray(0) glBindVertexArray(0) glUseProgram(shader_program) # 透視投影行列の作成 projection = Matrix44.perspective_projection(45.0, screen_width / screen_height, 0.1, 100.0) projection_loc = glGetUniformLocation(shader_program, "projection") glUniformMatrix4fv(projection_loc, 1, GL_FALSE, projection) # ビュー行列の作成(カメラの位置と向き) view = Matrix44.look_at( (4.0, 4.0, 6.0), # カメラの位置 (0.0, 0.0, 0.0), # 注視点 (0.0, 1.0, 0.0) # 上方向ベクトル ) view_loc = glGetUniformLocation(shader_program, "view") glUniformMatrix4fv(view_loc, 1, GL_FALSE, view) glEnable(GL_DEPTH_TEST) block_color_loc = glGetUniformLocation(shader_program, "color") while not glfw.window_should_close(window): glClearColor(0.2, 0.3, 0.3, 1.0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glUseProgram(shader_program) glBindVertexArray(VAO) # モデル行列の作成(ブロックの位置) model = Matrix44.translation(block_position) model_loc = glGetUniformLocation(shader_program, "model") glUniformMatrix4fv(model_loc, 1, GL_FALSE, model) glUniform4f(block_color_loc, 0.8, 0.3, 0.8, 1.0) # ブロックの色 draw_cube() glfw.swap_buffers(window) glfw.poll_events() if check_collision(): block_position[1] = field_bottom # 衝突したらその場に固定(簡単な例) glfw.terminate() if __name__ == "__main__": main()
注意: 上記のコードは非常に基本的な例であり、完全な3Dテトリスゲームとしては多くの機能が不足しています。例えば、ブロックの形状、回転、他のブロックとの衝突判定、ライン消去、スコアリングなどは実装されていません。
7. 実行結果と今後の展望
動作デモ
上記のコードを実行すると、シンプルな立方体が描画され、左右のキー入力で移動することができます。
改善点と今後の機能追加
- ブロックの形状と種類の追加: 様々なテトリスブロックの形状を定義し、ランダムに生成するようにします。
- ブロックの回転: ユーザーの入力に応じてブロックを回転させる機能を追加します。
- 衝突判定の完全な実装: ブロック同士やフィールドとの正確な衝突判定を実装します。
- ライン消去とスコアリング: 横一列揃ったラインを消去し、スコアを計算するロジックを追加します。
- 落下速度の調整: 時間経過とともにブロックの落下速度を上げるようにします。
- ゲームオーバー処理: ブロックが積み上がり、新しいブロックを生成できなくなったらゲームオーバーとなる処理を追加します。
- より洗練された描画: テクスチャやライティングを導入し、よりリアルな描画を目指します。
8. まとめ
本記事では、OpenGLを使って3Dテトリスを開発するための基本的なステップとコード例を紹介しました。3Dグラフィックスの知識とゲームロジックの実装は挑戦的ですが、完成した時の達成感は格別です。ぜひ、このブログ記事を参考に、あなた自身の3Dテトリスを開発してみてください。