概要
RPGゲームにおいて、Mapの敵を倒したら、さらに新しい敵が湧かないと枯れてしまうため、以下の処理が必要となる
1.敵と遭遇したら、戦闘画面になり、その後、敵を消込を行う。
2.新たな敵を生成し、Mapに追加する
ロジック処理
1.空のMapデータにモンスター配置情報を更新する
2.Playerの位置情報を配置しないエリアを更新する
3.モンスターを1つ追加生成し、配置する
4.Map上常に同じ数の敵を維持する
ロジック処理で必要な情報
1.Playerの位置(モンスター遭遇時)
2.モンスター配置していない空のMapデータ(Blockは必要)
3.モンスター配置情報(こちらはPlayerに倒されるため、変動情報となる)
Playerクラス
class Player(pg.sprite.Sprite):
def __init__(self, pos, collision_sprites, enemy_sprites):
super().__init__()
self.collision_sprites = collision_sprites
self.enemy_sprites = enemy_sprites
# イメージの読み込み
self.surface = pg.image.load(IMAGE_PATH).convert_alpha()
# 矩形(rect)と位置情報
self.rect = self.surface.get_frect(topleft=pos)
self.hit_box_rect = self.rect.inflate(-10,-10)
# 移動関連
self.key_speed = 5
self.direction = pg.Vector2(0, 0)
self.game_stage = 'main'
self.enemy_name = ''
def handle_input(self, dt):
"""キーボード入力で移動処理を行う"""
keys = pg.key.get_pressed()
# キーが押されたときだけ移動
self.direction.x = int(keys[pg.K_RIGHT]) - int(keys[pg.K_LEFT])
self.direction.y = int(keys[pg.K_DOWN]) - int(keys[pg.K_UP])
self.direction = self.direction.normalize() if self.direction else self.direction
def collision_enemy(self, dt, enemy_list, mob_pos_info, base_map):
collided_enemies = [] # 衝突した敵を記録するリスト
# 水平方向、垂直方向の衝突判定を処理
self.hit_box_rect.x += self.direction.x * self.key_speed * dt
self.hit_box_rect.y += self.direction.y * self.key_speed * dt
for enemy_sprite, enemy in zip(self.enemy_sprites, enemy_list):
if enemy_sprite.rect.colliderect(self.hit_box_rect):
print(f'{enemy.name}と衝突しました!')
self.game_stage = 'battle'
self.enemy_name = enemy.name
collided_enemies.append((enemy_sprite, enemy))
# 衝突した敵を削除
for enemy_sprite, enemy in collided_enemies:
if enemy_sprite in self.enemy_sprites:
self.enemy_sprites.remove(enemy_sprite) # スプライトグループから削除
if enemy in enemy_list:
enemy_list.remove(enemy) # インスタンスリストから削除
# print(f'mobpos:{enemy.mob_pos}')
if enemy.mob_pos in mob_pos_info:
mob_pos_info.remove(enemy.mob_pos)
self.general_mob(base_map, mob_pos_info, enemy_list)
# print(f'mob_pos_info:{mob_pos_info}')
# enemy.destroy() # 敵のリソースを解放
# メイン矩形をヒットボックスに同期
self.rect.center = self.hit_box_rect.center
def cal_mob_area(self, mob_pos_info, max_y, max_x):
all_list = []
for px, py in mob_pos_info:
# NONエリアの計算
px_min = max(0, px - MOB_AREA)
px_max = min(max_x - 1, px + MOB_AREA)
py_min = max(0, py - MOB_AREA)
py_max = min(max_y - 1, py + MOB_AREA)
mob_area = []
for p_y in range(py_min, py_max + 1):
for p_x in range(px_min, px_max + 1):
mob_area.append([p_x, p_y]) # 各座標をリストに追加
all_list.extend(mob_area)
return all_list
def place_exits_mob(self, map_data, mob_pos_info, num):
max_y = len(map_data)
max_x = len(map_data[0])
for x, y in mob_pos_info:
# 範囲外アクセスを防ぐチェック
# if 0 <= y < max_y and 0 <= x < max_x:
if map_data[y][x] == 0: # 配置可能な場所
map_data[y][x] = num # モンスターを配置
else:
# print(f"座標 ({x}, {y}) にモンスターを配置できません。")
pass
# else:
# print(f"座標 ({x}, {y}) はマップの範囲外です。")
return map_data
def get_base_map(self, current_map):
base_map = []
for i, row in enumerate(current_map):
row_area = []
for j, column in enumerate(row):
if column == 'B':
row_area.append(1)
else:
row_area.append(0)
base_map.append(row_area)
return base_map
def general_mob(self, map_data, mob_pos_info, enemy_list):
max_map_y = len(map_data)
max_map_x = len(map_data[0])
# Mob配置禁止エリアの計算
self.non_mob_area = self.cal_mob_area(mob_pos_info, max_map_y, max_map_x)
# 既存のMobを配置する
self.updated_map = self.place_exits_mob(map_data, self.non_mob_area, 9)
# 配置禁止エリアの計算
p = self.cal_player_area(max_map_y, max_map_x)
self.place_result_map = self.place_exits_mob(self.updated_map, p, 3)
for row in self.place_result_map:
print(row)
# 配置可能な座標を取得
zero_positions = [(i, j) for i, row in enumerate(self.place_result_map) for j, val in enumerate(row) if val == 0]
if zero_positions:
new_mob = random.sample(zero_positions, 1)
flattened_list = [item for tup in new_mob for item in tup]
print(f'配置したモンスター: {flattened_list}')
# ランダムでMobを生成
id = random.randint(0, 2)
x = flattened_list[0] * TILE_SIZE
y = flattened_list[1] * TILE_SIZE
enemy = Enemy((y, x), id, flattened_list, self.enemy_sprites)
enemy_list.append(enemy)
else:
print("モンスターを配置できるスペースがありません。")
def cal_player_area(self, max_y, max_x):
# プレイヤーのタイル位置を計算
px = int(self.rect.centerx / TILE_SIZE)
py = int(self.rect.centery / TILE_SIZE)
# NONエリアの計算
px_min = max(0, px - NON_ENEMY_AREA)
px_max = min(max_x - 1, px + NON_ENEMY_AREA)
py_min = max(0, py - NON_ENEMY_AREA)
py_max = min(max_y - 1, py + NON_ENEMY_AREA)
player_area = []
for p_y in range(py_min, py_max + 1):
for p_x in range(px_min, px_max + 1):
player_area.append([p_x, p_y]) # 各座標をリストに追加
return player_area
def collision_block(self,dt):
"""ブロックとの衝突判定と位置調整を行う"""
# 水平方向の衝突処理
self.hit_box_rect.x += self.direction.x * self.key_speed *dt
for block in self.collision_sprites:
if block.rect.colliderect(self.hit_box_rect):
if self.direction.x > 0: # 右に移動中
self.hit_box_rect.right = block.rect.left
elif self.direction.x < 0: # 左に移動中
self.hit_box_rect.left = block.rect.right
# 衝突後、移動量をリセット
self.direction.x = 0
# 垂直方向の衝突処理
self.hit_box_rect.y += self.direction.y * self.key_speed *dt
for block in self.collision_sprites:
if block.rect.colliderect(self.hit_box_rect):
if self.direction.y > 0: # 下に移動中
self.hit_box_rect.bottom = block.rect.top
elif self.direction.y < 0: # 上に移動中
self.hit_box_rect.top = block.rect.bottom
# 衝突後、移動量をリセット
self.direction.y = 0
# メイン矩形をヒットボックスに同期
self.rect.center = self.hit_box_rect.center
def update(self, dt, enemy_list, mob_pos_info, current_map):
base_map = self.get_base_map(current_map)
self.handle_input(dt)
self.collision_block(dt)
self.collision_enemy(dt, enemy_list, mob_pos_info, base_map)
Mapクラス
class Map(pg.sprite.Sprite):
def __init__(self, collision_sprites, enemy_sprites):
self.block_images = {
"B" : "../maps/tree.png"
}
self.name = 'map_01'
self.current_map = TILE[self.name]
self.collision_sprites = collision_sprites
self.enemy_sprites = enemy_sprites
self.map_list = []
self.enemy_list = []
# mob配置はキューで管理する
self.mob_pos_info = []
self.player = None
def create(self):
# BlockとPlayerの配置
for i, row in enumerate(self.current_map):
for j, column in enumerate(row):
x = j * TILE_SIZE
y = i * TILE_SIZE
if column == 'B':
self.block = Block((x,y), self.block_images['B'], self.collision_sprites)
self.map_list.append(self.block)
if column == 'P':
self.player = Player((x,y), self.collision_sprites, self.enemy_sprites)
# 敵の配置
self.create_enemy()
#
self.base_map = self.get_base_map()
return self.player, self.map_list, self.enemy_list, self.mob_pos_info, self.current_map
def create_enemy(self):
id = 0 #生成時作業用ID
# 敵の配置
self.replace_zeros_with_nines()
for i, row in enumerate(self.grid):
# print(row)
for j, column in enumerate(row):
x = j * TILE_SIZE
y = i * TILE_SIZE
if column == 9:
print(f'x:{j} y:{i}に{column}を配置')
self.mob_pos_info.append([j,i])
self.enemy = Enemy((x,y), id, [j,i], self.enemy_sprites)
id += 1
if id >2: id = 0
self.enemy_list.append(self.enemy)
# print(self.mob_pos_info)
def cal_player_area(self, max_y, max_x):
# px
px = -1
py = -1
for i, row in enumerate(self.current_map):
for j, column in enumerate(row):
if column == 'P':
px = j
py = i
break
# NONエリアの計算
px_min = max(0, px - NON_ENEMY_AREA)
px_max = min(max_x - 1, px + NON_ENEMY_AREA)
py_min = max(0, py - NON_ENEMY_AREA)
py_max = min(max_y - 1, py + NON_ENEMY_AREA)
return px_min, px_max, py_min, py_max
def cal_non_enemy_area(self):
map_data = self.get_base_map()
# mapデータの最大値を取得する
max_map_y = len(map_data)
max_map_x = len(map_data[0])
# Playerエリアの計算
px_min, px_max, py_min, py_max = self.cal_player_area(max_map_x, max_map_y)
# 敵を配置できるエリアの取得
self.loc_enemy_area = []
for i, row in enumerate(self.current_map):
row_area = []
for j, column in enumerate(row):
if column == '.':
px = j
py = i
if (px > px_min and py > py_min) and (px < px_max and py < py_max):
row_area.append(1)
else:
row_area.append(0)
else:
row_area.append(1)
self.loc_enemy_area.append(row_area)
return self.loc_enemy_area
def replace_zeros_with_nines(self):
"""リスト内の0をランダムに選択して指定された数を9に置き換える。"""
# 0の座標を探す
self.grid = self.cal_non_enemy_area()
zero_positions = [(i, j) for i, row in enumerate(self.grid) for j, val in enumerate(row) if val == 0]
# 指定された数だけランダムに選択
if MAX_ENEMY_NUM > len(zero_positions):
raise ValueError("0の数より置き換える数が多いです。")
random_positions = random.sample(zero_positions, MAX_ENEMY_NUM)
# 選択された位置を9に置き換える
for i, j in random_positions:
self.grid[i][j] = 9
def get_base_map(self):
base_map = []
for i, row in enumerate(self.current_map):
row_area = []
for j, column in enumerate(row):
if column == 'B':
row_area.append(1)
else:
row_area.append(0)
base_map.append(row_area)
return base_map
まとめ
今回は、モンスター配置において、Playerの周りに配置しない、モンスター同士が近くに配置しないなど、ロジックを組み込んだ。
若干バグがあるが、とりあえずは機能する
Playerクラスには、戦闘画面遷移、戦闘後のMobを消す、生成、配置
Mapクラスには、初期のMob配置を分けた。
課題:
MapクラスとPlayerが大きくなりすぎた。