Unityに関連する記事です

 ステージシーンにおける、プレイヤーとエネミーのシンボルの移動速度の変更機能を実装します。

 画面内にボタンを配置し、押すたびに、【等倍速 => 1.5倍速 => 2倍速 => 等倍速】と切り替わるようにします。


<実装動画 
動画ファイルへのリンク


<実装動画◆
動画ファイルへのリンク

<新しく学習する内容>
 ・タプル型による実装例
 ・三項演算子と % 演算子による実装例



設計


 ユーザビリティを考慮し、ステージシーンでのプレイヤーとエネミーシンボルの移動速度の調節機能の設計を行います。

 多くのゲームで採用されているように、ゲーム画面上に速度変更用のボタンを用意し、それを押すたびに移動速度が変わるように設計を考えます。

 どのようにすれば管理が可能になるのか、ボタンを押すたびに処理を連動させるにはどうすればいいのか、ロジックの組み合わせを考えてみてください。


移動速度の切り替え用ボタンの作成を行う


 今回は、残り移動回数(スタミナ回数)の表示部分のゲームオブジェクトに Button コンポーネントを追加してボタン化しています。
その場合、Image コンポーネントの Raycast Target にチェックを入れて、画像をクリックやタップが感知できる状態にしてください。

 もちろん、新しくボタンを作成しても配置してもよいです。
自分の考えた設計で実装を行いましょう。

 下記の画像は参考例です


ヒエラルキー画像



インスペクター画像




移動速度の切り替え用ボタンの子オブジェクトとして、現在の移動速度の状態をアイコン表示するためのゲームオブジェクトを作成する


 移動速度の切り替えが行えるということは、ユーザーに対して、現在の移動速度がどのような状態であるかを提示する必要があるということでもあります。
そのためのゲームオブジェクトを作成します。

 今回は移動速度の切り替え用ボタンの子オブジェクトとして Image コンポーネントを持つゲームオブジェクトを作成しています。
名前は imgSpeedIcon に変更してください。

 RaycastTarget のスイッチは忘れずにオフにしておいてください。ボタンの反応を妨げる要因になります。

 画像はまだ設定していなくても構いません。サイズも、後程修正できますので、目安として作成しておきましょう。


ヒエラルキー画像



インスペクター画像




移動速度の調整できる分に合わせたアイコン用の画像を準備する


 無料のアイコン・イラストサイトを活用し、移動速度の調整要のアイコン画像を準備します。
自分で考えている速度の調整数に合わせて画像を準備してください。

 教材の場合は、等倍速(通常)、1.5倍速、2倍速の3つの速度を利用するので、3つのアイコン画像を準備しています。

 この画像を先ほど作成した imgSpeedIcon ゲームオブジェクトの部分に適用する形で利用します。

 実際に画像を当てはめていって、残りの移動回数表示を隠してしまっていないか、サイズが小さすぎないか、逆に大きすぎないか、などをチェックします。
スクリプトを使ってゲーム実行時に画像を自動的に読み込むようにしますので、調整が終了したら、画像をなしにしておいてもいいですし、仮の画像をつけておいても構いません。

 下記の画像は利用例です。


<利用例 通常時(ステージ開始時)>



<利用例 1.5倍速時>



<利用例 2倍速時>



GameData スクリプトを修正する


 新しく移動速度調整の数値を保持するための変数を追加します。
この値が、移動速度調整のボタンを押すたびに変更されて、プレイヤーとエネミーのシンボルの移動速度に対して調整値として適用されるロジックです。

  InitialzeGameData メソッドにも処理を追加し、この値の初期値を設定しています。


GameData.cs

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



MoveTimeScaleController スクリプトを作成する


 移動速度の変更ボタンを押した際の処理を実装するため、新しいスクリプトを作成して制御を行います。
ボタンを押すたびに、移動速度の変更が行われて、画面の速度のアイコンも切り替わる処理が必要になります。

 ポイントは、移動速度の設定を行うだけで、移動速度の実数値は管理していない、という部分です。
