概要 Edit

このページについて Edit

AngelScript(以下AS)を全く知らない人が,ASについてなんとなく理解してもらえるような記事を書きます。
細かいことについては各ページを参照してください。

特徴 Edit

ASはLightweightLanguage(以下LL)の1つでLuaやSquirrelなどと同じような用途で使われます。
 

静的型付け
ASの最大の特徴は静的型付けにあります。
静的型付けとはC/C++などと同様に
未定義の型・シンボルの使用をコンパイル時にチェックすることができ
もしそのようなものが使用されていたらエラー扱いにできます。

LuaやSquirrelなど他のLLの多くは動的型付けを採用しています。
動的型付けですと,コンパイルという作業をスキップできる代わりに
実際にそのコードが実行されるまで未定義の型・シンボルのチェックができません。

小規模・少人数なアプリケーションの開発では動的型付けでもなんとかやっていけますが
大規模・多人数なアプリケーションの開発になればなるほど,
コンパイル時エラーチェックの重要性が増してきます。

Squirrelでゲームのほとんどを実装したという
「小さな王様と約束の国 ファイナルファンタジー・クリスタルクロニクル」の開発者さんが
「自前である程度のスクリプトコードの事前チェックをできるようにした」というような内容を話していました。
それぐらいアプリケーション実行前のエラーチェックはアプリケーションの品質維持に重要なものです。
 

構文がC++にとても似ている
ASは構文がC++にとても似ているため,C++経験者であれば構文について少し勉強するだけでコーディングを始められます。

また,C++ソースコードをメインとして使用するプロジェクト(ほとんどのゲーム開発はC++を使っています)では
ASソースコードとC++ソースコードの2つをコーディングしていくことになります。
そのような状況では,両者の構文が似ていることから頭を大きく切り換える必要がなくなります。
これが意外と重要なことで,ミスや変な混乱が少なくなります。
 

C/C++バインディング機能
ASのスクリプトからC/C++の関数や構造体・クラスなどにアクセスができ
逆にC/C++からASの関数や構造体・クラスなどにアクセスができます。

C/C++の関数・構造体・クラスなどをASのスクリプトからアクセスできるようにするには
ASライブラリの何個かの関数を呼ぶだけで大丈夫です。
 

コルーチン機能
途中で処理を中断するコルーチン機能に対応しています。
この機能があるためテキストアドベンチャースクリプトやRPGのイベントスクリプトとして
ASを導入することが比較的簡単です。
 

バイトコードをバイナリ保存可能
スクリプトコードをデータとしてROM媒体(光ディスクやフラッシュカードのこと)に収録すると
ソースコードを配布していることと同じになってしまいます。
これは会社・団体的にNGなことがよくあります。

ASではスクリプトコードをコンパイルしたあと,バイトコードを出力するという機能があります。
バイトコードとは,C言語でいうところのコンパイル後のオブジェクトファイルに相当するもので
それだけを見てもぱっとは何をしているのか分からない状態になります。

この機能を使い,製品版のROM媒体にはスクリプトコードでなくバイトコードをのせることで
ソースコード配布状態を回避することができます。
 

十分なデバッグ機能
スクリプトをメインに使ってゲーム開発をするとなるとデバッグ機能が必要になります。
ASでは
・実行ファイル・行の取得
・変数の中身のダンプ
・コールトレースの取得
・エラー・例外ハンドリング
といった機能をライブラリが提供しています。
これらの機能をアプリケーション側で使えるようにさえすれば
バグ調査時も特別な苦労はせずにデバッグすることができます。

HelloWorld Edit

ASのスクリプトを実行するサンプルコードです。
本家のsampleコードを参考にしています。

すべてを展開すべてを収束
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
 
 
 
 
 
 
 
 
 
 
 
 
 
-
|
!
 
 
 
-
|
|
|
-
|
|
!
|
|
|
|
-
|
|
!
|
|
|
|
|
-
|
|
|
!
|
|
|
|
-
|
|
|
|
!
|
|
|
|
|
|
|
|
-
|
|
|
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
!
|
|
!
|
-
|
|
|
!
|
|
|
|
|
|
|
|
!
 
 
-
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
|
!
|
-
|
|
|
!
|
|
|
|
|
|
|
|
!
 
 
-
|
|
|
|
|
-
|
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
!
|
|
|
|
|
|
|
|
|
-
|
|
!
|
|
|
|
|
|
|
-
|
|
!
|
|
|
|
|
|
|
|
|
|
|
|
!
 
 
-
|
|
|
|
|
|
|
|
!
 
 
 
