どうも!賢者と魔物の巣というゲームを製作中の壁です。
色々な魔物を作っていかないといけないのですが
その中でタイトルのような魔物を作りたいと思いました。
言葉だけだとかなり分かりずらいので
最初に完成図をお見せします!
お分かりいただけましたでしょうか?
とりあえずどこに動こうと賢者の方向を見続けます。
そして定期的に現れたり消えたりするわけです。
これの実装が意外と難しかったのでこちらに記載していきます!
ではいきましょー
ずっとプレイヤーと視線を合わせる処理を作る
まずはプレイヤー(賢者)に目を合わせ続ける処理を作りましょう。
考え方は意外とシンプルです。目の中心から円形の移動制限をかけてあげればよいです。
この円形の制限のかけかたは以下に実装が載っていますので参照してください。
私もこちらを参考にさせていただきました。
最初にお絵描き!
まず画像がないと話にならないので
今回の魔物である「大目玉」の絵を描きました。
毎度出来が悪いですが、以下に載せておきます。
黒目を大目玉の子オブジェクトにする
黒目は大目玉と一緒に動き回りますので
大目玉の子オブジェクトにした方がよさそうですね。
ということで画像のインポート→黒目の子オブジェクト化まで
一通りやります。
もう分かるわって方は飛ばしてください。
①画像をプロジェクトにインポート
こちらは画像を選択してプロジェクトにドラッグアンドドロップします。
②インポートした画像をゲームオブジェクトとして生成
更にインポートした画像をヒエラルキーウィンドウにドラッグアンドドロップします。
③黒目を大目玉の子オブジェクト化する
ヒエラルキーウィンドウ上で黒目を大目玉にドラッグアンドドロップします。
これで完了です、ドラッグアンドドロップの連続ですね!
黒目を賢者の方向に移動させる
ではスクリプトを作成して黒目を賢者の方向に移動させるようにしましょう。
ここで前述した円形に移動を制限する処理を実装します。
次のようなスクリプトを作成しました。(EyeMovingという名前にしました)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EyeMoving : MonoBehaviour
{
[SerializeField]
private GameObject sage;
[SerializeField]
private GameObject eyeOut;
[SerializeField]
private float radius;
// Start is called before the first frame update
void Start()
{
eyeOut = this.gameObject.transform.parent.gameObject;
}
// Update is called once per frame
void Update()
{
float distance = Vector3.Distance(sage.transform.position, eyeOut.transform.position);
if (distance > radius)
{
Vector3 nor = sage.transform.position - eyeOut.transform.position;
nor.Normalize();
this.transform.position = nor * radius;
}
}
}
そしてこれを黒目のゲームオブジェクトにアタッチしてあげます。
で、黒目を移動させる方向を決めるために必要な変数を設定してあげます。
具体的にどこからどこへ移動させたいかを決める必要があるので
移動先、移動元の二つの変数を設定するわけです。
具体的に何をしているかは以下を参照ください
sageが移動先(後ろ姿の賢者)、Eye Outが移動元(大目玉)です。
Radiusは円形に制限をかける円の半径ですね。
これで一旦実行してみましょう!
な…なんだ…これは!?
黒目が白目から飛び出して空中で静止してしまいました。
そしてどう考えても制限をかけた円の半径よりも外側に移動してしまっています…。
一瞬でわかります、失敗ですね 笑
どこかがバグっていたわけですが、どこがおかしかったか分かりますか?
私はこの謎を解くのに2日ほど掛かってしまいました 笑
知ってる人からしたら「あぁ、そーいうことね」って簡単に分かるようなことが
出来ていなかったのが原因です。
バグの答え
じゃぁ答えです!
実はpositionでなくlocalPositionで考えないといけなかったんです。
今回、黒目を子オブジェクト化したので移動制限するのは
localPosition、つまり親オブジェクトから見た
相対位置で考える必要があったんですね。
なので正解のスクリプトは以下になります!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EyeMoving : MonoBehaviour
{
[SerializeField]
private GameObject sage;
[SerializeField]
private GameObject eyeOut;
[SerializeField]
private float radius;
// Start is called before the first frame update
void Start()
{
eyeOut = this.gameObject.transform.parent.gameObject;
}
// Update is called once per frame
void Update()
{
float distance = Vector3.Distance(sage.transform.position, eyeOut.transform.position);
if (distance > radius)
{
Vector3 nor = sage.transform.position - eyeOut.transform.position;
nor.Normalize();
this.transform.localPosition = nor * radius;
}
}
}
this.transform.positionでなくthis.transform.localPositionが正解だったということです。
これで実行してみましょう!
ちゃんと想定通りの動きになりました!
消えたり現れたりする動きを実装する
次に消えたり現れたりする動ぎを実装しましょう
とりあえず私が調べた範囲では以下の2つの方法があります。
①setActive(true) setActive(false)
②Color(r,g,b,0)
①についてはactiveの切り替えなので理想的にはこちらを使いたいのですが
自分自身にアタッチしたスクリプト内で
アクティブ→非アクティブに切り替えると
そこから再度ゲームオブジェクトを
アクティブ化するっていうのは出来ないです。
ゲームオブジェクトを非表示にすると
それにアタッチしたスクリプトの動作が
停止してしまうんですね。
つまりゲームオブジェクトをアクティブにしたいのであれば
別のスクリプトでやる必要が出てくるのでめんどいです。
②については透明にするだけなので
透明化したゲームオブジェクトのスクリプトは動作し続けます。
ということで②で実装するのが簡単かなと思いました。
一定間隔でゲームオブジェクトを透明化するスクリプトは以下になります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EyeOutController : MonoBehaviour
{
[SerializeField]
private GameObject eyeOut;
[SerializeField]
private GameObject eyeIn;
private SpriteRenderer srOut;
private SpriteRenderer srIn;
private Color colorOut;
private Color colorIn;
[SerializeField]
private float repeatTime;
private float realTime;
// Start is called before the first frame update
void Start()
{
realTime = 0f;
srOut = eyeOut.GetComponent<SpriteRenderer>();
srIn = eyeIn.GetComponent<SpriteRenderer>();
colorOut = srOut.color;
colorIn = srIn.color;
}
// Update is called once per frame
void Update()
{
realTime += Time.deltaTime;
if (realTime >= repeatTime)
{
switchAlpha();
realTime = 0.0f;
}
}
private void switchAlpha()
{
if (colorOut.a == 0f)
{
colorOut.a = 1f;
colorIn.a = 1f;
}
else
{
colorOut.a = 0f;
colorIn.a = 0f;
}
srOut.color = colorOut;
srIn.color = colorIn;
}
}
このスクリプトの処理を要約すると
SpriteRendererを取得して
さらにそのColorを取得して
そのColorのa(alpha)を0(透明)にしたり1(非透明)にしたりしています。
SpriteRendererはゲームオブジェクトにくっついている画像ってイメージでしょうか。
alphaっていうのは透明度を表しています。
上記の説明通りalpha = 0の場合、画像が透明になります。
repeatTimeで設定した時間で透明と非透明を繰り返すっていう感じです。
ということで以下のように透明にするゲームオブジェクトや繰り返しの時間を
設定してあげましょう!
これで全ての作業が終了しました!
ゲームを実行してみたのが以下です!
最初のやりたいことが実現できました!
めでたし!!
おわりに
一見簡単そうな処理なのですが
正直結構どうすればよいか分からなくなってしまって
3、4日処理が実現できずにハマってしまいました…。
いや、そう考えるとマジでちゃんとした会社から発売されている
ゲームの処理ってマジですごいっすよね!!