概要
こんにちは、最近ゲーム制作にかまけてエロゲができていないエロゲーマー蟹丸です。Ren'Pyを使った2D RPG制作第三回です。
今回はアイテムと道具屋を作っていきます。
前回:Ren'Pyで2D RPGを作る(2)―パーティーとステータス
本記事の範囲
アイテムを実装し、メニュー画面からの使用、道具屋での売買を可能にする
背景
RPGにどうしてアイテムが必要なんだ!という思いを抱く人はいないと思います。
バトルを作る前にアイテムを作っておけば戦闘中も回復できるようになるので、先にアイテムを作ってしまいます。
1. アイテムを作る
まずはアイテムを作る~メニュー画面から使用する部分を実装していきます。
システム実装
前回と同様こちらはRen'PyではなくただのPythonなので、Pythonがわかる人は自分の好きなようにやってしまえると思います。
※この部分はitem.rpyというファイルのinit pythonステートメント内に書いているものとして読んでください。
まずはinventory
をdict型で作ります。keyをアイテムのインスタンス、valueを所持アイテム数として使います。ユーザ定義型のインスタンスはhashableなのでdict型のkeyとして使えるんですね。便利です。
普通にPythonコードを書くときはあんまりグローバル変数は使いたくないですが、Ren'Pyはグローバル変数を使ってなんぼなところがあるので容赦なく使います。
inventory = {}
次にアイテムの基本となるItem
クラスを作ります。
インスタンス変数として値段、使用可能フラグなどを定義しておきます。
メソッドは使用時のメッセージを返すgetter、取得・売却時の処理などを定義しています。
class Item():
def __init__(self, name, desc, price):
self.name = name
self.desc = desc
self.price = price
self.sell_price = int(price / 2.0)
self.marketable_flag = True # Falseの場合売ることができない
self.usable_flag = True # Falseの場合通常時使うことができない
self.battle_flag = True # Falseの場合戦闘中に使うことができない
self.icon_image = "images/icons/medical_item.png"
### 中略:基本のgetter ###
def get_confirm_msg(self):
return self.name + "を使いますか?"
def get_result_msg(self):
return self.name + "を使いました"
# お店での購入
def buy_item(self, buy_amount = 1):
party_money.add_value(-self.price * buy_amount)
inventory[self] += buy_amount
# お店での売却
def sell_item(self, sell_amount = 1):
party_money.add_value(self.sell_price * sell_amount)
inventory[self] -= sell_amount
# 拾ったりもらったりしたとき
def find_item(self, find_amount = 1):
inventory[self] += find_amount
アイテム種別ごとにItem
クラスを継承したクラスを作ります。回復アイテム、ステータスアップアイテム、素材アイテム、レシピアイテムなどなど。自分のゲームで必要になるものを実装します。
以下には例として回復アイテムのクラスのコードを載せます。
- 回復アイテムの対象ステータス
- 回復量
- アイテム使用時のメソッド
を新たに定義しています。
アイテム使用時は対象ステータスのインスタンスのadd_current_value(diff)
メソッドを呼び出して回復し、先ほど定義したinventory
からそのアイテムのvalueを一つ減らします。
# 回復アイテム
class MedicalItem(Item):
def __init__(self, name, desc, price, target_stat, amount):
super(MedicalItem, self).__init__(name, desc, price)
self.target_stat = target_stat
self.amount = amount
def get_target_stat(self):
return self.target_stat
def get_amount(self):
return self.amount
def get_usable_flag(self):
if self.target_stat.get_current_value() == self.target_stat.get_max_value():
return False
else:
return True
# 現在値を上げる
def use_item(self):
self.target_stat.add_current_value(self.amount)
inventory[self] -= 1
ここまでできたら、以下のようにしてinventory
に所持アイテムを追加します。
medical_hp_small = MedicalItem(name = "すこし苦い薬", desc = "体力を小回復する。", price = 50, target_stat = stat_hp, amount = 40)
inventory[medical_hp_small] = 3
これで所持アイテムを管理できるようになりました。
UI実装
こちらがRen'Py独自のもの。今回実装した結果を先に図で示しておきます。
主だった機能と利用した変数などは以下のようになります。
- メニュー画面にアイテム一覧を表示(
inventory
からvalueが0より大きいkeyを表示) - 現在使用可能なアイテムと使用不可能なアイテムで色分け(
usable_flag
を利用) - アイテム名にhoverすると右にアイテム説明が表示される(Ren'Pyのtooltipを利用)
- 使用可能なアイテムをクリックすると道具を使用する(
use_item()
を使用)
それではコードを載せていきます。
# アイテム画面の設定
screen inventory():
tag menu
use game_menu(_("アイテム"), scroll="None"):
style_prefix "inventory"
hbox: # ステータス、アイテム、アイテム一覧を横に並べる
spacing 40
vbox:
### 中略:ステータススクリーン(前回参照) ###
viewport: # アイテムが多いときにスクロールできるようviewportを使用する
scrollbars "vertical"
mousewheel True
draggable True
pagekeys True
xsize 350
ysize 500
vbox:
label _("アイテム")
null height (2 * gui.pref_spacing)
for item, amount in inventory.items():
if amount > 0:
hbox:
$item_name = item.get_name()
$usable_flag = item.get_usable_flag()
$confirm_msg = item.get_confirm_msg()
$desc = item.get_desc()
$icon_image = item.get_icon_image()
add icon_image
if usable_flag and amount > 0: # アイテムが使用可能なとき
textbutton _(item_name + " " + str(amount)):
tooltip desc
action Confirm(confirm_msg, UseItem(item))
else: # 使用不可能なとき。insensitiveにするとtooltipが使えないので注意
textbutton _(item_name + " " + str(amount)):
tooltip desc
action NullAction()
text_style "unusable_text" # 色を変える
null height (gui.pref_spacing)
vbox:
xsize 350
label _("アイテム詳細")
$ tooltip = GetTooltip()
if tooltip:
null height (4 * gui.pref_spacing)
text "[tooltip]"
### style省略 ###
style unusable_text:
color gui.insensitive_color
このコードでは使用可能なアイテムがクリックされたとき、confirm_msg
を出すConfirm
スクリーンを表示し、「はい」が選択された場合UseItem
アクションを呼びます。UseItem
は独自に実装したActionです。
init:
python:
class UseItem(Action):
def __init__(self, item):
self.item = item
def predict(self):
renpy.predict_screen("notify")
def __call__(self):
renpy.notify(self.item.get_result_msg())
self.item.use_item()
Ren'PyではAction
クラスを継承したクラスはActionとして呼ぶことができ、呼ばれたとき__call__()
内の処理が行われます。
ここではUseItem(item)
が呼ばれたとき、item
のresult_msg
を取得してそれをnotifyに表示したのち、item
のuse_item()
メソッドを呼ぶようにしています。
最後にここで作ったinventory()
スクリーンをゲームメニューに表示します。ステータスの場合と同様ですね。
screen navigation():
### 中略 ###
if main_menu:
### 中略 ###
else:
textbutton _("ステータス") action ShowMenu("stats")
textbutton _("アイテム") action ShowMenu("inventory") # この行を追加
これでゲームメニューからアイテムを使えるようになりました。
2. 道具屋での売買
アイテムを使えるようになったので、道具屋で売買できるようにしましょう。Item
クラスで定義していたprice
やbuy_item()
メソッドの出番です。
システム実装
こちらはあまりやることがなく、道具屋さんにおいてあるアイテムを定義するくらいです。今回はlistにしましたが、在庫の概念がある場合はdictで作れば対応できます。
# 道具屋においてあるアイテムを登録する
init python:
store = [medical_hp_small, medical_hp_medium, enhancing_hp]
UI実装
こちらも実装の結果を載せておきます。
いったんありものの画像で作っているので若干不自然ですが(スクロールバーなど特に)、機能的には完全に道具屋さんです。しかも画像では伝わりませんが購入・売却時にチャリーンと音が鳴ります。ゲームっぽいですね!
※使用した素材は記事の最後に載せています。
以下コードです。クラス定義も一気に載せます。
init 1 python:
# 購入imagemapのtextbuttonが押されたとき
class BuyItem(Action):
def __init__(self, item):
self.item = item
def __call__(self):
self.item.buy_item()
renpy.restart_interaction()
renpy.play("audio/sounds/coins.mp3")
# 売却imagemapのtextbuttonが押されたとき
class SellItem(Action):
def __init__(self, item):
self.item = item
def __call__(self):
self.item.sell_item()
renpy.restart_interaction()
renpy.play("audio/sounds/coins.mp3")
screen store_screen(store_instance):
style_prefix "store"
$current_money = party_money.get_value() # グローバル変数party_money(適当に作ったmoney用クラスのインスタンスから現在値を取得)
frame:
background "gui/store/blank.png"
style_group "mm"
xalign 0
yalign 0
hbox:
xalign 0.05 yalign 0.03
add "images/icons/money.png"
text str(current_money)
null height (4 * gui.pref_spacing)
hbox: # 購入imagemapと売却imagemapを横に並べる
xalign 0.05
yalign 0.4
imagemap:
ground "gui/store/background.png"
label _("購入")
viewport:
#yinitial yinitial
scrollbars "vertical"
mousewheel True
draggable True
pagekeys True
side_yfill True
vbox:
for item in store_instance: # 道具屋の在庫を金額とともに表示
$item_name = item.get_name()
$item_price = item.get_price()
hbox:
if item_price > current_money: # 所持金よりも高いアイテム
textbutton item_name sensitive False
add "images/icons/money.png" yalign 1.0
text str(item_price)
else: # 買えるアイテム
textbutton item_name action BuyItem(item)
add "images/icons/money.png" yalign 1.0
text str(item_price)
null width (2 * gui.pref_spacing)
imagemap:
ground "gui/store/background.png"
label _("売却")
viewport:
#yinitial yinitial
scrollbars "vertical"
mousewheel True
draggable True
pagekeys True
side_yfill True
vbox:
for item, amount in inventory.items(): # inventoryのアイテムを売値と数量とともに表示
if amount > 0:
$item_name = item.get_name()
$item_sell_price = item.get_sell_price()
$marketable_flag = item.get_marketable_flag()
hbox:
if marketable_flag: # 売れるアイテム
textbutton item_name action SellItem(item)
add "images/icons/money.png" yalign 1.0
text str(item_sell_price)
text "×" + str(amount)
else: # 売れないアイテム
textbutton item_name sensitive False
button: # 戻るボタン
xalign 0.9
yalign 1.0
xsize 145
ysize 100
text "戻る" yalign 0.35
background "gui/store/blank.png"
action Return(True) # 呼び出し元のlabelに戻る
### style省略 ###
これだけのコードで道具屋さんが作れるのはとても嬉しいですね!
結論と今後
今回はアイテムを実装し、メニュー画面からの使用と道具屋での購入・売却ができるようになりました。
そろそろバトルができそうなので、次回は戦闘システムを作っていこうと思います。
参考にしたコードや素材(敬称略)
コード参照&GUIを拝借:
Dragonaqua on Lemmasoft(https://lemmasoft.renai.us/forums/viewtopic.php?f=51&t=57105)
背景:
成瀬直瑚
立ち絵:
わたおきば(https://wataokiba.net/)
アイコン:
素材屋Rosa(http://sozairosa.blog.fc2.com/)