Unityに関連する記事です

スキル機能を設計・実装する



設計と実装の手順


 手順,鉢△泙任如▲好ル用のボタンの作成と、UIManagerスクリプトの修正が終了しました。

 最後の手順では、スキルの名前を登録する SkillTypeスクリプトを作成します。

 それに合わせて、GameDataスクリプトにはSkillTypeを設定できるように、SkillType型の selectedSkillType 変数を追加します。
この変数を利用してゲーム内で使用するスキルの種類を設定できるようにするロジックです。

 GameManagerスクリプトを修正し、スキルの効果を実行するメソッドや、スキルを登録する際の自動分岐を持つメソッドを作成し、実装していきます。

 実装の手順です。

 1.enum型のSkillTypeスクリプトを作成する
 2.GameDataスクリプトを修正する
 3.GameManagerスクリプトを修正する
 4.ゲームを実行して動作を確認する

 ここで学習する処理は難しいため、一度実装して終了ではなくて、繰り返して学習することを勧めます。



 新しく学習する内容です。

・Dictionaryの実装例 〜Addメソッド、ContainsKeyメソッド、KeyValuePair型構造体〜
・Linqの機能の実装例 〜OrderByDescendingメソッド、First()メソッド、メソッドチェーンについて〜
・メソッドの引数に、戻り値を持つメソッドを利用する方法
・UnityActionの実装例 〜Switch文と戻り値の活用〜


1.enum型のSkillTypeスクリプトを作成する


 スキルの名前を管理できるように enum型のスクリプトを作成しておきます。
ここにまとめてスキルの名前を登録しておくことで管理がしやすく、参照する際に SkillType で記述できますので、
例えば、処理のたびにスキル名を文字列で書く必要がなくなります。

 enum の列挙子は日本語でも登録できます。先頭の文字が数字の場合には、全角・半角問わずエラーになります。

 今回は英語で、メソッド名と同名でスキル名を登録しています。


SkillType.cs

 <= クリックすると開きます。


 以前に作成した EtoType と同じで enum型のみのスクリプトは MonoBehaviour クラスを継承していませんので、ゲームオブジェクトにアタッチすることはできませんが
アサインの必要なく、すべてのスクリプトから参照することが出来ます。


2.GameDataスクリプトを修正する


 宣言フィールドに SkillType型の変数を追加します。この値を参照して使用するスキルの種類を設定するようにしています。


GameData.cs

 <= クリックすると開きます。




 GameDataスクリプトの修正が終了したら、GameDataゲームオブジェクトを選択してインスペクターを確認します。
新しく selectedSkillType がアサイン情報として表示されますので、こちらを確認しておきます。
下記の画像のように、DeleteMaxEtoType と表示されていれば問題ありません。もしも表示されていない場合にはプルダウンメニューを押して選択しておきます。

 現在は1つだけですがSkillTypeのenum列挙子を増やすとプルダウンメニューに追加されます。
今後、例えば、干支の種類に応じてスキルを変更したいような場合には、この変数を上手く活用してください。


GameDataゲームオブジェクトのインスペクター画像



3.GameManagerスクリプトを修正する


 スキルボタンとスキルポイント(ImageコンポーネントのFillAmountの値)の追加に合わせて、複数のメソッドに処理を追加・修正を行います。また、新しいメソッドを3つ作成します。


GameManager.cs

 <= クリックすると開きます。



<Dictionaryの実装例 〜Addメソッド、ContainsKeyメソッド、KeyValuePair型構造体〜>


 DictionaryはListなどと同じコレクションと呼ばれる機能です。KeyとValueをセットにして要素を登録・まとめて(コレクション)いくことが出来ます。

SamuraiBlog様
【C#入門】DictionaryのKey、Valueの使い方(要素の追加、取得も解説)
https://www.sejuku.net/blog/41326
 

 Dictionaryクラスには色々な機能がありますが、今回使用している機能について説明していきます。



 まずは宣言です。なおDictionary機能を使うためには using System.Collections.Generic の宣言が必要になりますが
この宣言はスクリプトファイルを作成すると自動的に追加されていますので、削除していない限りは宣言すれば使用できると思っていて問題ありません。

 Dictionary<Keyの型名, Valueの型名> 変数名 = new Dictionary<Keyの型名, Valueの型名>(コンストラクタ(普段は空でよい));

 コンストラクタ・メソッドを利用したり、宣言と同時に初期化をすることも出来ます。
