CODE Python RPG

独学でPythonでRPGを作成する(第20回)プログラム(ソースコード)公開

更新日:

こんにちは。
「Pythonでつくる ゲーム開発 入門講座」のChapter11,12(本格RPGを作ろう!全編・後編)を基にRPGを作っていきます。

前回は先制攻撃や不意打ちを追加しました。

前回の最後に、次回からマップ作りとかすると記載しましたが、
このシリーズも気がつけば20回目になりましたので、
今回は今まで変更を加えたプログラム(ソースコード)を公開しようと思います。

目次

  1. エフェクトの位置修正
  2. 先制攻撃・不意打ちの確率の変更
  3. 戦闘開始時のモンスター名
  4. 最後に(プログラム公開)

エフェクト修正

プログラム公開の前にいくつか修正します。

最初にモンスターに攻撃した時のエフェクトの位置です。
モンスターの出現数を最大4匹にしていますが、
今はどこを攻撃しても真ん中にエフェクトが表示されるので
攻撃したモンスターに重なるようにします。

変更前(一番左を攻撃)

変更後(一番左を攻撃)

 

変更前

        elif idx == 12: # プレイヤーの攻撃
(省略)
            if 2 <= tmr and tmr <= 4:
                screen.blit(imgEffect[0], [600-tmr*100, tmr*100])

変更後

        elif idx == 12: # プレイヤーの攻撃
(省略)
            if 2 <= tmr and tmr <= 4:
                screen.blit(imgEffect[0], [monster[btl_enemy].x+monster[btl_enemy].img.get_width()-tmr*80, tmr*80])

 

変更前はエフェクトの位置は固定です。
[600-tmr*100, tmr*100]

変更後のx座標は
モンスターの位置(x座標) + モンスター画像の幅 + エフェクトの位置
に変更しています。
[monster[btl_enemy].x + monster[btl_enemy].img.get_width() - tmr*80, tmr*80]

 

 

先制攻撃・不意打ちの確率の変更

次に、前回に追加した先制攻撃・不意打ちの確率を変更します。

前回は先制攻撃・不意打ち・通常はそれぞれ1/3にしました。
btl_start = random.randint(0, 2)
1:先制攻撃
2:不意打ち
0:通常

以下のように変更します。
btl_start = random.randint(1, 32)
1:先制攻撃
2:不意打ち
3〜32:通常

なので、先制攻撃と不意打ちの確率はそれぞれ1/32、
通常の確率は30/32です。
前回は先制攻撃・不意打ちの確率が高すぎました。。(ノ∀`*))))

            elif tmr == 9:
                btl_start = random.randint(1, 32)
                if btl_start == 1: # 先制攻撃
                    init_message()
                    if random.randint(0, 1) == 0:
                        set_message("しかし " + mon_typ + "は")
                        set_message("まだ こちらに きづいていない!")
                    else:
                        set_message("しかし " + mon_typ + "は")
                        set_message("おどろき とまどっている!")
                    draw_battle(screen, fontS, turn_obj)
                    time.sleep(0.5)
                elif btl_start == 2: # 不意打ち
                    init_message()
                    if random.randint(0, 1) == 0:
                        set_message(mon_typ + "は")
                        set_message("いきなり おそいかかってきた!")
                    else:
                        set_message(mon_typ + "は")
                        set_message(player.name + "が みがまえるまえに")
                        set_message("おそいかかってきた!")
                    draw_battle(screen, fontS, turn_obj)
                    time.sleep(0.5)
                else: # 通常攻撃
                    btl_start = 0

 

 

戦闘開始時のモンスター名

修正点の最後です。

先制攻撃・不意打ちについて、
戦闘開始時のモンスター名を修正します。

前回は以下のようにしました。
・1種類ならモンスターの名前
・2種類以上なら「まもののむれ」

def init_battle(bg): # 戦闘に入る準備をする
(省略)
    if len(dic_typ) == 1:
        return monster[0].name[:-2] # モンスターが1種類
    else:
        return "まもののむれ" # モンスターが複数種類

 

同じ種類のモンスターが複数出た時にA,B,Cを付けています。
詳細はこちら
例:Green slime A

return monster[0].name[:-2]
の-2は空欄+Aを消す為です。
例:Green slime Aの「 A」を消す

ただ、出現モンスターが1匹の時はA,B,Cは付けません。
例:Green slime

なので、この時に-2をすると「Green sli」に
なってしまいますので、これを修正します。

変更前

変更後

 

def init_battle(bg): # 戦闘に入る準備をする
(省略)
    if len(dic_typ) == 1: # 1種類はモンスター名
        if num_enemy == 1: # 1匹はそのまま表示
            return monster[0].name
        else: # 複数は" A"や" B"などを消す
            return monster[0].name[:-2] # モンスターが1種類(-2はモンスター名+ A,B,CのA,B,Cを消す)
    else: # 複数種類
        return "まもののむれ" # モンスターが複数種類

 

 

最後に(プログラム公開)

全体のプログラムが長くなってきたので
次回は機能ごとにファイルを分割しようと思います。

そして、冒頭でお伝えしたように
プログラムを公開して今回は終わります!(`・ω・´)

まとめサイトへ

 

スプレッドシート

シート「勇者」

シート「モンスター」

 

chara.py

import gspread
import pygame
import random
from pygame.locals import *

import os.path
os.chdir(os.path.dirname(os.path.abspath(__file__)))

from oauth2client.service_account import ServiceAccountCredentials 
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('ダウンロードしたJSONファイル名.json', scope)
gc = gspread.authorize(credentials)
SPREADSHEET_KEY = 'スプレッドシートキー'

wb = gc.open_by_key(SPREADSHEET_KEY)
ws_chara = wb.worksheet("勇者")
ws_mon = wb.worksheet("モンスター")

chara1_name = ws_chara.acell('C2').value
chara1_maxhp = ws_chara.col_values(5) # E列
chara1_hp = ws_chara.acell('F2').value
chara1_maxmp = ws_chara.col_values(7) # G列
chara1_mp = ws_chara.acell('H2').value
chara1_quick = ws_chara.col_values(9) # I列
chara1_atk = ws_chara.col_values(10) # J列
chara1_dfs = ws_chara.col_values(11) # K列
chara1_exp = ws_chara.col_values(12) # L列

