ゲーム化!tomo_manaのブログ

ゲーム化!tomo-manaのブログ

Unityでゲームを作る方法について紹介しています

Unity×会計#11 複数年での決算書の比較(決算書の追加・削除)(Unity2019.4.4f1)

f:id:tomo_mana:20211220011416p:plain

これまで作ってきた決算表を、5年分まで表示できるように拡張します。


(変更イメージ)

f:id:tomo_mana:20211107121013p:plain
5年分の決算を表示

入力画面の整理

これまでの画面

f:id:tomo_mana:20211109230215p:plain
これまでの画面(#11)

前回までの記事と、各入力画面の名前を変えています。

SettlementItemInput は決算項目の入力フォームです。TradeMultiInput と Trade1Input は各取引の仕訳を入力するためのフォームです。

階層は以下のようになっています。

f:id:tomo_mana:20211109230103p:plain
Hierarchy - これまでの画面(#11)

今回新しくする画面

これまでの画面が、図の左側です。これを5年表示にするため、右側のように変更します。

f:id:tomo_mana:20211109225937p:plain
新画面構成
f:id:tomo_mana:20211109230855p:plain
Hierarchy - 新画面構成

貸借対照表を直接編集できるモードは、非表示にしています。そのうちに追加します。

配置換え

配置換えについて、実際の作業を簡単にまとめます。

(1) オブジェクトの座標移動(省略)
(2) 貸借対照表損益計算書の高さを変更できるようにする
(3) 貸借対照表損益計算書をグループ化し、プレハブにする
(4) 複数の財務諸表を整列するグループを作成する
(5) 財務諸表をリスト管理するコードを作成する

オブジェクトの座標移動

省略します。

貸借対照表損益計算書の高さを変更できるようにする

5年分の比較をするにあたり、貸借対照表損益計算書を、それぞれ他の年と比較できるようにする必要があります。大きさを変えられるようにするために、RectTransformの設定を見直します。

f:id:tomo_mana:20211107220840p:plain
RectTransform、VerticalLayoutGroup - 貸借対照表(上向きに伸びる)、損益計算書(下向きに伸びる)

貸借対照表損益計算書をグループ化し、プレハブにする

貸借対照表損益計算書をひとまとめにしたグループ FinancialStatement を作成します。これは Emptyゲームオブジェクトで作成します。プレハブにする予定なので名前にPrefabを追加しました。

f:id:tomo_mana:20211107221712p:plain
1年分の財務諸表をまとめるグループ(FinancialStatementsPrefab)

このグループはさらに上のグループから整列を行います。

f:id:tomo_mana:20211110233422p:plain
複数年分の財務諸表を整列するグループ(StatementWindow)

すでにレイアウトを組んである貸借対照表損益計算書を崩してしまわないように、LayoutGroupを使わずに、LayoutElement を付与しておきます。

f:id:tomo_mana:20211107222432p:plain
RectTransform、LayoutElement - FinancialStatementPrefab

上記の設定の後、グループごとプレハブにします。

作成したグループをプレハブ化するには、Hierarchy ウィンドウから Project ウィンドウの Assets フォルダにドラッグ&ドロップすればよいのでした。

f:id:tomo_mana:20211107222206p:plain
プレハブ化

複数の財務諸表を整列するグループを作成する

複数の財務諸表を横一列に整列するグループStatementWindowを作ります。こちらもEmptyオブジェクトで作ります。

f:id:tomo_mana:20211107222738p:plain
複数年分の財務諸表を整列するグループ(StatementWindow)

作成したオブジェクトには、HorizontalLayoutGroupを設定します。

f:id:tomo_mana:20211107222948p:plain
RectTransform、HorizontalLayoutGroup - StatementWindow

先ほど作成したプレハブを5つに増やしてみて、表の幅や間隔などを確認します。

f:id:tomo_mana:20211107223518p:plain
プレハブのHierarchyへの追加
f:id:tomo_mana:20211107223627p:plain
財務諸表の幅と間隔の確認(Sceneビュー)

幅と間隔の確認が終わったら、プレハブは一旦削除します。

f:id:tomo_mana:20211107223735p:plain
プレハブを削除(Assetsフォルダには残っています)

財務諸表をリスト管理するコードを作成する

コードの関係性を整理する

決算処理ボタンから表を直接書き変えていたのですが、間に表示処理を追加します(StatementWindow)。図にしてしまうと大した修正ではないのですが、複数のコードにわたって修正をすると、思いのほか複雑に感じます。

f:id:tomo_mana:20211109235211p:plain
処理の流れ(赤線を追加)

動作仕様
最初はサンプルを表示します。
1年目の決算が行われた時、サンプルを1年目の決算と置換します。
2年目以降は、財務諸表を追加します。
5年目以降は、古い財務諸表を消し、新しい財務諸表を追加します(実際は表示・非表示を使って並べ替えをします)

f:id:tomo_mana:20211110000500p:plain
表示イメージ

財務諸表の追加、削除

ゲームオブジェクトのリストといえば、親子関係を持っているTransformでした。起動時に5個分の財務諸表を作っておいて、表示・非表示だけを切り替えるようにします。

void 追加()
{
    if( 表示数 < 5 ){
        GameObject obj = gameObject.transform.GetChild( 表示数 ).gameObject;
        obj.SetActive( true );
        表示数++;
    }
}
void 削除()
{
    if( 0 < 表示数 ){
        GameObject obj = gameObject.transform.GetChild( 表示数 ).gameObject;
        obj.SetActive( false );
        表示数--;
    }
}

財務諸表の入れ替え(古いものを削除して、新しいものを追加)

void 並べ替え()
{
    gameObject.transform.GetChild( 0 ).SetAsLastSibling();
}

財務諸表のリセット

void リセット() {
    for( i = 0; i < 表示数; i++ ){
        if( i != 0 ){
            gameObject.transform.GetChild( i ).gameObject.SetActive( false );
        }
    }
    表示数 = 0;
}

コード

表の表示部(StatementWindow.cs)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StatementWindow : MonoBehaviour
{
    [SerializeField] GameObject financialStatementPrefab = default;
    
    // 財務諸表の表示数
    int displayedNumber = 0;
    
    // 選択されているインデックス
    int activeStates {
        set {}
        get
        {
            // 選択できるようになったら、その値を返す
            return displayedNumber - 1;
        }
    }
    
    BalanceSheet GetBalanceSheet(int selectedIndex)
    {
        if( (selectedIndex < 0) || (4 < selectedIndex) ){
            selectedIndex = 0;
        }
        Transform statement = gameObject.transform.GetChild( selectedIndex );
        
        BalanceSheet bs;
        foreach( Transform child in statement ){
            bs = child.gameObject.GetComponent<BalanceSheet>();
            if( !ReferenceEquals( bs, null ) ){
                return bs;
            }
        }
        return null;
    }
    
    ProfitAndLoss GetProfitAndLoss(int selectedIndex)
    {
        if( (selectedIndex < 0) || (4 < selectedIndex) ){
            selectedIndex = 0;
        }
        Transform statement = gameObject.transform.GetChild( selectedIndex );
        
        ProfitAndLoss pl;
        foreach( Transform child in statement ){
            pl = child.gameObject.GetComponent<ProfitAndLoss>();
            if( !ReferenceEquals( pl, null ) ){
                return pl;
            }
        }
        return null;
    }
    
    void ShiftStatements()
    {
        // SetAsFirstSibling, SetAsLastSibling を使用する
        gameObject.transform.GetChild(0).SetAsLastSibling();
    }
    
    public void UpdateStatements(BalanceSheetDB bsdb, ProfitAndLossDB pldb, int selectedIndex)
    {
        bool shift = false;
        
        if( (selectedIndex < 0) || (5 < selectedIndex) ){
            Debug.Log( "selectedIndex = " + selectedIndex + " out of range" );
            return;
        }
        if( selectedIndex == 5 ){
            selectedIndex--;
            if( displayedNumber <= selectedIndex ){
                Add();
                selectedIndex = activeStates;
            } else {
                shift = true;
            }
        }
        
        if( shift ){
            ShiftStatements();
        }
        GetBalanceSheet( selectedIndex ).ChangeAreaValues( bsdb );
        GetProfitAndLoss( selectedIndex ).ChangeAreaValues( pldb );
        
        // 最大のデータを基準に、各指標の比率を決定
        int i, bs_val, pl_val, bs_max = 0, pl_max = 0;
        for( i = 0; i < displayedNumber; i++ ){
            bs_val = GetBalanceSheet( i ).GetMaxValue();
            if( bs_max < bs_val ){
                bs_max = bs_val;
            }
            pl_val = GetProfitAndLoss( i ).GetMaxValue();
            if( pl_max < pl_val ){
                pl_max = pl_val;
            }
        }
        for( i = 0; i < displayedNumber; i++ ){
            GetBalanceSheet( i ).SetMaxValue( bs_max );
            GetProfitAndLoss( i ).SetMaxValue( pl_max );
        }
    }
    
    public void ResetStatements(BalanceSheetDB bsdb, ProfitAndLossDB pldb)
    {
        int i;
        for( i = 0; i < displayedNumber; i++ ){
            if( i != 0 ){
                gameObject.transform.GetChild( i ).gameObject.SetActive( false );
            }
        }
        displayedNumber = 0;
        
        GetBalanceSheet( 0 ).ChangeAreaValues( bsdb );
        GetProfitAndLoss( 0 ).ChangeAreaValues( pldb );
    }
    
    void Add(){
        if( displayedNumber < 5 ){
            GameObject obj = gameObject.transform.GetChild( displayedNumber ).gameObject;
            obj.SetActive( true );
            displayedNumber++;
        }
    }
    
    void Remove(){
        if( 0 < displayedNumber ){
            GameObject obj = gameObject.transform.GetChild( displayedNumber ).gameObject;
            obj.SetActive( false );
            displayedNumber--;
        }
    }
    
    // Start is called before the first frame update
    void Start()
    {
        int i = 0;
        
        for( i = 0; i < 5; i++ ){
            GameObject obj = Object.Instantiate( financialStatementPrefab );
            if( i != 0 ){
                obj.SetActive( false );  // 最初はサンプル表示
            }
            obj.transform.SetParent( gameObject.transform, false );
        }
        displayedNumber = 0;
    }
}

貸借対照表損益計算書は、それぞれ自身の子・孫にあたる領域を見つけるために GameObject.Find() を使用していたのですが、これだとプレハブのように複数のオブジェクトを同名で管理するような場合、正しく参照を得られないことが分かったので、Transformの参照を使うようにしました。


貸借対照表(BalanceSheet.cs)
(長くなるので、変更点のみ。損益計算書も同じ修正をしています。)

public class BalanceSheet : MonoBehaviour, ILangSwitchHandler
{
    Vector2 v_ini = new Vector2();      // 初期寸法
    Vector2 v_calced = new Vector2();   // 現在寸法
    
    public int GetMaxValue()
    {
        return bsArea[(int)(BS_AREA.BS_CASH)].value + bsArea[(int)(BS_AREA.BS_FLA)].value + bsArea[(int)(BS_AREA.BS_FIA)].value + bsArea[(int)(BS_AREA.BS_NAN)].value;
    }
    
    float calculatedRateY = 1.0f;
    public void SetMaxValue( int max )
    {
        if( max > 0 ){
            calculatedRateY = (float)GetMaxValue() / max;
            RedrawBalanceSheet();
        }
    }
    
    private void RedrawBalanceSheet()
    {
        /* 略 */
        
        // 貸借対照表の大きさ
//        int height = (int)(((RectTransform)(gameObject.transform)).sizeDelta.y * calculatedRateY);
        int height = (int)(v_ini.y * calculatedRateY);
        v_calced.x = v_ini.x;
        v_calced.y = height;
        ((RectTransform)(gameObject.transform)).sizeDelta = v_calced;
        
        /* 略 */
    }
}


決算ボタンの処理(SettleInput.cs)
(長くなるので、変更点のみ)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SettleInput2 : MonoBehaviour
{
    enum BUTTON_NAME {
        BUTTON_SETTLE,
        BUTTON_RESET,
    };
    [SerializeField]
    BUTTON_NAME buttonName = BUTTON_NAME.BUTTON_SETTLE;
    [SerializeField]
    StatementWindow w = default;
    [SerializeField]
    JournalLog log = default;
    [SerializeField]
    SalesTransaction2 ts = default;
    
    void ExecuteSettle()
    {
        int e = -1;

        if( !ReferenceEquals(ts, null) ){
            e = ts.ExecuteSettle();
        }
        if( e == 0 ){
            e = 3;
            BalanceSheetDB bsdb = ts.GetBalanceSheet();
            ProfitAndLossDB pldb = ts.GetProfitAndLoss();
            w.UpdateStatements( bsdb, pldb, 5 );
            UpdateLog( e );
        }
    }
    
    void ExecuteReset()
    {
        int e = -1;

        if( !ReferenceEquals(ts, null) ){
            e = ts.ExecuteReset();
        }
        if( e == 0 ){
            e = 3;
            BalanceSheetDB bsdb = ts.GetBalanceSheet();
            ProfitAndLossDB pldb = ts.GetProfitAndLoss();
            w.ResetStatements( bsdb, pldb );
            UpdateLog( e );
        }
    }
    /* 略 */
}

動作テスト

ゲームのURL(前回までと同じURLです)
tomo-mana.hatenablog.com

次回

商品の複数化