SphereScriptは『関数』が使えます。例えば三角関数です。
=============== t=45*(3.1416/180); //45°をラジアンに変換 a=sin(t); //変数aにsin(45°)を代入 ===============
SphereScriptにおける関数は、数学的な意味の関数に限定されません。 演算子と制御構文以外のあらゆる機能、例えば文字を表示する機能や経過時間を知る機能なども全て関数で定義されています。 実際、何度も出てきているprint_stringも関数です。文字列を定義域に取り、文字を表示します。print_string関数には値域はありません。 定義域と値域をとる、数学的な関数とはイメージがだいぶ異なりますが、英語でfunction(機能)と呼んでいるものの和訳ですので、深く考えずに覚えてください。
定義域のことを『引数(ひきすう)』と呼び、値域のことを『戻り値』または『返り値』と呼びます。
関数の使い方の話に戻ります。引数は一つだけとは限りません。複数の引数を指定する関数もあります。具体的には乗数を求めるpow関数です。
=============== a=pow(10, 3.7); //10の3.7乗を求めてaに代入 ===============
string_head関数は異なるタイプの2つの引数を指定します。
=============== str = string_head("ABCDEFG", 3); //変数strに"ABCDEFG"の最初の3文字である"ABC"が代入される。 ===============
なお、これらの関数がどのような引数をいくつ必要とするかは、インクルードファイルに記述されています。 1章のプログラムの冒頭で「#include "SpeSystem/script/system.inc"」という意味不明な1行がありました。 SphereEngineSDKに含まれているUnpacPackage.rspを使い、SpeSystem.pacを展開するとこのファイルが入っているのですが、中を見てみると以下のような記述があります。
=============== systemcall float sin(float a); systemcall float pow(float x, float p); systemcall int print_string(string msg); systemcall string string_head(string str, int n); ===============
systemcallで始まる行が、関数の名前と引数と返り値を決めています。 インクルードファイルはその部分にファイル全体を「コピペ」したかのように振舞いますので、SphereScriptはまずsystemcallで指定された引数と返り値を読み取って、実際に処理の中で使われている関数を解釈します。 なお、『インクルードファイル』については「8.プリプロセッサ」の項目で詳しく説明します。
関数はSphereScriptによって事前に用意されているものの他に、自分で作成することも出来ます。具体的な例を挙げます。
=============== function my_function(x) { return x*10; } ===============
こうすることで「引数を10倍して戻り値にしてくれる関数」であるmy_functionが作成できました。 以下のように使うことが出来ます。
=============== a=my_function(3); //3の10倍をaに代入。 ===============
関数の中でreturnという新しい単語が出てきましたが、「return 返り値;」とすることで、関数の外に値を返すことが出来ます。 この例では、引数の3がmu_functionのxにコピーされて、10倍された後にreturnにより関数の戻り値となりました。sin関数などと同じ仕組みで、上記の例の場合、関数の戻り値はaに代入されます。 これだけでは何も利用価値が無いように思えますが、例えばベクトルの長さを取得する関数vector_lengthを定義したとします。
=============== function float vector_length(float x, float y, float z) { length=sqrt(x*x + y*y + z*z); //ベクトル長を求める(sqrtは平方根を計算する関数) return length; } ===============
こうすると、例えばゲームにおいてプレイヤーと敵との距離を計算するような場面では、 以下のように書くことができるようになります。
=============== distance=vector_length(player_x-enemy_x, player_y-enemy_y, player_z-enemy_z); ===============
この処理を一行の式で書こうとすると、とても長くなって読みにくくなってしまうのですが、関数を使うことでスリムになりました。 vector_lengthと書くだけで長さを求めることが出来るようになるため、何度も同じことを書く必要が無くなり、楽を出来るだけでなく書き間違いによるバグもなくすことが出来ます。 また、「vector_length」という名前をつけることでこれを見たときに「ベクトルの長さを計算する関数だな」ということが一目瞭然です。 関数定義は以下の構文を取ります。
=============== function 戻り値の型 関数名(引数の型 変数名 , ...) {関数の中身} ===============
戻り値の型と、引数の型はそれぞれ省略可能です。実際、最初の例のmy_functionでは書いていません。 省略した場合、valiable型を指定したことと同じ意味になります。valiable型引数は、整数を代入しようとすれば整数型となり、文字列を代入しようとすれば文字列になる、というように、代入される型に応じて勝手に型が変わる変数です。 なお、戻り値は1つだけです。2つ以上の戻り値を得るには「6.関数の使い方(応用編)」の「値渡しと参照渡し」の項を参照してください。
関数の中で関数を使うことももちろん可能です。vector_lengthを使ってさらに実用的な関数を作ってみます。
=============== function float vector_length(float x, float y, float z) { float length=sqrt(x*x + y*y + z*z); //ベクトル長を求める(sqrtはルートを計算する関数) return length; } funttion float character_distance(int id_1,int id_2); { float x1,x2,y1,y2,z1,z2; get_character_pos(id_1, x1, y1, z1); get_character_pos(id_2, x2, y2, z2); return vector_length(x1-x2, y1-y2, z1-z2); } ===============
character_distance関数は、2つのキャラクターの距離を求める関数として機能します。 上記の例で引数に「x1-x2」としたり、戻り値で「vector_length(x1-x2, y1-y2, z1-z2)」としているように、それぞれ自由に式が記述できます。 数学における関数がそうであるように、SphereScriptの関数は値として振る舞い、引数には関数を含む式が代入できます。
上記の例では使う順番に上から記述していますが、先に関数を使う部分を書いてから、下のほうで関数の中身を記述する順番でもかまいません。 具体的には以下のプログラムは正しくコンパイルされ、動作します。
=============== funttion float character_distance(int id_1,int id_2); { float x1,x2,y1,y2,z1,z2; get_character_pos(id_1, x1, y1, z1); get_character_pos(id_1, x2, y2, z2); return vector_length(x1-x2, y1-y2, z1-z2); //ここで呼んでいる関数vector_lengthは下で記述している。 } function float vector_length(float x, float y, float z) { float length=sqrt(x*x + y*y + z*z); //ベクトル長を求める(sqrtはルートを計算する関数) return length; } ===============
独特の言い回しとして、関数を実行することを『呼ぶ』または『関数コールする』などと呼びます。 returnして関数の値域を決定することを『戻す(または返す)』と呼びます。これらは昔CPUの機械語のプログラムを行っていた時代(1980年代以前)の専門用語「call」や「return」の名残です(returnは名残ではなくそのまま残っていますが)。 同様に、何らかの値を引数に指定することを『渡す』と呼ぶことがあります。いわゆるスラングの類ですが、専門書などにも何の説明もなしにこのような言葉が使われていますので、ここで紹介しました。 三角関数を例にすると「角度をラジアンに変換した値をsin関数の定義域にすると、値域として正弦の値が得られる」ことを「sin関数を呼ぶ場合、角度をラジアンに変換して引数に渡すと、正弦の値が返ってくる」と言い換えられます。 こう言うと、なんとなくプロっぽい感じになったと思いませんか?
SphereScriptに限らず、現行のほとんどのプログラミング言語もそうですが、基本的に関数を次々に作り、それを多重に組み合わせて希望とするソフトウェアを設計していきます。 ひとまとまりの処理を適切に関数に分割すると、プログラムが大変読みやすくなるだけでなく、同じコードを何度もコピペすることがなくなるのでバグも減るはずです。