かさたな日記

主にUnityを使った制作記録。書いてる人は初心者です。一緒に頑張ろう。

Unity1week:Space編

Unityroomさんの1週間ゲームジャム、通称Unity1weekに参加しました。
無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう
参加は今回で3回目となります。
提出できなかった時も作ってはいたのですが、
間に合わないことが多かったのですよ、よよよ。。。

今回のお題は「space」。
この単語からは宇宙、空間、スキマ、あるいはspaceキーなんかが連想できました。
......我ながら発想が貧困。

しかしまあ、奇をてらってまた間に合わないというのもアレなので
ここはシンプルに宇宙ネタ、シューティングで行くことにしました。

とはいえ、シューティングは実は前回作っています。
なので今回はちょっと趣向を変えて、
宇宙からの連想で惑星の引力を利用するものにしてみました。
そんなわけで作られたのが今回の「Swing By Me」です。
URLはこちら:https://unityroom.com/games/swingbyme
地球の引力で月を振り回すアクションシューティング。
よかったら遊んでくださいな。


制作するにあたって、ケプラーの法則を調べたりしてたのですが、
月が吹っ飛んだり地球(自機)が引っ張られたりでゲームにならないので
簡単なウソ引力で行くことにしました。コードは以下のような感じです。

//引っ張る方
public class GravityAttractor : MonoBehaviour {

	public float gravity = -10;

	public void Attract(Transform moon) 
	{
		Vector3 gravityUp = (moon.position - transform.position).normalized;
		moon.GetComponent<Rigidbody>().AddForce(gravityUp * gravity);
        }
}

地球から月への方角ベクトルを求めてその方向と逆に力を加えているだけです。
このメソッドを月の側のUpdateで呼び出します。

[RequireComponent (typeof (Rigidbody))]
[RequireComponent(typeof(LineRenderer))]
public class Moon : MonoBehaviour {

    public GravityAttractor attractor;
    private Transform myTransform;
    private Rigidbody myRigidbody;
    private LineRenderer myLine;

    [Range(-360, 360)]
    public float rotateSpeed = 30;

    [Range(0, 5f)]
    public float minDistance = 0;

    [Range(0, 20f)]
    public float maxDistance = 5.0f;

    public bool isLookAtAttractor = true;
    public bool isLine = true;

    void Start () 
    {
        myTransform = transform;
        myRigidbody = GetComponent<Rigidbody>();
	myRigidbody.useGravity = false;
        myLine = GetComponent<LineRenderer>();
        
    }

    void Update()
    {
        transform.RotateAround(attractor.transform.position, Vector3.up, rotateSpeed * Time.deltaTime);

        float distance = Vector3.Distance(attractor.transform.position, myTransform.position);
        if (distance < minDistance)
        {
            Vector3 angle = (myTransform.position - attractor.transform.position).normalized;
            myRigidbody.MovePosition(attractor.transform.position + angle * minDistance);
        }
        else if (distance > maxDistance)
        {
            Vector3 angle = (myTransform.position - attractor.transform.position).normalized;
            myRigidbody.MovePosition(attractor.transform.position + angle * maxDistance);
        }

        if (isLine)
        {
            if (attractor.gameObject.activeInHierarchy)
            {
                myLine.positionCount = 2;
                myLine.SetPosition(0, myTransform.position);
                myLine.SetPosition(1, attractor.transform.position);
            }
            else
            {
                myLine.positionCount = 0;
            }
        }

        if (isLookAtAttractor)
        {
            Quaternion targetRotation = Quaternion.LookRotation(transform.position - attractor.transform.position);
            transform.rotation = new Quaternion(0, targetRotation.y, 0, targetRotation.w);
        }
  }

  void FixedUpdate () 
 {
	if (attractor)
	{
		attractor.Attract(myTransform);
	}

 }

}

rotateSpeedは最終的に0にしておくことにしました。
よって、月はプレイヤーの操作によってのみ移動させることができます。