-
|
!
 
 
 
-
|
|
!
 
 
 
-
|
!
 
// include
#include <iostream>  // cout
#include <assert.h>  // assert()
#include <string.h>  // strstr()
#include <conio.h>   // kbhit(), getch()
#include <windows.h> // timeGetTime()
 
// AS用ヘッダのインクルード
#include <angelscript.h>
#include "../../../add_on/scriptstring/scriptstring.h"
 
// コンソール出力用関数
void PrintString(string &str)
{
    std::cout << str;
}
 
// メイン関数
int main(int argc, char **argv)
{
    // スクリプトエンジンの作成
    asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
    if( engine == 0 )
    {// エラー
        cout << "Failed to create script engine." << endl;
        return -1;
    }
    
    // スクリプトをコンパイル
    r = CompileScript(engine);
    if( r < 0 )
    {// エラー
        engine->Release();
        return -1;
    }
 
    // コンテキストを作成
        // 1つのコンテキストは1つの実行スレッドのようなもの。
    asIScriptContext *ctx = engine->CreateContext();
    if( ctx == 0 ) 
    {// エラー
        cout << "Failed to create the context." << endl;
        engine->Release();
        return -1;
    }    
 
    // Find the function id for the function we want to execute.
    int funcId = engine->GetModule(0)->GetFunctionIdByDecl("float calc(float, float)");
    if( funcId < 0 )
    {
        cout << "The function 'float calc(float, float)' was not found." << endl;
        ctx->Release();
        engine->Release();
        return -1;
    }
 
    // Prepare the script context with the function we wish to execute. Prepare()
    // must be called on the context before each new script function that will be
    // executed. Note, that if you intend to execute the same function several 
    // times, it might be a good idea to store the function id returned by 
    // GetFunctionIDByDecl(), so that this relatively slow call can be skipped.
    r = ctx->Prepare(funcId);
    if( r < 0 ) 
    {
        cout << "Failed to prepare the context." << endl;
        ctx->Release();
        engine->Release();
        return -1;
    }
 
    // Now we need to pass the parameters to the script function. 
    ctx->SetArgFloat(0, 3.14159265359f);
    ctx->SetArgFloat(1, 2.71828182846f);
 
    // Set the timeout before executing the function. Give the function 1 sec
    // to return before we'll abort it.
    timeOut = timeGetTime() + 1000;
 
    // Execute the function
    cout << "Executing the script." << endl;
    cout << "---" << endl;
    r = ctx->Execute();
    cout << "---" << endl;
    if( r != asEXECUTION_FINISHED )
    {
        // The execution didn't finish as we had planned. Determine why.
        if( r == asEXECUTION_ABORTED )
            cout << "The script was aborted before it could finish. Probably it timed out." << endl;
        else if( r == asEXECUTION_EXCEPTION )
        {
            cout << "The script ended with an exception." << endl;
 
            // Write some information about the script exception
            int funcID = ctx->GetExceptionFunction();
            asIScriptFunction *func = engine->GetFunctionDescriptorById(funcID);
            cout << "func: " << func->GetDeclaration() << endl;
            cout << "modl: " << func->GetModuleName() << endl;
            cout << "sect: " << func->GetScriptSectionName() << endl;
            cout << "line: " << ctx->GetExceptionLineNumber() << endl;
            cout << "desc: " << ctx->GetExceptionString() << endl;
        }
        else
            cout << "The script ended for some unforeseen reason (" << r << ")." << endl;
    }
    else
    {
        // Retrieve the return value from the context
        float returnValue = ctx->GetReturnFloat();
        cout << "The script function returned: " << returnValue << endl;
    }
 
    // We must release the contexts when no longer using them
    ctx->Release();
 
    // Release the engine
    engine->Release();
 
    return 0;
}
 
