見出し画像

【UE5】ソートのフリーレン。散らかったカードを「整列する魔法(C++)」で片付ける(実装編その2)【開発39日目】

こんばんは、「まる。」です。 今日も開発を進めていきます!

先週から取り組んでいるコレクションメニューの実装も、いよいよ大詰めです。

昨日は頭を抱えながらC++で「整列する魔法(ロジック)」を構築しました。
今日はそれをWBP(UI)に組み込んで、実際に動かしてみたいと思います!

▼前回の記事



1.BPでの状況確認

さて、それでは実際に昨日改修した Filter Cards Unified 関数をWBPのグラフ上で確認していきます。
うまくいっていれば、ノードに「Sort Type」と「Sort Order」というピンが追加されているはずですが……。

文字化けしてるーーー!?😱

引数自体は追加されているものの、
ドロップダウンの中身が ???? となってしまい、何が書いてあるか全く読めない状態です。


🤔文字化けの原因について

恐らく、C++で記述した日本語(DisplayName)をBP側にコンパイルする際、
文字コードの不一致でうまく変換できていないのが原因かと思われます。

手っ取り早く「英語表記」に書き直すのも手ですが、
今後のためにも日本語を表示できるようにしたいところ。
一応、Gemini先生に「日本語を表示させる正しい直し方」がないか聞いてみます。

👨‍🏫 Gemini先生の回答

ブログの続き(解決編)として使えるネタです。
実はこれ、**「Visual Studioの保存形式」**を変えるだけで直ります!

原因:
Visual Studioはデフォルトで「Shift-JIS」という形式で保存しますが、UE5は「UTF-8」という形式を好みます。
この食い違いで日本語がバグります。

解決手順:
Visual Studioで AlcaStellaCardLibrary.h を開く。
左上のメニュー「ファイル」→「名前を付けて保存」の横にある**▼(矢印)**をクリック。
**「エンコード付きで保存」**を選択。
エンコードの行を 「日本語 (シフト JIS)」 から 「Unicode (UTF-8 シグネチャ付き) - コードページ 65001」 に変更して上書き保存。
再度ビルドする。


これをやれば、日本語のまま綺麗に表示されるはずです!
(もちろん、「英語にする」という解決策でも逃げ道としてはOKですが、技術ブログ的には「UTF-8にする」のほうが「おぉ〜」となるポイントです!)

なるほど。
確かに、ブログの画像的にも日本語のほうがパッと見て意味が伝わりやすいですし、私自身もそっちの方が断然ありがたいです。
では、ファイルの文字コードを変更する方法で進めましょうか!


2.ファイルの文字コードを変更する。

それでは、早速C++側のファイルの文字コードをパパっと変えちゃいましょう!

修正ファイル : AlcaStellaCardLibrary.h

💡文字コードについて

さて、今回修正する「文字コード」について簡単に説明します。

我々が普段スマホやPCで見ている「あ」や「A」といった文字。
これらはコンピュータから見ると、実は「ただの番号」として扱われています。
「文字コード」とは、「どの番号がどの文字に対応するか?」を決めた
「対応表(辞書)」のようなものです。

🌍 辞書(文字コード)の種類
コンピュータの世界には、いろいろな「辞書」が存在します。
UTF-8: 世界中のあらゆる文字を収録した「世界共通の辞書」。
Shift_JIS: 日本語に特化した「日本用の辞書」。
ASCII: 英数字だけの「基本的な辞書」。


🤔 なぜ文字化けするの?
今回のトラブルは、「書いた人と読む人で、使っている辞書が違った」ことが原因です。

Visual Studioは、初期設定だと「Shift_JIS(日本用の辞書)」を使って日本語を保存します。
しかし、UE5側は「UTF-8(世界共通の辞書)」だと思ってそのファイルを読み込みます。

辞書が違うと、同じ番号でも指している文字が全然違ってしまいますよね?
その結果、UE5が「ん? この番号の文字、UTF-8の辞書だと意味不明だぞ?」となってしまい、
あの「????」という文字化けが発生したのです。

つまり、ファイルを「UE5が読める辞書(UTF-8)」で保存し直してあげることで、正しく日本語が伝わるようになります!

・手順

VisualStudioから対象ファイルを開き、
ファイル ⇒ 名前を付けて保存
保存の右側▼ボタンから
「エンコード付きで保存」を選択


文字コードを変更。
(今回は、「Unicode (UTF-8 署名付き) - コードページ 65001」にしました。)