ほかはほとんど普通のシューティングの作り方ですが
何分、オブジェクトが大量に出てくるので弾からパーティクル、
敵キャラまですべてプーリングしています。
ホントはコインが出てきて集めると月のほかにも
太陽系の兄弟たちを振り回せる予定だったのですが
残念ながら時間切れとなりました。1週間って意外と短い。

敵を倒したときの効果音やエフェクトのタイミングに
こだわりたかったのでそこに時間を使ってしまいました。
でもそれは間違ってなかったと思います。


反省点としては難易度の調整です。
操作感が独特になるので、初めて触れるプレイヤーには難しい、ハズ。
なので、そこは慎重にならなくてはいけなかったのですが....
難易度が高いという感想をいくつか頂きました。

一応、スコアに応じて徐々に強い敵が出るようになっているのですが
そんな調整だけでは小手先の小細工...

やはり操作に慣れが必要であることが大きかった。
大量の作品が投稿されている中で1作品の操作練習に時間を割いてもらうのは
やっぱり難しいですね。うーむ、考えが甘い。
次回はその辺、もう少し考えようと思います。
でも久しぶりに完成品を提出できたことはよかったです。


さて、今回のプロジェクトにはアセットストアのアセットを
結構たくさん使用させて頂いているので
その紹介をしたいと思います。
思いますが....ちょっと時間が押してきたのでつづきはまた明日。

自分用Unityメモ:EventTriggerにスクリプトからEventを追加する

EventTriggerもButtonなどと同じように
スクリプト側からEventを追加することができます。
ただ少しやり方が異なるのでメモしておこうと思います。

using UnityEngine.EventSystems; //この名前空間が必要です
~~~~~

 public void AddEventSample()
 {
           EventTrigger currentTrigger = AddComponent<EventTrigger>();
           currentTrigger.triggers = new List<EventTrigger.Entry>();
           //↑ここでAddComponentしているので一応、初期化しています。
           
           EventTrigger.Entry entry = new EventTrigger.Entry();
           entry.eventID = EventTriggerType.PointerClick; //PointerClickの部分は追加したいEventによって変更してね
           entry.callback.AddListener( (x) => OnClick() );  //ラムダ式の右側は追加するメソッドです。

           currentTrigger.triggers.Add(entry);
            
 }

 public void OnClick()
 {
          Debug.Log("クリックされたよ")
  }

追加したEventをあとで消したいという場合は
entryをフィールドにしておいて
currentTrigger.triggers.Remove(entry);
で、消すことが出来るはずです。

注意点として、この方法で追加したeventはインスペクタに表示されません。
表示されないケド、動くから大丈夫...のハズ。

自分用Unityメモ:特定レイヤーがレイヤーマスクに含まれているか調べる

以下の簡単な拡張メソッドをどこかに用意しておきます。
(※ボクの場合は UnityExtension.cs というstaticなクラスを用意して、
簡単な拡張メソッドをまとめて置いています)

   /// <summary>
    /// LayerMaskに指定のLayerが含まれているかを調べます
    /// </summary>
    /// <param name="layerMask"></param>
    /// <param name="layer"></param>
    /// <returns></returns>
    public static bool Contains(this LayerMask layerMask, int layer)
    {
        return layerMask == ( layerMask | (1 << layer) );
    }

レイヤーマスクはビット演算を使用して作られているので
調べるレイヤーと「or演算」を行って、
元と比べて変化がなければそのレイヤーはマスクに含まれている、
という判定になります。

あとはこんな感じで使います。

 pulic LayerMask mask; //マスク。中身はインスペクタから入力するなり、何か参照するなり
 pulic int layer = *;  //調べるレイヤー

 if (mask.Contains(layer))
 {
    Debug.Log("レイヤーが含まれているよ");
 }


単にオブジェクトの種類で処理を分けたいときはtagなり、
コンポーネントの参照をするなりすればよいとは思うのですが、
どうしてもレイヤーで調べたいときに。

個人的には LayerMask がインスペクタから設定できる点が便利だと思います。



こう、プロジェクトごとにレイヤーの名前が微妙に違ってたりするので (