Operation KiWi

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

Python3でRPGを作る時に使えそうな技術の断片 -ワールドマップ作成-

f:id:sakage24:20170628200017p:plain

たまにはプログラミング以外の記事でも書こうと思ったんですが、悲しいほどに何も浮かばなかったので…

以前RPG作ってた時に思いついたアイディアとか、テクニックとかを記事にします!!

www.kiwi-bird.xyz

以前に書いた記事でも使っているやり方です。ただ細かく説明していなかったのでPython初心者の方でも分かりやすいように説明します。

前提

  • 入門 Python 3を読んでいること(もしくは学習と並行でも、全然構いません)
  • Python3のインストール、初期設定を終えていること
  • pygame, pygletなどのゲームライブラリは利用しません。よく分かりません
    • print()などの標準出力を使ってゲームを表現していきます。要するに80年代のレトロなPCで動くローグライクゲーム風です。

入門 Python 3

入門 Python 3

ワールドマップの作り方

RPGと言えばワールドマップ!若草に覆われた広大な草原、屍肉を貪る魔物が跳梁する荒涼とした大地、りゅうおうが住んでいそうなお城…どれも欠かすことは出来ない要素ですよね。

RPGを作るならマップ制作は避けて通ることは出来ません。ただ、素のPython上で動かすゲームですので、当然スプライトとかは使えません。画像もNGです。頑張ってもアスキーアートぐらいですw

色々やり方はあると思うのですが、私の場合2次元リスト構造を使います。さあ、10*10の小さなマップを作ってみましょう。とっても簡単です。

run.pyを文字コードUTF-8で作成して下さい。

ソースコード

### run.py
import pprint

height = 10
width = 10
pp = pprint.PrettyPrinter()
world_map = [[0 for x in range(width)] for y in range(height)]
pp.pprint(world_map)

実行結果

[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

ほら、わずか8行でもうワールドマップが出来上がりましたwwwこんなのでいいの?って思う方もたくさんおられると思いますが、プログラムはなるべく単純な方が扱いやすくて管理もしやすいです。

実際の商業ゲーム(DQ, FF…)がどんなアプローチを取っているのか分かりませんが、視覚的にも作業量的にもこのやり方が良いんじゃないかと思ってますw

使っている技術

まだ10行にも満たないコードですが、パイソニスタとしてスルー出来ないコードが既に出てます。内包表記と呼ばれる形式です。

リスト内包表記とは?

入門 Python 3でもページによってはリスト包括表記とか若干表記ブレして紹介されていますが、わずか1行で配列から配列を生み出すとっても便利な表記法です。計算量も通常のfor文のネストより少なく、メモリ消費も少なくて見やすいといういい事ずくめな形式です。

初心者殺しとしても名高いですが、これを使いこなせるかどうかでPython初心者判定が出来る…かもしれませんw

world_map = [[0 for x in range(height)] for y in range(width)]

ちなみに初期化は以下のように手動でやっても平気です。私も内包表記ってなぁにって状態の時は以下のようにしていました。

### 手動バージョン
world_map = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]

こっちの方が大変ですが、初心者の方には分かりやすくて良いかもしれません…w

ただ、100*100とかになってくるとミスらずにやるのは無理なので、やっぱり頑張って内包表記を覚えましょう。

入門 Python 3

入門 Python 3

pprint

あと、pprintはプリティプリンターといいます。何も考えず普通に2次元配列をprint()すると以下のようになります。

[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

これでは何がなんだか分からないので、ちゃんとワールドマップに見えるようにpprintを使って整形してもらっているんですね。for文回して書いても良いのですが、結構めんどくさいので今回はこれで勘弁してください。

0が草原、1が村

それでは、数値に役割を当てはめましょう。2次元配列は対照表のようなものです。我々はそれを翻訳してゲームに見せているのです。

今回の例では、下記の通りとします。

  • 0 = 草原(grass)
  • 1 = 村(village)
import pprint

height = 10
width = 10
pp = pprint.PrettyPrinter()
world_map = [[0 for x in range(width)] for y in range(height)]
grass = 0
village = 1
pp.pprint(world_map)

だんだんワールドマップ作成関連の規模が大きくなってきました。迷宮に迷い込む前に迷わずクラス化しましょう!!

クラス化後

import pprint


class WorldMap(object):
    # MAPの高さ
    __height = 10
    # MAPの横幅
    __width = 10
    # 草原
    __grass = 0
    # 村
    __village = 1
    # height * widthの2次元配列
    __world_map = list()

    def __init__(self):
        WorldMap.__world_map = [[0 for x in range(WorldMap.__width)] for y in range(WorldMap.__height)]

# ↓値は直接操作せず、ゲッター、セッターを定義して使う(任意)↓

    @classmethod
    def get_height(cls):
        return cls.__height
    
    @classmethod
    def set_height(cls, n):
        cls.__height = n

    @classmethod
    def get_width(cls):
        return cls.__width
    
    @classmethod
    def set_width(cls, n):
        cls.__width = n

    @classmethod
    def get_world_map(cls):
        return cls.__world_map

    @classmethod
    def get_rect(cls):
        return cls.__height, cls.__width

    @classmethod
    def set_grass(cls, y, x):
        cls.__world_map[y][x] = cls.__grass

    @classmethod
    def set_village(cls, y, x):
        cls.__world_map[y][x] = cls.__village

pp = pprint.PrettyPrinter()
world_map = WorldMap()
world_map.set_village(y=2, x=4)
world_map.set_village(y=8, x=3)
pp.pprint(world_map.get_world_map())

…なんか急にでかいプログラムになってしまいました。やったことを順番に解説していきますね。

  • ワールドマップ関連の処理をWorldMapクラスにまとめました。
  • WorldMapクラスにはクラス変数として以下を持たせています。
    • ※注意点として、変数の先頭に__が先頭についているのは、他のクラスから直接値を呼び出したり、変更されないようにしているからです(マングリングと言います)。
    • __height
      • ワールドマップの高さです。
    • __width
      • ワールドマップの横幅です。
    • __grass
      • 先ほど決めた値です。草原=0ですね。初期値。
    • __village
      • 先ほど決めた値です。村=1です。
    • __world_map
      • ワールドマップ本体です。__height * __widthの2次元配列。
  • get_○○, set_○○
    • 値を直接変更したり参照したりするのはなんとなく気持ちが悪いので、間接的に操作するためのセッター、ゲッターと呼ばれる関数を定義しています。
    • ※Pythonは全てのオブジェクトが公開属性なので、本当はそんなことしなくてもよいのですが、暗黙の礼儀なのだそうです。
  • WorldMapクラス初期化時にマップを生成しています。
注意点

クラス変数やクラスメソッドに行われた変更は、全てのオブジェクトに影響します。

実行結果

[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

変更できました!!2つの村ができましたね。(1のことですよ)

いや、ていうかどこがマップなんだよ

はい、仰る通りですw

このままでは只の数字カレンダーです。次はマップのビューを作成していきましょう…と思いましたが、思ったより長くなったので次回に回します!

入門 Python 3

入門 Python 3

買って読んでおくこと!!!!w

辞書を使ったマップ作成方法もあります

こちらの方が汎用性があって良いかもしれません。

www.kiwi-bird.xyz

終わり

次回はマップビューの作成と、プレイヤー座標の管理とかその辺をやっていきましょう

www.kiwi-bird.xyz

ソースコード

www.dropbox.com