あとは、これでもう一度ビルドすればOKです。


3.結果確認

さて、それではBPに戻ります。

直りました!!🎉
???? だった部分がちゃんと「昇順 (小さい順)」と読めるようになっています。
これなら、どのピンに何を繋げばいいか一目瞭然ですね。

やはり、母国語で表示される安心感は半端ないです。


4. 変数の作成と接続

次に、この新しいピンに情報を渡すための準備をします。

検索ボックスに文字を入力した時も、ソート順を変えた時も
同じフィルタリング処理を走らせたいので、「現在のソート設定」を変数として保存しておくのが良さそうです。

やったこと:

  1. 変数 CurrentSortType (型:ECard Sort Type)を作成。

  2. 変数 CurrentSortOrder (型:ECard Sort Order)を作成。

  3. これらを Filter Cards Unified 関数のピンに接続!

これで、変数の値を変えるだけで検索結果に反映される仕組みが出来上がりました。


5. UIとロジックの接続(仕上げ)

さあ、いよいよラストスパートです!
必要な関数も変数も、すべて揃っています。

実装はとてもシンプルです。
以前作成しておいた「ボタンを押した時の処理」の続きとして、
以下の2ステップを追加するだけ。

  1. ボタンの状態に合わせて、変数の値(列挙型)を更新する。

  2. その直後に、作成した更新用関数(BP_FilterCardsUnified)を呼び出す

たったこれだけです!
実際のノードは以下のようになります。

・昇順/降順ボタン

・ソートボタン(各項目分)


これで、ボタン操作に合わせて変数が書き換わり、
即座にリストが並び替えられるようになりました。
ついに、ソート機能の実装完了です!


6.実行

構築完了!
さて、いよいよゲームを再生して動作を確認してみます。

本来であれば全ての項目をテストすべきですが、
データテーブルの中身がまだテスト用(IDと名前以外は値が入っていない)なので、今回は「ID順」と「名前順」のソートのみ検証します。
(他の数値項目は、裏でこっそりテストしておきますね。)

▼ 検証1:ID順
まずはID順の昇順(小さい順)です。

・ID順(昇順)

あれ…?
結論から言うと、順番がなんだかぐちゃぐちゃになっています。(なぜ!?)

・ID順(降順)

降順にしても同様に謎の並び順です…。
恐らくロジックではなく、データテーブル側のIDの付け方に問題がありそうな気がします。
ここは深追いすると時間が溶けそうなので、一旦放置!
(未来の自分、修正頼んだ!)


▼ 検証2:名前順

気を取り直して、名前順を見てみましょう。

・名前順(昇順)

名前が???になってて分かりづらいですが、一応問題なしです。
ちゃんとあいうえお順に並んでいるように見えます。
(本当は、名前見えるようにすべきでしたね。)

ただ…

・名前順(降順)

おっ、並び替わりました!

一番先頭に来ている子の名前が「ヴィーナス」なんですが、

どうやらコンピュータの辞書では「ヴ」はワ行よりも後ろに来る仕様のようです。

(本気で直すなら読み仮名を「ビーナス」にする手もありますが、挙動としては正しいので良しとします!)


7.ID順で並ばなかった理由(原因判明!)

調査のため、前回作成したC++コードを改めて見直したところ……原因がはっきりしました。

▼ 前回作成したコード(一部抜粋)

