Pyxel 迷路ゲームの作成4.2点間の距離を調べる

Pyxel 迷路ゲームの作成(2点間の距離を調べる)

 Python向けレトロゲームエンジン「Pyxel(ピクセル)」を使ってのゲーム作成を通してプログラミングを学習します。

 次の2点を大きな目標にして,迷路ゲームを作成する例を紹介します。
 ・キャラクターが上下左右に歩くアニメーションを作る
 ・タイルマップの迷路を自動生成する

 前回の記事
 Pyxel 迷路ゲームの作成3.迷路の自動生成 - 勉強ボックス管理者ブログ

 前回の記事で迷路を作りゴールの座標を決めました。キャラクターのゴール到着を判定するために,キャラクターの座標とゴールの座標の距離を求めてみたいと思います。(また,スタート位置の座標もランダムに決めたいのですが,ゴールの座標と近すぎたら選びなおしにできるようにします)

 

2点間の距離を求める

 「次の座標を持つ2点間の距離を求めなさい。A(1,2),B(8,7)」という問題を解くときは,2点を結ぶ辺を斜辺とし,座標軸に平行な2つの辺を持つ直角三角形をつくり,三平方の定理を使います。

 2乗して74になる数を,74の平方根といいます。74の平方根は,正の数が√74,負の数が-√74です。斜辺の長さなので正の方の√74がABの距離になります。(√は根号といい,√74でルート74と読みます)

 Pythonのシェル上で計算してみましょう。

>>> ah = 8-1
>>> hb = 7-2
>>> val = ah**2 + hb**2
>>> val
74
>>> import math
>>> math.sqrt(val)
8.602325267042627

・ah**2はahの2乗です。(ah*ahと同じ)
・math.sqrt(x)がxの平方根(正の方)を返します。
 Pyxelにも平方根を返すsqrt()命令があるので,こちらを利用します。
 
 平方根三平方の定理は中学3年生の数学で学習します。平方根は特に大事で,その後の二次方程式三平方の定理の学習に必要な考え方なので,難しいときは出てくるたびに何度でも復習しましょう。

 

2つの円が重なっているか判定するプログラム

 2点がそれぞれ円の中心座標のケースを考えます。
 2つの円の中心間の距離(2点間の距離)が2つの円の半径の合計値未満のときは円が重なる位置関係にあります。
  円1 中心座標 (x1, y1) 半径 r1
  円2 中心座標 (x2, y2) 半径 r2

2つの円を描画して,円1の座標がマウス座標となるPyxelのプログラムを作ってみます。
distance.py(迷路ゲームには使用しないファイルです)

import pyxel
pyxel.init(128, 128, display_scale=3,capture_scale=3)

x1 = 30
y1 = 20
r1 = 16
x2 = 50
y2 = 60
r2 = 32

def getdistance():
    dx = x2 - x1
    dy = y2 - y1
    ret = pyxel.sqrt(dx**2 + dy**2)
    return ret

def update():
    global x1,y1
    x1 = pyxel.mouse_x
    y1 = pyxel.mouse_y
    pass

def draw():
    pyxel.cls(0)
    distance = getdistance()
    pyxel.text(1,1, "distance="+str(distance),7)
    if distance < r1 + r2:
        pyxel.circ(x1,y1,r1,8)
    else:
        pyxel.circb(x1,y1,r1,8)
    pyxel.circb(x2,y2,r2,5)
    pyxel.line(x1,y1,x2,y2,7)
    pyxel.pset(x1,y1,8)
    pyxel.pset(x2,y2,5)

pyxel.run(update,draw)

実行結果
 

・getdistance()関数内で円の中心間の距離を計算して値を返します。

・中心間の距離が,2つの円の半径の合計値より小さいときに円1を塗りつぶしています。

 この計算は,円同士の衝突判定(ヒットチェック)に使われます。(サイズの異なるキャラクター同士の接触を画像の中心座標を使って調べることができます)

 今回作成している迷路ゲームでは,プレイヤーキャラクターとゴールの絵のサイズが同じなので,左上座標同士の距離判定にします。
 

【ソースコード】はこちらのリンクから確認してください(Googleドキュメント)

 

Stageクラスの作成(続き)

 本記事で作成するゲームのソースファイルとリソースファイルは下記を参照してください。
 Googleドライブ 迷路ゲーム04 - Google ドライブ
 最終版ソース game/pyxel/maze_random at main · benkyoubox/game · GitHub

