Operation KiWi

一生使える言語はPythonだと信じてる

Python3でRPGを作る時に使えそうな技術の断片 -追跡と逃避-

f:id:sakage24:20170628200017p:plain

仮にもRPG作ってるのに敵がいないっていうのもおかしいなと思ったので、何回かに分けてプレイヤーに敵対的なNPCを作っていきます。

前回のあらすじ

www.kiwi-bird.xyz

色つけたり、画面をクリアする方法を学びましたね。

今回やること

  1. npc用のManagerクラスを作ろう
  2. 追跡と逃避
  3. ViewFilterクラスへの追記

以上です。まずはplayerとnpcをクラスに分けましょう。npcは友好的もしくは敵対的な属性を持っているようにしましょう。

やってみよう

npc用のManagerクラスを作ろう

npcs.py
import sources.coordinates


class Manager(object):
    def __init__(self, name="名無しのジョン", hostile=False):
        self._hostile = hostile  # 敵対的であるか?
        self._name = name
        self.current_position = sources.coordinates.Manager()
        self.current_position.y = 2
        self.current_position.x = 2

    def get_reaction(self):
        return self._hostile

    # 単純な追跡アルゴリズム

    def chase(self, player_pos):
        pos = self.current_position.position
        if pos[0] > player_pos[0]:
            self.current_position.y -= 1
        elif pos[0] < player_pos[0]:
            self.current_position.y += 1
        if pos[1] > player_pos[1]:
            self.current_position.x -= 1
        elif pos[1] < player_pos[1]:
            self.current_position.x += 1

    # 単純な逃避アルゴリズム

    def escape(self, player_pos):
        pos = self.current_position.position
        if pos[0] > player_pos[0]:
            self.current_position.y += 1
        elif pos[0] < player_pos[0]:
            self.current_position.y -= 1
        if pos[1] > player_pos[1]:
            self.current_position.x += 1
        elif pos[1] < player_pos[1]:
            self.current_position.x -= 1

npcs.pyはインスタンス変数hostileを持っています。これがTrueなら敵、Falseなら友好的としてます。現在のところ特に活かされていませんw

特筆すべきは、プレイヤーを追跡するchase()関数、プレイヤーから逃避するescape()関数を持っています。非常に単純ですが、単純故に高速ですw

追跡と逃避

ちなみに追跡と逃避はどちらかを作成したら後は、加減算を逆転させるだけで出来ちゃいます。

ロジックとしてはとても単純で、プレイヤーの座標に対して自身の座標を近づけていくだけなんですね。

ただし、これらの関数はプレイヤーとの間に壁や障害物があると機能しません。

壁や障害物を避けるには、最短経路を求めるアルゴリズム(ダイクストラ法やA*法)が必要となります。後に解説する…かも?

www.kiwi-bird.xyz

上記に関しては、こちらの本が非常に参考になります。Pythonのコードはありませんが、CだろうがPythonだろうが、アルゴリズムは普遍的なものなので、あまり問題はありませんでした。

こちらでは紹介していない、最短経路アルゴリズムやゲームにおける確率の問題など、豊富な事例が紹介されています。

ゲーム開発者のためのAI入門

ゲーム開発者のためのAI入門

まあ、とりあえず簡単そうなのから解説していくスタイルで!!

ViewFilterクラスを追記しよう

npcの追加によって、create_view(),pretty_view()関数も修正する必要が出てきましたね。

# view.py
from sources.utils import TextDecoration as deco


def create_view(self, player_pos, exists_npc_list):
    def map_evaluation(npc, i, j):
        s = ""

        def terrain(s):
            if i == player_pos[0] and j == player_pos[1]:
                s = "@"
            elif self._view[i][j] == 0:
                s = "ー"
            elif self._view[i][j] == 1:
                s = "村"
            return s

        if npc is not None:
            npc_positions = [n.current_position.position for n in npc]
            for npc_pos in npc_positions:
                if (i, j) == npc_pos:
                    s = "&"
                    self.printer(width=j, strings=s)
                    return True
            self.printer(width=j, strings=terrain(s))
        else:
            self.printer(width=j, strings=terrain(s))

    for i in range(self._view_height):
        for j in range(self._view_width):
            map_evaluation(npc=exists_npc_list, i=i, j=j)

