ゲーム制作   Unity ソフトウエア 

Unity ソフトウエアでゲーム制作#1モグラたたき編(26.エフェクトその1)

キャッチ画像モグラたたきスクリプトエフェクトその1

本投稿は2024年9月時点の内容になります。アップデートにより変更となる場合があります。また環境によって違いがあると思いますのであくまで参考として、ご了承ください。

様々な書籍、ブログや動画を参考にさせていただきました。多すぎて一つ一つはお紹介できませんが感謝です。

初心者の自分がUnity ソフトウエアでゲームを作ってみました。とりあえずシンプルなものということでモグラたたきに挑戦です。ゲーム作ってみるかという感じになったときに、いいタイミングで某ゲームのイベントシナリオ内ミニゲームにモグラたたきが実装されていたのでUIとかエフェクトとか、諸々の仕様をぱくって参考にして作ってみましたよ。様々なHowToの中の選択肢のひとつとして、同じ初心者さんの参考になればよいです。

\ チェック /

アニメーションを使ったエフェクトを作成していきます

Unity プロジェクトでエフェクトをつけるには主に2つ方法があります。

ひとつはパーティクルシステムを使う方法。もうひとつはアニメーションを使って自作する方法です。

今回はスコアを獲得した時に表示するエフェクトと、ライフ(残機)が減ったときにハートが出るエフェクトを、アニメーションを使った方法で実装しつつ、簡単に説明していこうと思います。

本記事のポイント

  • アニメーションを使ったエフェクトを作って実装します。
  • おまけでUnityActionとUnityEventを使った例を紹介してます。

エフェクトの素材を準備します

まずはUIを使って素材を用意します。

アニメーションを使ったエフェクト実装例(その1オブジェクトの準備①スコアが出るやつ)

  1. 「ScoreEffect」の名前でCanvasを作成。Canvasの設定は以前行ったものを参考にしてみてください。重要なのは敵のいるところに出すために「ワールド空間」にすることと、UI以外のゲームオブジェクトの中で一番手前に表示されるように「ソートレイヤー」を設定しておくことです。
  2. 「ScoreEffect」の子に「ScoreEffectTMPro」の名前でTextMeshProを作成します。

アニメーションを使ったエフェクト実装例(その1オブジェクトの準備②ハートが出るやつ)

  1. 「LostLifeEffect」の名前でCanvasを作成。こちらもCanvasの設定は前回までに行ったものを参考にしてみてください。重要なのは敵のいるところに出すために「ワールド空間」にして、UI以外のゲームオブジェクトの中で一番手前に表示されるように「ソートレイヤー」を設定してください。
  2. 「LostLifeEffect」の子にハートのイメージ「HeartImg」とTextMeshProで「-」を表示する「MinusText」を作成します。

アニメーションを付けます

いい感じにアニメーションを付けます。

アニメーションを使ったエフェクト実装例(その2アニメーションを付けます)

  1. お好みでアニメーションをつけてエフェクトの演出をつけます

\ 簡単なアニメーションの作り方です /

プレハブ化してInstantiateします

アニメーションを使ったエフェクト実装例(その3スクリプト①)

  1. 作成したエフェクトをプレハブにします。
  2. ヒエラルキー上に空のオブジェクト「EffectManager」します。
  3. スクリプト「EffectManager」を作成して上記にアタッチします。
  4. 以下スクリプトです。
  5. プレハブをアサインします。
class EffectManager
public class EffectManager : MonoBehaviour
{
    [SerializeField] GameObject ScoreEffect;
    [SerializeField] GameObject LostLifeEffect;

    //EffectManagerの子にScoreEffectをインスタンス化します
    public void ShowScoreEffect(int score,Vector3 pos)
    {
        GameObject obj=Instantiate(ScoreEffect,pos,Quaternion.identity, this.gameObject.transform);
        //TextMeshProコンポーネントを取得してスコアの表示をする
        obj.GetComponentInChildren<TextMeshProUGUI>().text = "+" + score.ToString();
        //エフェクトの自動消滅
        //Destroy(obj,1f);
    }

    //EffectManagerの子にLostLifeEffectをインスタンス化します
    public void ShowLostLifeEffect(Vector3 pos)
    {
        GameObject obj = Instantiate(LostLifeEffect, pos, Quaternion.identity, this.gameObject.transform);
        //エフェクトの自動消滅
        //Destroy(obj,1f);
 }

    //ゲームオーバー時にエフェクトをすべて破棄します
    public void RemoveAllEffects()
    {
        foreach(Transform _effect in transform)
        {
            Destroy(_effect.gameObject);
        }
    }
}