このスクリプト内では、切り替えのみ行い、GameData クラス内で管理している変数を利用して、移動速度をゲーム内に反映させます。
 
 これは、プレイヤーと各エネミーのシンボルがアクセスしやすいために、GameData クラスに移動速度の変数を用意しています。

 大切なことは、変更を行う場所(クラス)と変更の内容を処理する場所(クラス)を必ずしも同じにする必要はない、という考え方です。

 MoveTimeScaleController クラスでは移動速度の変更と、現在の移動速度の状態を管理していますが、移動速度の数値は管理せず、別のクラスに管理されています。
この MoveTimeScaleController クラスで数値の情報を管理している場合、プレイヤーとエネミーのクラスに対して移動速度を提供する必要がありますが、
MoveTimeScaleController クラスはシングルトンクラスではないため、いずれのクラスも、移動速度を参照するために、事前に MoveTimeScaleController クラスの情報の取得が必要になります。

 ですが、移動速度の状態のみを MoveTimeScaleController クラスで管理し、実際に適用する値を GameData クラスで管理している場合はどうでしょうか。
GameData クラスはシングルトンクラスですので、いずれのクラスからでも参照がすぐに可能です。つまり、移動速度の状態と数値とを一本化せずに、あえて分けておいた方が、今回の場合は利便性が高いことが分かります。

 広い視野で、どのクラスに変数の情報があれば一番最適な処理が実装できるのかをイメージしながらロジックを作っていきます。

 なお、スクリプト内に一緒に MoveTimeScale 型の enum の宣言を行っています。
これは別ファイルにして作成しても構いません。

 また、新しくタプル型を利用した戻り値の処理と、三項演算子と % 演算子を利用した値の初期化処理を実装しています。
そちらも一緒に学習を行いましょう。


MoveTimeScaleController.cs

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



<タプル型の実装例>


 タプル(tuple)型は C# の持つ機能の1つです。複数のオブジェクトのデータをひとまとめにして管理することができます。
また、戻り値として利用する場合には、複数のオブジェクトのデータを同じようにまとめて戻してくれることが出来ます。

 タプル型の宣言の書式は複数ありますので、それぞれ紹介します。今回は△僚饉阿納汰しています。
 
<記述例 
  (int, bool) damage = (0, false);

 (Sprite, MoveTimeScale) nextTimeScaleValue;

 上記の例の場合、damage 変数や nextTimeScaleValue 変数には、2つの型の情報が含まれていることになります。
より丁寧に書く場合には、今回のように、通常の変数のように型に対して宣言も可能です。
出来るだけタプル内のオブジェクトの型にも変数の宣言をつけて利用することをおすすめします。

<記述例◆
  (int value, bool isWeakness) = (0, false);

 (Sprite nextSprite, MoveTimeScale nextMoveTimeScaleType);

<記述例>
  (int value, bool isWeakness) damage = (0, false);

 (Sprite nextSprite, MoveTimeScale nextMoveTimeScaleType) nextTimeScaleValue;



 タプル型の情報を扱う場合、タプル内のオブジェクトに対して変数の宣言を行っているか、いないかによって、参照する場合の記述が変わります

 記述例,里茲Δ法▲織廛詁發之燭里澆靴宣言していない場合には、タプル内の情報は Item1、Item2 というように自動的に採番されます
その場合は、「タプルの変数名.タプル内のオブジェクトの宣言順のItemの番号」の書式で記述できます。

<記述例,両豺腓了仮販磧
  Hoge(damage.Item1);  // int 型の引数を参照して渡しています

  Hpge(nextTimeScaleValue.Item2)

 変数の宣言を行っている場合には、通常の変数のように「タプルの変数名.タプル内のオブジェクトの変数名」の書式で記述できます。
そのため、タプルの変数名から、値の推測が可能です。