def pretty_view(self, player_pos, exists_npc_list):
    def map_evaluation(npc, i, j):
        s = ""

        def terrain(s):
            if i == player_pos[0] and j == player_pos[1]:
                s = deco.bgcolor_red(deco.yellow("@"))
            elif self._view[i][j] == 0:
                s = deco.bgcolor_green("ー")
            elif self._view[i][j] == 1:
                s = deco.bgcolor_blue("村")
            return s

        if npc is not None:
            npc_positions = [n.current_position.position for n in npc]
            for npc_pos in npc_positions:
                if (i, j) == npc_pos:
                    s = deco.bgcolor_red(deco.yellow("&"))
                    self.printer(width=j, strings=s)
                    return True
            self.printer(width=j, strings=terrain(s))
        else:
            self.printer(width=j, strings=terrain(s))

    for i in range(self._view_height):
        for j in range(self._view_width):
            map_evaluation(npc=exists_npc_list, i=i, j=j)

run.pyを書き換えよう

だんだんrun.pyが大きくなってきています。初期化処理とかは別のクラスに移譲したほうが良いかもしれませんね。要検討!!!

# run.py
import copy
import os
from sources.maps import WorldMap
from sources.view import ViewFilter
from colorama import init
import sources.player
import sources.npcs

# windowsOS = nt
if os.name == 'nt':
    init()

# 初期設定
world_map = WorldMap()
player = sources.player.Manager(name="あなた")
npc_bob = sources.npcs.Manager(name="bob", hostile=True)
npc_instance_lists = [npc_bob]
world_map.set_village(y=2, x=4)  # ほげ村
world_map.set_village(y=8, x=3)  # ぴよ村
map_viewer = ViewFilter(v=(copy.deepcopy(world_map.get_world_map())), rect=world_map.get_rect())
while True:
    map_viewer.pretty_view(exists_npc_list=npc_instance_lists, player_pos=player.current_position.position)
    player.move(rect=world_map.get_rect())
    for index, npc in enumerate(npc_instance_lists):
        npc.chase(player_pos=player.current_position.position)
        if player.current_position.position == npc.current_position.position:
            # 衝突時の処理
            del npc_instance_lists[index]
            npc_instance_lists.append(sources.npcs.Manager("hoge"))

    print("\033[2J")

実行結果

f:id:sakage24:20170704183325g:plain

gifにすると色も無くなってるし、残像も凄いことになってますねwwちゃんと動いてますよ!!npcがプレイヤーを追跡しているのがわかると思います。もしもプレイヤーに重なったら…みたいな処理を入れても良いんですが、とりあえず今回はここまで!!

出来ればコピペで満足せず、色々弄ってみて下さい。ソースコードは置いてあるので、破壊しても平気ですよw

オススメの教科書類

指定教科書

入門 Python 3

入門 Python 3

これ無しでPython開発は無理です。私的にはあまり演習問題の類は説かずに知りたいことがあったら読む、辞書的な感じで使っています。

  • スライスって何?
  • リスト内包表記って何なんだよあれ?
  • リストとタプルはどう違うの?
  • そもそも集合って何に使うの?

少しでも上記に当てはまるなら購入しましょう。せっかくPythonを使っているんですから、有効に使えるようにしてみませんか?

副読本

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

  • 他言語を得意としているプログラマがPythonを書いた時にやりがちなこと…
  • ゲームの保存に使えるpickle
  • この機能はこう使うべきである
  • while, for文のelseを使うべきではない理由
  • Pythonでセッター, ゲッターを使うべきではない理由

みたいに、様々な事例とともにPythonのベストノウハウ、やってはいけないアンチパターンが紹介されています。pycon2016でも確か推されてましたw

初心者から上級者まで、手元に置いておくべき本です。

副読本2

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

ノンプログラマー向けとありますが、経験者の方も結構アイディアを貰える本でした。 序盤は文法の解説ですので、読み飛ばせます。後半は様々な局面で使えそうな自動化処理の書き方が載っていました。

ゲームの所持アイテムを辞書で表現しよう…みたいにゲームプログラマーに役立つ演習もありましたw

副読本3

ゲーム開発者のためのAI入門

ゲーム開発者のためのAI入門

C言語での解説が主ですが、しっかりとした説明があるので、だいたい分かりました。Pythonでも使える知識がほとんどです。ただ、ファジー理論など、数学的な要素は私にはどうともしがたい躓きポイントでしたw

ローグライクゲームに使える最短経路探索法やAIの作成法、確率の基本まで教えてもらえる良本でした!!

ソースコード

www.dropbox.com

終わり

気を抜くと6000文字とかいってるんですね~。気をつけないと…次回は何するかまだ未定です。

続き

www.kiwi-bird.xyz