読者です 読者をやめる 読者になる 読者になる

好き勝手に・げーあにん?

ファミコンと同い年の社会人ヌルオタの日記

Unity の JavaScript でよくわからないことメモ

UnityのスクリプトC#JavaScript、Booのどれで書こうかを悩んでいるなら、C#を使いましょう。おわり。


……という、結論に達するまでにJavaScriptで頑張ってみて、いろいろ悩んだり、悩んだが結局わからなかったことのメモ。

コードは Unity3.5.2 で確認しているつもり。

やたらと語尾が「〜〜っぽい」になってるのは、よくわからないまま書いているせいです。わからないことメモなので。

私の Unity の JavaScript に対する認識は「.NETのライブラリが呼べて、C#と大体同じ結果になるコードが書ける謎言語」なので、C#でできてることを、どうやって書いたらいいのかが主な要点。というか、このエントリの大半は、C# プログラミングガイドを見ながら書いた。


参考サイト

このエントリを読むより、下記のサイトを見たほうがタメになると思うので、最初に参考サイトを紹介しておきます。

その他、いろいろぐぐって出てきたサイトは割愛。というか、メモを取ってなかったので、もうどこを参考にしたんだかよくわからん。

配列

// 一次元配列の生成
var array1 : int[] = new int[5];

// 初期値指定ありで一次元配列
var array2: int[] = [ 1, 2, 3, 4, 5, 6 ];

// サイズの揃った二次元配列
var multiDimensionalArray1 : int[,] = new int[2, 3];
multiDimensionalArray1[1, 2] = 3;

// 初期値ありで二次元配列を書こうとすると、配列の配列になる
var jaggedArray1 = [ [ 1, 2, 3 ], [ 4, 5 ] ];
print(jaggedArray1[0][1]); ///=> 5

ここまではわかるんだけど、

// 配列の配列の型定義をどうしたらいいのかがわからない
var jaggedArray2 : int[][] = [ [ 1, 2, 3 ], [ 4, 5 ] ]; // コンパイルエラー

// new の仕方もわからない
var jaggedArray3 = new int[6][]; // コンパイルエラー

// ※これはなぜか配列にすらならず、intの変数になる????
var jaggedArray4 = new int[6][5];
jaggedArray4 = 1;
print(jaggedArray4); ///=> 1

最後のやつは一体どういう理屈なんだ……

ちなみに、UnityのInspector上で見ることができるのは、一次元配列だけ。他のものもInspectorに対応させたい場合は自力で頑張れ。

ちなみにちなみに、最後のやつ(jaggedArray4)は、Inspector上でもintの変数として見れますw

プロパティ(getter/setter)

class Huga {
    var mA = 100;
    
    function get a() {return mA;}
    function set a(value : int) {mA = value;}
    
    static function Sample() {
        var huga = new Huga();
        Debug.Log(huga.a); ///=> 100
        huga.a = 200;
        Debug.Log(huga.a); ///=> 200
    }
}

setter の引数名を value 以外にするとコンパイルエラーになるお約束っぽい。

JavaScript で書いていると、明示的に class 宣言をしないと、自動的に MonoBehaviour を継承したクラスが作られるのだが、プロパティは class 宣言を書かないとコンパイルが通らない罠がある。

#pragma strict
/// file : HogeJS.js

//class HogeJS extends MonoBehaviour { // ※この行と(つづく)

    var mA = 10;
    
    function get a() {return mA;} // この行でコンパイルエラー
    function set a(value : int) {mA = value;}
//} // (つづき)この行の行頭のコメントを外すと、コンパイルが通る

これでだいぶハマった……。

static 変数のプロパティは書けないっぽい。

演算子オーバーロード

class Hoge {
    var mA = 0;
    var mB = 0;
    
    function Hoge(a : int, b : int) {
        mA = a;
        mB = b;
    }
    
    static function op_Addition(a : Hoge, b : Hoge) {
        return new Hoge(a.mA + b.mA, a.mB + b.mB);
    }
}

function Start() {
    var a = new Hoge(10, 200);
    var b = new Hoge(30, 20);
    var c = a + b;
    Debug.Log("a:"+a.mA+","+a.mB + " b:"+b.mA+","+b.mB + " c:"+c.mA+","+c.mB); ///=> a:10,200 b:30,20 c:40,220
}

op_Addition が + のオーバーロード

.NET の Operator Overloads のページに使えそうなリストがある。

+= などの Assignment 系はUnityではコンパイルが通らないっぽい。+ だけ定義すれば、+= も使えているもよう。

indexer

インデクサは定義できないっぽい……? getter/setterが使えて、演算子オーバーロードがあるのに、インデクサが無いというガッカリ感。気づいてないだけでありそうな気がするんだけども……

Generics

C# にある Generics 対応のクラスの使い方

var list = new System.Collections.Generic.List.<int>();

List ではなく、List. なのが見慣れないが、そのうち慣れる。



