pythonでゲーム #6 ツリービューで種族をリストアップ

pygame
はじめに

(;´・ω・)久しぶりの投稿です

最近蒸し暑い日が続いておりますね!
今日はまたひとつ進捗をあげていきたいと思います

さて、

6月11日にGipheはこんなつぶやきをしていました

6月上旬に告知してそれからノータッチ

Gipheはなぜだかスプレッドで実装しようと考えていたようですね

だが、それはなしです

理由はスプレッドにすると、
Delete/Insertで実装することになりそうだからですね

①ある種族を選択→②選択した種族の進化先(複数)をスプレッドに表示

という画面を考えたのですが

スプレッドだとどこか一か所更新があった場合、
ピンポイントでの変更箇所の割り出し→変更箇所のみ更新というのが難しそうなんですね

新規追加の場合、スプレッドの新規行を追加するので、

更新と追加が同時に起こりうるわけで、
これらをそれぞれの場合に正確に動作させるのは難しそうなんですね

この場合Delete/Insert、表の全削除、全挿入が有効になるわけなのですが、

どうにもそのメリットが薄いようなんです

なぜか、それは進化先が3種類までしかない想定だからです (笑)

だからもっとシンプルでいいいという結論に至った次第です

思ったよりめんどくさ工数がかかりそうだったので
スプレッドを使わない方向で進めていきたいと思います!

やりたいこと

要件

①キャラクターの属性に種族を設定したい
②①の理由で種族を新規登録、更新できるGUIを作りたい
③種族はレベルで進化、進化前後のデータに互換性を持たせたい
④進化先は最大3枠まで設定可能にしたい
⑤種族ごとに、能力値の差異を持たせたい
⑥種族の削除ができるようにしたい
⑦ランダムでレベル、ステータス値を設定できるようにしたい

おおまかな課題

・進化前後のつながりをどのようにデータ上で保証するか
・ランダムで種族を決定した場合の、値の制御
・進化前データのUPDATEの考慮

ツリービューを使う

ツリービューはカテゴリ別に要素を並べる表示方法です

下のソースは果物と、その加工物を例にして
カテゴリ分けするサンプルです

import tkinter as tk
from tkinter import ttk
class FruitTree:
    def __init__(self,root):
        self.root = root
        self.iid=""
        self.rootiid=""
        self.frout_list = [
            {'id':1,'root_id':0,'name':'リンゴ'},
            {'id':2,'root_id':0,'name':'イチゴ'},
            {'id':101,'root_id':1,'name':'アップルパイ'},
            {'id':102,'root_id':1,'name':'リンゴジュース'},
            {'id':103,'root_id':2,'name':'イチゴ大福'},
        ]
        self.fruitRoot = {}
        self.fruitTree = {}
        # 画面表示
        self.createDisplay()
    def createDisplay(self):
        # フルーツフレーム
        self.pw_main = self.createTreeView(self.root)
        self.pw_main.pack(expand=True, fill = tk.BOTH, side="left")
    def createTreeView(self, pw_main):
        treeFrame = tk.Frame(pw_main, bg="cyan")
        # 検索したfruitをツリーに表示
        self.setSearchTree(treeFrame)
        return treeFrame
    def setSearchTree(self, treeFrame):
        self.tree = ttk.Treeview(treeFrame)
        # 選択イベント(必要であれば)
        # self.tree.bind("<<TreeviewSelect>>",self.targetFruit)
        # ツリー名
        self.tree.heading("#0",text="fruit_tree")
        self.tree.pack()
        # rootのiidを登録
        self.rootiid = self.tree.insert("","end",text="Home")
        self.iid = self.rootiid
        # 果物ツリー作成
        self.makeTree()
    # ツリー構成
    def makeTree(self):
        # 根となるfruitを取得
        initial_fruit = [fruit for fruit in self.frout_list if fruit['root_id'] == 0]
        # ツリーごとの果物要素を取得
        self.setFruitTree(initial_fruit,self.iid)
    # fruitのツリー構造を生成
    def setFruitTree(self,_addTree,_iid):
        # _addTree:アクティブツリーのノード
        for _fruit in _addTree:
            rootiid = self.tree.insert(_iid,"end",text=str(_fruit['id']) + ':' + _fruit['name'])
            iid = rootiid
            # 選択イベント用
            self.iid = iid
            # 選択果物を格納
            self.fruitRoot[iid] = _fruit
            # 進化先fruitを取得
            addFruit = [fruit for fruit in self.frout_list if _fruit['id'] == fruit['root_id']]
            # 進化先が存在する場合
            if len(addFruit) != 0:
                self.fruitTree[iid] = addFruit
                self.setFruitTree(addFruit,iid)