<記述例△両豺腓了仮販磧
  Hoge(damage.value);  // int 型の引数を参照して渡しています

  Hoge(nextTimeScaleValue.nextSprite);   // Sprite 型の引数を参照して渡しています

 Item1、Item2 でも処理は動きますが、プログラムは処理を見て、誰でもすぐに内容が理解できる設計が理想です。
なるべく変数名をつけてタプルの宣言をした方がいいというのは、このようにプログラムの可読性に関わるためです。
damage.Item1 よりも、damage.value の方が、変数名だけ見てもどのような値が代入されているか判断がつきやすいので、処理を読み解きやすいということです。



 今回の実装では、switch 文による分岐の結果をタプル型の変数を準備しておくことにより、戻り値の情報を2つまとめて受け取れるようにしています。

 この switch 文では、currentMoveTimeScale 変数の情報を精査し、その値がどの case に該当するかを判定しています。
該当した case 内には、Sprite 型と MoveTimeScale 型の2つの情報が用意されており、それが nextTimeScaleValue 変数に戻り値として戻ってきます。

        (Sprite nextSprite, MoveTimeScale nextMoveTimeScaleType) nextTimeScaleValue = currentMoveTimeScale switch
        {
            MoveTimeScale.Normal => (oneHalfSpeedIcon, MoveTimeScale.One_Half),
            MoveTimeScale.One_Half => (doubleSpeedIcon, MoveTimeScale.Double),
            MoveTimeScale.Double => (normalSpeedIcon, MoveTimeScale.Normal),
            _ => (normalSpeedIcon, MoveTimeScale.Normal)
        };

 例えば、currentMoveTimeScale 変数の値が MoveTimeScale.One_Half であるならば、その行の処理に該当するため、その値を適用します。
その場合、Sprite 型の doubleSpeedIcon 変数と MoveTimeScale 型の MoveTimeScale.Double の値がそれぞれ適用されて、それが戻り値として nextTimeScaleValue 変数に渡されます。

 そのため、この処理が実行されると、nextTimeScaleValue 変数内の nextSprite 変数には doubleSpeedIcon 値の情報が代入され、
nextTimeScaleValue 変数内の nextMoveTimeScaleType 変数には MoveTimeScale.Double の値が代入されている状態になります。

 この情報をさらに利用することで、currentMoveTimeScale 変数の値が更新されて、移動速度の状態が変更され、
アイコンの画像の情報が変更されています。

        // MoveTimeScale を変更
        currentMoveTimeScale = nextTimeScaleValue.nextMoveTimeScaleType;

        // アイコン画像の変更し、現在の MoveTimeScale に合わせる
        imgSpeedIcon.sprite = nextTimeScaleValue.nextSprite;



 なお、タプルも入れ子を作ることができます。通常であれば2つの型ですが、この機能を使えば複数の情報を持たせることが出来ます。

<入れ子になっているタプル>
  (int x, (string a, int b) y) tuple = (0, ("String", 100));


参考サイト
MicroSoft C#リファレンス
タプル型
https://docs.microsoft.com/ja-jp/dotnet/csharp/lan...


<三項演算子と % 演算子を利用した値の初期化処理>


 % 演算子は条件式に利用すると便利な処理を実装することが出来ます。

  currentTimeScaleNo = currentTimeScaleNo % (int)MoveTimeScale.Count == 0 ? 0 : currentTimeScaleNo;

 今回のケースでは、currentTimeScaleNo 変数の値を MoveTimeScale.Count 列挙子を int 型にキャストした値で除算し、その余りの値が 0 であるかどうかを評価しています。

 MoveTimeScale.Count 列挙子を int 型にキャストした際の値は 3 で設定されていますので、こちらは常に 3 になります。
