Unityを使ってみた(1)3D編

【MacOSX 10.11.5 + Unity 5.3.5f1 + JavaScript】

先日来、Nintendo Developer Portalや、Unityによる3DS、Wii Uのサポートなど、いろいろ話題が聞こえてくるので、遅まきながらUnityを使ってみました。

まずはネットのチュートリアルからと思って、下記サイトを参考に触ってみました。

【第4回】ブロック崩しを作ろう (Unity入門) | OpenBook

※前段として、上記サイトの第2回第3回に基礎的なことが書かれているので、ご一読をオススメします。

出来上がりはこんな感じ。

Unityで作ってみたブロック崩しもどき。
Unityで作ってみたブロック崩しもどき。

全体説明は、上のリンクをみてもらうとして、作りながら引っかかったり、気になった点を書いてみます。

古いAPIはUnityが補正してくれる。


今回参考にしたサイトのように、バージョンによって書き方が変わったスクリプトをUnityが補正してくれます。
例えば、MonoDevelop(Unity付属のコードエディタ)で下記のスクリプトを書いて保存します。

#pragma strict
var Speed : float = 15.0;
 
function Update () {
    if (Input.GetButtonUp("Jump") && rigidbody.velocity == Vector3(0, 0, 0)){
        rigidbody.AddForce((transform.forward + transform.right) * Speed, ForceMode.VelocityChange);
    }
}

そしてMonoDevelopからUnityに戻ると、下記のようなダイアログが表示されます。

APIのアップデートが必要な旨を通知するポップアップ。「I Made a Backup,Go Ahead!」を押すと、スクリプトが更新される。
APIのアップデートが必要な旨を通知するポップアップ。「I Made a Backup,Go Ahead!」を押すと、スクリプトが更新される。

ここで「I Made a Backup,Go Ahead!」を押してMonoDevelopに戻ると、下記のようにスクリプトが自動更新されます(Rigidbodyへの参照方法が更新されている)。

#pragma strict
var Speed : float = 15.0;

function Update () 
{
    if (Input.GetButtonUp("Jump") && GetComponent.<Rigidbody>().velocity == Vector3(0, 0, 0)){
        GetComponent.<Rigidbody>().AddForce((transform.forward + transform.right) * Speed, ForceMode.VelocityChange);
    }
}

これは変更されたAPIに対応するようにスクリプトを更新するための Script Updater というプログラムの機能だそうです。適用される範囲に限界はあるようですが、便利な機能ですね。ちなみに「No Thanks」を選んだ場合でも、あとから「Assets>Run API Updater…」で呼び出せます。

Assets > Run API Updater... でAPI Updaterを呼び出せる。
Assets > Run API Updater… で Script Updater を呼び出せる。

【参考】【Unity】Unity4で書いたコードをUnity5向けに変換する「Script Updater」 – テラシュールブログ

他のGameObjectに適用されたスクリプトを参照する


基本的にUnityでは、Hierarchyパネルで作成したGameObjectに対して、ProjectパネルのAssets内に作成したスクリプトをドロップして適用する、という形になるようです(Directorにおけるビヘイビアみたい、といっても理解されないか…(笑))。

この作例では、空のGameObject「SceneObject」を作成し、そこに「SceneScript」というスクリプトを作成、適用して、得点や残機数などを管理しています。

・SceneScript.js

#pragma strict

import UnityEngine.SceneManagement;

//残存玉数、スコア、ブロック総数
var life :int = 3;
var score :int = 0;
var blockCt :int = 35;


//シーン移動時にこのスクリプトを削除しないようにする
function Start () {
    DontDestroyOnLoad(this);
}

function Update () {
	//ゲームクリア監視
    if (blockCt == 0){
	//Unity5.3からは、SceneManager.LoadSceneを使用すること(冒頭のimport必須)
        SceneManager.LoadScene ("GameClear");
//        Application.LoadLevel("GameClear");
    }
}