root = tk.Tk()
c = FruitTree(root)
root.mainloop()

若干長いですがこれを実行してみましょう!
はい、こんな画面です

Homeの隣の+を押してみましょう

メンバ変数で指定した

{‘id’:1,’root_id’:0,’name’:’リンゴ’},
{‘id’:2,’root_id’:0,’name’:’イチゴ’},

が表示されました!
さらに+を押してみましょう

リンゴの下に
{‘id’:101,’root_id’:1,’name’:’アップルパイ’},
{‘id’:102,’root_id’:1,’name’:’リンゴジュース’},

イチゴの下には
{‘id’:103,’root_id’:2,’name’:’イチゴ大福’},

が表示されていますね

最初の幹から枝がどんどん派生するように見えることから
ツリービューと呼ばれるようですね

今日はこれを活用して「種族」を管理するGUIを作っていきたいと思います

成果物

さっそくいきましょう

・種族ツリー(画面左)

種族を選択して、ステータスに反映

・モード選択

新規追加か、更新を選ぶ

・ステータス、名前

選択した種族を編集可能、種族選択はモード:更新の場合のみ可能

・ボタン、スケール

登録ボタン押下→modeによってUPDATE/INSERTいづれかを実施
スケールを変更→ランダムボタン押下でランダムで表示される数値を操作

とはいえわかりづらいので、
実際に動かしてみたいと思います

既存の進化先を選択し、進化先「クマムシ」を登録します

人間の進化先が「クマムシ」ではおかしいので
「勇者」に変更してあげましょう

さらに勇者を魔王に進化させてあげます

下のつまみ(スケール)をあげることで、
ランダムボタンを押したときに
強いステータスを狙って出すことが可能です

ちなみに「傾き」、「重み」は
#4のソフトマックス関数の応用です

#4では重み付けで確率を変動させましたが、
さらにweightのスケールを加えて

桁数、傾き、倍率を渡してランダムな値を取得する
ファンクションを使っています

# num:桁数、tilt:傾き、weight:倍率
def rand_num(self, num, tilt, weight):
        import numpy as np
        import matplotlib.pyplot as plt
        # 傾きを変数にして確率を変動
        a = np.arange(0,tilt+1,0.1)
        exp_a = np.exp(a)
        sum_exp_a = np.sum(exp_a)
        y = exp_a / sum_exp_a
        # ここで倍率かけて桁数を調整
        rn_int = int(random.choice(y)*(10**num)*weight/(weight+80))
        if rn_int > 10**(num-1):
            rn_int = 10**(num-1)
        if rn_int == 0:
            rn_int = random.randint(1,10**(num-2))
        # plt.plot(a,y)
        # plt.show()
        # rad_int = random.randint(1,10**(num-1))
        return rn_int

再帰データの扱い

データ構成のお話です

今回使用した種族マスタテーブルはこんな感じです

1種族に持たせる情報は
ステータス、進化先1~3、進化レベル1~3、進化元のあるなし、です

全ての進化元の場合は
進化元フラグをたてます

進化先は最大3種類まで、
最終進化の場合は進化先1に0を設定します

これを再帰的に実施することで
進化先のデータがさらに別の進化先のデータへ、というようにつながっていき
上のツリービューのような進化のツリーが完成します

実際に形になった時には感動しました(笑)

今回、最も難しかったのは、進化元の更新の考慮ですね

削除、更新、登録すべての場合に

進化元、あるいはすべての種族データを参照する必要があります

例えば削除。

1種族を削除したとします

するとその削除を進化先に設定していた種族のデータから

今ほど削除した種族のidを削除しなければなりません

進化先が1つならSQL1種類で済みますが、
進化先は3種類まであるので、3回UPDATEを流さなければなりません

という考慮がさらに更新、登録の処理ごとにそれぞれある感じです

UPDATE/INSERTだと小手先の操作が多くなる印象ですね、、

DELETE/INSERTの方が実は楽だったりするのでしょうか?

まとめ

キャラメイク画面、種族生成画面がとりあえず完成しました!

これまでpygameを一切使用していませんでしたが

これから本格的に着手していく予定です

今まで上げてきたソースの類は
以下からプルリクエストを入手可能です

GipheのGitHub

exeとかは全く作っていないので、create_character.pyを起動させて
「種族生成ボタン」から移動が可能です

dbAccess、DTO、DAOが軌道に必要ですので、
全部一緒にプルしてもらえればと思います

あとはmysqlのインストール、DBの作成ですが、
以下が参考になると思います

ER図はこちらをご利用ください!

ここまでお読みいただきありがとうございました。

質問等ございましたら、コメントやメール、twitterなどでお寄せください!!

]]>

コメント

タイトルとURLをコピーしました