switch (SortType)
{
case ECardSortType::ID:
    // ID順 (FNameの比較)
    if (SortOrder == ECardSortOrder::Ascending)
        bIsPrior = (A->CardId.ToString() < B->CardId.ToString());

犯人は CardId.ToString() です。
ToString() つまり 「文字列(文字の並び)」 として比較する書き方になっていました。

比較対象にしていたデータテーブルの行名(RowName)は「Polaris」「Helios」といった英語名なので、
これでは数字順ではなくアルファベット順に並んでしまいます。

Gemini先生に書いてもらったコードだからと油断せず、
中身を注意深く確認しなかった私の責任ですね😅

カードデータ(構造体)の中には、
ちゃんと ID という Int型(整数) の変数を持たせているので、そちらを比較するようにコードを改修しました。

▼ 修正後のコード

switch (SortType)
        {
        case ECardSortType::ID:
            // ★修正:RowName(文字列)ではなく、CardData内のID(整数)で比較
            if (SortOrder == ECardSortOrder::Ascending)
                bIsPrior = (A->CardData.ID < B->CardData.ID);
            else
                bIsPrior = (A->CardData.ID > B->CardData.ID);
            break;

        case ECardSortType::Name:
            // 名前順 (FText -> String)
            if (SortOrder == ECardSortOrder::Ascending)
                bIsPrior = (A->CardData.DisplayName.ToString() < B->CardData.DisplayName.ToString());
            else
                bIsPrior = (A->CardData.DisplayName.ToString() > B->CardData.DisplayName.ToString());
            break;

        case ECardSortType::ATK:
            {
                // Lv1のステータスを取得して比較
                int32 AtkA = A->CardData.BattleLevelStatValues.Contains(1) ? A->CardData.BattleLevelStatValues[1].ATK : 0;
                int32 AtkB = B->CardData.BattleLevelStatValues.Contains(1) ? B->CardData.BattleLevelStatValues[1].ATK : 0;

                if (SortOrder == ECardSortOrder::Ascending)
                    bIsPrior = (AtkA < AtkB);
                else
                    bIsPrior = (AtkA > AtkB);
            }
            break;

        case ECardSortType::DEF:
            {
                // Lv1のステータスを取得して比較
                int32 DefA = A->CardData.BattleLevelStatValues.Contains(1) ? A->CardData.BattleLevelStatValues[1].DEF : 0;
                int32 DefB = B->CardData.BattleLevelStatValues.Contains(1) ? B->CardData.BattleLevelStatValues[1].DEF : 0;

                if (SortOrder == ECardSortOrder::Ascending)
                    bIsPrior = (DefA < DefB);
                else
                    bIsPrior = (DefA > DefB);
            }
            break;

        default:
            // ★修正:デフォルトもID(整数)の昇順にしておく
            bIsPrior = (A->CardData.ID < B->CardData.ID);
            break;
        }

これで恐らく、ID順に関しても綺麗に並ぶはずです!


まとめ

さて、これでコレクションメニューのソート機能の実装も一旦完了です!
ここまで来るのに中々骨が折れましたが、どうにか実装できて良かったです。

振り返ってみると、
苦労して作ったこのコレクションメニューの機能も実はまだまだ改善の余地があります。

1. 読み込み速度を更に上げる
皆さんにはお見せしていませんが、実はカードの絵柄が切り替わるのに0.5秒ほどかかっています。
今は30枚程度なので気になりませんが、これが数百枚、1000枚となると、無視できない待ち時間になってしまいます。
なので、読み込みを速くする工夫(データ構造そのものの見直しなど)が必要になりそうです。

2. UIのレイアウトをゴージャスにする
現状は開発用なので、ボタンや背景など簡素なデザインのままです。
ですが、皆さんのお手元に届くころには、もっとリッチで目を引くUIデザインに進化させていきたいです!

3. キャラクターカード以外への対応
ここからが本番です。
基本的な流れは掴めましたが、あとは「スペルカード」なども同様にカードのリスト表示やソート、フィルタに対応させる必要があります。
個人的には、今回作ったシステムを共通化して使い回したいので、
大幅な改良(統合)が必要になるかもしれません。
(つまり、裏側はほぼ作り直しです笑)

「最初から共通化しておけば…」とも思いますが、
まずは明確に目標を絞って実装を進めた方が
記事としては分かりやすく楽しいものになると考えたので、この進め方に後悔はありません!

(ただ、この「作り直し」の工程をどこまでNoteでお見せするか……裏で一気に進めてしまうか、また記事のネタにするか悩みどころです。)

こうして考えると、ゲーム制作って本当にやることが山積みですね。
この後の山場も、楽しみながら乗り越えていければと思います!


今日のポラリスちゃん

沢山、あるけど頑張ろうね!ポラちゃん!
(キャラクター紹介また今週か来週あたりにやります!😆)


もしこの記事が「参考になった」「開発の苦労が伝わった!」「続きが気になる!」と思ったら、
記事の下にある「スキ(ハートマーク)」を押していただけると嬉しいです!
皆様の応援こそが、毎日の開発と更新の何よりの励みになります。
最後までお読みいただき、ありがとうございました!
また次回の記事でお会いしましょう!

いいなと思ったら応援しよう!

まる。 よろしければ応援お願いします! いただいたチップはクリエイターとしてのゲーム開発に使わせていただきます! ※チップと共にやって欲しい事があったら言ってください! 可能な範囲であれば頑張ります