mon_name = ws_mon.col_values(2) # B列
mon_maxhp = ws_mon.col_values(4) # D列
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列

class Chara():
    def attack(self, obj):
        base_dmg = self.atk/2 - obj.dfs/4
        if base_dmg <= 0:
            return 0
        width_dmg = base_dmg/16 + 1
        min_dmg = int(base_dmg - width_dmg)
        max_dmg = int(base_dmg + width_dmg)
        return random.randint(min_dmg, max_dmg)
    def defense(self):
        self.dfs *= 2

class Brave(Chara):
    def __init__(self):
        self.name = chara1_name
        self.lv = 1
        self.maxhp = int(chara1_maxhp[self.lv])
        self.hp = self.maxhp
        self.maxmp = int(chara1_maxmp[self.lv])
        self.mp = self.maxmp
        self.quick = int(chara1_quick[self.lv])
        self.atk = int(chara1_atk[self.lv])
        self.dfs = int(chara1_dfs[self.lv])
        self.lv_exp = int(chara1_exp[self.lv])
        self.exp = 0
        self.act = 0 # 戦闘のコマンド(行動)12:攻撃、18:防御
        self.img = [
                pygame.image.load("image/mychr0.png"), # 上
                pygame.image.load("image/mychr1.png"), # 上
                pygame.image.load("image/mychr2.png"), # 下
                pygame.image.load("image/mychr3.png"), # 下
                pygame.image.load("image/mychr4.png"), # 左
                pygame.image.load("image/mychr5.png"), # 左
                pygame.image.load("image/mychr6.png"), # 右
                pygame.image.load("image/mychr7.png"), # 右
                pygame.image.load("image/mychr8.png") # 倒れた
            ]
        self.x = 0 # プレイヤーのx座標
        self.y = 0 # プレイヤーのy座標
        self.d = 0 # プレイヤーの向き 0:上 1:下 2:左 3:右
        self.a = 0 # imgPlayerの添字
    def reset(self):
        self.hp = self.maxhp
        self.mp = self.maxmp
    def lv_up(self):
        self.lv += 1
        self.maxhp = int(chara1_maxhp[self.lv])
        self.maxmp = int(chara1_maxmp[self.lv])
        self.quick = int(chara1_quick[self.lv])
        self.atk = int(chara1_atk[self.lv])
        self.dfs = int(chara1_dfs[self.lv])
        self.lv_exp = int(chara1_exp[self.lv])

    def re_defense(self):
        # 呪文効果とかが残っているか確認
        self.dfs = int(chara1_dfs[self.lv])

class Monster(Chara):
    def __init__(self, num):
        self.num = num
        self.img = pygame.image.load("image/enemy"+str(num)+".png")
        self.name = mon_name[num]
        self.maxhp = int(mon_maxhp[num])
        self.hp = self.maxhp
        self.quick = int(mon_quick[num])
        self.atk = int(mon_atk[num])
        self.dfs = int(mon_dfs[num])
        self.exp = int(mon_exp[num])
        self.x = 0 # 画像の表示位置
        self.y = 0 # 画像の表示位置
    def set_name(self, num):
        if num == 0:
            self.name += " A"
        elif num == 1:
            self.name += " B"
        elif num == 2:
            self.name += " C"
        elif num == 3:
            self.name += " D"
    def set_x(self, x):
        self.x = x
    def set_y(self, y):
        self.y = y
    def re_defense(self):
        # 呪文効果とかが残っているか確認
        self.dfs = int(mon_dfs[self.num])

one_hour_dungeon.py

import pygame
import sys
import random
from pygame.locals import *
import chara
import mojimoji # 半角⇄全角変換
import time
import collections
import os.path
os.chdir(os.path.dirname(os.path.abspath(__file__)))

# 色の定義
WHITE = (255, 255, 255)
WARNING = (255, 191, 0)
DANGER = (255, 101, 101)
BLACK = (0, 0, 0)
RED   = (255, 0, 0) # プレイヤーの体力・食料が僅かの時、ゲームオーバーの時
CYAN  = (0, 255, 255)
BLINK = [(224,255,255), (192,240,255), (128,224,255), (64,192,255), (128,224,255), (192,240,255)] # 選択中の戦闘コマンドなどを点滅させる

# 画像の読み込み
imgTitle = pygame.image.load("image/title.png")
imgWall = pygame.image.load("image/wall.png")
imgWall2 = pygame.image.load("image/wall2.png")
imgDark = pygame.image.load("image/dark.png")
imgPara = pygame.image.load("image/parameter.png")
imgBtlBG = pygame.image.load("image/btlbg.png")
# imgEnemy = pygame.image.load("image/enemy0.png")
imgItem = [
    pygame.image.load("image/potion.png"),
    pygame.image.load("image/blaze_gem.png"),
    pygame.image.load("image/spoiled.png"),
    pygame.image.load("image/apple.png"),
    pygame.image.load("image/meat.png")
]
imgFloor = [
    pygame.image.load("image/floor.png"),
    pygame.image.load("image/tbox.png"),
    pygame.image.load("image/cocoon.png"),
    pygame.image.load("image/stairs.png")
]
imgEffect = [
    pygame.image.load("image/effect_a.png"), # 攻撃
    pygame.image.load("image/effect_b.png") # blaze
]

# 変数の宣言
speed = 1 # 速度(1-3) 大きいほど速い sキーで変化
# 0: タイトル画面 1: プレイヤーの移動 2: 画面切り替え(階段) 3: アイテム入手もしくはトラップ(宝箱・繭) 9: ゲームオーバー 10: 戦闘開始 11: プレイヤーのターン(入力待ち)
# 12: プレイヤーの攻撃 13: 敵のターン、敵の攻撃 14: 逃げられる? 15: 敗北 16: 勝利 17: レベルアップ 20: Potion 21: Blaze gem 22: 戦闘終了
idx = 0
tmr = 0
floor = 0 # 階層 出現する敵や敵のレベルに影響(階層が上がるほど出現する敵の種類が増え、レベルの上限が上がる)出現する敵とレベルはランダム
fl_max = 1 # 最高到達階層 タイトル画面に表示
welcome = 0 # Welcome to floorの表示時間

