こんにちは。
「Pythonでつくる ゲーム開発 入門講座」の
Chapter11,12(本格RPGを作ろう!全編・後編)を基にRPGを作っていきます。
前回はマップの1マスの大きさを変えました。
今回はマップ(エリア)ごとにモンスターエリアという値を追加して
出現するモンスターを変えます。
目次
モンスターエリア
マップごとに出現するモンスターを変えます。
例えば、以下の図の四角で囲ったエリアで
モンスターに遭遇した時に
色が異なるエリアでは出現するモンスターを変えます。
※同じ色のエリアは同じモンスターが出現です。

まず、スプレッドシートのモンスターシートに
エリアの列を追加します。(L列)

次にマップにも同じくエリアを追加します。
今までは以下のように、
mapsにはマップの画像の数字のみ指定していました。
※maps.py です。

これを以下のような値に変更します。
maps = [["0000", "0101",......]]
"0000"の前の2つ(00)をエリア、
後ろの(00)をマップの画像の数字にします。
マップのエリアとモンスターシートのエリアが
一致するモンスターが出現するようにします。
ちなみにpythonのリストは
以下のようにスライス(切り取り)します。
list[start:end]
※start と end はインデックス。
startを省略すると最初から、
endを省略すると最後まで
スライスします。
※詳細はこちら。
ex_list = ["1234"]
の場合、
ex_list[:2] = "12"
ex_list[2:] = "34"
です。
なので、mapsは
[:2]とするとモンスターエリア(最初の2文字)
[2:]とするとマップの画像の数字(最後の2文字)
となります。
モンスターエリアの追加
コードにモンスターエリアを追加して行きます。
今回は珍しく全てのファイルを変えます。
chara.py
最初に chara.py です。
(省略)
mon_name = ws_mon.col_values(2) # B列 名前
mon_maxhp = ws_mon.col_values(4) # D列 最大HP
mon_quick = ws_mon.col_values(8) # H列 素早さ
mon_atk = ws_mon.col_values(9) # I列 攻撃力
mon_dfs = ws_mon.col_values(10) # J列 防御力
mon_exp = ws_mon.col_values(11) # K列 経験値
mon_area = ws_mon.col_values(12) # L列 エリア ⇦追加
(省略)
def get_mon_area_list():
return mon_area
mon_area 追加しています。
また、このmon_areaを返す関数get_mon_area_listを
最後に追加しました。
後述しますが、battle.pyで使います。
maps.py
次に maps.py ですが、
これはファイルをアップしました。
※mapsは手打ちで作っているので
エライ苦労しています(^_^;)
画像データを読み取って
楽に作ることも出来そうな・・
私には出来ませんが。
one_hour_dungeon.py
続いて one_hour_dungeon.py です。
最初に使わなくなった変数が
残っていたので消します(^_^;)
(省略)
potion = 0 # ポーションを使える回数(使うと全快する) # 削除
blazegem = 0 # blazeを使える回数 # 削除
COMMAND = [["こうげき", "どうぐ"], ["じゅもん", "そうび"], ["ぼうぎょ", "にげる"]] # 削除
TRE_NAME = ["Potion", "Blaze gem", "Food spoiled.", "Food +20", "Food +100"] # 削除
(省略)
def main(): # メイン処理
global speed, idx, tmr, floor, fl_max
global n_map
global food, potion, blazegem # 削除
そして変更です。
変更前
(省略)
def main(): # メイン処理
(省略)
elif idx == 1:
idx, tmr = move.main(screen, clock, font, fontS, n_map, player)
elif idx == 2: # マップの設定
n_map = maps.get_map(0, player)
idx = 1
elif idx == 10: # 戦闘開始
idx, tmr = battle.main(screen, clock, font, fontS, player)
変更後
(省略)
def main(): # メイン処理
global speed, idx, tmr, floor, fl_max
n_map = [[]] # 0:床 1:宝箱 2:繭 3:階段 9:壁
mon_area = 99 # 追加
(省略)
elif idx == 1:
tmr = 0
idx, mon_area = move.main(screen, clock, font, fontS, n_map, player)
(省略)
elif idx == 10: # 戦闘開始
tmr = 0
idx = battle.main(screen, clock, font, fontS, player, mon_area)
モンスターエリアを入れる変数mon_areaを追加しました。
初期値(99)は適当です。
move.pyのmainの戻り値と
battle.pyのmainの引数に
mon_areaを追加しました。
idx, mon_area = move.main(screen, clock, font, fontS, n_map, player)
idx = battle.main(screen, clock, font, fontS, player, mon_area)
move.pyでプレイヤーが移動中にモンスターと遭遇した時に
モンスターエリアを取得して、それをbattle.pyに渡します。
あと、変更前はtmrをなぜか戻り値として
受け取っていましたが、0固定で返していたので
tmr = 0
にしています。
※one_hour_dungeon.py では
tmrの意味が薄れてきている気がしますが一応(^_^;)
move.py
更に続いて move.py です。
まず、型変換をします。
これはmapsが数値から文字列へ変更した為です。
変更前は数値。
maps = [[16, 16, ...]]
変更後は文字列。
maps = [["0000", "0101",......]]
このmapsは move.py で
変数n_map として使っています。
それをimgmap(マップの画像)が
imgmap[n_map[dy][dx]]
のようにインデックスにしています。
※imgmap = [pygame.image.load("map/0.png"), ...]
なので、n_mapの値は数値(int型)で
ある必要があります。
と、いうわけで型変換です。
(省略)
def draw_map(bg, fnt, player):
(省略)
if 0 <= dx and dx < map_w and 0 <= dy and dy < map_h:
bg.blit(imgmap[int(n_map[dy][dx][2:])], [X, Y])
(省略)
def move_player(key, player): # 主人公の移動
(省略)
if key[K_UP] == 1:
player.d = 0
if int(n_map[player.y-1][player.x][2:]) != 9:
player.y = player.y - 1
if key[K_DOWN] == 1:
player.d = 1
if int(n_map[player.y+1][player.x][2:]) != 9:
player.y = player.y + 1
if key[K_LEFT] == 1:
player.d = 2
if int(n_map[player.y][player.x-1][2:]) != 9:
player.x = player.x - 1
if key[K_RIGHT] == 1:
player.d = 3
if int(n_map[player.y][player.x+1][2:]) != 9:
player.x = player.x + 1
(省略)
def main(screen, clock, font, fontS, use_map, player):
(省略)
elif idx == 10: # 戦闘 モンスターエリアを返す
return 10, int(n_map[player.y][player.x][:2])
以下のように変更しています。
bg.blit(imgmap[n_map[dy][dx]], [X, Y]) # 変更前
bg.blit(imgmap[int(n_map[dy][dx][2:])], [X, Y]) # 変更後
先に述べたようにn_mapの後ろの2文字が
マップの画像になるので、[2:]でスライスしています。
他にもいくつか同様の箇所がありますので
同じく変更します。
※同じ変更をする時、VSCodeでは
こちらのショートカットが便利です。
あと、以下のようにn_mapを
使っている箇所(移動の制限)が
ありますので、ここも変更します。
if n_map[player.y-1][player.x] != 9: # 変更前
if int(n_map[player.y-1][player.x][2:]) != 9: # 変更後
この移動の制限は画像も変わったことで
おかしくなっているので
別回で修正したいと思います。
変更箇所としてはもう一つ、
戻り値をモンスターエリアにしています。
変更前はtmrの0を返していました。(変更前:return 10, 0)
elif idx == 10:
return 10, int(n_map[player.y][player.x][:2])
※モンスターエリアなので
n_mapの最初の2文字([:2])です。
one_hour_dungeon.py 経由で
battle.py へモンスターエリアを渡します。
battle.py
最後に battle.py です。
まず、モンスターエリアを受け取り
変数mon_areaに格納します。
(省略)
def main(screen, clock, font, fontS, player, area): # areaを追加
(省略)
mon_typ = "" # モンスターの種類が複数:"まもののむれ"、1種類:モンスターの名前
mon_area = area # プレイヤーのいる場所のモンスターエリア
(省略)
if idx == 9: # ゲームオーバー
if tmr == 10:
monster.clear() # モンスターオブジェクトを削除
dead_monster.clear()
return 0 # 変更前は return 0, 0
(省略)
elif idx == 10: # 戦闘開始
if tmr == 1:
mon_typ = init_battle(screen, mon_area) # mon_areaを追加
(省略)
elif idx == 22: # 戦闘終了
idx = 1
monster.clear() # モンスターオブジェクトを削除
dead_monster.clear()
btl_exp = 0
return 1 # 変更前は return 1, 0
また、ゲームオーバー(idx9)や
戦闘終了時(idx22)に
戻り値を1つにしています。
one_hour_dungeon.py で
idx, tmr = battle.main(screen, clock, font, fontS, player)
から
idx = battle.main(screen, clock, font, fontS, player, mon_area)
と変更した為です。
次に関数init_battleにmon_areaを渡して
出現するモンスターを指定します。
変更前
def init_battle(bg): # 戦闘に入る準備をする
global monster
num_enemy = random.randint(1, 4) # 出現モンスターの数
sum_x = 0
list_typ = [] # 出現モンスターのNoを格納(A列)
for _ in range(num_enemy):
if floor >= 10:
typ = random.randint(1, 10)
else:
typ = random.randint(1, floor+1)
list_typ.append(typ)
変更前はフロアごとに出現モンスターが
変わっていました。
変更後
def init_battle(bg, mon_area): # 戦闘に入る準備をする
global monster
num_enemy = random.randint(1, 4)
sum_x = 0
list_typ = [] # 出現モンスターのNoを格納(A列)
list_mon_area = chara.get_mon_area_list() # モンスター全体のエリアをリストで取得
# 該当するエリアのモンスターの要素(=モンスターのNo)を格納,mon_areaはプレイヤーのいる場所のエリア
list_land = [i for i, tmp in enumerate(list_mon_area) if tmp == str(mon_area)]
for _ in range(num_enemy):
typ = random.choice(list_land)
list_typ.append(typ)
chara.py で新しく作成した
関数get_mon_area_list()で
モンスターエリアのリストを取得して
リストlist_mon_areaに格納します。
※モンスターシートのL列です。
また、該当するモンスターエリアのモンスターを
リストlist_landに格納します。
この時、内包表記で格納しています。
※内包表記の詳細はこちら。
list_mon_areaの中から該当する
モンスターエリアと同じモンスターを
list_landに入れてきます。
具体的に・・
モンスターエリア(mon_area)が0の時です。
リストlist_mon_areaは
モンスターシートのL列なので
['エリア', '0', '0', '1', '1', '2', '2', '3', '3', '4', '4']
となります。
このうち、モンスターエリア0と等しいのは
list_mon_area[1]
list_mon_area[2]
の2つなので、リストlist_landは
[1, 2]
となります。
上記の通り、
list_landにはlist_mon_areaのインデックスが
格納されますが、このインデックスの値は
モンスターのNoと同じになります。
※以下の赤枠