void ConfigureEngine(asIScriptEngine *engine)
{
    int r;
 
    // Register the script string type
    // Look at the implementation for this function for more information  
    // on how to register a custom string type, and other object types.
    // The implementation is in "/add_on/scriptstring/scriptstring.cpp"
    RegisterScriptString(engine);
 
    if( !strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
    {
        // Register the functions that the scripts will be allowed to use.
        // Note how the return code is validated with an assert(). This helps
        // us discover where a problem occurs, and doesn't pollute the code
        // with a lot of if's. If an error occurs in release mode it will
        // be caught when a script is being built, so it is not necessary
        // to do the verification here as well.
        r = engine->RegisterGlobalFunction("void Print(string &in)", asFUNCTION(PrintString), asCALL_CDECL); assert( r >= 0 );
        r = engine->RegisterGlobalFunction("uint GetSystemTime()", asFUNCTION(timeGetTime), asCALL_STDCALL); assert( r >= 0 );
    }
    else
    {
        // Notice how the registration is almost identical to the above. 
        r = engine->RegisterGlobalFunction("void Print(string &in)", asFUNCTION(PrintString_Generic), asCALL_GENERIC); assert( r >= 0 );
        r = engine->RegisterGlobalFunction("uint GetSystemTime()", asFUNCTION(timeGetTime_Generic), asCALL_GENERIC); assert( r >= 0 );
    }
 
 
    // It is possible to register the functions, properties, and types in 
    // configuration groups as well. When compiling the scripts it then
    // be defined which configuration groups should be available for that
    // script. If necessary a configuration group can also be removed from
    // the engine, so that the engine configuration could be changed 
    // without having to recompile all the scripts.
}
 
int CompileScript(asIScriptEngine *engine)
{
    int r;
 
    // We will load the script from a file on the disk.
    FILE *f = fopen("script.as", "rb");
    if( f == 0 )
    {
        cout << "Failed to open the script file 'script.as'." << endl;
        return -1;
    }
 
    // Determine the size of the file	
    fseek(f, 0, SEEK_END);
    int len = ftell(f);
    fseek(f, 0, SEEK_SET);
 
    // On Win32 it is possible to do the following instead
    // int len = _filelength(_fileno(f));
 
    // Read the entire file
    string script;
    script.resize(len);
    int c =    fread(&script[0], len, 1, f);
    fclose(f);
 
    if( c == 0 ) 
    {
        cout << "Failed to load script file." << endl;
        return -1;
    }
 
    // Add the script sections that will be compiled into executable code.
    // If we want to combine more than one file into the same script, then 
    // we can call AddScriptSection() several times for the same module and
    // the script engine will treat them all as if they were one. The script
    // section name, will allow us to localize any errors in the script code.
    asIScriptModule *mod = engine->GetModule(0, asGM_ALWAYS_CREATE);
    r = mod->AddScriptSection("script", &script[0], len);
    if( r < 0 ) 
    {
        cout << "AddScriptSection() failed" << endl;
        return -1;
    }
    
    // Compile the script. If there are any compiler messages they will
    // be written to the message stream that we set right after creating the 
    // script engine. If there are no errors, and no warnings, nothing will
    // be written to the stream.
    r = mod->Build();
    if( r < 0 )
    {
        cout << "Build() failed" << endl;
        return -1;
    }
 
    // The engine doesn't keep a copy of the script sections after Build() has
    // returned. So if the script needs to be recompiled, then all the script
    // sections must be added again.
 
    // If we want to have several scripts executing at different times but 
    // that have no direct relation with each other, then we can compile them
    // into separate script modules. Each module use their own namespace and 
    // scope, so function names, and global variables will not conflict with
    // each other.
 
    return 0;
}
 
void LineCallback(asIScriptContext *ctx, DWORD *timeOut)
{
    // If the time out is reached we abort the script
    if( *timeOut < timeGetTime() )
        ctx->Abort();
 
    // It would also be possible to only suspend the script,
    // instead of aborting it. That would allow the application
    // to resume the execution where it left of at a later 
    // time, by simply calling Execute() again.
}
 
// Function implementation with native calling convention
void PrintString(string &str)
{
    cout << str;
}
 
// Function implementation with generic script interface
void PrintString_Generic(asIScriptGeneric *gen)
{
    string *str = (string*)gen->GetArgAddress(0);
    cout << *str;
}
 
// Function wrapper is needed when native calling conventions are not supported
void timeGetTime_Generic(asIScriptGeneric *gen)
{
    gen->SetReturnDWord(timeGetTime());
}

スクリプトのサンプルコード Edit

以下がASのソースコードの例です。

すべてを展開すべてを収束
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
 
 
-
|
|
|
|
|
|
|
|
-
!
|
|
|
-
|
|
|
!
|
|
|
-
!
|
|
|
|
|
-
|
|
|
!
|
|
|
-
|
!
|
|
|
|
|
|
-
|
|
!
|
!
 
 
 
-
|
|
|
|
|
|
!
 
 
 
 
-
|
!
 
-
|
!
 
 
 
-
|
-
!
|
-
!
!
 
 
 
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
!
|
-
!
|
|
|
-
|
!
|
|
|
-
|
!
|
|
|
-
|
!
|
|
|
-
|
|
|
!
|
|
|
|
|
|
|
|
|
-
|
|
|
|
!
|
!
 
// クラスの宣言。
class Vector3
{
  // メンバ変数の宣言
  // public,privateなどのアクセス修飾子はありません
  float x;
  float y;
  float z;
 
  // デフォルトコンストラクタ
  Vector3()
  {
  }
 
  // コンストラクタ
  Vector3(float aX, float aY, float aZ)
  {
    mX = aX;
    mY = aY;
    mZ = aZ;
  }
 
  // デストラクタ
  ~Vector3()
  {
  }
  
  // メンバ関数
  void add( 
    const Vector3 &in aVec // 引数にconstや参照修飾子を付けることができます
    )
  {
    mX += aVec.x;
    mY += aVec.y;
    mZ += aVec.z;
  }
 
  // constメンバ関数
  float sum()const
  {
    return mX + mY + mZ;
  }
 
  // 演算子のオーバーロード
  // これは += をオーバーロードしています
  Vector3@ opAddAssign(
    const Vector3 &in aVec
    )
  {
    add( aVec );
    return this;
  }
 
}; // セミコロンはあってもなくても可能。
 
// enumの宣言
enum SampleKind
{
    SampleKind_A
    , SampleKind_B
    // term
    , SampleKind_TERMINATE
    , SampleKind_MIN = 0
    , SampleKind_MAX = SampleKind_TERMINATE-1
};
 
// interfaceを使ったサンプル
// C#やD言語のinterfaceと同様です
interface IUpdatable
{
    void updatableUpdate();
};
interface IDrawable
{
    void drawableDraw()const;
};
class Chara 
    : IUpdatable 
    , IDrawable
{
    void updatableUpdate()
    {
    }
    void drawableDraw()const
    {
    }
};
 
// グローバル変数
int globalInt32Value = 0;
 
// グローバル関数
void func()
{
  // 組み込み型の紹介
  int8   int8val;
  int16  int16val;
  int    int32val;
  int32  int32val2;
  int64  int64val;
  uint8  uint8val;
  uint16 uint16val;
  uint   uint32val;
  uint32 int32val2;
  uint64 uint64val;
  bool   boolVal;
  float  floatVal;
  double doubleVal;
 
  // 変数は明示的に初期化値を指定できます
  int initializedValue = 10;
 
  // 未確認情報:最新版では初期化値を指定しないと0(boolはfalse)で初期化されるようです。
  // D言語やC#のように未初期化変数が発生しない仕組みは素晴らしいですね
  // (手元はかなり古いr320版を使っており,この当時ではまだ実装されていません)
  int notInitializedValue; // 0で初期化される
 
  // boolの値としてtrue,falseが使えます
  bool trueValue = true;
  bool falseValue = false;
 
  // const変数が使えます
  const int constantValue = 100;
 
  // 16進数が使えます
  const int hexValue = 0xFFFF;
 
  // float値の接尾語が使えます
  const float floatValue = 1.2f;
 
  // if-else文が使えます
  if ( true )
  {
  }
  else
  {
  }
 
  // for文が使えます
  for ( int i = 0; i < 10; ++i )
  {
    break; // continue/breakが使えます
  }
 
  // while文が使えます
  while ( true )
  {
    break; // continue/breakが使えます
  }
 
  // do-while文が使えます
  do
  {
    break; // continue/breakが使えます
  } while( false );
 
  // switch文が使えます
  switch( 3 )
  {
  case 1  : break; // breakが使えます
  case 2  : break;
  default : break; // defaultが使えます
  }
 
  // クラスのインスタンスはC++と同じように書きます
  Vector3 vec(1,1,1);
 
  // オブジェクトハンドルというポインタのような概念があります
  Vector3@ vecHandle = @Vector3();
 
  // オブジェクトの寿命は参照カウンタで管理されます
  // 参照カウンタが0になるとデストラクタが呼ばれます
  {
    Vector3@ objA = @Vector3(); // カウンタ1
    Vector3@ objB = @objA; // カウンタ2
    @objA = null; // カウンタ1
    @objB = null; // カウンタ0 デストラクタが呼ばれる
  }// スコープを抜けるタイミングでnullでないスコープローカル変数の参照カウンタは減ります
 
}

    ホーム 一覧 検索 最終更新 バックアップ リンク元   ヘルプ   最終更新のRSS