わからん人のUnity基本操作3 スクリプトの作成


今回はスクリプトをオブジェクトに適用します。

Unityはスクリプトの作成の言語にC#かJavascriptの形式を用いることができますが、Unityで使うスクリプトには書き方にあまり違いはありません。ここではC#でやっていきます。


1.環境について

Unityをインストールした方はすでに入っているかもしれませんが、コードを書くときはVisual Studio 2015を使用しています。

Visual Studio for Unity(https://msdn.microsoft.com/ja-jp/dn833194.aspx)を一緒にインストールすると、コードを書くときの助けになってくれると思います。エラーチェックも赤い波線で示してくれるのでドジな僕の心強い味方です。


2.スクリプトのファイルを作成

projectビューで右クリックcreate > C#Script

スクリプトの名前はscriptAにしておきます。ファイルの名前は自由に変えられますが、ファイル名を変える時はクラス名(コードのclassの直後)も同じ名前に揃えてください。違うと実行時に怒られるので注意してください。

ファイルをダブルクリックをするとVisual Studioが起動します。

(Mono Developで開いていて、それよりもVisual Studioがいいという人は左上のEditからPreferencesを選択、External ToolのExternal Script EditorをVisual Studioに切り替えるとVisual Studioで起動するようになります。)

ファイルをダブルクリックするとこのようにウィンドウが開きます。

Unityから作るとこのように先にクラス名などのテンプレートが打ち込まれています。ちょっと便利。


3.下準備

シーンの状況は毎度出てきている水面と、unityで生成できる立方体が出ている状況にしてください。

こんな感じ

そして前回解説したようにCubeにRigidbodyコンポーネントを追加しておいてください。

そして水面オブジェクトにもBox Colliderを追加しておいてください。

 

緑色の四角形ができます。この中が当たり判定をする範囲。
これをクリック
緑の点をドラッグすると直方体状にColliderの形が伸びていきます。
下のほうにこれくらい伸ばしておきましょう

そして、Colliderのis Triggerのチェックをつけておきます。これで物体がすり抜けるようになります。

つまり、触っているけどすり抜けるといったものになります。これはスクリプトで面白い役割をします。

 

次にタグをいじります。ざっくりいうとタグはオブジェクトが敵なのか地形なのかなどを分類する仕組みです。敵だけを攻撃するNPCを作るときなどによく役立ちます。今回はオブジェクトを認識させるためだけに使うのですが、一応機能紹介も兼ねて。

  1. 水面オブジェクトを選択
  2. inspector上部のTagからAdd Tagをクリック
  3. +をクリックして名前を「water」にしてsave
  4. もう一度Tagから、追加したwaterを選択

これで水面オブジェクトにwaterというタグがつけられました。

 

そして、最後にCubeのinspectorの中に先ほど作ったscriptAをドラッグアンドドロップします。

projectビューからinspectorへ

選択したオブジェクトにスクリプトをアタッチ(適用)するにはオブジェクトを選択した状態のinspectorにドラッグアンドドロップをします。

 

これで下準備ができました。


4.スクリプトを書いていく

ではスクリプトを書いていきましょう。


public class scriptA : MonoBehaviour {
    public Rigidbody rb;
    public BoxCollider col;
    public GameObject waterPlane;
    public BoxCollider wprb;

    
    void Start () {
        rb = GetComponent<Rigidbody>();
        col = GetComponent<BoxCollider>();
        waterPlane = GameObject.FindWithTag("water");
        wprb = waterPlane.GetComponent<BoxCollider>();

	}
	
	
	void Update () {
        if (Input.GetKey(KeyCode.W)) {
            rb.AddForce(0, 0, 0.1f);
        }
        if (Input.GetKey(KeyCode.S))
        {
            rb.AddForce(0, 0, -0.1f);
        }
        if (Input.GetKey(KeyCode.A)){
            rb.AddForce(-0.1f, 0, 0);
        }
        if (Input.GetKey(KeyCode.D)){
            rb.AddForce(0.1f, 0, 0);
        }
        
	}

    double BoxS, FP;

    void OnTriggerStay(Collider collision)
    {
        
         
        if (( waterPlane.transform.position.y - (col.transform.position.y - col.transform.localScale.y / 2)) < col.transform.localScale.y) { BoxS = col.transform.localScale.x * (waterPlane.transform.position.y - (col.transform.position.y - col.transform.localScale.y / 2)) * col.transform.localScale.z; } if (( (waterPlane.transform.position.y - wprb.size.y) - (col.transform.position.y - col.transform.localScale.y / 2)) >= col.transform.localScale.y)
        {
            BoxS = col.transform.localScale.x * col.transform.localScale.y * col.transform.localScale.z;
        } else { }
        FP = BoxS * 98;
        rb.AddForce(0, 1 * (float)FP, 0);
    }
}


まずはいろいろ打ち込んで、出来上がったものがこちらになります(いきなり3分クッキング)

ちょっと全体見せないと、どこ説明してるかわからなくなるかもしれないので。

 

 public Rigidbody rb;//クラス内グローバル変数
 public BoxCollider col;
 public GameObject waterPlane;
 public BoxCollider wprb;
 
 void Start () {
  rb = GetComponent<Rigidbody>();
  col = GetComponent<BoxCollider>();
  waterPlane = GameObject.FindWithTag("water");
  wprb = waterPlane.GetComponent<BoxCollider>();
 }

最初にクラス内グローバル変数を作ってオブジェクトについているコンポーネントの情報を代入します。

例えるなら仕事場のようなものでしょうか。スクリプト内(作業部屋)にコンポーネントなどのために席(変数)を作ってあげます。

 

ちなみにスクリプト内の変数Unityのinspectorで変数を確認することができます。

publicを型名の前につけるとinspectorに項目を表示できます。(スクリプトを保存して更新しないと適用されないので注意)

型名にRigidbodyとかBoxColliderのようにコンポーネントの名前と同じものがあります。

これは前回に説明したinspectorに表示されているコンポーネントの数値が格納されます。

型名は先ほどの例えでいうと職種みたいなものにあたります。Rigidbodyは重力や質量を扱うスペシャリストだけど、当たり判定はやらないといった感じです。スクリプトでやらせることができる仕事(関数)も違うというわけです。

そしてGameObjectはコンポーネントをひっくるめたオブジェクト自体のことです。コンポーネントの所属部署みたいなものにあたります。

 

Start関数はシーンが始まる度に1度呼び出されるものです。RPGの戦闘シーンなら、戦闘シーンに移る時にモンスターが表示されて「〇〇があらわれた!」が表示されるタイミングです。この辺はシーンの管理について覚えるとわかると思います。

ここでは変数に何を代入するかをシーン再生時に指定してあげます。

  • GetComponent<コンポーネント名>();

これはオブジェクト(GameObject)の中にあるコンポーネントの数値を変数へ渡します。コンポーネントを席に着かせるわけです。

<>の中のコンポーネント名は代入先の型と一致していなければエラーが起きてしまいます。Box Colliderさん用の席にRigidbodyさんが座れと言われれば仕事が違うぞとダメ出しされます。

特定されたGameObjectに含まれているコンポーネントを代入するには、GameObject型の変数を頭に付け足します。

wprb = waterPlane.GetComponent<BoxCollider>();

上のwprbはWaterPlaneに代入されているオブジェクトの中のRigidbodyを呼び出して代入しています。

  • GameObject.FindWithTag(" ");

これはタグで分類されているオブジェクトを検索して代入するものです。ここでは”water”というタグのついたオブジェクトを検索させています。これで水面オブジェクトがWaterPlaneに代入されます。

 

 void Update () {
  if (Input.GetKey(KeyCode.W)) {
   rb.AddForce(0, 0, 0.1f);
  }
  if (Input.GetKey(KeyCode.S)){
   rb.AddForce(0, 0, -0.1f);
  }
  if (Input.GetKey(KeyCode.A)){
   rb.AddForce(-0.1f, 0, 0);
  }
  if (Input.GetKey(KeyCode.D)){
   rb.AddForce(0.1f, 0, 0);
  }
 
 }

さて、次はキーを押したときに動くようにしましょう。

Updateはシーンが再生中毎フレーム呼び出される関数です。この中に書かれた処理は毎フレーム読み込まれるので、力を加え続ける、車のアクセルなど、継続的な処理に用います。

AddComponentを不用意にここに使ったりすると1秒毎に何十回もコンポーネントを延々と追加しまくったりしてしまうので気を付けましょう。(重い処理を書き込んだりしたらUnityが停止するかもしれません)

あまり重い処理を書かないようにするか、if文を使うなどして制御しましょう。

if (Input.GetKey(KeyCode.W)) {
  rb.AddForce(0, 0, 0.1f);
}

if文の条件にキーの入力を指定します。

  • キー入力を受け取るには

キーの入力を受け取るには、Input.GetKey(KeyCode./キーのアルファベット大文字/) を打ち込みます。

その他特殊キーは特定の文字列を打ち込みます。(スペースキー:space  ,バックスペース:Backspace など)

Visual Studioの候補にも挙がるかと思うのでいろいろ試してみてください。

  • オブジェクトを動かす

オブジェクトを動かすには基本的に物体の座標値を直接書き換えるやり方と、物体に力を加えるやり方があります。テレポートさせたりするやり方なら前者ですが、物理的に自然な動きにしたいときは後者をお勧めします。

オブジェクトに力を加えるにはAddForce(*,*,*)を使います。

rb.AddForce(0,0,0.1f);

これはRigidbody型の変数を頭につけて用います。それぞれ座標軸xyzに対応していてそれぞれの軸に力を加えます。デフォルトのままであれば、パラメータの単位は質量*距離/時間^2とのことで、高校物理で習うあのN(ニュートン)と考えていいと思います。小数点を用いるときは後にfを付けないと怒られます。

 

これを同様にして前後左右それぞれに進むようにします。

 

double BoxS, FP;

void OnTriggerStay(Collider collider)
 {
 
 
 if (( waterPlane.transform.position.y - (col.transform.position.y - col.transform.localScale.y / 2)) < col.transform.localScale.y) {
  BoxS = col.transform.localScale.x * (waterPlane.transform.position.y - (col.transform.position.y - col.transform.localScale.y / 2)) * col.transform.localScale.z;
 }
  if (( (waterPlane.transform.position.y - wprb.size.y) - (col.transform.position.y - col.transform.localScale.y / 2)) >= col.transform.localScale.y)
 {
  BoxS = col.transform.localScale.x * col.transform.localScale.y * col.transform.localScale.z;
 } else { }
 FP = BoxS * 98;
 rb.AddForce(0, 1 * (float)FP, 0);
}

そしてOnTriggerStayはColliderがトリガー状態(is Triggerにチェックが入っている)のColliderと重なっている間に呼び出され続ける関数です。

引数のCollider型のcolliderにはこのスクリプトがアタッチされたオブジェクトに触れた相手のColliderコンポーネントの情報が渡されます。

せっかくなのですが、この記事を書いているころではうっかり引数を活用し忘れてしまいました。すみません。

これは体積をTransformコンポーネントの座標値から計算して、浮力の公式にあてはめたものです。

Colliderコンポーネントは同じオブジェクトについているTransformコンポーネントの座標値の値を受け取ることができます。

col.transform.localScale.x

これは変数col(BoxCollider型)のColliderがついているオブジェクトのx方向の大きさの値を返します。回転角や座標もxyz軸それぞれの値を同様に返すことができます。

 

OnTriggerStayのほかにも、重なった瞬間に一度だけ呼び出されるOnTriggerEnter、離れた瞬間に呼び出されるOnTriggerExitもあります。

トリガー状態でないColliderの場合は衝突した瞬間に一度だけ呼び出されるOnCollisionEnter、触れている間毎フレーム呼び出されるOnCollisionStay、離した時に呼ばれるOnCollisionExitがあります。

 

Unityで物理の公式を使うときは単位の認識をあいまいにしてしまうとうまくいかなくなってしまうので注意です。

Unityでのデフォルトの単位は

長さはm(メートル)、角度は°(度)、

mass(重さ)はkg(キログラム)、Addforceはkg‣m/s²(N:ニュートン)となっています。

 

今回のスクリプトはちょっとゴリ押しな感じですみませんでしたが、これで動く浮遊物ができると思います。

やってみましょう!

 

まずは再生ボタンを押して、そして……

 

 

ちくしょう!さっそく数値を打ち間違えてるじゃないか。僕はいつもそうだ。

このコードはまるで(以下略)

 

冗談はさておき、これが成功例です。

ちゃんと水に浮いて、前後左右にも動いていますね。

ちなみに動画ではRigidbodyのmassの値を変更して浮き沈みさせていました。

これもスクリプトからキーを押して値を変えられるようにすれば潜水艦のようにすることもできるかもしれません。

 

今回のスクリプトはちょっと書き方が雑な部分があると思いますが、ちょっとでも使い方を覚える助けになってくれたらうれしいです。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です