今回はメソッド内で宣言のみしています。(変数のスコープがメソッド内のみで使用できればよいため、宣言フィールドではなくメソッド内で宣言しています。)

 Dictionary<EtoType, int> dictionary = new Dictionary<EtoType, int>();

 KeyがEtoType型、Valueがint型、変数名はdictionaryにしています。


Addメソッド

 Addメソッドでは引数に指定した値を要素に追加することが出来ます。ListのAddメソッドと同じですが、KeyとValueの両方を指定して値を追加します。

 dictionary.Add(eto.etoType, 1);   // 引数にKey, Valueを指定する


ContainsKeyメソッド

 ContainsKeyメソッドでは、引数に指定したKeyの値が、対象のDictionaryに含まれているかどうかを判断し
含まれている場合には true、含まれていない場合には false を戻す処理です。

 if (dictionary.ContainsKey(eto.etoType)) { }

 dictionaryが検索対象のDictionaryです。このDictionaryに対して、ContainsKeyメソッドを実行し、要素の中にある Key の値に eto.etoType 、
つまり 5つの干支のうち、どの EtoType であるかを etoList内を順番に判定しています
この処理の結果によって、Dictionaryに要素を追加したり、すでにある要素についてはValueの値を増やす処理を行っています。

 このIf文節の処理によってdictionary変数には、5つの干支の要素が順に追加され、すでに追加されている干支については数を増やしていく、という処理を実装しています。
そして「すでに追加されている干支」という情報を、今回はContainsKeyメソッドを用いて、if条件の分岐に利用しています。


KeyValuePair型構造体

 KeyValuePair構造体は、Dictionary内のKeyとValueの両方を取得する処理です。
ここではこの処理を利用して、5つの種類の干支がDictionary内に要素として追加され、その数がそれぞれいくつになっているかを確認しています。

 foreach (KeyValuePair<EtoType, int> keyValuePair in dictionary) {
  Debug.Log("干支 : " + keyValuePair.Key + " 数 : " + keyValuePair.Value);
  }

デバッグログで確認



<Linqの機能の実装例 〜OrderByDescendingメソッド、First()メソッド、メソッドチェーンについて〜>


 Linq(リンク)とは、コレクション(DictionaryやListなど)の要素を操作して、検索したり集計する処理を簡潔に記述することができるライブラリ(複数の機能をまとめたもの)です。

 Linqを使用するためには宣言が必要になります。 

using System.Linq;

 Linqを記述する際にはラムダ式の記述を用います。ラムダ式についてはこちらをご確認ください。
またこのページ内の下段にも記事があります。(ラムダ式による戻り値の処理)
SamuraiBlog様
【C#入門】LINQの使い方総まとめ(Select、Where、GroupByなど)
https://www.sejuku.net/blog/56519


 Linqには多くの機能がありますが今回利用している機能についてまとめておきます。
そのほかの機能については記事がたくさんありますが、こちらのサイトも参考になります。
地平線に行く様
LINQの拡張メソッド一覧と、ほぼ全部のサンプルを作ってみました。
https://yujisoftware.hatenablog.com/entry/20111031...


OrderByDescendingメソッド

 指定したコレクションを、指定した引数の値を基準に、コレクション内の要素の並び順を降順並びにソートします。

 dictionary.OrderByDescending(x => x.Value)

 このメソッドの前に書かれてるコレクション、あるいはメソッドの戻り値に対して処理が行われますので今回の場合には
dictionary変数内にあるすべての要素に対して降順並びによるソート処理を行います。並び替える基準となる値には、Value の値を指定しています。
このDictionaryは<EtoType, int>で宣言していますので、Value は int の値になります。

 そのため、intの値が大きい要素を先頭に、Dictionary内の並び替えの処理を行っています。


First()メソッド

 このメソッドを実行すると、コレクション内の最初の要素を返します。この処理を行う前にOrderByDescendingメソッドが実行されていますので
Dictionaryの並び順は降順に並び直されている状態です。その最初の要素を取得するという処理になりますので、
下記の処理の場合には、最も数の多い干支の種類(EtoType)が値として返ることになります。
そしてその結果が、maxEtoType 変数に代入されます。左辺と右辺の型が同じであることが分かります。

 EtoType maxEtoType = dictionary.OrderByDescending(x => x.Value).First().Key;


