Pythonistaのsceneライブラリで追加したオブジェクトはそのままだと他のオブジェクトに接触してもすり抜けます。
よって「プレイヤーがアイテムに接触したら拾う」、「プレイヤーが敵に接触したらダメージを受ける」などの処理を作成したい場合は当たり判定(オブジェクト同士がぶつかったかどうかの処理)の作成が必要です。
本記事では、Pythonista+sceneで当たり判定を作成する方法を紹介します。
目次
Pythonista+sceneで当たり判定の前提情報と知識
当たり判定を作るために必要な前提情報と知識は以下の4つです。
・オブジェクトのサイズ
・オブジェクトの接触の方向
・当たり判定の距離の計算方法
・当たり判定の処理を追加する場所
オブジェクトのサイズ
画面に追加されたオブジェクトは位置情報を保持しているため、これを基準に接触判定を考えます。
しかし、各オブジェクトの持つ位置情報は一点のみです。
なので当たり判定を作る場合はオブジェクトのサイズを考慮する必要があります。
オブジェクトのサイズはprint文で調べることができます。
1 2 3 | from scene import * player = SpriteNode('spc:PlayerShip2Blue') print(player.size) |
オブジェクトの接触の方向
接触の方向は上下左右の4つです。
接触した方向によって当たり判定の比較方法が変わります。
接触の方向 | 比較方法 |
左に接触した場合 | x方向距離プラスで比較 |
右に接触した場合 | x方向距離マイナスで比較 |
上に接触した場合 | y方向距離プラスで比較 |
下に接触した場合 | y方向距離マイナスで比較 |
当たり判定の距離の計算方法
オブジェクトの「x座標」と「y座標」をそれぞれ比較し、「計算した結果の絶対値がxyとも一定値以下」の場合は接触とみなします。
注意しなければならないのは計算結果がマイナスの時は絶対値で見る必要があることです。
絶対値はabs()関数で取得できます。
画像サイズは実際のオブジェクトよりも大きめの枠で取られていることが多いので、判定は30〜50pxくらい小さめに作っておくと良いです。
あとは実際に動かしてみて違和感があったら判定の値を微調整しましょう。
当たり判定の処理を追加する場所
当たり判定はupdate()メソッドの処理内でチェックします。
判定の部分は切り出して関数化しておくと良いです。
Pythonista+scene当たり判定の実装例
上記を踏まえてPythonista+scene当たり判定を作成します。
今回はキャラクターを動かしてアイテムを取る処理を作成です。
当たり判定以外のアクションの説明は割愛するのでご了承ください。
以下はアイテムと接触した時にスコアを加算する処理です。
アイテムはプレイヤーと接触した場合画面から消去します。
1 2 3 4 5 6 7 8 9 10 11 | # update()メソッドから呼び出す 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) |
上記処理では「プレイヤーとオブジェクトのx方向距離の絶対値が30以下」かつ「プレイヤーとオブジェクトのy方向距離の絶対値が30以下」の場合に接触とみなします。
今回は、だいだい同じ大きさのオブジェクトのため判定はかなり簡略化していますが、大型のオブジェクトを使いたい場合は左右や上下で値を変えるなどかなり複雑な計算が必要です。
上記当たり判定を利用したプログラムの実装例は以下の通り。
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | # coding: utf-8 from scene import * from sound import * 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 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 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' 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) if __name__ == '__main__': run(Game(), PORTRAIT, show_fps=True) |
大半のゲームでは当たり判定が必要なので、今回の記事やサンプルゲームを参考にしつつ作成してみてください。
以上、Pythonista+sceneで当たり判定を作成する方法でした。
Pythonistaについては「Pythonistaの使い方まとめ」にまとめています。