メインコンテンツへスキップ
見出し画像

【UE5】カードをセンターに入れて…クリック。アンケートの結果を元に『新世紀な』自作TCGのデッキ編成UIを作ってみた【58回目】

    今日やった事
    ・クリック連打でデッキにカードを追加するようにした。
    ※Youtubeにアップロードすると何故か画質が悪くなる🤔


    どうも、「まる。」です!


    今回は開発記事を書いていきます。


    先日皆さまからいただいたアンケートをもとに、
    デッキへどんどんカードを追加していくシステムを、満を持して実装することにしました!


    アンケートツールの仕様上、具体的な回答者数は分からないようになっているのですが、
    パーセンテージを見る限り、かなり多くの方にご協力いただけたようです😎


    皆さま、本当にありがとうございます!!!🤭

    ■アンケート結果

    画像

    クリック派…圧倒的ですね。😱
    コメント欄を見る限り、薄々察してはおりましたが😏


    今回は、UE開発者向けにガッツリ実装過程も詳細に書きました!


    参考になるかは定かではないですが、良かったら見ていってください。


    1.実装方法を検討する

    まずは、今回作成するシステムについて整理していきます。


    (以前は「要件定義」なんて難しい言葉を使っていましたが、
    今回からは誰にでもスッと伝わるように、簡潔にまとめていきますね!)


    実際のゲーム画面を見ながら進めていくので、
    よろしければ皆さまも「開発チームの一員」になったつもりで一緒に考えてみてください。😏


    ■やりたいことを整理する

    さて、今回ベースとなる画面はこちらになります。

    画像

    目標は

    「カードをクリックするだけでデッキに追加できて、削除も簡単にできること」

    です。


    …さて、ここで少し考えてみてください。😏


    もしあなたがゲームデザイナーだとしたら、
    この画面にどんな機能を追加すれば、ユーザーが「遊びやすい!」と満足してくれるUIができると思いますか?


    少し想像してみてから、ゆっくり下にスクロールしてみてくださいね。👇





    ■まる。の考え

    思いつきましたか?😉


    こういうシステム実装に正解はないと思いますが、
    私なりに考えた機能はこちらです!

    画像

    すごくざっくりしていますが、
    こんな感じのことをやりたいと考えています。


    もっと言うと、カードにカーソルを合わせた時に、操作方法を教えてくれるUIも表示したいのですが……


    それはシステムが完成してから最後にやっていきたいので、今回は一旦除外します。


    ■さらに、それぞれの機能を細分化してみる

    ここまでで何となくのイメージがつかめましたが、
    それだけでは実際に動くシステムを作ることは難しいと考えています。


    そこで、そのイメージからさらに各機能の要素を細分化してみて、

    「まず何を作らないといけないのか」
    「どこから始めるべきなのか?」

    を明らかにしていきます。


    こうして分割した要素たちが全ての役割を果たすことで、
    ようやく先ほどやりたかった機能全体を作ることができると私は考えています。

    【左側:カードリストの処理】
    ・投入上限のチェック
     
    デッキに入れられる上限枚数を超えていないか判定します。
    (上限に達している場合は、そもそもクリックさせないようにします)

    ・デッキへの追加処理 
    クリックした際、右側のリストにそのカードを1枚追加します。
    (※すでに投入済みのカードなら、枚数のカウントだけを「+1」します)

    ・UIアニメーション 
    クリックした時に、カードが右のデッキリストへスッと流れていく演出を作ります。


    【右側:デッキリストの処理】
    ・ウィジェットの生成
     
    新しいカードが追加されたら、リスト上にカードのUI(ウィジェット)を生成します。

    ・自動ソートと総枚数カウント 
    リストが更新されたタイミングで、カードをID順に綺麗に並び替えます。
    同時進行で、現在のデッキの総枚数も計算します。

    ・データの一時保存 
    デッキリストの中身を、作成中デッキの一時的なデータ(構造体)としてしっかり保持しておきます。

    ・ホイール操作での増減機能 
    マウスホイールを使って、直感的にカードの追加・削除ができるようにします。
    (※ここでも上限枚数をチェックし、0枚になったらリストからUIを消して、一時データも都度更新します)

    私が思いつく限りだと、ざっとこんな感じでしょうか?🤔


    ゲーム開発のノウハウについては手探りでやっているの身ですが、
    普段からこんな風に頭の中を整理しながらシステムを構築しています。


    もっと効率の良い設計もあるかもしれませんが、
    そこは開発を進めながら先人の方々から吸収していきたいですね!


    それにしても……こうして分解してみると、
    ただ「デッキにカードを入れる・外す」だけでも裏側の処理はややこしく、やらなきゃいけないことが多いのがよく分かりますね😅


    2.実装手順を整理する

    次に、先ほど作ったリストの中から、
    「まずは何から作っていくか?」を整理していきます。


    ここで順番がめちゃくちゃだと、
    例えば「カードを追加したいのに、肝心のデータがどこにあるのか分からない!」となったり、


    「枚数を計算するロジックを一生懸命作ったのに、カードを追加する処理がないせいで上手く動いているか確認できない……」

    なんてことが起きてしまいます😅


    なので、目に見える部分の根元の処理から一つずつ作り、
    動くことを確認して、そのシステムの上にさらに処理を追加して……

    という風に、上流から下流へ川が流れていくようなイメージで根本から組み立てていきます。


    今回は、皆さんに「カードがデッキに追加される」という一連の処理の流れをお見せしたいので、
    私なりに考えた実装の優先順位は以下の通りです!


    【今回考える実装の順番】
    ① デッキへの追加処理(左側)
     ⇒ まずはクリックして処理が走るか確認!

    ② 投入上限のチェック(左側)
     ⇒ 際限なく追加できないように制限をかける。

    ③ データの一時保存(右側)
     ⇒ 追加されたカードのデータを、裏側でしっかり記憶する。

    ④ ウィジェットの生成(右側)
     ⇒ 記憶したデータをもとに、画面右側にカードのUIを表示する。

    ⑤ 自動ソートと総枚数カウント(右側)
     ⇒ 表示されたカードを綺麗に整理し、枚数を数える。

    ⑥ ホイール操作での増減機能(右側)
     ⇒ さらに操作を快適にするための便利機能を追加。

    ⑦ UIアニメーション(左側)
     ⇒ 最後の仕上げ!カードがスッと飛んでいくリッチな演出をつける。

    今回の記事はこの①-④を目指して実装していきます!😎



    3.デッキへの追加処理を実装

    それでは、実際に実装を進めていきます!

    一人で悩まず、自分がやりたい事や現在の進行状況をAI(Gemini先生やClaude)にしっかり共有し、
    壁打ちしながら進めていくのが最近の私の開発スタイルです。

    一通り手順を確認し…流れを把握しながら一つづつ実装していきます!😎


    STEP 1 : リストのカードからの送信処理


    ①.クリック時のイベントを作る

    まずは、リストに並んでいるカードをクリックした時に、親であるデッキのリストビューへ「自分をデッキに追加して!」と伝える処理を作ります。

    画像

    💡豆知識
    UE5でクリック時のイベントをお手軽に作る方法といえば、Buttonコンポーネント(ボタン)をまず思い浮かべると思いますが…
    今回はあえて使用しておりません。

    ボタンを配置してしまうと、クリック判定を強制的に消費してしまい、アルカステラの方で実装済み「ドラッグ&ドロップ」と競合してしまいそちらが動かなくなります。

    そのため、今回はWBPの OnMouseButtonUp 関数(マウスボタンを離した時のアクション)をオーバーライドして、
    純粋なクリック処理を作っていきます。

    オーバーライドの項目からOnMouseButtonUp 関数を追加します。

    画像

    ②.左クリックを検知する

    オーバーライドした OnMouseButtonUp ノードの MouseEvent ピンから Get Effecting Button を呼び出し、== (Equal (Key)) ノードで比較します。


    これをBranch(ブランチ)で繋ぎ、
    マウスの「左クリック」だった場合(True)のみ、先の処理へ進むようにします。


    画像


    ③ さらに分岐(条件)を増やしていく

    クリックされたからといって無条件に処理を進めるわけにはいかないので、以下の条件に合致する時のみ実行させます。

    • 条件1:デッキ編成画面を開いていること(Is Deck Build Mode)

    • 条件2:クリックの対象が「スペルカード」であること (※アルカステラの場合はドラッグ処理を走らせたいので、Falseルートへ逃がして処理から外します)

    ※Is Deck Build Mode(bool型)
    事前に実装済みの今デッキ編成中かを確認する為のbool型の変数です。

    💡 豆知識:Return Nodeの返し方
    処理の最後にある紫色の Return Node には、
    必ず Handled(処理完了・イベント消費)か Unhandled(処理スルー)を繋いで渡してあげないと、
    ブループリントのコンパイルで怒られてしまいます。


    画像

    試しに実行してみます。


    予め、PrintStringノードでどちらの分岐に行ったかを確認するように仕込みます。



    ■アルカステラの場合

    画像

    ■スペルカードの場合

    画像

    左クリックで問題なく動作することを確認しました!


    ……が、今のままだとアルカステラを押したときに(スペルカード用のデータがないため)参照エラーが発生してしまいます。😅

    画像

    ④ より安全な処理へのブラッシュアップ

    エラーを防ぎ、挙動を完璧にするために少し改修を加えます。

    1. IsValid の追加
      分岐の直前に IsValid を挟み、スペルカード用のデータがちゃんと存在するかどうかで処理を分けます。
      これでアルカステラカードをクリックした時のエラーが消滅します。

    2. Is Hovered の追加
      UEの仕様上、一度ウィジェットの上でクリックイベント(Down)が発生すると、UIを「掴んだまま」になってしまい、
      カード外の虚空でマウスを離しても処理が走ってしまいます。

      これを防ぐため、
      「マウスを離した時にウィジェットがカーソルに重なっている時のみ処理する」
      ように Is Hovered ノードで分岐させました!


    画像

    STEP2 : 投入枚数チェック&デッキに一時保存

    次に、クリックされたカードを

    「デッキに何枚入っているか」
    「まだ追加できるか」

    をチェックし、一時データとして保存する処理を作ります。


    画像

    ① 親ウィジェット(メニュー画面)に受け入れ口を作る

    まずは、親であるデッキ編成のメニュー画面側に、
    編集中のデッキ構造体へカードを追加するための「カスタムイベント」を作成し、処理を書いていきます。


    画像

    ② 子(カード)から親(メニュー)へデータを渡す

    STEP 1で作ったカード側のクリック処理の最後に、先ほど作った親のカスタムイベントを呼び出します。


    ここで「自身(クリックされたカード)のデータ」を引数として渡してあげます。


    画像

    ③ C++関数で枚数チェック&保存を自動化

    デッキ追加と上限枚数チェックの処理については、
    以前Claudさんに作ってもらったC++の AddSpellCardToDeck 関数を呼び出して使います。


    内部的なプログラムの話は、『HorizonNotes』のカード構造体にまつわる複雑な仕様が絡むので割愛しますが、
    裏側では以下のような処理を一瞬で行っています。

    【メニュー画面が持つ一時的なデッキデータに対する処理】
    1.
    プレイヤーが対象のカードをセーブデータ内に持っているか確認する

    2.持っていれば、デッキ全体の上限枚数(30枚)をまだ超えていないかチェックする

    3.同名カードの投入上限枚数を超えていないかチェックする

    4.すべての条件をクリアしていれば、編集中の一時デッキに追加する!


    (※さらに「URカードは同名問わずデッキ全体で合計3枚まで」というルールもあるため、その確認も今後別処理で実装する予定です)


    ブループリントで組むと複雑でスパゲッティになりがちなこれらの処理を、裏で一手に引き受けてくれています。

    Claudさんには本当、頭があがりません……!


    STEP3 : デッキリストの確認・表示

    それではいよいよ、追加したカードを右側のデッキリストに視覚的に反映させます。


    画像

    ① デッキ内の枚数をチェックする(親ウィジェット側)

    まずは Get Spell Card Count in Deck というノードを使い、
    編集中のデッキ内に指定されたカードが「現在何枚あるか」を調べます。
    (※こちらも以前、Claudeさんに作ってもらった超有能なC++関数です😎)


    そして、出てきた枚数に対して == (Equal) ノードを使い、

    「これがデッキに加わる『1枚目』かどうか」

    を分岐(Branch)させます。


    画像

    ② 1枚目の場合(True):リストに新規追加する

    「1枚目」だった場合は、デッキリストの画面(TileView)に新しくカードの枠(ウィジェット)を追加します。


    この時、Add Item ノードの引数(Item)には、イベントで受け取った CardData をそのまま渡してあげます。


    画像
    引数はイベントに渡されたCardDataにしてます



    ③ デッキ内枚数の表示処理を作る(子ウィジェット側)

    次に、デッキリストに表示されるカード(子ウィジェット)側の処理を作ります。

    (※元々実装している見た目やアニメーションの処理については、
    結構ガッツリ作り込んでしまっているので、詳細なノードの解説は勘弁してください!🙏)

    画像


    ここでは、親メニュー(WBP_Collection_View)が持っている情報にアクセスして、
    枚数を表すテキストを更新します。


    ・Cast(キャスト)を使って親のビューを取り出す。

    画像

    ・親が持っている編集中のデッキの一時データ(Editing Deck)から、
    自分のカードが何枚入っているかを取得する。

    ・取得した数値を文字に変換し、枚数を表示するテキストオブジェクト(Text Block)に書き込む。


    画像

    ④ 処理を関数化して再利用しやすくする

    この「枚数テキストを更新する処理」は、
    カードが追加された時だけでなく、カードを外した(削除した)時など、今後いろいろなタイミングで使い回すことになります。

    そのため、この一連の処理はまるっと「関数(Function)」にしておきました。


    画像

    いざ、テストプレイ!

    テストしてみる。

    無事に成功しました!!!🎉🎉


    STEP 4 :複数枚入れた時の処理(2枚目以降の追加)を作成する

    あとは、同じカードを複数枚入れた時(2枚目以降)の処理を追記します。


    先ほどの「デッキに加わる1枚目か?」の分岐で、
    False(2枚目以降)となった先の処理を作っていきます。


    画像

    ① Regenerate All Entries でリストを更新する

    ここで、TileView用のノードである Regenerate All Entries(すべてのエントリを再生成)を使います。


    ただ、リスト内の「すべて」のウィジェットを更新するという処理名を見て、

    「これってメモリに無駄な負荷を掛けないのかな?」

    と心配になったので、Gemini先生に確認してみました。

    💡 Gemini先生の解説:TileViewの「ウィジェットプーリング」という神機能

    TileViewやListViewは、内部で**「ウィジェットプーリング(Widget Pooling)」**という高度なメモリ管理を行っています。

    普通のUI(ScrollBoxなど)の場合: アイテムが30個あれば、ウィジェットを30個まるごと生成(Construct)します。再生成しようとすると、30個を破棄してまた30個作り直すので、メモリもCPUも激しく消費します(まる。さんが懸念された通りです!)。

    TileViewの場合: アイテムが100個あろうと、「画面に映る限界の数(例えば10個)+予備の数個」しかウィジェットを生成しません。スクロールすると、見えなくなったウィジェットをワープさせ、中身のデータだけをすり替えて再利用します(これが OnListItemObjectSet の正体です)。


    つまり、Regenerate All Entries がやっていることは…
    このノードを呼んでも、ウィジェットの破壊や新規生成は一切発生しません。
    すでに画面に出ている数個のウィジェットに対して、「持ってるデータをもう一回読み直して!」と命令を出しているだけです。
    文字列の更新処理が走るだけなので、メモリ消費は実質ゼロ、処理負荷も超軽量です!

    Gemini先生 回答

    なるほど。

    あくまで「現在画面に表示されているウィジェット」に対して更新命令を出しているだけだから、そこまで動作は重くならないのですね🤔

    であれば、Regenerate All Entries ノードを使うのがベストな気がします。

    Gemini先生の案に従います!👍


    ② デッキの総枚数テキストを取得する

    次に、カードが追加されたあとの「デッキ合計枚数」の表示を更新するための準備をします。

    C++関数の Get Total Card Count In Deck ノードを出して、Deck ピンに現在編集中の EditingDeck を繋ぎ、現在のデッキ枚数を取得します。



    ③ 処理を合流させてテキストに反映する

    1枚目の追加処理(Add Item)の先と、
    2枚目以降の追加処理(Regenerate All Entries)の先を合流させます。


    取得した総枚数の数値を SetText に渡し、
    画面上部のデッキ内のカード総枚数(0/30枚の部分)を更新すれば完了です!

    画像

    テストしてみる

    それでは、早速テストしてみます。


    同じカードをカチカチと複数回クリックした時に、

    「同名カードの上限枚数(コモンやレアなら3枚)まで正しくデッキに入れられるか」

    そして

    「デッキ総枚数の表示が連動して正しく反映されるか」

    を確認します。


    ■動作確認動画


    おっけー!ばっちり動いてます😎

    これで、基本的なクリック操作によるデッキ追加処理のコア部分は完成です🎉🎉


    今後の課題(ブラッシュアップ)
    あとは実際に触ってみて、以下のような気になる点も見えてきました。

     ・別のカードを続けて入れる時に、なぜか2回クリックが必要になってしまう。
     ・右側のデッキリストに追加されたカードの表示サイズが少し小さい。


    ただ、この辺りの微調整はそこまで時間はかからないと思うので、
    空き時間を使ってサクッと修正していこうと思います!😏


    まとめ

    今回は、結構詳細に実装過程までを書いてみました。


    そこで私が思ったこと……


    思いのほか、めっっっっちゃ!!!!疲れた!!!!😭


    実装するシステムが段々と複雑になっているうえに、
    元々作っていたシステムと絡む部分も多く、それを分かりやすく文章にまとめて手順化するのは本当にしんどかったです……😰


    なので、今後は私の調子(と体力)次第で、
    ここまで詳細な実装過程は省略させていただくかもしれません。

    (現に、今回の作業時間の1/3くらいがこの記事の文章作りだったので……
    技術系のお話を期待している方には本当に申し訳ないです!🙏💦)


    ……ただ!

    今回の様にアンケート企画で皆さんに決めてもらった実装に関しては、
    今回みたいに詳細を書いても良いかもしれないですね😏

    「あの機能、裏でどうやって作ったの?」

    と気になる方もいらっしゃると思いますし😎

    その辺りのバランスについては、
    今後やりながら検討していこうと思います!


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

    #UE5 #UnrealEngine #ゲーム開発 #個人開発 #インディーゲーム #ゲームUI #UI実装 #ブループリント #UMG #TileView #デッキ構築 #自作TCG #HorizonNotes #開発日誌 #AI活用 #スキしてみて

    この記事が参加している募集

     
     
    読者参加型でゲームを作っている、まる。です。 UE5でオリジナルのカードゲームRPG『HorizonNotes』を開発してます 育成SRPG『ネイト育成計画』体験版、無料公開中です! ■公式サイト https://horizonnotes.netlify.app/

    あなたへのおすすめ