前回はグラフィックの賑やかしと背景を作成しました。今回は敵のバリエーションを増やしていきたいと思います。
この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。
<飛び道具を飛ばす敵を作ろう>
さて、敵のバリエーションを増やすにあたって、ただ動くだけの敵ばかりだと面白くないので、飛び道具を飛ばしてくる敵を作ってみましょう。
とりあえず、ベースとなる敵の画像を描くか、アセットストアで素材を探してきましょう。
自分は↓のアセットを使わせていただきました。
3Dを2Dにする方法は↓を参考にしてください。
アニメーションは通常と攻撃の2種類用意してください。(デザインによっては歩きモーションも必要な人はそれも用意してください)
アニメーションの作り方を忘れてしまった方は↓の記事を参考にしてください。
↑のアセットを2Dに変更してアニメーションを設定すると↓のようになりました。
何か発射できそうです。アニメーターは↓のようになっています。
通常の状態→攻撃はattackのトリガーがオンになったら遷移します。Fixed Durationのチェックを外し、Transition Durationを0にします。
攻撃→通常の状態はHas Exit Timeにチェックをし、Exit Timeを1に、Fixed Durationのチェックを外し、Transition Durationを0にしています。
皆さんも自分で好みの形にしてみてください。自分で描いてみてもいいですし、気に入ったアセットを探してみるのもいいかもしれません。
アセットストアを利用する場合は気をつける事があるので、アセットストアの利用に慣れていない方は↓の記事を参考にしてください。
<攻撃を飛ばそう>
次は飛ばす攻撃を作っていこうと思います。
自分は炎を飛ばしたかったので↓のアセットを使用させていただきました。
各々、飛ばす攻撃を自分で描くなり、ダウンロードしてくるなりして好みのものを作っていきましょう。
↓のような炎のエフェクトを使わせてもらおうと思います。
アセットストアではエフェクトがプレハブになっている事が多いのでそれをシーンに持って来れば使えます。プレハブの使い方を忘れてしまった人は↓の記事を参考にしてください。
では、このオブジェクトに攻撃用の設定を加えていきます。
と、言っても今までやってきた事を思い出して頂ければ簡単です。↑の炎に↓のような設定を加えます。
まず、攻撃用のタグを用意して変更します。以前針を作ったときに作成したHitAreaというタグを使用しました。
次に攻撃の判定にあったコライダーを設定しましょう。コライダーについて忘れてしまった方は↓の記事を参考にしてください。
自分は円形の攻撃判定を設定したかったのでCircle Collider 2Dを設定しました。
そして、Radiusの数値を入れて大きさをちょうどよくします。
次にRigidbody 2Dを追加してBody TypeをKinematicに、Freeze RotationのZにチェックを入れます。
Rigidbodyの設定を詳しく知りたい方は↓の記事をご覧ください。
設定ができたら、敵の子オブジェクトに持ってきて非アクティブにします。
はい、これでオブジェクトの準備は完了です。
<敵のスクリプトを書こう>
敵と飛び道具の準備ができたら新しい敵用のスクリプトを書いていきましょう。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy_Zako2 : MonoBehaviour { [Header("攻撃オブジェクト")] public GameObject attackObj; [Header("攻撃間隔")] public float interval;private Animator anim;
private float timer;
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animator>();
if (anim == null || attackObj == null)
{
Debug.Log("設定が足りません");
Destroy(this.gameObject);
}
else
{
attackObj.SetActive(false);
}
}
// Update is called once per frame
void Update()
{
AnimatorStateInfo currentState = anim.GetCurrentAnimatorStateInfo(0);
//通常の状態
if (currentState.IsName("idle"))
{
if(timer > interval)
{
anim.SetTrigger("attack");
timer = 0.0f;
}
else
{
timer += Time.deltaTime;
}
}
}
}
↑敵にこのようなコンポーネントをセットします。
Object Collisionは以前に作成したスクリプトです。
新たに作ったスクリプトは一定間隔でアニメーションを行うスクリプトになっています。
AnimatorStateInfo currentState =
anim.GetCurrentAnimatorStateInfo(0);
//通常の状態
if (currentState.IsName("idle"))
{
if(timer > interval)
{
anim.SetTrigger("attack");
timer = 0.0f;
}
else
{
timer += Time.deltaTime;
}
}
↑通常の状態になった時に時間をカウントして一定間隔たったらまた攻撃アニメーションに入るようにします。
さて、このままではアニメーションをループさせるだけなので、攻撃を発生させる処理を追加していきます。
<アニメーションからスクリプトを呼ぼう>
さて、攻撃を発生させる処理を追加したいのですが、単にスクリプトから呼ぶとアニメーションと合わない事が多いです。
そのため、今回はアニメーションに合わせて攻撃判定を発生させてみましょう。
まず、敵のスクリプトに↓のようなメソッドを追加します。
public void Attack()
{
Debug.Log("攻撃");}
次にヒエラルキーで敵を選択した状態でアニメーションウィンドウを開きます。選択中のアニメーションを攻撃用のアニメーションに変更してください。(↓の場合はflower idleではなくflower attackを選択)
そして、攻撃をするタイミングのフレームを選択します。
そして、アニメーション名の右側にあるボタンの一番右のボタンを押します。
そうすると↓のようなものが追加されます。
もし、これを消したかったら選択した状態で右クリックでDeleteする事ができます。
この、追加されたものを選択している状態でインスペクターを見て下さい。
するとFunctionという項目があるので、ここをクリックすると先ほど敵に追加したメソッドが出てきます。
ここに出てくるのは、選択しているアニメーターと同じゲームオブジェクトについているスクリプトのメソッドになります。
これで、アニメーションからメソッドを呼ぶ事ができるので、アニメーションに合わせて攻撃判定を発生させる事ができそうです。
アニメーションに合わせてメソッドの呼び出すところまでできました。
<攻撃判定を発生させよう>
さて、いよいよ攻撃判定を発生させましょう。
Attackを↓のように書き換えます。
public void Attack()
{
GameObject g = Instantiate(attackObj);
g.transform.SetParent(transform);
g.transform.position = attackObj.transform.position;
g.transform.rotation = attackObj.transform.rotation;
g.SetActive(true);
}
Instantiateというのはインスタンスを作成するメソッドです。これはMonoBehaviourを継承しているから使用できる命令です。
最初の方で作成した飛び道具のゲームオブジェクトのインスタンス(実体)を作成するので、作成した飛び道具を元にして新たにゲームオブジェクトを作成します。
SetParentというのはスクリプトから親オブジェクトを変更する命令です。実体化したばかりのゲームオブジェクトは親を持たないので、自分自身を指定します。transformというのは自分自身のTransformです。
rotationというのは回転で、元のオブジェクトと同じ方向を向くようにしています。
また、Instantiateは元のゲームオブジェクトの情報をそのまま持ってくるので非アクティブになっています。そのため、元のオブジェクトは非アクティブなまま、新しく作成されたオブジェクトはアクティブにします。
次に発生した攻撃を移動させます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemyAttack : MonoBehaviour { [Header("スピード")] public float speed = 3.0f; [Header("最大移動距離")] public float maxDistance = 100.0f;private Rigidbody2D rb;
private Vector3 defaultPos;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
if (rb == null)
{
Debug.Log("設定が足りません");
Destroy(this.gameObject);
}
defaultPos = transform.position;
}
// Update is called once per frame
void FixedUpdate()
{
float d = Vector3.Distance(transform.position, defaultPos);
//最大移動距離を超えている
if (d > maxDistance)
{
Destroy(this.gameObject);
}
else
{
rb.MovePosition(transform.position += transform.up * Time.deltaTime * speed);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
Destroy(this.gameObject);
}
}
ただ一定方向に進んでいくスクリプトで、何かにぶつかったら破棄されるようになっています。
ポイントとしては最大移動距離を設定する事です。
float d = Vector3.Distance(transform.position, defaultPos);
//最大移動距離を超えている
if (d > maxDistance)
{
Destroy(this.gameObject);
}
Vector3.Distanceというのは2つの位置の距離を算出してくれる便利なメソッドです。
Vector3の使い方について詳しく知りたい方は↓の記事で解説しています。
敵から攻撃が一定間隔で出てくるので、何かにぶつからなければ無限に存在する事ができます。そうするとメモリを圧迫してしまうので、最大移動距離を設定して、それ以上移動したら消えるようにします。
Instantiateは新たに生成するものなので、作り続けてしまうと処理落ち、アプリ落ちの原因になるので使わないものはキッチリ破棄しましょう。
そして、何かにぶつかってしまうと破棄してしまうので、最初に生成される位置は敵自体のコライダーとぶつからないようにしましょう。
↑発射する元々のゲームオブジェクトの位置を離してあげると大丈夫です。
できたら↓のようになります。
ちゃんとプレイヤーにも当たってますね。
もうそろそろステージをちゃんとした形で完成させる事ができそうです。というわけで次回はいよいよゲームを形作っていきましょう。
何かうまくいかない事があった場合は↓の記事を参考にしてみてください
最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。
また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。