メソッドチェーンについて
 
 Linqでは、メソッドの後にピリオドをうつことで、前の処理に続けて他のメソッドの処理を実行することが出来ます。DoTweenのSetEaseメソッドやOnCompleteメソッドなどと同じです。
このように1つの処理に複数のメソッドを書いて、前の処理の結果を受けて、次に書かれたメソッドを処理することが出来ます。
この記法をメソッドチェーンと呼びます。

 EtoType maxEtoType = dictionary.OrderByDescending(x => x.Value).First().Key;

 今回のケースではまず dictionary変数に対して OrderByDescendingメソッドが実行され、List内の並び順が降順並びにソート操作されます。
その結果を受けてFirst()メソッドが実行される、という処理の流れになっています。
別々の処理として記述をする必要がないため、処理を追いやすく、また簡潔に書くことが出来ます。


<メソッドの引数に、戻り値を持つメソッドを利用する方法>


 普段メソッドを呼び出す際の引数には、変数であったり、実数を指定することが多いですが、
呼び出し先の引数と同じ型の戻り値を持つメソッドであれば、それをメソッドの引数として利用することが出来ます。

 ここでは2か所で利用しています。順番に見ていきます。


〜1.メソッドを呼び出す際の引数として、戻り値を持つメソッドを呼び出す処理〜

 まずはメソッドの引数として、戻り値を持つメソッドを呼び出す処理です。