party = 1 # パーティーの数
player = chara.Brave()
food = 0 # プレイヤーの食料(1歩ごとに1減少) 0になったら体力が1歩ごとに5減少
potion = 0 # ポーションを使える回数(使うと全快する)
blazegem = 0 # blazeを使える回数
treasure = 0 # TRE_NAMEの添字 1,2,3:宝箱、4,5:繭

monster = [] # 敵のオブジェクト
dead_monster = [] # 倒した敵のオブジェクト
emy_step = 0 # 敵が攻撃する時の動きの大きさ(前に出るステップの大きさ)
emy_blink = 0 # 攻撃した時に敵を点滅させる(奇数:表示させない、偶数:表示させる)

dmg_eff = 0
btl_cmd_x = 0 # コマンド選択の時の"▶︎"のx位置
btl_cmd_y = 0 # コマンド選択の時の"▶︎"のy位置
btl_enemy = 0 # 敵選択の時の"▶︎"の位置
battle_order = {} # 戦闘の順番

COMMAND = [["こうげき", "どうぐ"], ["じゅもん", "そうび"], ["ぼうぎょ", "にげる"]]
TRE_NAME = ["Potion", "Blaze gem", "Food spoiled.", "Food +20", "Food +100"]

MAZE_W = 11
MAZE_H = 9
maze = []
for y in range(MAZE_H):
    maze.append([0]*MAZE_W)

DUNGEON_W = MAZE_W*3
DUNGEON_H = MAZE_H*3
dungeon = [] # 0:床 1:宝箱 2:繭 3:階段 9:壁
for y in range(DUNGEON_H):
    dungeon.append([0]*DUNGEON_W)

def make_dungeon(): # ダンジョンの自動生成
    XP = [ 0, 1, 0,-1]
    YP = [-1, 0, 1, 0]
    #周りの壁
    for x in range(MAZE_W):
        maze[0][x] = 1
        maze[MAZE_H-1][x] = 1
    for y in range(1, MAZE_H-1):
        maze[y][0] = 1
        maze[y][MAZE_W-1] = 1
    #中を何もない状態に
    for y in range(1, MAZE_H-1):
        for x in range(1, MAZE_W-1):
            maze[y][x] = 0
    #柱
    for y in range(2, MAZE_H-2, 2):
        for x in range(2, MAZE_W-2, 2):
            maze[y][x] = 1
    #柱から上下左右に壁を作る
    for y in range(2, MAZE_H-2, 2):
        for x in range(2, MAZE_W-2, 2):
         d = random.randint(0, 3)
         if x > 2: # 二列目からは左に壁を作らない
             d = random.randint(0, 2)
         maze[y+YP[d]][x+XP[d]] = 1

    # 迷路からダンジョンを作る
    #全体を壁にする
    for y in range(DUNGEON_H):
        for x in range(DUNGEON_W):
            dungeon[y][x] = 9
    #部屋と通路の配置
    for y in range(1, MAZE_H-1): # for文はMAZEで回す
        for x in range(1, MAZE_W-1):
            # 通路はMAZE(3つ)の真ん中
            dx = x*3+1
            dy = y*3+1
            if maze[y][x] == 0:
                if random.randint(0, 99) < 20: # 部屋を作る
                    for ry in range(-1, 2):
                        for rx in range(-1, 2):
                            dungeon[dy+ry][dx+rx] = 0
                else: # 通路を作る
                    dungeon[dy][dx] = 0 # MAZE(3つ)の真ん中を0(通路)にする
                    # MAZEの通路になっている方向を0(通路)にする(部屋にする時は9個とも通路にする)
                    if maze[y-1][x] == 0:
                        dungeon[dy-1][dx] = 0
                    if maze[y+1][x] == 0:
                        dungeon[dy+1][dx] = 0
                    if maze[y][x-1] == 0:
                        dungeon[dy][dx-1] = 0
                    if maze[y][x+1] == 0:
                        dungeon[dy][dx+1] = 0

def draw_dungeon(bg, fnt): # ダンジョンを描画する
    bg.fill(BLACK)
    for y in range(-4, 6):
        for x in range(-5, 6):
            X = (x+5)*80
            Y = (y+4)*80
            dx = player.x + x
            dy = player.y + y
            if 0 <= dx and dx < DUNGEON_W and 0 <= dy and dy < DUNGEON_H:
                if dungeon[dy][dx] <= 3:
                    bg.blit(imgFloor[dungeon[dy][dx]], [X, Y])
                if dungeon[dy][dx] == 9:
                    bg.blit(imgWall, [X, Y-40])
                    if dy >= 1 and dungeon[dy-1][dx] == 9:
                        bg.blit(imgWall2, [X, Y-80])
            if x == 0 and y == 0: # 主人公キャラの表示
                bg.blit(player.img[player.a], [X, Y-40])
    bg.blit(imgDark, [0, 0]) # 四隅が暗闇の画像を重ねる
    draw_para(bg, fnt) # 主人公の能力を表示

def put_event(): # 床にイベントを配置する
    # 階段の配置
    while True:
        x = random.randint(3, DUNGEON_W-4)
        y = random.randint(3, DUNGEON_H-4)
        if(dungeon[y][x] == 0):
            for ry in range(-1, 2): # 階段の周囲を床にする
                for rx in range(-1, 2):
                    dungeon[y+ry][x+rx] = 0
            dungeon[y][x] = 3
            break
    # 宝箱と繭の配置
    for _ in range(60):
        x = random.randint(3, DUNGEON_W-4)
        y = random.randint(3, DUNGEON_H-4)
        if(dungeon[y][x] == 0):
            dungeon[y][x] = random.choice([1,2,2,2,2])
    # プレイヤーの初期位置
    while True:
        player.x = random.randint(3, DUNGEON_W-4)
        player.y = random.randint(3, DUNGEON_H-4)
        if(dungeon[player.y][player.x] == 0):
            break
    player.d = 1
    player.a = 2
                