スクリプトざっくり解説

  • (7-14)ScoreEffectをEffectManagerの子として発生させます。
  • (17-22)LostLifeEffectをEffectManagerの子として発生させます。
  • (25-31)EffectManagerの子のエフェクトをすべて破棄します。
  • このままだとゲーム進行中にエフェクトが発生したままシーンに残ってしまうのでDestroyが必要です。一番簡単なのは発生させた直後にDestroy(obj,秒数)を使って自動で破棄されるようにします(12-13,20-21)。もしくは、エフェクトにスクリプトをアタッチしてアニメーションイベントでDestroy(gameobjct)を実行するメソッドを呼びます(下記)。
class ScoreEffect
public class ScoreEffect : MonoBehaviour
{
    public void EffectEnd()
    {
      Destroy(gameObject);
    }
}

アニメーションを使ったエフェクト実装例(その3スクリプト②)

  1. BattleManagerの必要な個所でエフェクトを発生させます。
class BattleManager
public class BattleManager : MonoBehaviour
{
    //省略
    
    //Playerターンの戦闘
    public void PlayerTurn(Enemy enemy)
    {
        EnemyStatus _enemy = enemy.EnemyStatus;
        _player.Attack(_enemy);

        if (_enemy.Status.HasFlag(Statuses.Dead))
        {
            //EnemyのHPが0ならPlayerのスコア加算、今回はEnemyの経験値Expをスコアとして加算
            _player.UpdateScore((int)_enemy.Exp);
            //エフェクト
            effectManager.ShowScoreEffect((int)_enemy.Exp,enemy.transform.position);
        }
    }

    //Enemyターンの戦闘
    public void EnemyTurn(Enemy enemy)
    {
        int playerHp;
        playerHp=_player.CurrentHp;
        EnemyStatus _enemy = enemy.EnemyStatus;
        _enemy.Attack(_player);
        if (playerHp > _player.CurrentHp)
        {
            //エフェクト
            effectManager.ShowLostLifeEffect(enemy.transform.position);
            CheckPlayerDead();
        }
    }

    public void CheckPlayerDead()
    {
        if (_player.Status.HasFlag(Statuses.Dead))
        {
            GameManager.Instance.UpdateCurrentState(GameState.gameover);
        }
    }
    
    //省略

}

ゲーム再生してみます。このままだとゲームオーバーが、ハートのエフェクト(LostLifeEffect)が発生する前に実行されてしまいます。なのでCheckPlayerDead()をエフェクトが終了した時に発生させます。

アニメーションを使ったエフェクト実装例(その3スクリプト③)

  1. EnemyTurn内のCheckPlayerDead()を一旦削除。
  2. 「LostLifeEffect」にスクリプトをアタッチします。
  3. アニメーションイベントでエフェクトの最後に実行します。
class LostLifeEffect
public class LostLifeEffect : MonoBehaviour
{
    public BattleManager BattleManager {  get;  set; }

    public void EffectEnd()
    {
      BattleManager.CheckPlayerDead();
      Destroy(gameObject);
    }
}

スクリプトざっくり解説

  • (5-9)アニメーションイベントでCheckPlayerDead()を実行
  • BattleManagerはEffectManagerからプロパティとして渡してください(以下を参考)
class EffectManager
  [SerializeField] BattleManager battleManager;
  
   public void ShowLostLifeEffect(Vector3 pos)
   {
       GameObject obj = Instantiate(LostLifeEffect, pos, Quaternion.identity, this.gameObject.transform);
       obj.GetComponent<LostLifeEffect>().BattleManager=battleManager;
   }

これでおそらくハートのエフェクトの後にゲームオーバーになって、いい感じになったと思います。ゲーム再生して確認してみてください。

さて、ここまでは、いままで使ってきた基本的な方法でしたが以下で少し違うアプローチでやってみます。

おまけ UnityActionとUnityEventを使って少し便利にしてみました

ここからは少しだけ難しくなるので初心者の方はスルーしてもらってOKです(自分も初心者ですが)。

アニメーションを使ったエフェクト用に作った汎用的なスクリプトです。オブジェクトの破棄や非表示と、アニメーション終わりでのコールバックをセットにしたスクリプトです。ものすごくニッチですが興味のある方は参考にしてもらえたらうれしいです。

ちなみにコールバックとは、「○○さんからの電話あり」「△△△さんから電話あり」(実行するメソッド)という感じで電話番号をメモしておいてあるタイミングで折り返し電話(メソッドを実行)するイメージかなと思います。

Unity プロジェクトでよく目にする「Button.OnClick.AddListener」みたいなのです。