04 ゴール到達判定の追加

 引数で指定したxy座標がゴールに触れているか判定するメソッドを追加します。

【ソースコード】はこちらのリンクから確認してください(Googleドキュメント)

stage.py抜粋

・・・
class Stage:
    ''' 迷路表示 '''
・・・
    def chkgoal(self,x,y,limit=15):
        ''' ゴール到達判定 '''
        ret = False
        dx = self.gx - x
        dy = self.gy - y
        dis = pyxel.sqrt(dx**2+dy**2)
        if dis <= limit :
            ret = True
        return ret

・chkgoal()メソッド
 先の実験プログラムでは,16ドットのキャラクターの円の半径8ドットの合計値で16ドットで計算しましたが,少し内側にずらして15ドットに近づいたらTrueを返すようにしました。

・・・
# テストコード
if __name__ == '__main__':
    pyxel.init(512, 512, display_scale=1,capture_scale=1)
    pyxel.load("alice.pyxres")
    stg = Stage()
    size = 42
    pyxel.mouse(True)
    x = y = 0

    def update():
        global x,y
        if pyxel.btnp(pyxel.KEY_SPACE):
            stg.makemaze(size)

        x = pyxel.mouse_x
        y = pyxel.mouse_y

    def draw():
        stg.draw()
        pyxel.text(1,1,"Stage Test",7)
        if stg.chkgoal(x,y) :
            pyxel.text(1,10,"Goal",7)

    pyxel.run(update,draw)

・テストコードでマウス座標がゴールに触れたら画面左上にGoalの文字を表示させて,動作を確認します。
実行結果

 

05 スタート位置設定

 迷路作成時にスタート位置もランダムに決めるようにします。04で追加したchkgoal()メソッドでゴールから離れているか判定します。

【ソースコード】はこちらのリンクから確認してください(Googleドキュメント)

stage.py抜粋

・・・
class Stage:
    ''' 迷路表示 '''

    def __init__(self):
        self.sx = 8
        self.sy = 8
        self.gx = 40
        self.gy = 32
        pyxel.tilemap(TM).refimg = IMG_NO
        return

・スタート位置のx座標,y座標を変数に追加

・・・
    def makemaze(self,size):
        ''' 迷路作成 引数:size 一辺のタイル数 '''

        size = min(size,252)    # タイルマップ最大256
        self.size = size
        self.width = (size+1)*8
        self.height = (size+1)*8

        self.initmaze()
・・・

・sizeで大きな値を指定したときに,タイルマップの範囲に収まる6の倍数が最大となるように判定を追加しました。
・sizeは迷路作成時のタイル数なので,ステージ全体の幅と高さはドットの値にして保持させます。(スクロール処理にも使う)

    def setgoal(self):
        ''' ゴール作成 '''
・・・
        rs = 1
        re = self.size // 2
        if self.gx < self.width / 2 :
            rs = re
            re = self.size - 1

        while True:
            sxi = pyxel.rndi(rs,re)
            syi = pyxel.rndi(1,self.size-1)
            if (
                    TILE_BLANK == pyxel.tilemap(TM).pget(sxi,syi)
                and TILE_BLANK == pyxel.tilemap(TM).pget(sxi+1,syi)
                and TILE_BLANK == pyxel.tilemap(TM).pget(sxi,syi+1)
                and TILE_BLANK == pyxel.tilemap(TM).pget(sxi+1,syi+1)
                and self.chkgoal(sxi * 8,syi * 8,limit=200) == False
                ):
                self.sx = sxi * 8
                self.sy = syi * 8
                break;
        
        return
・・・

・ゴール位置がステージ左半分にあれば,スタート位置は右半分に,右半分にあればスタートは反対側となるように判定をしています。x座標はドット数のwidthと計算して,乱数の範囲はsizeのタイル数で扱っています。
・if文の条件式で self.chkgoal(sxi * 8,syi * 8,limit=200) == False を判定してゴールまでの距離が200以下ならやり直しになります。(この値はsizeに比例させても良いかと思います)

# テストコード
if __name__ == '__main__':
・・・
    def draw():
        stg.draw()
        pyxel.rect(stg.sx,stg.sy,16,16,8)
        pyxel.text(1,1,"Stage Test",7)
        if stg.chkgoal(x,y) :
            pyxel.text(1,10,"Goal",7)

    pyxel.run(update,draw)