def move_player(key): # 主人公の移動
    global idx, tmr, food, potion, blazegem, treasure

    if dungeon[player.y][player.x] == 0: # 何もない床
        # プレイヤーがいる場所(リストimgFloor 0:床 1:宝箱 2:繭 3:階段 -にしたらリストの逆からになるので-4にしたら床になる)
        # これがないと連続して敵が出る。下の方で、移動後にいた場所を0にする
        dungeon[player.y][player.x] = -4
        r = random.randint(0, 99)
        if r < 50: # 敵出現
            idx = 10
            tmr = 0
            return
    elif dungeon[player.y][player.x] == 1: # 宝箱に載った
        dungeon[player.y][player.x] = 0
        treasure = random.choice([0,0,0,1,1,1,1,1,1,2])
        if treasure == 0:
            potion = potion + 1
        if treasure == 1:
            blazegem = blazegem + 1
        if treasure == 2: # ハズレ
            food = int(food/2)
        idx = 3
        tmr = 0
        return
    elif dungeon[player.y][player.x] == 2: # 繭に載った
        dungeon[player.y][player.x] = 0
        r = random.randint(0, 99)
        if r < 40: # 食料
            treasure = random.choice([3,3,3,4])
            if treasure == 3:
                food = food + 20
            if treasure == 4:
                food = food + 100
            idx = 3
            tmr = 0
        return
    elif dungeon[player.y][player.x] == 3: # 階段に載った
        idx = 2
        tmr = 0
        return
    # 方向キーで上下左右に移動
    x = player.x # 移動したかを確認する為に今の位置を保存
    y = player.y
    if key[K_UP] == 1:
        player.d = 0
        if dungeon[player.y-1][player.x] != 9:
            player.y = player.y - 1
    if key[K_DOWN] == 1:
        player.d = 1
        if dungeon[player.y+1][player.x] != 9:
            player.y = player.y + 1
    if key[K_LEFT] == 1:
        player.d = 2
        if dungeon[player.y][player.x-1] != 9:
            player.x = player.x - 1
    if key[K_RIGHT] == 1:
        player.d = 3
        if dungeon[player.y][player.x+1] != 9:
            player.x = player.x + 1
    player.a = player.d*2 # 0:上 1:下 2:左 3:右
    if player.x != x or player.y != y: # 移動したら食料の量と体力を計算
        player.a = player.a + tmr%2 # 移動したら足踏みのアニメーション
        dungeon[y][x] = 0 # いた場所を0にする
        if food > 0:
            food = food - 1
            if player.hp < player.maxhp:
                player.hp = player.hp + 1
        else:
            player.hp = player.hp - 5
            if player.hp <= 0:
                player.hp = 0
                pygame.mixer.music.stop()
                idx = 9
                tmr = 0

def draw_text(bg, txt, x, y, fnt, col): # 影付き文字の表示
    sur = fnt.render(txt, True, BLACK)
    bg.blit(sur, [x+1, y+2])
    sur = fnt.render(txt, True, col)
    bg.blit(sur, [x, y])

def draw_para(bg, fnt): # 主人公の能力を表示
    X = 30
    Y = 600
    bg.blit(imgPara, [X, Y])
    col = WHITE
    if player.hp < 10 and tmr%2 == 0:
        col = RED
    draw_text(bg, "{}/{}".format(player.hp, player.maxhp), X+128, Y+6, fnt, col)
    draw_text(bg, str(player.atk), X+128, Y+33, fnt, WHITE)
    col = WHITE
    if food == 0 and tmr%2 == 0:
        col = RED
    draw_text(bg, str(food), X+128, Y+60, fnt, col)
    draw_text(bg, str(potion), X+266, Y+6, fnt, WHITE)
    draw_text(bg, str(blazegem), X+266, Y+33, fnt, WHITE)

def init_battle(bg): # 戦闘に入る準備をする
    global monster
    num_enemy = random.randint(1, 4)
    sum_x = 0
    list_typ = []
    for _ in range(num_enemy):
        if floor >= 10:
            typ = random.randint(1, 10)
        else:
            typ = random.randint(1, floor+1)
        list_typ.append(typ)

    dic_typ = collections.Counter(list_typ) # 重複を抽出
    i = 0
    for k, v in dic_typ.items(): # k:typ、v:数(typの種類がvの数だけ存在)
        for j in range(v):
            monster.append(chara.Monster(k))
            if v != 1: # 同じ種類が複数いる時
                monster[i].set_name(j)
            sum_x += monster[i].img.get_width() + 35 # 35はモンスターの間隔
            monster[i].set_y(bg.get_height()*0.6 - monster[i].img.get_height()) # どのモンスターも画面の7割が一番下に揃えられる
            i += 1
    sum_x -= 35 # 最後のモンスターは間隔を開ける必要がない
    x_i_i = bg.get_width()/2 - sum_x/2 # 一番左のモンスターのx座標
    monster[0].set_x(x_i_i) # 1匹目
    for i in range(num_enemy-1): # 2匹目
        x_i_i += monster[i].img.get_width() + 35 # 2匹目以降のモンスターの横幅+間隔 (2匹目は1匹目の横幅+間隔分をずらす)
        monster[i+1].set_x(x_i_i) # 各モンスターのx座標 2匹目以降なので i+1

    if len(dic_typ) == 1: # 1種類はモンスター名
        if num_enemy == 1: # 1匹はそのまま表示
            return monster[0].name
        else: # 複数は" A"や" B"などを消す
            return monster[0].name[:-2] # モンスターが1種類
    else: # 複数種類
        return "まもののむれ" # モンスターが複数種類