よって、currentTimeScaleNo 変数の値を毎回 3 で除算し、その余りの値を確認する処理になります。

 この処理のポイントは、割り切れない場合には、その値をそのまま利用し、割り切れた場合(余りが 0)には、値を 0 にして初期化していることです。

 実際に、どのような処理が動いているかを、実数値を代入しながら確認してみます。


  currentTimeScaleNo = 0 の場合 (0 % 3 == 0)  となり、割り切れないので、false の評価となり、余りがそのまま適用される。よって、currentTimeScaleNo は 0 のまま

 currentTimeScaleNo = 1 の場合 (1 % 3 == 0)  となり、割り切れないので、false の評価となり、余りがそのまま適用される。よって、currentTimeScaleNo は 1 のまま

  currentTimeScaleNo = 2 の場合 (2 % 3 == 0)  となり、割り切れないので、false の評価となり、余りがそのまま適用される。よって、currentTimeScaleNo は 2 のまま

  currentTimeScaleNo = 3 の場合 (3 % 3 == 0)  となり、割り切れる。よって true の評価となり、currentTimeScaleNo は 0 になる。次は最初の処理に戻る

 いかがでしょうか。

 今回、currentTimeScaleNo 変数は、インクリメントの処理が行われているため、本来であれば、2 => 3、3 => 4 のように、数字がどんどんと増えていってしまうはずです。
ですが、この三項演算子に % 演算子による余りを評価した条件式を組み合せることにより、自動的に、currentTimeScaleNo 変数の値を 0 〜 2 までの中でぐるぐる回るように処理が動いていることがわかると思います。

 あまり馴染みのない % 演算子ですが、活用方法が色々とあります(秒数など)ので、上手くロジックに組み合わせる方法を考えてみてください。


Stage ゲームオブジェクト内に MoveTimeScaleController ゲームオブジェクトを作成し、MoveTimeScaleController スクリプトをアタッチして設定を行う


 Stage ゲームオブジェクトの上で右クリックをしてメニューを開き、Create Empty を選択します。
Stage ゲームオブジェクト内に子オブジェクトとして新しいゲームオブジェクトが作成されますので、名前を MoveTimeScaleController に変更します。

 先ほど作成した MoveTimeScaleController スクリプトをドラッグアンドドロップしてアタッチし、各変数にアサインを行います。


ヒエラルキー画像



インスペクター画像



MapMoveController スクリプトと EnemySymbol スクリプトを修正する


 これら2つのスクリプトには DOTween のメソッドを利用して移動を行っている処理があります。
その部分の、移動にかかる秒数の部分に、GameData クラスに用意した MoveTimeScale 変数を使い、移動速度が変化するように処理を修正してください。

 MoveTimeScale 変数は 1.0f => 0.75f => 0.5f と変化しますので、そのまま係数として利用すれば、移動速度が速くなることにつながります。

 挑戦してみてください。


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


 ステージシーンにおいて、動作を確認します。
画面のアイコン表示だけではなく、内部データも確認したいので、MoveTimeScaleController ゲームオブジェクトや GameData ゲームオブジェクトのインスペクターを確認してください。

 移動速度の切り替えボタンを押すたびに、MoveTimeScaleController の CurrentMoveTimeScale の値が Normal => OneHalf => Double => Normal のように、順番に切り替わるかみてください。
このときに、一緒に画面上のアイコン表示も連動して切り替わっていれば制御成功です。

 GameData ゲームオブジェクトの MoveTimeScale 変数の値も 1.0f => 0.75f => 0.5f => 1.0f と順番に切り替わっているはずですので、そちらも確認しましょう。

 その後、その値が実際に移動速度に適用されているかを調べるために、プレイヤーを移動させてください。
プレイヤーとエネミーのシンボルの移動速度の両方が早くなれば制御成功です。

 確認する場所が多いので、順番に1つずつ、ゆっくりとデバッグを行いましょう。


<実装動画 
動画ファイルへのリンク


<実装動画◆
動画ファイルへのリンク

コメントをかく


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

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

Menu



技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

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

3D脱出ゲーム(抜粋)

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

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

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

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

VideoPlayer イベント連動の実装例

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

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

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

private



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

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