Pygame Zero の連載記事第2回です。今回はランダムウォークのアルゴリズムを使って、ボールをジグザグに動き回らせてみます。
Pythonでランダムウォーク
次の移動地点がランダム(確率的)に決定される運動をランダムウォークといいます。日本語では酔歩とか乱歩ということもあります。酔歩というのは言い得て妙ですね。すぐ後で実装して確認しますが、まさに酔っ払いのごとく、ふらふらとあちこちを動き回ります。実際のゲームでは、全くでららめに動くキャラクターというのは使い道がなさそうです。しかし、ランダムウォークの基本を知っておくと、予測不能な動きを交えながら全体としてある方向に動くようなキャラクターも作れます。
単純なランダムウォークのモデルを実装してみましょう。前回と同様に、最初に画像を用意しておきます。適当な場所に mygame フォルダを作成し、その中に imgages という名前の画像保管用フォルダを作成します。次に、KENNEY のサイトの Rolling Ball Assets のページに行って、rollingballassets_kenney.zip ファイルをダウンロードしてください。rollingballassets_kenney.zip を開いて、PNG フォルダ、Default フォルダの順に開き、ball_red_small.png を images フォルダにドラッグ & ドロップでコピーします。さらにファイルの名前を ball_red.png に変更しておきます。以下のサンプルコード (.py ファイル) はすべて、mygame フォルダに保存してください。
四方向に転がるボールを考え、どの方向も等しい確率で選択されるとします。つまり、上下左右どの方向に転がる確率も等しく 1/4 ずつです。以下のコードを実行すると、赤いボールが動き回ります。
# random_walk_01.py
import pgzrun
import random
WIDTH = 600
HEIGHT = 600
ball = Actor('ball_red', center=(300, 300))
# ボールの速度
v = 2
# 4方向の動き
motion_list = [(v, 0), (-v, 0), (0, v), (0, -v)] # (1)
# 移動方向を格納するリスト
motion = 0
def update():
global motion
motion = random.choice(motion_list) # (2)
ball.x += motion[0]
ball.y += motion[1]
def draw():
screen.clear()
ball.draw()
pgzrun.go()

(1) では、(x 方向の移動距離、y 方向の移動距離) を 4 種類リストに格納しています。たとえば、(-v, 0) はスクリーンの x 方向のマイナス方向に v だけ動く移動を表しています。
(2) は motion_list から動き方を等しい確率でランダムに選択して、変数 motion に格納するコードです。random.choice() という便利な関数があるおかげで、if で条件分枝させなくてすんでいます。
random_walk_01.py はボールが小刻みに動きすぎて、実際のゲームでは使えそうにありません。そこで、経過時間を格納する変数を用意して、ある程度一定方向に進んでから方向転換するようにしてみます。
# random_walk_02.py
import pgzrun
import random
WIDTH = 600
HEIGHT = 600
ball = Actor('ball_red', center=(300, 300))
v = 2
count = 0 # カウンター
motion_list = [(v, 0), (-v, 0), (0, v), (0, -v)]
motion = random.choice(motion_list)
def update():
global count
global motion
count += 1
if count > 30: # 前の方向転換から0.5秒経過したら
motion = random.choice(motion_list)
count = 0 # カウンターを初期化
ball.x += motion[0]
ball.y += motion[1]
def draw():
screen.clear()
ball.draw()
pgzrun.go()
count には update() が呼び出された回数が記録されます。update() は 1 秒間に 60 回呼び出されるので、count が 60 になったとき、1 秒が経過していることになります。random_walk_02.py では、0.5 秒経過するごとに random.choice() を再実行して、進む方向を変えるようにしてあります。random_walk_02.py を実行すると、ボールが揺れることなく、時々向きを変えながら動き回ります。
次はボールが四方向だけでなく、360°自由な方向に移動できるようにしてみましょう。一定時間が経過するごとに、次に進む方角θを 0 ~ 360°の範囲でランダムに決めます。図のように、x 軸から時計回りに角度θを測り、速度が v ピクセルに設定されているとします。

このとき、x 座標と y 座標の更新幅は、それぞれ三角関数を用いて
⊿x = v cosθ, ⊿y = v sinθ
と表されます。次のコードを実行して、ボールが色々な方向へ動き回る様子を確認してください。
# random_walk_3.py
import pgzrun
import random
import math
WIDTH = 600
HEIGHT = 600
ball = Actor('ball_red', center=(300, 300))
# ボールの速度
v = 2
# ボールの移動角度
angle = 0
# タイムカウンター
count = 0
def update(delta_x=0, delta_y=0):
global angle
global count
count += 1
if count == 30: # 前回の方向転換から0.5秒経っていたら
angle = 2 * math.pi * random.random()
count = 0
delta_x = v * math.cos(angle) # x方向の移動距離
delta_y = v * math.sin(angle) # y方向の移動距離
if ball.x < 10: # ボールが左端に来たら
delta_x += abs(delta_x) # (3)
elif ball.x > WIDTH - 10: # ボールが右端に来たら
delta_x -= abs(delta_x) # (4)
if ball.y < 10: # ボールが上端に来たら
delta_y += abs(delta_y)
elif ball.y > HEIGHT - 10: # ボールが下端に来たら
delta_y -= abs(delta_y)
ball.x += delta_x
ball.y += delta_y
def draw():
screen.clear()
ball.draw()
pgzrun.go()
今回のコードでは境界条件も設定してあるので、ボールがスクリーンの外へ行かないようになっています。たとえば、(3) ではボールが左端にきたとき、abs() 関数で delta_x の絶対値をとって、ボールが必ず x 軸のプラス方向へ動いて端から離れるようにしてあります。逆に (4) では、ボールが右端に来たとき、必ず x 軸のマイナス方向へ動くようにしています。
コメント