def draw_battle(bg, fnt, obj): # 戦闘画面の描画 obj:戦闘で行動中のオブジェクト
    global emy_blink, dmg_eff
    bx = 0
    by = 0
    if dmg_eff > 0:
        dmg_eff = dmg_eff - 1
        bx = random.randint(-20, 20)
        by = random.randint(-10, 10)
    bg.blit(imgBtlBG, [bx, by])
    for i, mon in enumerate(monster):
        display_flg = False # 生存しているモンスターを表示
        if mon.hp> 0 and btl_enemy != i:
            display_flg = True
        elif mon.hp> 0 and btl_enemy == i: # 攻撃対象のモンスターにエフェクト効果
            if emy_blink%2 == 0:
                display_flg = True
        if display_flg:
            if monster[i] is obj: # 行動中のモンスターなら
                bg.blit(mon.img, [monster[i].x, monster[i].y+emy_step])
            else:
                bg.blit(mon.img, [monster[i].x, monster[i].y])
            # 敵の体力を表示するバー
            # draw_bar(bg, 340, 580, 200, 10, monster.hp, monster.maxhp)
            x_i = monster[i].x + mon.img.get_width()/2 - 100 # 体力バーのx座標
            x_w = 200 # 体力バーの幅
            y_i = bg.get_height()*0.6 + 10 # モンスター表示の一番下(画面の7割)
            y_h = 10
            pygame.draw.rect(bg, WHITE, [x_i-2, y_i-2, x_w+4, y_h+4])
            pygame.draw.rect(bg, BLACK, [x_i, y_i, x_w, y_h])
            if mon.hp > 0:
                pygame.draw.rect(bg, (0,128,255), [x_i, y_i, x_w*mon.hp/mon.maxhp, y_h]) # 残り体力

    if emy_blink > 0:
        emy_blink = emy_blink - 1

    # パラメータ表示
    x_i = int(bg.get_width()/4) # x座標の開始位置
    x_w = int(bg.get_width()/8) # 幅
    y_i = 20 # y座標の開始位置
    y_h = 120 # 高さ
    x_i_t = x_i+x_w/2-50 # テキストのx座標の開始位置
    if party == 1: # 一人の時の全体の表示幅
        x_w *= 1
    col = WHITE
    if player.hp < player.maxhp/4:
        col = DANGER
    elif player.hp < player.maxhp/2:
        col = WARNING
    pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h], 2, 5)
    pygame.draw.line(bg, col, [x_i, y_i+33], [x_i+x_w-1, y_i+33], 2) # -1がないと少し出る
    draw_text(bg, "{}".format(player.name), x_i_t, y_i+6, fnt, col)
    if len(str(player.hp)) == 3:
        draw_text(bg, mojimoji.han_to_zen("H{}".format(player.hp)), x_i_t, y_i+36, fnt, col)
    elif len(str(player.hp)) == 2:
        draw_text(bg, mojimoji.han_to_zen("H {}".format(player.hp)), x_i_t, y_i+36, fnt, col)
    elif len(str(player.hp)) == 1:
        draw_text(bg, mojimoji.han_to_zen("H  {}".format(player.hp)), x_i_t, y_i+36, fnt, col)

    if len(str(player.mp)) == 3:
        draw_text(bg, mojimoji.han_to_zen("M{}".format(player.mp)), x_i_t, y_i+66, fnt, col)
    elif len(str(player.mp)) == 2:
        draw_text(bg, mojimoji.han_to_zen("M {}".format(player.mp)), x_i_t, y_i+66, fnt, col)
    elif len(str(player.mp)) == 1:
        draw_text(bg, mojimoji.han_to_zen("M  {}".format(player.mp)), x_i_t, y_i+66, fnt, col)
    
    if len(str(player.lv)) == 2:
        draw_text(bg, mojimoji.han_to_zen("L:{}".format(player.lv)), x_i_t, y_i+96, fnt, col)
    elif len(str(player.lv)) == 1:
        draw_text(bg, mojimoji.han_to_zen("L: {}".format(player.lv)), x_i_t, y_i+96, fnt, col)

    if idx == 11 or idx == 19: # コマンド選択 or モンスター選択
        # コマンド表示
        x_i = 50 # x座標の開始位置
        x_w = bg.get_width()*0.4
        y_i = bg.get_height()*0.6 + 40
        y_h = bg.get_height()*0.4 - 40 # 高さ
        if len(player.name) <= 2:
            x_i_t = x_i+x_w/2-25 # テキストのx座標の開始位置
        else:
            x_i_t = x_i+x_w/2-50 # テキストのx座標の開始位置
        y_i_t = y_i + 6
        pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h], 2, 5)
        pygame.draw.line(bg, col, [x_i, y_i+33], [x_i+x_w-1, y_i+33], 2) # -1がないと少し出る
        draw_text(bg, "{}".format(player.name), x_i_t, y_i_t, fnt, col)

        # COMMAND = [["こうげき", "どうぐ"], 
        # ["じゅもん", "そうび"], 
        # ["ぼうぎょ", "にげる"]]
        for i in range(3):
            for j in range(2):
                draw_text(bg, COMMAND[i][j], x_i+50+j*x_w/2, y_i+70+i*60, fnt, col)
        if idx == 11:
            if tmr%5 != 0:
                draw_text(bg, "▶︎", x_i+btl_cmd_x*x_w/2, y_i+70+btl_cmd_y*60, fnt, col)
        
        # 敵の名前表示
        x_i = bg.get_width()*0.4 + 60 # x座標の開始位置
        x_w = bg.get_width()*0.6 - 100 # 100は両端の50の合計
        y_i = bg.get_height()*0.6 + 40
        y_h = bg.get_height()*0.1 - 10 # 高さ(敵の名前1個分の高さ:62)
        x_i_t = x_i + 50 # テキストのx座標の開始位置
        y_i_t = y_i + 21 # テキストのy座標の開始位置 20+21+21=62の21
        pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h*len(monster)], 2, 5) # 枠
        for i, mon in enumerate(monster):
            draw_text(bg, "{}".format(mon.name), x_i_t, y_i_t+y_h*i, fnt, col) # font:20(20+21+21=62)
        if idx == 19:
            if tmr%5 != 0:
                draw_text(bg, "▶︎", x_i_t-50, y_i_t+y_h*btl_enemy, fnt, col) # btl_nenmy(モンスターの名前の位置)

    else:
        # 戦闘メッセージ表示
        x_i = 50 # x座標の開始位置
        x_w = bg.get_width() - 100 # 幅(両端の50を引いたのが幅)
        y_i = bg.get_height()*0.6 + 40 # 6割+40のところからメッセージ枠を出す
        y_h = bg.get_height()*0.4 - 40 # 高さ
        pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h], 2, 5)

        for i in range(5): # 戦闘メッセージの表示
            draw_text(bg, message[i], x_i+10, y_i+10+i*40, fnt, col)