この場合、引数の値には戻り値を持つメソッドの処理結果(returnされた値)が値として使われます。

 メソッドの引数の型
 public IEnumerator SetUpSkillButton(UnityAction unityAction) {

 上記のメソッドであれば、UnityAction型を引数としています。そのため、この引数を呼び出して処理を行うには、
引数にUnityAction型の値を渡す必要があります。


 上記のメソッドの呼び出し
 yield return StartCoroutine(uiManager.SetUpSkillButton(GetSkill(skillType)));

 引数を確認しましょう。変数でも実数でもなく、メソッドが引数として記述されています。
それでは次に、このGetSkillメソッドを確認してみます。


 戻り値を持つメソッド
  public UnityAction GetSkill(SkillType chooseSkillType) {
      switch (chooseSkillType) {
          case SkillType.DeleteMaxEtoType:
	      return DeleteMaxEtoType;

          // TODO スキルが増えた場合には追加する
      }
      return null;
  }

 戻り値を持つメソッドであるため、voidではなく、指定している型を return 処理によって、呼び出し元に値を戻す処理が実行できるメソッドになっています。

 このGetSkillメソッドでは引数として受け取ったSkillType型の変数を元に、UnityAction型の値を、呼び出し元であるSetUpSkillButtonメソッドの引数として戻しています。
つまり、先ほどの処理に戻り値をあてはめてみると、次のような処理が行われていることになります。


 yield return StartCoroutine(uiManager.SetUpSkillButton(GetSkill(skillType)の処理結果として、UnityAciton型の値が入っている));

  ↓ 呼び出し元と呼び出し先のメソッドの型が「UnityAction型」で一致するため、処理を行うことができる
 
 public IEnumerator SetUpSkillButton(UnityAction unityAction) {  <= この変数には、GetSkill(skillType)の処理結果が代入されている
 
 
 以上のような処理の流れになっています。変数や実数の値だけではなく、戻り値の値も引数として渡すことができることを覚えておいてください。
 

〜2.ラムダ式による戻り値の処理〜

 ListにはRemoveAllメソッドという処理があります。この処理は引数に指定した条件に合致する要素をList内から削除する処理です。
引数はデリゲートと呼ばれる処理が用いられており、変数を指定するか、戻り値を持つメソッドを指定するか、ラムダ式による記述によって削除する要素を指定することが出来ます。

 今回はラムダ式を用いることで、Listから削除する要素を、指定した条件に照らし合わせて決定する処理を行っています。

 // etoListから対象の干支を削除
 etoList.RemoveAll(x => x.etoType == maxEtoType);

 まず引数の最初の x 変数ですが、これは任意の変数を指定できます。ここでは x と宣言しています。
この x 変数の値ですが、etoList に含まれているすべての要素を指し示しています。そして要素の値が1つずつ、順番にこの x の値として代入されていきます。

 代入された値の持つ情報のうち、etoType という値情報がありますので、この値と maxEtoType の値とが合致するかを条件に、検索していきます。
x.etoType == maxEtoType の部分が条件です。== ですので、左辺と右辺の値が合致した要素については、削除の対象となって、Listから削除されます。


 処理の内容をわかりやすくするため、etoListの要素を例に書き出してみて、処理の流れを追ってみます。

 etoList.Add(eto0);
 etoList.Add(eto1);
 etoList.Add(eto2);

 if(eto0.etoType == maxEtoType){ 
      // 条件に合致したのでこの要素をListから削除します。合致しなければ何も処理せずに、下の処理にうつります
   etoList.Remove(eto0);
 }

  if(eto1.etoType == maxEtoType){ 
      // 条件に合致したのでこの要素をListから削除します。合致しなければ何も処理せずに、下の処理にうつります
   etoList.Remove(eto1);
 }

 if(eto2.etoType == maxEtoType){ 
      // 条件に合致したのでこの要素をListから削除します。合致しなければ何も処理せずに、下の処理にうつります
   etoList.Remove(eto2);
 }

 if文からの連続した処理が、ラムダ式の (x => x.etoType == maxEtoType) 部分の処理です。x の値が順番に、というのは、この流れを指しています。

 これは例題ですのでよいですが、本来Listとは可変であり、その都度、ゲームの状態によって要素がいくつ入っているかは特定できません。
つまり、if文を用意していたのでは処理が行えない状況になってしまいます。 

 そのため、今回のようなケースでは、ラムダ式を用いることで、その時のListの要素をすべて条件に当てはめて確認する、という処理が理にかなっている処理になります。


<UnityActionの実装例 〜Switch文と戻り値の活用〜>


 UnityAction型はUnityが用意しているデリゲートです。デリゲートはわかりやすくいうと、メソッドを代入することができる型(変数)です。

 UnityAction型はUnityEngine.Eventsとして名前空間に定義されていてますので、使用する場合には宣言が必要です。
一時的に使うだけならそのときに1回だけ宣言しても利用できますが、usingで宣言しておきましょう。

 using UnityEngine.Events;


 UnityAction型はデリゲートですので、下記のようにメソッドを代入することが出来ます。

 戻り値を持つメソッド
  public UnityAction GetSkill(SkillType chooseSkillType) {
      switch (chooseSkillType) {
          case SkillType.DeleteMaxEtoType:
	      return DeleteMaxEtoType;      <= DeleteMaxEtoTypeメソッドをUnityAction型として代入している

          // TODO スキルが増えた場合には追加する
      }
      return null;
  }

 そのため次のメソッドの引数には、DeleteMaxEtoTypeというメソッドがUnityActionの値として代入されていることになります。


 public IEnumerator SetUpSkillButton(UnityAction unityAction) {  <= この変数には、GetSkill(skillType)の処理結果 = DeleteMaxEtoTypeというメソッドが代入されている

   // UnityEventにunityActionを登録(UnityActionにはメソッドが代入されている)
      unityEvent.AddListener(unityAction); <= このAddListnerメソッドに登録される値は、DeleteMaxEtoTypeというメソッドになります

 デリゲートを使用することによって、メソッドも変数の値として代入し、他の引数と同じように同じ型内での受け渡しが可能になります。


4.ゲームを実行して動作を確認する


 以上でスキルの実装にかかわるすべての手順が終了しました。ゲームを実行して動作を確認していきます。

 確認ポイントは以下の通りです。


干支を消すとスキルポイントが加算されてスキルボタンのゲージが徐々に溜まる

スキルポイントが満タンになるとスキルボタンがアニメする

スキルボタンを押すとスキルが使用されゲーム内で最も多い種類の干支タイプ1つの干支がすべて消される


 点数は加算されますが、スキルポイントは増えないことを確認してください。

https://gyazo.com/dac33a484835491f9369998487bda351


スキルボタンを押すとスキルボタンのゲージが減少し、再度干支を消すと溜まり始める

残り時間0になるとスキルボタンとシャッフルボタンが非活性化して押せなくなる


https://gyazo.com/a6f2f765197abf3016bdabfb8fad86e5



 以上でスキルの実装は完成です。大変お疲れ様でした!




 以上でこの手順は終了です。

 次は 発展12 です。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます