Space Shooter 第二回 スクリプト編

2018年12月18日

こんにちは

前回事前準備が終わったので今回C#というプログラミング言語で実際に動きを付けてみたいと思います。

プレイヤーを操作できるようにする。


hierarchy上のプレイヤーシップを選択し、inspectorにて[add component]より[new Script]を選択 スクリプト名を[PlayerController.cs]に設定

シューティングゲームのプレイヤーとしてやりたいことはつぎの三つです。

1、キーボードなりのインプットでプレイヤーを移動させたい。

2、移動の際機体にトルクをかけたい。=演出

3、クリックをすることでシューティングゲームとして必要な射撃を行いたい。

まず1、に関して

入力(input)によって機体にForce(力)を加える。

まずForceの定義をしましょう。これがなければ力って何?とパソコンから言われてしまいます。

 今回プレイヤーシップにはrigidBodyを付けているためこれを重力ではなくForceとして使用しましょう。

そうなんです。rigidbodyって単に重力ではなく、力として使用できるんです。

  1. float moveHorizontal = Input.GetAxis("Horizontal");
  2.         float moveVertical = Input.GetAxis("Vertical");
  3.   Vector3 movement = new Vector3(moveHorizontal, moveVertical, 0.0f);
  4.   rb.velocity = movement * speed;
  5.    rb.position = new Vector3(
  6.             Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
  7.             Mathf.Clamp(rb.position.y, boundary.yMin, boundary.yMax),
  8.             rb.position.z
  9.         );

これは

 moveHrizontal(or moveVertical)は"Horizontal(orVertical)"がInputされた場合 movement(動作)のVector3(3次元ベクトル)は(x,y,z)=(moveHorizontal,moveVertical,0)と変化(new)します。

(rbの速度はmovement×speed)で

rbの位置はboundary.xMinとboundary.xMaxの間をとります。(yについても同様) zに関しては最大最小なしです。

という意味です。

 この記述でinput.GetAxis=縦横の入力でプレイヤーにrb(positionはboundary依存,Velocityはmovement=入力とspeedに依存)というForceで操作できます。

 ただ、そのままでは

「rbとかspeed,Boundaryってなんだおいらの知らない言葉勝手に使って命令するんじゃねえこのやろー」

とエラーを返されてしまいます。

 そしてこの命令で動いたとしてもどこに記述すれば動くのか? 

 そこを見ていきましょう。

記述場所について

C#の場合基本は以下のようにあらわします。

//○○と××というシステム使うよ!

//XXXっていう命令書でmonobehaviorつかうよ!

という場合

  1. //○○と××というシステムを使うよ!;
  2. using &○○;
  3. using××;
  4.  
  5. //XXXっていう命令書でmonobehaviorつかうよ!;
  6. Public class XXX : MonoBehaviour
  7. {
  8. Public start()
  9.   {
  10.    &スクリプトで最初に一回だけ呼ばれる=途中の変更がないもの;
  11. ;;}
  12. Public update()
  13.   {
  14.    & 毎フレームごと=実行環境に依存する。に実行される;
  15.   }
  16. }
  17.  
  18.  

では今回プレイヤーを動かす命令はどこに記述するでしょう?

そう、コマンド(キー)入力するたびに動いてくれなきゃ困るのでUpdateです

ただし、特別なルールがあってrigidBodyを使う際はFixedUpdateという関数を使います。

ここまでのまとめ

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5.  
  6. public class PlayerContoroller : MonoBehaviour {
  7. private void FixedUpdate()
  8.     {
  9.         float moveHorizontal = Input.GetAxis("Horizontal");
  10.         float moveVertical = Input.GetAxis("Vertical");
  11.  
  12.         Vector3 movement = new Vector3(moveHorizontal, moveVertical, 0.0f);
  13.         rb.velocity = movement * speed;
  14.        
  15.         rb.position = new Vector3(
  16.             Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
  17.             Mathf.Clamp(rb.position.y, boundary.yMin, boundary.yMax),
  18.             rb.position.z
  19.         );
  20.         }
  21. }
  22.  

privateというものは初期値が0というものです。今回初速なしなので...

では次に

「これなんだよ?かってなことばつかうんじゃね~」

とVisualStudioさんから怒られているので

扱えるように言葉の定義をします。

言葉の定義は最初の最初、

  1. public class PlayerContoroller : MonoBehaviour {
  2. public float speed;
  3. public float tilt;
  4. public Boundary boundary;
  5. private void FixedUpdate(){}


と表します。

floatというものはUnity上で、小数点以下の小刻みな値も入れることができるもので、floatと付けたものはUnityのinspectorで値を弄れます。

ここでBoundaryに関して聞かれると思います。

Boundaryに関しても定義するのですが、Boundary自体がPlayerControllerとは別のものであるため

  1. [System.Serializable]
  2. public class Boundary
  3. {
  4.     public float xMin;
  5.     public float xMax;
  6.     public float yMin;
  7.     public float yMax;
  8.     public float zMin;
  9.     public float zMax;
  10.        
  11. }

をusing ~と

public class PlayerContoroller : MonoBehaviour {

の間に記述しましょう。

これはBoundaryとはこういうものですよという意味です。

〇では、最後に、rbに関して定義をしましょう。

rbとはrigidBodyでしたね。

rigidBodyという言葉のままではpositionやベロシティを扱えないためrbという別の文字であらわしています。

rbはほかのものに依存しているわけでもなく変数でもないため

Private rb;

と定義します。

どこに記述するかはもうわかりますよね? そうです、public float speed;たちと同じ場所です。

ではrbとrigidBodyを紐づけましょう。

  1. //rbはrigidbodyのcomponentを得ています(使っています。)
  2.  rb = GetComponent<Rigidbody>();
  3.  

では、これをどこに記述すればいいのでしょうか?

public float speed;たちと同じ?

Update(){}?

迷ったときは これは言葉なのか動作なのか?で分類します

言葉=定義ならpublic float speedのように、動作ならUpdateやstartの関数の中に記述します。

「あ、これは定義だ!なら...」

と思ったそこのあ・な・た?

残念でした。

パット見て定義に見えますが、rbはすでに定義されています。

これは「rbって定義しているけど実はrbはrigidbodyっていう命令書から引っ張ってきて」という

動作を現しています。

では関数に記述します。

Update? Start?

と考えたとき毎回rbはrigidBodyっていう...と見直していては大変です。

最初に一回引っ張ってくれば変更が加わることがないので、start内に記述します。

  1. [System.Serializable]
  2. public class Boundary
  3. {
  4.     public float xMin;
  5.     public float xMax;
  6.     public float yMin;
  7.     public float yMax;
  8.     public float zMin;
  9.     public float zMax;
  10.        
  11. }
  12. public class PlayerContoroller : MonoBehaviour {
  13.  
  14. Private start()
  15. {
  16. rb = GetComponent<Rigidbody>();
  17. }
  18.  
  19. private void FixedUpdate()
  20.     {
  21.         float moveHorizontal = Input.GetAxis("Horizontal");
  22.         float moveVertical = Input.GetAxis("Vertical");
  23.  
  24.         Vector3 movement = new Vector3(moveHorizontal, moveVertical, 0.0f);
  25.         rb.velocity = movement * speed;
  26.        
  27.         rb.position = new Vector3(
  28.             Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
  29.             Mathf.Clamp(rb.position.y, boundary.yMin, boundary.yMax),
  30.             rb.position.z
  31.         );
  32.         }
  33. }
  34.  

ここまでの完成形でこれで保存してUnityのinspectorでfloatとして指定している変数を代入することで

プレイヤーを操作できます。

inputによって機体に回転のForceを加える。

では次に機体を動かす際チルトをかけてみましょう。

//rbのローテーションはQuarernion(3次元でrotationを使用する際の演算子)

rb.rotation = Quaternion.Euler(-90f, 0.0f, rb.velocity.x * -tilt);

これは操作に直結するので(プレイヤーシップの移動に合わせてチルトがかかるので)updateの中に記述しましょう。

3,クリック(input)によってビームを撃つ。

これをもっと簡潔に機会にわかる言葉で示すと

『もしクリックという動作がなされた場合、ビームを生成しろ』

と表せる。

前部の『もしクリックという動作がされた場合』をあらわすためには

If構文を使用する。

〇「もし『○○』が『××』かつ『αα』の場合『~~』しろ」というIf構文の基本は

  1. if(『○○』 演算子(= ,<など)『××』 $$ 『αα』){
  2. 『~~』
  3. }

であらわされる。

例えば『もし明日時間があってかつ晴れているなら買い物へ行ってきて!』という命令の場合

  1. if(明日時間 = ある $$ 明日 = 晴れ){
  2.  if(宿題終わった > 半分以上 ){
  3.   買い物
  4.    }
  5.  else{
  6.   宿題を終わらせる
  7.    }
  8. }

というようにIfのなかにifを用いることやそうでないなら・・・という使い方もできる。

クリック動作の表し方は

Input.GetButton("ボタン名")となる。

ボタン名はUnityのEdit>project setting>Inputにて指定できる。

Fire1はDefaultではクリックに相当する。

よって今回の場合

If(Input.GetButton("Fire1")){

}

を用いる。

今回ビームを生成するために必要なものは何か?

  • ビームのプレハブ
  • ビームの生成場所

まずUnity上でこれを作る。

  • hierarchyでcreate からのGameObjectとそれの子オブジェクトとしてquadを生成。
  • 名称をBoltとVFXに変更。(Boltの子オブジェクトとしてVFXが存在する)
  • projectからtextureで[fx_bolt_orange]をドラッグアンドドロップでVFXのテクスチャにする。
  • VFXのinspectorにてテクスチャ(一番↓に存在するはず)fx_bolt_orangeのshaderをParticls/additiveに変更

これによってquadのテクスチャ描画がビーム部分のみになる。(ビーム以外の黒い部分が透明化する)

  • project からaoudio、weapon_playerを探しVFXにインポート(ドラッグ&ドロップ)する。

これでビームが作れたがこのままではスクリプト付ける前のプレイヤーと同じでただのオブジェクトである。

そこで動くためのスクリプトを書く

boltに[add component]で新スクリプト[Mover]を作成

プレイヤーシップ同様動かすためのForceとしてrigidbodyを用いる。

動かすスピードはfloatとしてUnity上で操作できるように定義し

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. public class Mover : MonoBehaviour {
  6.  
  7.     public float speed;
  8.  
  9.     private Rigidbody rb;
  10.  
  11.     //use this for initialization
  12.     private void Start()
  13.     {
  14.         rb = GetComponent<Rigidbody>();
  15.  
  16.         rb.velocity = transform.forward * speed;
  17.     }
  18.  
  19. }

がMoverコンポ―ねんととなる。

Moverは更新する必要がない(常に一定の速度で移動させればいい)ので

Startに記述する。

  • Bolt にRigidbodyを作成use gravityのチェックを外す。
  • MoverのSpeedをとりあえず20に設定(ここはお好みで変更)
  • この状態でゲームを実行した場合Boltがビームとして飛んでいくはずである。
  • 最後に当たり判定を作るためboltにcapsule コライダーを設定。
  • Edit Clliderにてビームを覆うように当たり判定を設定。
  • 作成したBoltをProjectにドラッグ&ドロップしてprefabの作成
  • hierarchyにBoltが残っている場合deleteして削除

以上でビーム部分は完成 仕上げとして生成場所を決める。

スクリプトで指定することもできるがここではSpawnerとしてからのゲームオブジェクトを設定、

そのポジションをもとにBoltを生成するという手順を踏む。

  • hierarchyにからのゲームオブジェクトを生成。
  • プレイヤーシップの子オブジェクトにする。
  • [transform]より歯車からポジションをリセットする。
  • [Spawner]にリネームしてBoltを子オブジェクトとして配置。
  • このさいもBoltのTransformをリセットすることを忘れずに
  • Boltは触らずSpawnerのTransform操作してプレイヤーシップの当たり判定にビームの当たり判定が干渉しない最適な位置を決める。(このSpawnerのtransformがビームの生成ポジションになる。)
  • 位置を決めたらBoltを削除する。

では準備はできました。長かったですね。お待ちかねのスクリプトの時間です。

今回登場人物は・プレイヤーしっぷ・Boltですので新しいスクリプトではなくPlayerControllerに追加で記述しましょう。

今回何をしたいか?

『クリック動作があったら指定した場所にビームを生成して』

スクリプトに必要な新たな定義は

  • 指定した場所
  • ビーム

の二つになります。(クリック動作というのは標準で組み込まれているので)

これはどちらもゲームオブジェクトとしてhierarchyもしくはprojectに存在するので、

Public gameobject ShotSpawn;

Public gameobject Shot;

と定義します。

Public gameobject名前;

はUnity上でゲームオブジェクトをアタッチ(設定)できます。

今回アタッチしたSpawnerというオブジェクトの必要な情報はTransformです。

これはGameObject.Spawner.transform.positionとして扱うことができます(GameObjectは最初に定義してるので省略可能)

よって

  1.  if (Input.GetButton("Fire1") && Time.time > nextFire)
  2.         {
  3.             nextFire = Time.time fireRate;
  4.             Instantiate(shot,ShotSpawn.position, ShotSpawn.rotation);
  5.             audioSource.Play();
  6.  
  7.         }

と表現します。

最後のaudioに関しては生成されたらオーディオをプレイしてねという意味

生成に関しては

Instantiate(何を?,どこに?,どの回転で?);

で記述できます。

さてこれでUnityに戻りShotSpawnにSpawner, ShotにBoltを定義すればビームを撃てます。


ただ、このままでは「もしクリック動作があった場合、スポーンしなさい」と

本当は一定間隔なり、一発なり生成したいのにその条件なかったため一分追加しています。

一定間隔でという条件です。


今回は『クリック動作かつ発射感覚が開いている場合生成』となるので

Time.time(時間)がFirerateより大きい場合という条件を現します。

ではどこからともなくわいてきたFirerateについて

public floatで定義をして完成です。

完成したplayerController.csは以下の通りになります。

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5.  
  6. [System.Serializable]
  7. public class Boundary
  8. {
  9.     public float xMin;
  10.     public float xMax;
  11.     public float yMin;
  12.     public float yMax;
  13.     public float zMin;
  14.     public float zMax;
  15.        
  16. }
  17.  
  18.  
  19. public class PlayerContoroller : MonoBehaviour {
  20.     private Rigidbody rb;
  21.  
  22.     public float speed;
  23.     public float tilt;
  24.     public Boundary boundary;
  25.  
  26.    
  27.     public GameObject shot;
  28.     public Transform ShotSpawn;
  29.     private float nextFire;
  30.     public float fireRate;
  31.  
  32.     private AudioSource audioSource;
  33.     public GameObject playerPosition;
  34.    
  35.  
  36.     //Use this for initialization
  37.     private void Start()
  38.     {
  39.         rb = GetComponent<Rigidbody>();
  40.         audioSource = GetComponent<AudioSource>();
  41.     }
  42.    
  43.      private void Update()
  44.     {
  45.         if (Input.GetButton("Fire1") && Time.time > nextFire)
  46.         {
  47.             nextFire = Time.time fireRate;
  48.             Instantiate(shot,ShotSpawn.position, ShotSpawn.rotation);
  49.             audioSource.Play();
  50.  
  51.         }
  52.  
  53.     }
  54.  
  55.     //private void Instantiate(object position, Quaternion rotation)
  56.     //{
  57.     //  throw new NotImplementedException();
  58.     //}
  59.  
  60.     private void FixedUpdate()
  61.     {
  62.         float moveHorizontal = Input.GetAxis("Horizontal");
  63.         float moveVertical = Input.GetAxis("Vertical");
  64.  
  65.         Vector3 movement = new Vector3(moveHorizontal, moveVertical, 0.0f);
  66.         rb.velocity = movement * speed;
  67.        
  68.         rb.position = new Vector3(
  69.             Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
  70.             Mathf.Clamp(rb.position.y, boundary.yMin, boundary.yMax),
  71.             rb.position.z
  72.         );
  73.         rb.rotation = Quaternion.Euler(-90f, 0.0f, rb.velocity.x * -tilt);
  74.     }
  75. }

途中//でコメントアウトしている部分がありますが、これは定義があいまいな場所で自動生成される構文です。


ではプレイヤーに関するスクリプトは以上です。