ちなみにリストlist_mon_areaは
['エリア', '0', '0', '1', '1', '2', '2', '3', '3', '4', '4']
と文字列になっているので
if tmp == str(mon_area)
のようにmon_areaを文字列に型変換しています。
※mon_areaはmove.py から one_hour_dungeon.py へ
int型で渡しているので、数値です。
return 10, int(n_map[player.y][player.x][:2])
あとはlist_landの中からランダムで選択して、
出現するモンスターのNoを決定します。
※モンスターエリアが0の場合、
モンスターのNoは1もしくは2の
どちらかが選択されます。
typ = random.choice(list_land)
※choiceの詳細はこちら。
これでマップ(エリア)ごとに出現するモンスターを
変えることが出来ました。
結果
maps.py では
モンスターエリアは以下のようにしています。

なので、それぞれのエリアで出現するのは
以下の同じ色で囲ったモンスターになります。

というところで、今回は以上です!(`・ω・´)
最後に
今回はマップ(エリア)ごとに
出現するモンスターが変わるようにしました。
次回はマップ上で歩けるところ、歩けないところの
制限を修正したいと思います。
【ご注意】
プログラムやデータなどは著作権法により保護されています。
著作者の許諾を得ずに、プログラムおよびデータそのものまたは改変したものを
配布したり販売したりすることはできません。
また、これらを利用して発生した損害などに関して、著作者は一切責任を負いません。