で、残機数や得点を適宜更新する必要がありますが、このスクリプトを参照するには、該当するGameObject >> その中のComponentという順で参照します。

・ParamScript.js

#pragma strict

var style :GUIStyle;
var obj :GameObject;
var script :SceneScript;
 
function OnGUI() {
    obj = GameObject.Find("SceneObject");
    script = obj.GetComponent(SceneScript);
 
    GUI.Label(Rect(10, 10, 200, 40), "点数", style);
    GUI.Label(Rect(100, 10, 200, 40), "" + script.score, style);
    GUI.Label(Rect(10, 120, 200, 40), "残機数", style);
    GUI.Label(Rect(100, 120, 200, 40), "" + script.life, style);
}

・「obj = GameObject.Find(“SceneObject”);」 >> 該当オブジェクト(SceneObject)を参照
・「script = obj.GetComponent(SceneScript);」 >> 該当オブジェクトに適用されているスクリプトをコンポーネントとして参照。
・各変数は、参照したスクリプト(script)からドットシンタックス(例:script.score)で参照。

という感じです。

※参考サイトでは「GameObject.Find(“SceneScript”)」とスクリプト名を指定していますが、GameObject.Find()はGameObjectを参照するメソッドなので、これでは参照出来ません。たぶん誤植だと思います。

シーンの移動は、SceneManager.LoadScene()を使う


このゲームは・・・

・GameStart
・Main(実際のプレイ画面)
・GameOver
・GameClear

の4つのシーンで構成されています。参考サイトでは「Application.LoadLevel()」を使っていますが、これだと下記のワーニングが表示されます。

Assets/SceneScript.js(21,21): BCW0012: WARNING: ‘UnityEngine.Application.LoadLevel(String)’ is obsolete. Use SceneManager.LoadScene

ver5.3以降は「SceneManager.LoadScene()」を使うようになったようです。実際の例は上にある「SceneScript.js」を参照して下さい。SceneManager.LoadScene()を使う際は「import UnityEngine.SceneManagement;」が必須です。

【参考】Unity 5.3 で Application.LoadLevel が Obsolete になりました – NinaLabo

※上記の参考サイトを含め、SceneManager.LoadScene()について書かれている記事では、

using UnityEngine.SceneManagement;

と書かれていますが、本記事のスクリプトはJavaScriptで書いているので「using」ではなく「import」になります(usingはC#の文法)。

平面のUI定義について


Unityの画面で、得点や残機数など平面上に表示したいものは、カメラオブジェクト(通常はMain Camera)にスクリプトを適用します(上に書いたParamScript.jsを参照)。

ここでは、GUI.Labelを動的に生成してテキストを表示しています。

・ParamScript.js(抜粋)

 
    GUI.Label(Rect(10, 10, 200, 40), "点数", style);
    GUI.Label(Rect(100, 10, 200, 40), "" + script.score, style);
    GUI.Label(Rect(10, 120, 200, 40), "残機数", style);
    GUI.Label(Rect(100, 120, 200, 40), "" + script.life, style);
}

この方法だと、例えばゲームタイトルなど常にセンターに表示する場合に、書き出す画面サイズが変わる度に数値を書き換えないといけないので面倒です。で、調べてみたところ、直接GUIオブジェクトを作って配置すればいいらしい。

1.空のGameObjectを作って(Create empty)、Inspectorパネルの「Add Component」ボタンで「GUI Text」を追加します。

空のGameObjectにGUI Text を追加。
空のGameObjectにGUI Text を追加。

2.TransformやGUI Textの値を調整して、表示するテキストや位置などを調整する。

表示位置や内容などはInspectorパネルで調整する。
表示位置や内容などはInspectorパネルで調整する。

【参考】【プログラム】UnityでGUI Textを画面の中央にする方法 – 英姿颯爽

残機数と設定値を合わせる


このサンプルをそのまま作って実行すると、残機数(残玉数)が合わないと思います。「SceneScript.js」で残機数(life)を3にしているのに、実際は4つボールをなくしたところでゲームオーバーになります。