これはdelegateという関数を格納できる特殊な型(上記の例でいうところのメモ)を使うことで実装できます。

Unity プロジェクトではUnityActionという定義済みデリゲート(定義と宣言を一個にまとめて簡素化してくれているイメージ)と、ノンプログラマーが簡単にデリゲートを使用できる(関数の登録にインスペクターを介したり、AddListenerという関数使ったりすることでより直感的になっています)UnityEventというのが用意されています。

詳しくはdelegateを学習してみてください。初心者の自分には詳細を解説するほどの知識がありませぬ。。。

delegateデリゲートの「event」キーワードとUnityEvent 実は別物???

  • デリゲートには「event」キーワードというのがあります。デリゲートへの代入(追加ではなく書換)、や実行を発行元のスクリプトに限って行えるように制限をかけるものです。
  • 制限をかけてない状態は、最初に書いた電話の例だと、第三者が勝手にメモの電話番号を新しいものに換えたり、勝手に折り返し電話をかけることができる状態です。これだと困っちゃうときもありますよね。
  • UnityEventという名前なのでeventキーワードのUnity バージョンかなって思っていたのですがどうやら他のスクリプトからも実行できるようです。
  • eventキーワードを使う場合やインスペクターを使わなくてもいい(もしくは保守などの関係でインスペクターを使いたくない)場合はUnityActionやC#標準のdelegate、Actionなどを使う方がいいようです。

長くなりましたがおまけのスクリプトです。

アニメーションを使ったエフェクト実装例(その4スクリプトおまけ①)

  1. 「EffectEndListener」スクリプトを作成。
  2. プレハブ化する(した)エフェクトのCanvasにアタッチします。(上記でエフェクトをDestroyするためにアニメーションイベント用に作ってアタッチしていた場合は、そのスクリプトは削除します)
  3. スクリプトは以下
class EffectEndListener
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//UnityActionを使うためのnamespace
using UnityEngine.Events;

public class EffectEndListener : MonoBehaviour
{
    //アニメーションの終わりを検出するのに使うフィールド
    Animator animator;
    //エフェクトの終わりの処理を指定するenum型、インスペクターから設定できます
    [SerializeField] StopAction stopAction;

    //イベントハンドラOnEffectStopped
    //UnityActionデリゲート(変数に関数を格納できる特殊な型)を宣言
    public event UnityAction OnEffectStopped;

    //stopActionのプロパティ、一応スクリプトからも設定できるように
    public StopAction StopAction { 
        get { return stopAction; } 
        set { stopAction = value; }
    }

    //エフェクトの終わりに実行するメソッド
    public void EffectEnd()
    {
        //イベントハンドラOnEffectStoppedに関数が格納されていれば実行する
        OnEffectStopped?.Invoke();
    }
    
     //設定されたstopActionによってエフェクトを破棄もしくは非アクティブにする
    public void DoStopAction()
    {
        switch (stopAction)
        {
            case StopAction.Destroy:
                Destroy(gameObject);
                break;

            case StopAction.Disable:
                gameObject.SetActive(false);
                break;

            default:
                Destroy(gameObject);
                break;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        //このエフェクトにアタッチされているAnimatorを取得
        animator = GetComponent<Animator>();
        //リスナーにStopActionの実行を追加
        OnEffectStopped+=DoStopAction;
    }

    // Update is called once per frame
    void Update()
    {
        //アニメーションがフル再生されたらEffectEndを実行
        if (animator?.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1)
        {
            EffectEnd();
        }
    }
}


//エフェクト終わりの処理の種類
public enum StopAction
{
    Destroy,
    Disable,
}

スクリプトざっくり解説

  • (4)UnityActionを使うためのnamespace。
  • (12,72-76)エフェクトの最後に実行する処理のタイプ。
  • (16)デリゲートUnityAction「OnEffectStopped」を宣言。
  • (25-29)エフェクト終わりのイベントの実行。「?」でnullチェックしてInvokeで実行します。今回はUpdateで自動に実行してますが、アニメーションイベントから行ってもOK
  • (32-48)タイプ別で破棄か非アクティブに。
  • (54)このオブジェクトのAnimatorを取得。
  • (56)リスナーにDoStopActionを追加。
  • (63-67)アニメーション(エフェクト)が終わったらEffectEndを実行。

「LostLifeEffect」に関しては「BattleManager」の「CheckPlayerDead()」メソッドを実行したいので、もう少し工夫が必要です。エフェクトはプレハブ化することが多く、プレハブ化時にアサインが外れることがよくあります。なのでEffectManagerのUnityEventと二段構えにしています(下記のEffectManager変更点を参照)。

EffectManagerの変更点です。

アニメーションを使ったエフェクト実装例(その4スクリプトおまけ②)