・テストコードでスタート位置に赤い矩形を描画します。
実行結果

 ややパターンが決まってしまうところがあるので,右半分左半分の判定はなくしてsizeに応じてスタート位置が離れる方式が良いかもしれませんが,とりあえずはこのまま作成を続けます。

 

06 スクロール処理追加

 指定したxy座標が画面内に収まるようにスクロールを制御する処理を,Stageクラスのupdate()メソッドに追加します。(スクロール処理の考え方は「Pyxel 画面をスクロールさせる例(アクションゲーム) - 勉強ボックス管理者ブログ」を参照してください)


【ソースコード】はこちらのリンクから確認してください(Googleドキュメント)

stage.py抜粋

・・・
class Stage:
    ''' 迷路表示 '''

    def __init__(self):
        self.scroll_x = 0
        self.scroll_y = 0
        self.left_border = pyxel.width *0.33
        self.right_border = pyxel.width *0.66 -16
        self.upper_border = pyxel.height * 0.33
        self.bottom_border = pyxel.height * 0.66 -16
        self.sx = 8
        self.sy = 8
        self.gx = 40
        self.gy = 32
        pyxel.tilemap(TM).refimg = IMG_NO
        return
・・・

・スクロール位置を管理する変数 self.scroll_x と self.scroll_y を追加。この値でカメラ位置を設定します。
・画面端の1/3を超えるとスクロールするように範囲を設定しました。

    def update(self,x,y):
        ''' 画面のスクロール '''

        if x < self.scroll_x + self.left_border:
            self.scroll_x = x - self.left_border
            if self.scroll_x < 0:
                self.scroll_x = 0
        if self.scroll_x + self.right_border < x:
            self.scroll_x = x - self.right_border
            if self.width - pyxel.width < self.scroll_x:
                self.scroll_x = self.width - pyxel.width
            
        if y < self.scroll_y + self.upper_border:
            self.scroll_y = y - self.upper_border
            if self.scroll_y < 0:
                self.scroll_y = 0
        if self.scroll_y + self.bottom_border < y:
            self.scroll_y = y - self.bottom_border
            if self.height - pyxel.height < self.scroll_y:
                self.scroll_y = self.height - pyxel.height

        return

・update()メソッドに引数を追加して,スクロール位置を更新します。

    def draw(self):
        pyxel.cls(1)
        pyxel.camera()
        pyxel.bltm(0,0, TM, self.scroll_x,self.scroll_y, pyxel.width,pyxel.height, 0)
        pyxel.camera(self.scroll_x,self.scroll_y)
        # ゴール表示
        pyxel.blt(self.gx,self.gy, 0, 0,112, 16,16, 0)

        return

・タイルマップの表示開始座標をスクロール位置にして,カメラ位置も設定します。

# テストコード
if __name__ == '__main__':
    pyxel.init(240, 160, display_scale=3,capture_scale=3)
    pyxel.load("alice.pyxres")
    stg = Stage()
    size = 42
    x = y = 8

    def update():
        if pyxel.btnp(pyxel.KEY_SPACE):
            stg.makemaze(size)
            x = stg.sx
            y = stg.sy

        if pyxel.btn(pyxel.KEY_UP):
            y -= 4
        if pyxel.btn(pyxel.KEY_DOWN):
            y += 4
        if pyxel.btn(pyxel.KEY_LEFT):
            x -= 4
        if pyxel.btn(pyxel.KEY_RIGHT):
            x += 4
        stg.update(x,y)

    def draw():
        stg.draw()
        pyxel.rect(stg.sx,stg.sy,16,16,8)
        pyxel.text(1,1,"Stage Test",7)
        if stg.chkgoal(x,y) :
            pyxel.text(x,y-10,"Goal",7)
            pyxel.blt(x,y, 0, 0,80, 16,16,2)
        else:
            pyxel.blt(x,y, 0, 0,16, 16,16,2)

    pyxel.run(update,draw)

・テストコードでキャラクターの上下移動と描画を行います。
実行結果

 これでランダムなステージを作るクラスができました。
 次回はアプリ本体のゲームの流れを作成します。

次の記事
kinutani.hateblo.jp

 

関連記事

kinutani.hateblo.jp