こんにちは、みやびのです。
今回は、「Pythonゲームにおけるリアルタイム処理の基本」というテーマでお話しします。
アクションゲームやシューティングゲーム・RPGなどほぼ全てのゲームでリアルタイム処理は必須です。
リアルタイム処理がなければゲームは成立しません。
「リアルタイム処理」と聞くと難しく考えてしまうかもしれませんが、基本的な構造は単純です。
PythonだけでなくUnity(C#)などにも応用できるので覚えておきましょう。
本記事の内容は以下の通り。
・ゲーム制作におけるリアルタイム処理のポイント
・リアルタイム処理実装の具体例~Pythonistaを例に解説~
目次
ゲーム制作におけるリアルタイム処理のポイント
リアルタイム処理で押さえておくべきポイントは「初期設定処理」、「更新ループ処理」、「キー入力時処理」の3つです。
ゲームを起動するとまずは「初期設定処理」が呼ばれ、以降は更新ループ処理に入ります。更新ループ処理中にキーが入力された場合にキー入力処理が動作します。
他にも処理やタイミングは存在しますが、「初期設定処理」、「更新ループ処理」、「キー入力時処理」の3つを覚えておけば、簡単なリアルタイム処理を作成できます。
初期設定処理
ゲームを起動した直後に1度だけ呼ばれる処理です。背景やキャラクターの描画、初期BGMの追加などに活用できます。
更新ループでもデータを読み込むことができますが、毎回データを全て読み込んでいたら処理が重くなり、画面の切り替えやキャラクターの動きが遅くなります。
初期設定で一通りのデータを読み込んでおくことで更新時の処理を軽くすることが可能です。
更新ループ処理
初期設定が終わった後は無限ループに入ります。1フレーム(1/60秒)ごとに更新処理を呼び出します。
更新処理はリアルタイム処理の中で最も重要な処理で、以下のようにゲームを進行させるための様々な場面で活用可能です。
・背景の切り替え
・プレイヤーキャラクターを自然な形で移動させる
・アニメーションを表示
・敵キャラクターや弾道・アイテムを動かす
キー入力時処理
キーボードやマウスをクリックしたとき、スマホの場合は画面をタッチした時などに動く処理です。
キャラクターの移動開始あやジャンプなどのアクションを起こす時に活用できます。
リアルタイム処理実装の具体例~Pythonistaを例に解説~
リアルタイム処理の実装例についてiPhoneアプリであるPythonistaのsceneライブラリを用いて実装する場合を例に解説します。
sceneはPythonista専用ですが、簡単にアニメーションやアクションゲームを作れるライブラリです。サンプルゲームもあるので遊びながら学べます。
関連記事>>遊んで学ぶPythonista・sceneライブラリの基本
今回は「Pythonista+sceneで当たり判定を作成する方法」という記事で紹介しているコードを例に説明します。
実行例は以下の通り。
ソースコードの細かい処理を理解する必要はありません。
「初期設定では必要などういった項目を追加するのか?」
「更新処理では何をすべきか?」
といったイメージができればOKです。
Pythonistaで実装する準備
Pythonistaではゲーム作成用のテンプレートがあるので簡単に実装の準備ができます。
+ボタンをクリック
sceneの「Game/Animation」を選択します
適当なファイル名を入力して「Create」をクリックします
初期設定処理の実装
初期設定には、「setup()」メソッドに記述します。
初期設定で行う処理は、以下の通り。
・背景の追加
・キャラクターやアイテムの追加
・スコアの追加
上記をコードにした実装例は以下の通り。
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 60 61 | from scene import * import sound class MyScene (Scene): class Game (Scene): def setup(self): # バックグラウンド設定 self.background_color = '#004f82' ground = Node(parent=self) x = 0 # 地面の高さ self.base_height = 50 # 背景の設定 while x <= self.size.w + 64: tile = SpriteNode('plf:Ground_Planet', position=(x, self.base_height)) ground.add_child(tile) x += 64 # プレイヤーの初期設定 self.player_height = self.base_height + 32 self.player = SpriteNode('emj:Ghost') self.player.anchor_point = (0.5, 0) self.player.position = (self.size.w/2, self.player_height) self.add_child(self.player) # ジャンプボタンの初期設定 self.jump = 'ready' self.jump_button = SpriteNode('emj:Blue_Circle', position=(320,50)) self.add_child(self.jump_button) mushroom = SpriteNode('emj:Mushroom') mushroom.anchor_point = (0.5, 0) mushroom.position = (self.size.w, self.player_height) self.add_child(mushroom) self.mushrooms = [mushroom] star = SpriteNode('plc:Star') star.anchor_point = (0.5, 0) star.position = (self.size.w/2, self.player_height + 300) self.add_child(star) self.stars = [star] self.items = [] for i in range(0, 5): for j in range(0,2): item = SpriteNode('plf:HudCoin') item.anchor_point = (0.5, 0) item.position = (80 + i * 50, self.player_height + 100 + 60 * j) self.add_child(item) self.items.append(item) self.charge = False self.power = 0 score_font = ('Futura', 40) self.score_label = LabelNode('0', score_font, parent=self) # The label is centered horizontally near the top of the screen: self.score_label.position = (self.size.w/2, self.size.h - 70) # The score should appear on top of everything else, so we set the `z_position` attribute here. The default `z_position` is 0.0, so using 1.0 is enough to make it appear on top of the other objects. self.score_label.z_position = 1 self.score = 0 self.walk_step = -1 |
上記例は1画面ですが、最初の画面でアイテムが描画されない場合でも予め画像を読み込んでおくことで、更新処理の動作を軽くすることができます。
更新処理の実装
更新処理は「update」メソッドに記述します。
更新処理に記述する内容は「キャラクターの移動処理」、「当たり判定」、「ジャンプ中チェック」です。
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 | def update(self): # キャラクターの移動 g = gravity() x = self.player.position.x y = self.player.position.y # 左右の移動 if abs(g.x) > 0.05: max_speed = 40 x = max(0, min(self.size.w, x + g.x * max_speed)) self.player.position = (x, y) # ジャンプ中か判定 self.check_jump(x,y) if self.charge and self.power < 100: self.power += 1 for item in self.mushrooms: item.position = (item.position.x -1, item.position.y) # 当たり判定 アイテムによって得点を変える self.check_item_hit(self.mushrooms, score=500) self.check_item_hit(self.items) self.check_item_hit(self.stars, score=1500) def check_jump(self, x, y): # 上昇時の処理 if self.jump == 'up': max_height = 180 + self.power up_speed = 10 y = max(self.player_height, min(self.size.h, y + up_speed)) self.player.position = (x, y) if y > max_height + self.player_height: self.jump = 'down' # 落下時の処理 if self.jump == 'down': down_speed = 10 y = max(self.player_height, min(self.size.h, y - down_speed)) self.player.position = (x, y) if y == self.player_height: self.jump = 'ready' self.charge = False # 当たり判定 def check_item_hit(self, items, score=100): # 縦横一致したらスコア加算処理をしてアイテムを消す for item in items: if abs(self.player.position.x - item.position.x) < 30 and abs(self.player.position.y - item.position.y) < 30: item.remove_from_parent() self.score += score items.remove(item) self.score_label.text = str(self.score) |
キー入力処理(タッチ処理)の実装
キー入力処理(タッチ処理)には「touch_began()」、「touch_moved()」、「touch_ended()」の3つのメソッドがあります。
touch_began():タッチした直後に動く処理
touch_moved():タッチしたまま指をスライドさせた場合に動く処理
touch_ended():指を画面から離した時に動く処理
今回は「touch_began()」と「touch_moved()」を使ってキャラクターをジャンプさせる処理を実装します。
touch_began()でpowerのカウントアップを開始。
touch_ended()でpowerの数値に応じた高さのジャンプをします。
touch_ended()でやることはジャンプへの状態遷移のみで、実際のジャンプ処理はupdate()で行います。
1 2 3 4 5 6 7 8 9 10 11 12 | def touch_began(self, touch): self.power = 0 self.charge = True def touch_ended(self, touch): # タップした位置の取得 touch_loc = self.point_from_scene(touch.location) # タップした位置がボタンならジャンプ if touch_loc in self.jump_button.frame: if self.jump == 'ready': play_effect('game:Boing_1') self.jump = 'up' |
関連記事>>Pythonista+sceneで最初に理解しておくべき3つのメソッド
終わりに
今回はゲームのリアルタイム処理について紹介しました。
ゲームを作る場合はほぼ必須の処理であるため必ず覚えておきましょう。
考え方自体は難しくありませんし、Pythonistaのsceneのように予めリアルタイム処理のメソッドや関数が用意されている場合もあります。
是非挑戦してみてください。
おすすめ記事Python入門者のための100日勉強方法
関連記事>>Pythonistaの使い方まとめ