def battle_command(bg, key): # コマンドの入力と表示
    global btl_cmd_x, btl_cmd_y
    ent = False # Trueで返すと選択したコマンド(btl_cmd)を実行する
    if key[K_UP] and btl_cmd_y > 0: # ↑キー
        btl_cmd_y -= 1
    elif key[K_DOWN] and btl_cmd_y < 2: # ↓キー
        btl_cmd_y += 1
    elif key[K_LEFT] and btl_cmd_x > 0: # ←キー
        btl_cmd_x -= 1
    elif key[K_RIGHT] and btl_cmd_x < 1: # →キー
        btl_cmd_x += 1
    elif key[K_SPACE] or key[K_RETURN]: # 決定s
        ent = True
    return ent

def battle_select(bg, key):
    global idx, btl_enemy
    ent = False
    if key[K_UP] and btl_enemy > 0: # ↑キー
        btl_enemy -= 1
    elif key[K_DOWN] and btl_enemy < len(monster)-1: # ↓キー
        btl_enemy += 1
    elif key[K_SPACE] or key[K_RETURN]: # 決定
        ent = True
    elif key[K_b]: # キャンセル
        idx = 11
    return ent

def set_battle_turn(num): # 0:通常、1:先制攻撃、2:不意打ち
    global battle_order
    tmp_order = {}
    if num == 0 or num == 1:
        r = random.randint(66, 100)
        tmp_order[player] = int(player.quick * r/100)
    if num == 0 or num == 2:
        for mon in monster:
            r = random.randint(66, 100)
            tmp_order[mon] = int(mon.quick * r/100)
    # [(obj, quick), (ojb, quick), ...]のリストになる
    battle_order = sorted(tmp_order.items(), key=lambda x:x[1], reverse=True)

def get_battle_turn():
    global battle_order
    if not battle_order: # 全て消えたらプレイヤーのコマンド選択
        return None, 11
    # [(obj, quick), (ojb, quick), ...]のリスト、[0][0]で先頭(ターン)のobjを取得
    turn_key = battle_order[0][0]
    battle_order.pop(0) # 先頭を削除
    if turn_key is player:
        return turn_key, player.act
    else:
        return turn_key, 13

def del_battle_turn(obj1): # ターン内に気絶したオブエクトを削除(気絶したオブジェクトが攻撃しないように)
    for i, obj2 in enumerate(battle_order): # 行がobj2に入る, iはi行目
        if obj1 is obj2[0]: # obj2[0]は各行の1列目(オブジェクト)2列目は素早さ
            battle_order.pop(i) # i行目を削除

# 戦闘メッセージの表示処理
message = [""]*5
def init_message():
    for i in range(5):
        message[i] = ""
    
def set_message(msg):
    for i in range(5):
        if message[i] == "":
            message[i] = msg
            return
    for i in range(4): # 下が最新のメッセージ
        message[i] = message[i+1]
    message[4] = msg

