【ITニュース解説】Totally expected - Fishing World - Devlog #2
2025年09月05日に「Dev.to」が公開したITニュース「Totally expected - Fishing World - Devlog #2」について初心者にもわかりやすいように丁寧に解説しています。
ITニュース概要
ゲーム開発者が、マップ描画処理の問題を解決。画面外のタイルも描画されていた問題を修正し、表示範囲を最適化。仮の草タイルを作成し、タップ操作によるキャラクター移動機能を実装。UIの課題はあるものの、着実に開発を進めている。
ITニュース解説
このニュース記事は、ゲーム開発者が「Fishing World」というゲームを開発する過程で遭遇した、レンダリング(描画)に関する問題とその解決策、そしてゲームの基本的な要素であるキャラクターの移動処理について解説している。特に、システムエンジニアを目指す初心者にとって理解しやすいように、コードの意図や役割を中心に説明する。
最初のレンダリング関数は、画面にぴったり収まる小さなマップ(2x2)では正常に動作したが、より大きなマップで同じ関数を使用すると、マップ全体を一度に描画してしまうという問題が発生した。これは、画面に表示する必要のない部分まで描画してしまい、処理効率が非常に悪くなることを意味する。
開発者は、以前の投稿で書いたコードに誤りがあることに気づき、新しいレンダリング関数を作成した。以下に、そのコードの解説を示す。
1import pygame as pg 2import settings as stgs 3 4def render(layer, center): 5 gw, gh = stgs.grid_w, stgs.grid_h # Max tiles per grid axis 6 x, y = center # Player position 7 8 # Coordinates of the grid on the screen 9 left, right = x - gw // 2, x + gw // 2 10 top, bottom = y - gh // 2, y + gh // 2 11 12 # Offset needed if the player position makes the grid 13 # not appear entirely on the screen 14 offx, offy = 0, 0 15 16 # Fixing the values initiated earlier 17 if left < 0: 18 offx = -left 19 left = 0 20 if right > (l := len(layer[0])): 21 right = l 22 if top < 0: 23 offy = -top 24 top = 0 25 if bottom > (l := len(layer)): 26 bottom = l 27 28 # Getting the tile range that will be rendered 29 chunck = layer[top:bottom+1] 30 chunck = [ 31 i[left:right+1] for i in chunck 32 ] 33 34 ts = stgs.tile_size 35 36 # Drawing each tile on the screen 37 # (requires optimization) 38 for y, row in enumerate(chunck): 39 for x, tile in enumerate(row): 40 stgs.screen.blit( 41 tile.image, ( 42 (x+offx)*ts+stgs.margin_w, 43 (y+offy)*ts+stgs.margin_h 44 ) 45 )
このコードは、Pygameというゲーム開発ライブラリを使用しており、render関数が主要な役割を果たす。
import pygame as pg: Pygameライブラリをインポートし、pgという別名で使用できるようにする。Pygameは、ゲームの作成に必要な機能(画像の描画、音の再生、入力の処理など)を提供する。import settings as stgs:settings.pyファイル(このコードには記述されていないが、通常はゲームの設定を記述するファイル)をインポートし、stgsという別名で使用できるようにする。def render(layer, center)::render関数を定義する。この関数は、描画するレイヤー(layer)と、画面の中心座標(center)を引数として受け取る。gw, gh = stgs.grid_w, stgs.grid_h:settings.pyで定義された、グリッドの幅(grid_w)と高さ(grid_h)を取得する。これは、画面に表示するタイルの数を決定するために使用される。x, y = center:center(プレイヤーの位置)から、x座標とy座標を取得する。left, right = x - gw // 2, x + gw // 2: 画面に表示するグリッドの左端と右端の座標を計算する。//は整数除算を行う演算子。top, bottom = y - gh // 2, y + gh // 2: 画面に表示するグリッドの上端と下端の座標を計算する。offx, offy = 0, 0: オフセット(ずれ)を初期化する。これは、マップの端にプレイヤーが近づいた場合に、画面外のタイルを描画しないようにするために使用される。if left < 0: ...: 左端が0より小さい場合、つまり画面の左端より左にグリッドがはみ出している場合に、オフセットを調整し、leftを0にする。if right > (l := len(layer[0])): ...: 右端がレイヤーの幅を超えている場合、rightをレイヤーの幅にする。l := len(layer[0])は、Python 3.8で導入された代入式(セイウチ演算子)で、len(layer[0])の結果をlに代入すると同時に、その値をif文の条件で使用する。if top < 0: ...: 上端が0より小さい場合、オフセットを調整し、topを0にする。if bottom > (l := len(layer)): ...: 下端がレイヤーの高さより大きい場合、bottomをレイヤーの高さにする。chunck = layer[top:bottom+1]: 描画するタイルの範囲(chunck)をレイヤーから抽出する。これは、リストのスライスを使用している。chunck = [i[left:right+1] for i in chunck]: 各行から、さらに描画するタイルの範囲を抽出する。これは、リスト内包表記を使用している。ts = stgs.tile_size: タイルのサイズをsettings.pyから取得する。for y, row in enumerate(chunck): ...: 描画するタイルの範囲を、行ごとにループ処理する。enumerateは、リストの要素とそのインデックスを同時に取得するために使用される。for x, tile in enumerate(row): ...: 各行のタイルを、一つずつループ処理する。stgs.screen.blit(tile.image, ((x+offx)*ts+stgs.margin_w, (y+offy)*ts+stgs.margin_h)): 実際にタイルを描画する。blitは、Pygameの関数で、あるSurface(画像)を別のSurfaceに転送(描画)する。tile.imageは、描画するタイルの画像、((x+offx)*ts+stgs.margin_w, (y+offy)*ts+stgs.margin_h)は、描画する位置の座標。stgs.margin_wとstgs.margin_hは、画面の余白を設定ファイルから取得する。
このコードは、プレイヤーの位置を中心とした一定範囲のタイルのみを描画することで、処理負荷を軽減している。オフセットの計算により、マップの端でも正しく描画できる。
また、開発者は、簡単な草のタイルを作成し、タップ操作によるキャラクターの移動処理を実装した。画面をタップした位置をグリッドの座標に変換し、プレイヤーをその座標に移動させるというシンプルな仕組みだが、ゲームの基本的な操作性を実現している。
この開発日誌から、ゲーム開発におけるレンダリングの最適化の重要性と、基本的な移動処理の実装方法を学ぶことができる。初心者にとっては、実際のコード例を通して、ゲーム開発の具体的なステップを理解する良い機会となるだろう。