JavaScriptGenerics 対応のクラスや関数を宣言する方法はわからなかった……。Objectとダウンキャストで頑張ればなんとかなるけども……。

interface

classは単一継承で、interfeceは多重継承。

interface IHogeA {
    function GetA() : int;
}
interface IHogeB {
    function GetB() : float;
}

class CHoge {
    var c = 3000;
}

class Hoge extends CHoge implements IHogeA, IHogeB {
    function GetA() {return 10.5f;}
    function GetB() {return 200.5f;}
}

function Start() {
    var hoge = new Hoge();
    var hogeA : IHogeA = hoge;
    var hogeB : IHogeB = hoge;
    var hogeC : CHoge = hoge;
    print(hogeA.GetA()); ///=> 10
    print(hogeB.GetB()); ///=> 200.5
    print(hogeC.c); ///=> 3000
}

普通ですね。オーバーライドしている GetA() で float を return しても、IHogeA.GetA() : int の定義通りに、intが帰っていることに注意?

delegate

C#delegateをつかって書くもの。関数ポインタっぽい処理とか、匿名関数とか。

function Hoge(f0 : function(int, int) : int, f1 : function(int, int) : int) {
    print(f0(2,3) + f1(4,5));
}

function Start() {
    var func : function(int, int) : int;
    
    func = function(a : int, b : int) {
        return a * b;
    };
    
    print(func(3,8)); ///=> 24
    Hoge(func, func); ///=> 26
}

コロンが多めに登場するものの、いたって普通ですね。


C#みたいに、デリゲートの型(?)に名前を付ける方法はわからなかった。

// C#
delegate int Del(int x); // これに該当するJavaScriptでの書き方がわからん

void Start () {
    Del d = delegate(int x) {return x * 5;};
    print(d(4)); ///=> 20
}

foreach / yield

C# のコードをそれっぽく書き換えると普通に動いてくれる。

例としてC#のソースをUnityのJavaScript書き換えてみた。

class DaysOfTheWeek implements System.Collections.IEnumerable
{
    var days = [ "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" ];

    function GetEnumerator()
    {
        for (var i = 0; i < days.Length; i++)
        {
            yield days[i];
        }
    }
}

function Start() {
    var week = new DaysOfTheWeek();
    
    for (var day in week)
    {
        Debug.Log(day);
    }
}

普通ですね。

名前空間

namespaceもusingも使えないっぽくて、大変不便。型推論に頼るのも限度ってものがあってですね……

ドキュメントコメント

これがないのが地味に辛い。なんか無いのか。

MonoDevelopC#を書いていると、/// と打つと自動である程度のドキュメントコメントを生成してくれる。便利。

属性(?)

Serializable とかで使うやつ。

// C#
[System.Serializable]
public class Hoge
{
    int a;
}


Unity だと MenuItem をよく使う印象。

@MenuItem("Test/TestPrint")
static function TestPrint() {Debug.Log("Menu Test");}

[ ] を @ にするだけ。


ちなみに、UnityのiPhoneビルドだと BinaryFormatterが未対応らしいので、Serializableの出番がない。つらい。何で代用したらいいんだろう……

例外処理

function Start() {
    var array = [0,1,2];
    
    try {
        print(array[5]); // 配列の範囲外アクセス
        throw System.Exception(); // 既存の例外や自作の例外も、もちろん投げれる
    } catch (e : System.IndexOutOfRangeException) { // 例外クラス指定catch
        // printしてもみ消す
        print(e);
    } catch (e) { // 全 catch
        // printして投げ直し
        print(e);
        throw e;
    } finally {
        print("finally");
    }
}

普通ですね。

派生クラスでの基底クラス呼び出し

C# だと base なのかな? JavaScriptではsuper。

class Hoge {
    function Hoge() {Debug.Log("Hoge::Hoge");}
    function A() {Debug.Log("HogeA::A");}
    function B() {Debug.Log("HogeA::B");}
}

class HugaHoge extends Hoge {
    function B() {
        Debug.Log("HugaHoge::B");
        super(); // 基底クラスHogeの B() を呼び出す
        super.A(); // 他のメソッドも呼べる
        super.B();
    }
}

function Start() {
    var x : Hoge = new HugaHoge();
    x.B();
}

出力結果

Hoge::Hoge
HugaHoge::B
HogeA::B
HogeA::A
HogeA::B

普通ですね。


その他箇条書き

  • デフォルト引数は無い → オーバーロードで代用
  • enum が使える
  • 変数のスコープはクラスや関数ごと? for や if ではスコープは作られない(?)。
  • struct は無い。classでなんとかする。
  • const は無い(?)。変更しないように頑張る(?!)

結論とか、まとめとか特になく終わり。もうUnityのJavaScriptを書くことはないと思われるので、似たようなネタはもうやりませんのであしからず。わからなかったことの答えを偶然見つけたり、教えてもらったりしたら書くかもしれんが。