def main(): # メイン処理
    global speed, idx, tmr, floor, fl_max, welcome
    global food, potion, blazegem
    global emy_life, emy_step, emy_blink, dmg_eff
    global btl_cmd_x, btl_cmd_y, btl_enemy
    dmg = 0 # プレイヤーが与えるダメージ、受けるダメージ

    pygame.init()
    pygame.display.set_caption("One hour Dungeon") # タイトル
    screen = pygame.display.set_mode((880, 720))
    clock = pygame.time.Clock()
    font = pygame.font.Font(None, 40) # fontS以外の画面表示フォント
    fontS = pygame.font.Font("font/JKG-L_3.ttf", 20, bold=True) # プレイヤーのパラメータ・位置情報・スピードなどの表示フォント
    fontS.set_bold(True)
    turn_obj = ""
    btl_exp = 0 # 戦闘で獲得した経験値(逃げたら0)
    btl_start = 0 # 0:通常、1:先制攻撃、2:不意打ち
    mon_typ = "" # モンスターの種類が複数:"まもののむれ"、1種類:モンスターの名前

    se = [ # 効果音とジングル
        pygame.mixer.Sound("sound/ohd_se_attack.ogg"),
        pygame.mixer.Sound("sound/ohd_se_blaze.ogg"),
        pygame.mixer.Sound("sound/ohd_se_potion.ogg"),
        pygame.mixer.Sound("sound/ohd_jin_gameover.ogg"),
        pygame.mixer.Sound("sound/ohd_jin_levup.ogg"),
        pygame.mixer.Sound("sound/ohd_jin_win.ogg")
    ]

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN:
                if event.key == K_s:
                    speed = speed + 1
                    if speed == 4:
                        speed = 1

        tmr = tmr + 1
        key = pygame.key.get_pressed()

        if idx == 0: # タイトル画面
            if tmr == 1:
                pass
                # pygame.mixer.music.load("sound/ohd_bgm_title.ogg")
                # pygame.mixer.music.play(-1)
            screen.fill(BLACK)
            screen.blit(imgTitle, [40, 60])
            if fl_max >= 2:
                draw_text(screen, "You reached floor {}.".format(fl_max), 300, 460, font, CYAN)
            draw_text(screen, "Press space key", 320, 560, font, BLINK[tmr%6])
            if key[K_SPACE] == 1:
                make_dungeon()
                put_event()
                player.reset()
                monster.clear() # モンスターオブジェクトを削除
                dead_monster.clear()
                floor = 1
                welcome = 15
                food = 300
                potion = 0
                blazegem = 0
                idx = 1
                btl_exp = 0
                # pygame.mixer.music.load("sound/ohd_bgm_field.ogg")
                # pygame.mixer.music.play(-1)

        elif idx == 1: # プレイヤーの移動
            move_player(key)
            draw_dungeon(screen, fontS)
            draw_text(screen, "floor {} ({},{})".format(floor, player.x, player.y), 60, 40, fontS, WHITE)
            if welcome > 0:
                welcome = welcome - 1
                draw_text(screen, "Welcome to floor {}.".format(floor), 300, 180, font, CYAN)

        elif idx == 2: # 画面切り替え(階段)
            draw_dungeon(screen, fontS)
            if 1 <= tmr and tmr <= 5:
                h = 80*tmr
                pygame.draw.rect(screen, BLACK, [0, 0, 880, h]) # 上側を閉じていく
                pygame.draw.rect(screen, BLACK, [0, 720-h, 880, h]) # 下側を閉じていく
            if tmr == 5:
                floor = floor + 1
                if floor > fl_max:
                    fl_max = floor
                welcome = 15
                make_dungeon()
                put_event()
            if 6 <= tmr and tmr <= 9:
                h = 80*(10-tmr)
                pygame.draw.rect(screen, BLACK, [0, 0, 880, h]) # 上側を開いていく
                pygame.draw.rect(screen, BLACK, [0, 720-h, 880, h]) # 下側を開いていく
            if tmr == 10:
                idx = 1

        elif idx == 3: # アイテム入手もしくはトラップ(宝箱・繭)
            draw_dungeon(screen, fontS)
            screen.blit(imgItem[treasure], [320, 220]) # アイテム画像
            draw_text(screen, TRE_NAME[treasure], 380, 240, font, WHITE) # アイテムテキスト
            if tmr == 10:
                idx = 1

        elif idx == 9: # ゲームオーバー
            if tmr <= 30:
                PL_TURN = [2, 4, 0, 6]
                player.a = PL_TURN[tmr%4] # プレイヤーを回転
                if tmr == 30:
                    player.a = 8 # 倒れた絵
                draw_dungeon(screen, fontS)
            elif tmr == 31:
                # se[3].play()
                draw_text(screen, "You died.", 360, 240, font, RED)
                draw_text(screen, "Game over.", 360, 380, font, RED)
            elif tmr == 100:
                idx = 0
                tmr = 0

        elif idx == 10: # 戦闘開始
            if tmr == 1:
                # pygame.mixer.music.load("sound/ohd_bgm_battle.ogg")
                # pygame.mixer.music.play(-1)
                mon_typ = init_battle(screen)
                init_message()
            elif tmr <= 4:
                bx = (4-tmr)*220
                by = 0
                screen.blit(imgBtlBG, [bx, by]) # バトル画面を右から左へ挿入していく
                draw_text(screen, "Encounter!", 350, 200, font, WHITE)
            elif tmr == 5:
                set_message(monster[0].name + " が現れた!")
                draw_battle(screen, fontS, turn_obj)
            elif tmr == 6:
                if len(monster) >= 2:
                    set_message(monster[1].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj)
                    time.sleep(0.3)
            elif tmr == 7:
                if len(monster) >= 3:
                    set_message(monster[2].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj)
                    time.sleep(0.3)
            elif tmr == 8:
                if len(monster) == 4:
                    set_message(monster[3].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj)
                    time.sleep(0.3)
            elif tmr == 9:
                btl_start = random.randint(1, 32)
                if btl_start == 1: # 先制攻撃
                    init_message()
                    if random.randint(0, 1) == 0:
                        set_message("しかし " + mon_typ + "は")
                        set_message("まだ こちらに きづいていない!")
                    else:
                        set_message("しかし " + mon_typ + "は")
                        set_message("おどろき とまどっている!")
                    draw_battle(screen, fontS, turn_obj)
                    time.sleep(0.5)
                elif btl_start == 2: # 不意打ち
                    init_message()
                    if random.randint(0, 1) == 0:
                        set_message(mon_typ + "は")
                        set_message("いきなり おそいかかってきた!")
                    else:
                        set_message(mon_typ + "は")
                        set_message(player.name + "が みがまえるまえに")
                        set_message("おそいかかってきた!")
                    draw_battle(screen, fontS, turn_obj)
                    time.sleep(0.5)
                else: # 通常攻撃
                    btl_start = 0
                    
            elif tmr <= 16:
                draw_battle(screen, fontS, turn_obj)
                time.sleep(1)
                init_message()
                if btl_start == 2: # 表示時間の問題でここで改めて判定
                    idx = 23
                else:
                    idx = 11
                tmr = 0

        elif idx == 11: # プレイヤーのターン(入力待ち)
            btl_enemy = 0
            player.re_defense() # ぼうぎょを元に戻す(ぼうぎょ効果を消す)(呪文効果が残っているか確認)
            draw_battle(screen, fontS, turn_obj)
            if battle_command(screen, key) == True:
                if COMMAND[btl_cmd_y][btl_cmd_x] == "こうげき":
                    idx = 19
                    tmr = 0
                if COMMAND[btl_cmd_y][btl_cmd_x] == "じゅもん":
                    idx = 18
                    tmr = 0
                if COMMAND[btl_cmd_y][btl_cmd_x] == "ぼうぎょ":
                    player.act = 18 # get_battle_turnの戻り値idxを18
                    player.defense() # 選択した時点で防御力2倍(ターンが回ってきてからではない)
                    idx = 23
                    tmr = 0
                if COMMAND[btl_cmd_y][btl_cmd_x] == "にげる":
                    idx = 14
                    tmr = 0

                # コマンドの位置のリセット
                btl_cmd_x = 0
                btl_cmd_y = 0

        elif idx == 12: # プレイヤーの攻撃
            draw_battle(screen, fontS, turn_obj)
            if tmr == 1:
                set_message(turn_obj.name + " の攻撃!")
                # se[0].play()
                dmg = player.attack(monster[btl_enemy])
            if 2 <= tmr and tmr <= 4:
                screen.blit(imgEffect[0], [monster[btl_enemy].x+monster[btl_enemy].img.get_width()-tmr*80, tmr*80])
            if tmr == 5:
                emy_blink = 5
                if dmg > 0:
                    set_message(monster[btl_enemy].name + "に " + str(dmg)+"ポイントのダメージを与えた!")
                else:
                    set_message("ミス!" + monster[btl_enemy].name + "にダメージを与えられない!")
            if tmr == 11:
                monster[btl_enemy].hp = monster[btl_enemy].hp - dmg
                if monster[btl_enemy].hp <= 0:
                    monster[btl_enemy].hp = 0
                    set_message(turn_obj.name + " は " + monster[btl_enemy].name + " をやっつけた!")
                    btl_exp += monster[btl_enemy].exp
                    del_battle_turn(monster[btl_enemy]) # ターンオブジェクトから消す(倒したモンスターは攻撃しない)
                    dead_monster.append(monster[btl_enemy]) # 先に加える
                    monster.pop(btl_enemy)
                if not monster: # 空だとFalseを返すので not monsterがTrueだと空
                    idx = 16 # 勝利
                    tmr = 0
            if tmr == 16:
                init_message()
                idx = 24 # ターンの確認
                tmr = 0

        elif idx == 13: # 敵のターン、敵の攻撃
            draw_battle(screen, fontS, turn_obj)
            if tmr == 5:
                set_message(turn_obj.name + " の攻撃!")
                # se[0].play()
                emy_step = 30
            if tmr == 9:
                dmg = turn_obj.attack(player)
                if dmg > 0:
                    set_message(player.name + "は " + str(dmg) + "ポイントのダメージを受けた!")
                    dmg_eff = 5
                else:
                    set_message("ミス!" + player.name + " はダメージを受けない!")
                    dmg_eff = 0

                emy_step = 0
            if tmr == 15:
                player.hp = player.hp - dmg
                if player.hp <= 0:
                    player.hp = 0
                    idx = 15 # 敗北
                    tmr = 0
            if tmr == 20:
                init_message()
                idx = 24 # ターンの確認
                tmr = 0

        elif idx == 14: # 逃げられる?
            draw_battle(screen, fontS, turn_obj)
            if tmr == 1: set_message(player.name + " は逃げ出した!")
            if tmr == 5:
                if random.randint(0, 99) < 60:
                    btl_exp = 0 # 逃げたら0
                    idx = 22 # 戦闘終了
                else:
                    set_message("しかし、まわりこまれてしまった!")
            if tmr == 10:
                init_message()
                btl_start = 2 # 不意打ちと同じ状態にする
                idx = 23 # ターンセット
                tmr = 0
             
        elif idx == 15: # 敗北
            draw_battle(screen, fontS, turn_obj)
            if tmr == 1:
                pygame.mixer.music.stop()
                set_message(player.name + " は気絶した...")
            if tmr == 11:
                idx = 9 # ゲームオーバー
                tmr = 29

        elif idx == 16: # 勝利
            draw_battle(screen, fontS, turn_obj)
            if tmr == 1:
                set_message(player.name + " は " + mon_typ + " をやっつけた!")
                pygame.mixer.music.stop()
                # se[5].play()
                player.exp += btl_exp
                if player.exp >= player.lv_exp:
                    idx = 17
                    tmr = 0                
            if tmr == 28:
                idx = 22 # 戦闘終了

        elif idx == 17: # レベルアップ
            draw_battle(screen, fontS, turn_obj)
            if tmr == 1:
                set_message(player.name + " はレベルが上がった!")
                # se[4].play()
                player.lv_up()
            if tmr == 21:
                init_message()
                set_message("最大HP:"+str(player.maxhp))
            if tmr == 26:
                set_message("素早さ:"+str(player.quick))
            if tmr == 30:
                set_message("攻撃力:"+str(player.atk))
            if tmr == 34:
                set_message("防御力:"+str(player.dfs))
            if tmr == 40:
                if player.exp >= player.lv_exp:
                    idx = 17
                    tmr = 0
            if tmr == 50:
                idx = 22 # 戦闘終了
        
        elif idx == 18: # ぼうぎょ
            draw_battle(screen, fontS, turn_obj)
            if tmr == 1:
                player.defense()
                set_message(player.name + "は みをまもっている!")
            if tmr == 5:
                init_message()
                idx = 24 # ターンの確認
                tmr = 0

        elif idx == 19: # 敵の選択
            draw_battle(screen, fontS, turn_obj)
            if battle_select(screen, key) == True:
                player.act = 12 # get_battle_turnの戻り値idxを12
                idx = 23
                tmr = 0

        elif idx == 22: # 戦闘終了
            # pygame.mixer.music.load("sound/ohd_bgm_field.ogg")
            # pygame.mixer.music.play(-1)
            idx = 1
            monster.clear() # モンスターオブジェクトを削除
            dead_monster.clear()
            btl_exp = 0

        elif idx == 23: # ターンセット
            set_battle_turn(btl_start)
            btl_start = 0 # 戦闘は通常に戻す
            idx = 24
            tmr = 0

        elif idx == 24: # ターン確認
            tmr = 0 # 0にしないと idx=12では battle_calに行かない()
            turn_obj, idx = get_battle_turn()


        draw_text(screen, "[S]peed "+str(speed), 740, 40, fontS, WHITE)

        pygame.display.update()
        clock.tick(4+2*speed)

if __name__ == '__main__':
    main()

 

【ご注意】
プログラムやデータなどは著作権法により保護されています。
著作者の許諾を得ずに、プログラムおよびデータそのものまたは改変したものを
配布したり販売したりすることはできません。
また、これらを利用して発生した損害などに関して、著作者は一切責任を負いません。

 

 

-CODE, Python, RPG
-, , , , , , , ,

Copyright© kerublog , 2021 All Rights Reserved Powered by STINGER.