関連する2つのスクリプトを見比べてみます。

・SceneScript.js

#pragma strict

import UnityEngine.SceneManagement;

//残存玉数、スコア、ブロック総数
var life :int = 3;
var score :int = 0;
var blockCt :int = 35;


//シーン移動時にこのスクリプトを削除しないようにする
function Start () {
    DontDestroyOnLoad(this);
}

function Update () {
	//ゲームクリア監視
    if (blockCt == 0){
		//Unity5.3からは、SceneManager.LoadSceneを使用すること(冒頭のimport必須)
        SceneManager.LoadScene ("GameClear");
//        Application.LoadLevel("GameClear");
    }
}

・BottomWallScript.js

#pragma strict

import UnityEngine.SceneManagement;

//得点用
var obj :GameObject;
var script :SceneScript;
 
obj = GameObject.Find("SceneObject");
script = obj.GetComponent(SceneScript);
var ball :Transform;

function OnCollisionEnter (collision : Collision) {
    //衝突した相手を消す(相手>collision.gameObject,自分>gameObject)
    Destroy(collision.gameObject);

    //ボールの残数チェック
    if (script.life > 0){
	    Instantiate(ball);

	    script.life--;
    } else if (script.life == 0){
	//Unity5.3からは、SceneManager.LoadSceneを使用すること(冒頭のimport必須)
    	SceneManager.LoadScene ("GameOver");
//        Application.LoadLevel("GameOver");
    }
}

手順通りに作成すると、最初のボールはシーン上に配置されています。しかし、ボールがロストした時の処理は、Player後ろの壁に当たったとき(BottomWallScript.js)に行われるため、最初のボールがステージされる際に減算処理が実行されません。その為に残機数(life)での設定値より1つ余計にプレイできてしまいます。

対処方法はいろいろありますが、今回は次のようにしました。

1.Mainシーンに配置してある「Ball」を削除する(HierarchyパネルでBallを選択>右クリックでDeleteを選択)。

2.初期起動時のBall配置を BottomWallScript.js に組み込む。

・BottomWallScript.js

#pragma strict

import UnityEngine.SceneManagement;

//得点用
var obj :GameObject;
var script :SceneScript;
 
obj = GameObject.Find("SceneObject");
script = obj.GetComponent(SceneScript);
var ball :Transform;

function Start(){
    //ボールの残数チェック
     checkBallRemains();
}

function OnCollisionEnter (collision : Collision) {
    //衝突した相手を消す(相手>collision.gameObject,自分>gameObject)
    Destroy(collision.gameObject);

    //ボールの残数チェック
     checkBallRemains();
}


function checkBallRemains(){
    if (script.life > 0){
        Instantiate(ball);

        script.life--;
    } else if (script.life == 0){
	//Unity5.3からは、SceneManager.LoadSceneを使用すること(冒頭のimport必須)
    	SceneManager.LoadScene ("GameOver");
//        Application.LoadLevel("GameOver");
    }
}

BottomWallScript.js の変更は、下記の2点です。

・function Start() を追加して、ボールの残数チェックを行う。
・ボールの残数チェックは、function Start() と function OnCollisionEnter() で同じになるため、function checkBallRemains() で関数化して、どちらでもこの関数を呼び出すようにした。

こんな感じで一応動くゲームができました。ファイル一式をこちらに上げましたので、よければ参考にしてください。

c-geru/BlockGame: Unityで作ったブロック崩しゲームサンプル

【追記】2016.8.4
ダウンロードしたプロジェクトを開くには、UnityでFile >> Open Project… を選択して、表示されたProjectsウィンドウの右上にある「Open」ボタンを押して、ダウンロードしたプロジェクトのフォルダを開きます。次回以降は、プロジェクトが Projectsウィンドウに登録されます。

右上の「Open」ボタンを押して、ダウンロードしたプロジェクトのフォルダを開きます。
右上の「Open」ボタンを押して、ダウンロードしたプロジェクトのフォルダを開きます。