基底システム
まず最初にPygameの基底システムを作成します。
Unityベースで作成します。記述量が多いので、かなり端折ります。
GameObject
なんていうのか正式な名前はわかりませんがシーンを作成してGameObjectをシーンに追加する仕組みです
簡単なイメージでいうと、Sceneを作成したらリストに登録します。でそのシーンにGameObjectを生成します。
GameObjectクラスとSceneクラス
GameObjectはコンポーネントを所持します。
Componentは基底クラスでコンポーネントの機能を内包します。
また必ず親GameObjectを所持するようにします
次にCoroutineクラスとEventクラスも導入します。
Coroutineは基底メソッドUpdateに記述します。(またはthread)
コルーチンは同期の必要性が他の動作と比べて低いためThreadで並列処理として追加できてもよいかもしれません。
EventはイベントをTriggerするとRegisterしたアクションが実行されるものです。
例を記述します。
class EventManager:
"""イベント登録・通知の基底クラス"""
def __init__(self):
self.events = {}
def register_event(self, event_name, callback):
"""イベントを登録"""
if event_name not in self.events:
self.events[event_name] = []
self.events[event_name].append(callback)
def trigger_event(self, event_name, **kwargs):
"""イベントを発火 (登録されたすべてのコールバックを実行)"""
if event_name in self.events:
for callback in self.events[event_name]:
callback(**kwargs)
def unregister_event(self, event_name, callback):
"""イベントの登録解除"""
if event_name in self.events:
self.events[event_name].remove(callback)
class WaitForSeconds:
"""指定秒数待機する (delta_time ベース)"""
def __init__(self, seconds):
self.remaining_time = seconds # 残り時間
def update(self, delta_time):
"""delta_time を減算し、待機完了か判定"""
self.remaining_time -= delta_time
return self.remaining_time <= 0 # 0以下になったら完了
class Coroutine:
"""実行中のコルーチン"""
def __init__(self, generator):
self.generator = generator
self.done = False
self.current_wait = None # 待機オブジェクト
def step(self, delta_time):
"""コルーチンを1ステップ進める"""
try:
if self.current_wait: # もし待機中なら
if not self.current_wait.update(delta_time): # 時間経過を確認
return # まだ待機中なら処理しない
self.current_wait = None # 待機が完了したら解除
result = next(self.generator) # コルーチンを進める
if isinstance(result, WaitForSeconds):
self.current_wait = result # 次の待機オブジェクトを設定
except StopIteration:
self.done = True # コルーチン終了
class CoroutineManager:
"""コルーチンを管理するクラス"""
def __init__(self):
self.coroutines = []
def start_coroutine(self, func, *args):
"""関数をコルーチンとして開始"""
coroutine = Coroutine(func(*args))
self.coroutines.append(coroutine)
return coroutine # Coroutine インスタンスを返す
def stop_coroutine(self, coroutine):
"""コルーチンを停止"""
if coroutine in self.coroutines:
self.coroutines.remove(coroutine)
def update(self, delta_time):
"""毎フレーム更新 (delta_time ベース)"""
to_remove = []
for coroutine in self.coroutines:
if coroutine.done:
to_remove.append(coroutine)
continue
coroutine.step(delta_time) # delta_time を考慮して進める
for coroutine in to_remove:
self.coroutines.remove(coroutine)
updateには
self.clock = pygame.time.Clock()
delta_time = self.clock.tick(60) / 1000.0
このデルタタイムを入れます。
マルチプレイ
本来はこれ以上に機能が必要ですが、長くなるのでネットワークに入ります。
とにかくマルチプレイをするときは大きく二つにスタンスが分けられます。
- Server
- Client
この二つは動作が大きく異なります。
たまに見るのは完全に分離してしまう書き方です
ServerクラスとClientクラスを作成する感じです。
しかし私の場合は一緒に内包します。
そもそもサーバーとclientで同じ動作をする部分もあるためです。
まずGameObjectを継承してNetworkGameObjectを作成します。
これは特別にユニークなNetwrokIDが振り分けられます。
NetworkIDはサーバー側なら、NetworkManagerから生成する
clientなら、サーバーからNetworkGameObjectの生成要請と一緒にクラス名とNetworkIDが送られる仕組みにしますので、ジェネレートする必要がありません。
またクライアントはサーバー参加時に、現在のシーンを取得しないといけません。
それかつそのシーンに存在するNetworkGameObjectを取得する必要もあります。
しかしGameObject、つまりは、NetworkIDを持っていないオブジェクトは初期に生成されたもの以外存在しません。
このクライアントとサーバの同期はかなり大変ですが細分化していけば、開発時の記述量も減らすことができます。
Componentを付属することができましたね、ですからそのComponentにネットワーク関連のものを追加して自動同期を図ります。
NetworkComponent
コンポーネントはUnityを真似していますので、Transformクラスを作成します
pygame.Vector2を使って個別の変数を宣言します。
つぎにNetworkTransformを作成します。
これは自動でそのオブジェクトにアタッチされたTransformを取得して、サーバーのTransformをクライアントに送信するものです。
ですのでNetworkTransformをアタッチするだけでTransformの値がサーバーベースに同期されます。
私はNetworkComponentに変数をゲットセットで登録して、自動で前の値と比べ、数値が変化していたら、
データをクライアント全員にブロードキャストする仕組みで実装しました。
ゲットセットはLambdaでかけばできます。
Pythonにもポインターがあればもっと簡単にかけたのですが...
終わり
基本概念はこんな感じで、これをもとに多くのコンポーネントを追加してそのコンポーネントの自動同期コンポーネントを作成する。この流れで、機能を増やすことができます
次はこれを使って簡単なslither.ioを作ります。