  1. 「EffectManager」を変更します。
class EffectManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
//UnityEventを使うためのnamespace
using UnityEngine.Events;

public class EffectManager : MonoBehaviour
{
    //省略
    
    //UnityEventのLostLifeEffectEndHandler宣言
    public UnityEvent LostLifeEffectEndHandler = new UnityEvent();

    //省略

    //EffectManagerの子にScoreEffectをインスタンス化します
    public void ShowLostLifeEffect(Vector3 pos)
    {
        GameObject obj = Instantiate(LostLifeEffect, pos, Quaternion.identity, this.gameObject.transform);
        //エフェクト側のリスナーに OnLostLifeEffectEndを追加
        obj.GetComponent<EffectEndListener>().OnEffectStopped+=OnLostLifeEffectEnd;
    }
    //
    public void OnLostLifeEffectEnd()
    {
        LostLifeEffectEndHandler?.Invoke();
    }

    //省略
    private void OnDisable()
    {
        LostLifeEffectEndHandler.RemoveAllListeners();
    }
}

スクリプトざっくり解説

  • (6)UnityEventを使うためのnamespace。
  • (13)デリゲートUnityEvent「LostLifeEffectEndHandler」を宣言
  • (21,24-27)エフェクト終わりのイベントの実行。ヒエラルキー上のスクリプトからは「LostLifeEffectEndHandler」にリスナーを追加し、OnLostLifeEffectEnd()を介して、エフェクトのOnEffectStoppedにリスナーを追加してます。
  • (30-33)非永続的リスナー(スクリプトからAddListenerされたもの)を削除する処理。今回はインスペクターから追加するので必要ないですが、一応念のため。

インスペクターからエフェクト終わりに実行したい処理を追加します。

アニメーションを使ったエフェクト実装例(その4スクリプトおまけ③)

  1. ボタンコンポーネントのクリックと同じ要領で、インスペクターからリスナーを追加します。
  2. 「EffectManager」を選択。
  3. インスペクターの「LostLifeEffectEndHandler」の「+」をクリック
  4. オブジェクトに「BattleManager」をアサイン
  5. function「CheckPlayerLife」を追加します。
class BattleManager
    public void EndBattles(Enemy enemy)
    {
        enemyManager.RemoveEnemy(enemy);
        effectManager.LostLifeEffectEndHandler.RemoveAllListeners();
    }

スクリプトざっくり解説

  • 上記と(30-33)と同じで非永続的リスナー(スクリプトからAddListenerされたもの)を削除する処理。今回はインスペクターから追加するので必要ないですが、一応念のため。

アニメーションを使ったエフェクト用というニッチなスクリプトでした。どもども。

まとめ

まとめ

  • Unity エフェクトは基本パーティクルシステムだが、アニメーションやプレハブ化など基本的な内容でエフェクトを作ることもできる。
  • delegateていうのがあって、Unity プロジェクトではノンプログラマーでも使いやすいUnityEvent ていうのがあるよ。でもやっぱり初心者には難しい。
ユニティちゃん公式ホームページへ
ユニティちゃん公式ホームページ
ユニティちゃんライセンス
ユニティちゃんの画像、素材、ライセンスロゴはユニティちゃんライセンス条項を元に使用しています

\ Unityのスクリプトを書くのに役立ちます /

もっと早く教えてほしかった!Unity C#入門

MARU

マケイヌ的おすすめ度

わかりやすい度

目指せ脱初心者

〇おすすめポイント

ボリューム大でC#学習にもよく使う関数のチェックにもOK

×よくないポイント

始めたばかりの人にはちょっと難しい

\ 初学に使った書籍です /

動画×解説でかんたん理解! Unityゲームプログラミング超入門

大角 茂之/大角 美緒

マケイヌ的おすすめ度

わかりやすい度

提供素材がよくてテンション上がる度

〇おすすめポイント

素材が良くてモチベがあがります。

×よくないポイント

動画解説前提なので図版が小さいのが玉に瑕

おすすめ記事

 

プロフィール

マケイヌ

人生のメインストリームから外れた40代の♂。

90年代オルタナにはまり、文字通りメインストリームから逸脱。 その後もたびたび人生から逃亡。

心が動いた作品の紹介や 自分のちいさな経験、HowToを発信できればと日々模索中。

1年後までにイラストと写真のポートフォリオをつくりたい。

記:2019年12月

▼プロフィールはこちら

Follow me

アーカイヴ