Skip to content

Tutorial ja

github-actions[bot] edited this page Sep 30, 2026 · 5 revisions

Tutorial

🌐 English · 日本語

目次

Mana チュートリアル

案内役が話し、門が開く小さなイベントを作ります。3章目で一度完成させ、その後で状態、条件、繰り返し、同期を学びます。最後に同じイベントを複数のファイルと名前空間へ整理します。

はじめての Mana プログラムを実行できたところから始めます。プログラミング経験は前提にしません。

読む順番

  1. Actor と Action
  2. Action を Request する
  3. 会話して門を開ける
  4. 変数で状態を覚える
  5. 条件によって処理を変える
  6. 処理を繰り返す
  7. 関数で処理をまとめる
  8. Priority で割り込みと復帰を扱う
  9. 待機と同期を使い分ける
  10. 複数のファイルに分ける
  11. namespace で名前を整理する

この教材の使い方

  • 「ファイル全体」のコードは、そのまま保存して実行できます。前の章のコードへ追加せず、置き換えます。
  • 「抜粋」「置き換える例」などは、指定した場所へ入れるコードです。
  • 実行コマンドはターミナルへ、Mana のコードはエディタへ入力します。
  • 各章の期待する出力を確認してから、「一つ変えてみる」に進みます。
  • 数字の表示や yield() は、秒数を待つ機能とは限りません。教材では出力で処理の流れを観察します。

完成コード一覧からも実行できます。各例は独立したプログラムです。全部をまとめて import する用途ではありません。

構文を調べたいときは 言語リファレンス、仕組みを整理したいときは Mana の考え方を参照してください。

Actor と Action

このページでは、「行動を定義すること」と「行動を実行すること」を区別します。

Actor と Action の関係

動かして確かめる

ファイル全体です。Mana フォルダーへ lesson.mn として保存し、準備ページで設定したターミナルから mana lesson.mn を実行してください。前の章のコードへ追加せず、ファイル全体を置き換えます。

actor Guide
{
    action main()
    {
        print("Guide: Ready.\n");
    }

    action talk()
    {
        print("Guide: Welcome!\n");
    }
}

期待する出力:

Guide: Ready.

同梱の完成コードは、Mana フォルダーから次のコマンドでも実行できます。

mana examples/tutorial/02-actor.mn

なぜ Welcome! は出ないのか

Guide は一つの Actor で、main と talk という二つの Action を持ちます。main は起動時に VM から実行を依頼されますが、talk は定義しただけでは実行されません。

flowchart TD
    A["Guide : 案内役"] --> B["main : 起動時の処理"]
    A --> C["talk : 会話する処理"]
Loading

actor、action は Mana が意味を決めている単語です。Guide、talk は作者が付けた名前です。main には起動時に使われる特別な意味があります。

一つの Actor に複数の Action を定義できます。例えば門なら open と close、案内役なら talk と warn のように、目的に応じて分けます。

Actor はキャラクター以外にも使える

次の章では、イベント全体の進行役も Actor にします。一つのソースファイルに複数の Actor を定義でき、それぞれが自分の処理を持ちます。

複数の Actor に main があれば、それぞれが起動時の実行対象です。ファイル全体で一つの main だけを選ぶ仕組みではありません。Actor 間の順番を決めたいときは、次の章から学ぶ依頼と待機を使います。

一つ変えてみる

main の文字を Guide: Waiting. に変えて保存・実行してください。talk の文字を変えても、この段階では出力に現れません。

次は、その talk を実行させます。

次に読む

Action を Request するへ進みます。

Action を Request する

進行役の Event から、案内役の Guide に会話を依頼します。依頼するための命令が request です。

Request の流れ

動かして確かめる

ファイル全体です。Mana フォルダーへ lesson.mn として保存し、準備ページで設定したターミナルから mana lesson.mn を実行してください。前の章のコードへ追加せず、ファイル全体を置き換えます。

actor Event
{
    action main()
    {
        print("Event: Request.\n");
        request(10, Guide->talk());
    }
}

actor Guide
{
    action talk()
    {
        print("Guide: Welcome!\n");
    }
}

期待する出力:

Event: Request.
Guide: Welcome!

同梱の完成コードは、Mana フォルダーから次のコマンドでも実行できます。

mana examples/tutorial/03-request.mn

依頼する相手と行動を指定する

上のコードからの抜粋:

request(10, Guide->talk());

括弧の中で命令へ渡す値を 引数と呼びます。複数の引数は , で区切ります。

部分 意味
10 Priority(優先度)。数が大きい方が高い
Guide->talk() Guide の talk という Action
-> 左の Actor が持つ、右の Action を指し示す記号

10 は秒数でも繰り返し回数でもありません。この教材ではまず 10 を使い、優先度による割り込みは後の章で学びます。

Guide の定義が Event より後にあっても、この例はコンパイルできます。コンパイラはプログラム全体から名前を調べます。

request は完了を待たない

request は相手へ依頼を出し、その完了を待たずに呼び出し側の処理を続けます。次の行へ進んだことは、相手の Action が終わった証明にはなりません。

また、一つの Actor で同じ Priority が使用中・予約済みの場合、その Priority の新しい依頼は受理されません。request を二つ並べても、二つの仕事が必ず順番に実行されるわけではありません。 詳細は Priorityで確認します。

会話が終わってから門を開けるには、完了を待つ命令が必要です。次の章で使います。

一つ変えてみる

talk の名前を greet に変更してください。定義の action talk() と、依頼先の Guide->talk() の両方を変更すると、同じ出力になります。

片方だけ変更すると、存在しない名前を参照することになります。コンパイラの診断を読み、名前をそろえて直してみてください。

次に読む

会話して門を開けるへ進みます。

会話して門を開ける

ここで最初のイベントを完成させます。案内役が話し、その処理が終わってから門を開け、最後に終了を表示します。

画面上のキャラクターや門はまだ使いません。出力の順番で、イベントの進行を確かめます。

動かして確かめる

ファイル全体です。Mana フォルダーへ lesson.mn として保存し、準備ページで設定したターミナルから mana lesson.mn を実行してください。前の章のコードへ追加せず、ファイル全体を置き換えます。

actor Event
{
    action main()
    {
        await(10, Guide->talk());
        await(10, Gate->open());
        print("Event: Finished.\n");
    }
}

actor Guide
{
    action talk()
    {
        print("Guide: Welcome!\n");
    }
}

actor Gate
{
    action open()
    {
        print("Gate: Open.\n");
    }
}

期待する出力:

Guide: Welcome!
Gate: Open.
Event: Finished.

同梱の完成コードは、Mana フォルダーから次のコマンドでも実行できます。

mana examples/tutorial/04-event.mn

await で順番を作る

await(10, Guide->talk()); は会話を依頼し、完了を待ってから次へ進むために使います。書き方は request と同じく、優先度と Action を指定します。

この例では、他の処理が同じ Actor へ依頼せず、使う Priority も空いているため、順番に処理を完了できます。

sequenceDiagram
    participant E as Event
    participant G as Guide
    participant D as Gate
    E->>G: talk を依頼
    G->>G: Welcome! を表示
    G-->>E: 完了後、Event が再開
    E->>D: open を依頼
    D->>D: Open. を表示
    D-->>E: 完了後、Event が再開
    E->>E: Finished. を表示
Loading

待っているのは Event です。Mana VM 全体を止めるわけではないので、待たれている Guide や Gate は処理を進められます。

やりたいこと 最初に使う命令
依頼して、相手の終了を待たずに進む request
依頼した処理の終了後に進む await

await は実際には対象 Actor の Priority を条件に待ちます。要求が受理されない場合は待たずに進みます。複数の依頼元や割り込みを導入するときは 待機と同期の条件も確認してください。

一つ変えてみる

Event の二つの await の行を入れ替えます。保存して実行すると、門が先に開き、続いて案内役が話す順番になります。

元に戻したら、会話の依頼をもう一度、門の後ろに追加してください。出力は「会話 → 門 → 会話 → 終了」になります。前の会話が終わってから再び依頼するので、同じ Priority を再使用できます。

自分自身の Actor を await の相手にはできません。Action を分けただけでは別 Actor にはならず、実行時にエラーになります。

ゲームとの接続は次の段階

Gate->open() の中身は文字の表示なので、現時点では実際の扉の描画やアニメーションは起きません。ゲームへ接続するときは、その部分を C++ 側の処理につなぎます。まずはこのイベントに、回数や条件を加えていきましょう。

次に読む

変数で状態を覚えるへ進みます。

変数で状態を覚える

案内役へ2回話しかけ、会話回数を表示します。値を保存するために 変数を使います。

動かして確かめる

次はファイル全体です。Mana フォルダーの lesson.mn を置き換えて保存し、mana lesson.mn で実行してください。

actor Event
{
    action main()
    {
        await(10, Guide->talk());
        await(10, Guide->talk());
    }
}

actor Guide
{
    int mTalkCount;

    action init()
    {
        mTalkCount = 0;
    }

    action talk()
    {
        mTalkCount = mTalkCount + 1;
        print("Talk count: %d\n", mTalkCount);
    }
}

期待する出力:

Talk count: 1
Talk count: 2

同梱の完成コードは、mana examples/tutorial/05-variables.mn でも実行できます。

型、名前、値

int mTalkCount; は、整数を保存する変数を宣言しています。int は扱う値の種類である 型、mTalkCount は変数名です。init Action で最初の値を0にしています。

mTalkCount = mTalkCount + 1; は、現在の値に1を足してから保存する 代入です。数学の等式とは違い、右側で計算した値を左側へ入れる操作です。1回目は0から1、2回目は1から2になります。

値を表示する

print("Talk count: %d\n", mTalkCount); の %d は、後ろに渡した整数値を表示する位置を表します。\n は改行です。

変数の値を表示すると、どこまで処理が進み、値がどう変わったかを確認できます。

Action をまたいで値を覚える

今回の変数は Guide の中、Action の外に宣言した Actor 変数です。Guide に属する Action から直接参照でき、他の Actor から直接変更されることはありません。

m で始める名前は Actor のメンバーだと分かりやすくする約束で、言語の必須条件ではありません。

この変数は会話の Action が終わっても残ります。ただし、プログラムを終了して起動し直すと、また0から始まります。ファイルにセーブデータを保存したわけではありません。

状態を Guide に持たせることで、その状態を使う Action と一緒にカプセル化できます。詳しくは 変数リファレンスを参照してください。

その処理の中で使うローカル変数

Action 内に宣言する変数は、その処理で使う ローカル変数です。

talk の中身を置き換える例:

int count = 0;
count = count + 1;
print("Talk count: %d\n", count);

この場合は、会話のたびに count = 0 から始まるので、2回とも1を表示します。

よく使う型

以下は Action 内に書く宣言の例です。

int count = 3;
float distance = 1.5;
bool hasKey = true;
string message = "Welcome!";
型 保存する値
int 整数
float 小数を含む数
bool true または false の二つの値
string 文字列

一つ変えてみる

完成コードへ戻し、Event に3回目の会話を依頼する行を追加してください。Talk count: 3 まで表示できれば、状態の保持を確認できています。

次に読む

条件によって処理を変えるで、値に応じた分岐を作ります。

条件によって処理を変える

最初のイベントに「鍵があるときだけ門を開く」という条件を加えます。

動かして確かめる

ファイル全体です。Mana フォルダーへ lesson.mn として保存し、準備ページで設定したターミナルから mana lesson.mn を実行してください。前の章のコードへ追加せず、ファイル全体を置き換えます。

actor Event
{
    action main()
    {
        bool hasKey = true;

        await(10, Guide->talk());
        if (hasKey)
        {
            await(10, Gate->open());
        }
        else
        {
            print("Event: Find the key.\n");
        }
    }
}

actor Guide
{
    action talk()
    {
        print("Guide: Welcome!\n");
    }
}

actor Gate
{
    action open()
    {
        print("Gate: Open.\n");
    }
}

期待する出力:

Guide: Welcome!
Gate: Open.

同梱の完成コードは、Mana フォルダーから次のコマンドでも実行できます。

mana examples/tutorial/06-conditions.mn

if と else

bool hasKey = true; は、鍵を持っている状態を表します。true は成立、false は不成立を表す値です。

if (hasKey) は、その値が成立しているときだけ最初のブロックを実行します。不成立なら else のブロックを実行します。両方を実行するわけではありません。

flowchart TD
    A["会話が終わる"] --> B{"鍵を持っている?"}
    B -->|true| C["門を開ける"]
    B -->|false| D["鍵を探すよう表示する"]
Loading

一つ変えてみる

hasKey の初期値を false に変更して保存・実行してください。

Guide: Welcome!
Event: Find the key.

今回の鍵の状態は、自分でコードに設定しています。プレイヤーの操作や持ち物を自動で読み取っているわけではありません。

数を比べる

前の章の会話回数なら、mTalkCount == 0 と書くことで、0かどうかを調べられます。

書き方 意味
a == b 等しい
a != b 等しくない
a < b / a <= b 小さい / 以下
a > b / a >= b 大きい / 以上

= は代入、== は比較です。hasKey = true と書くと値を変更してしまいます。条件を調べる用途とは区別してください。

条件を組み合わせる

Action 内の例:

bool hasKey = true;
bool isOpen = false;
if (hasKey && !isOpen)
{
    print("Ready to open.\n");
}

&& は両方が成立、|| は少なくとも一方が成立、! は成立・不成立を逆にします。上の条件は「鍵があり、まだ開いていない」です。

会話を変える練習

前の章の完成コードに戻り、talk の中身を次に置き換えてください。

if (mTalkCount == 0)
{
    print("Guide: Welcome!\n");
}
else
{
    print("Guide: Welcome back!\n");
}
mTalkCount = mTalkCount + 1;

最初は Welcome!、2回目は Welcome back! になります。比較してから回数を増やしている点に注目してください。

次に読む

処理を繰り返すへ進みます。

処理を繰り返す

3、2、1と表示してから門を開きます。同じ形の処理を繰り返すために while を使います。

動かして確かめる

ファイル全体です。Mana フォルダーへ lesson.mn として保存し、準備ページで設定したターミナルから mana lesson.mn を実行してください。前の章のコードへ追加せず、ファイル全体を置き換えます。

actor Event
{
    action main()
    {
        int remaining = 3;
        while (remaining > 0)
        {
            print("Remaining: %d\n", remaining);
            remaining = remaining - 1;
        }
        await(10, Gate->open());
    }
}

actor Gate
{
    action open()
    {
        print("Gate: Open.\n");
    }
}

期待する出力:

Remaining: 3
Remaining: 2
Remaining: 1
Gate: Open.

同梱の完成コードは、Mana フォルダーから次のコマンドでも実行できます。

mana examples/tutorial/07-loops.mn

while は毎回、条件を先に調べる

while (remaining > 0) は、残りが0より大きい間、ブロックを繰り返します。毎回1を引くので、やがて0になり、繰り返しを抜けます。

条件を調べるときの値 行うこと
3 3を表示し、2へ変更
2 2を表示し、1へ変更
1 1を表示し、0へ変更
0 ブロックへ入らず、門を開ける処理へ進む

これは秒数のカウントダウンではありません。繰り返し自体には、時間を待つ機能はありません。

一つ変えてみる

最初の remaining を5にすると、5から1まで表示します。0にすると、数字を一度も表示せず門を開きます。実行前に結果を予想してください。

回数を指定する for

完成コードの main 全体を置き換える例:

for (int i = 0; i < 3; i++)
{
    print("Step: %d\n", i);
}
await(10, Gate->open());

for の括弧内は「最初に行う処理; 続ける条件; 1回終わるたびに行う処理」です。i++ は値を1増やします。ここでは0、1、2を表示し、門を開きます。

終わらないときは止めて調べる

完成コードから remaining = remaining - 1; を消すと、条件が変わらず、繰り返しが終わりません。このような状態を 無限ループと呼びます。試す必要はありませんが、誤って起きたらターミナルで Ctrl+C を押して止めてください。

「他の Actor がいつか変数を変えるだろう」と考えて待ち続けるだけのループにも注意が必要です。Mana では他の処理へ実行機会を渡す仕組みが関係します。待機と同期で yield() を学びます。

必要になったら調べるもの

break は繰り返しを途中で終了し、continue はその回の残りを飛ばします。do-while は少なくとも1回処理してから条件を調べます。

専用の loop 構文も含め、詳しい使い方は 文リファレンスへ進んでください。まずは、終了条件を持つ while と for を使えれば十分です。

次に読む

関数で処理をまとめるへ進みます。

関数で処理をまとめる

鍵があと何個必要かを計算する処理に、名前を付けて使います。入力を受け取り、結果を返す処理を **関数(Function)**として定義できます。

動かして確かめる

ファイル全体です。Mana フォルダーへ lesson.mn として保存し、準備ページで設定したターミナルから mana lesson.mn を実行してください。前の章のコードへ追加せず、ファイル全体を置き換えます。

int remainingKeys(int required, int owned)
{
    if (owned >= required)
    {
        return 0;
    }
    return required - owned;
}

actor Event
{
    action main()
    {
        int missing = remainingKeys(3, 1);
        print("Missing keys: %d\n", missing);
    }
}

期待する出力:

Missing keys: 2

同梱の完成コードは、Mana フォルダーから次のコマンドでも実行できます。

mana examples/tutorial/08-functions.mn

入力と結果を読む

int remainingKeys(int required, int owned) を分解すると、次の意味になります。

部分 意味
最初の int 結果として整数を返す
remainingKeys 関数の名前
int required 必要数を受け取る引数
int owned 所持数を受け取る引数

remainingKeys(3, 1) と呼ぶと、required に3、owned に1を渡します。return は計算結果を返し、その呼び出しを終了します。今回は 3 - 1 の結果である2が返り、missing に入ります。

必要数以上の鍵を持つ場合は、先に return 0; へ到達するので、下の引き算は実行しません。

一つ変えてみる

呼び出しを remainingKeys(3, 5) に変更してください。出力は Missing keys: 0 です。

前のイベントへこの関数を追加するなら、remainingKeys(3, 5) == 0 を if の条件として使い、必要数を持っているときに門を開けられます。

関数と Action を使い分ける

関数は、呼び出した処理の中で計算などを行い、結果を呼び出し元へ返します。Action は Actor の行動で、Request の対象になります。

目的 この教材での書き方
必要な鍵の数を計算する remainingKeys(3, 1)
門に開く行動を依頼する await(10, Gate->open())

値を返さない関数

Actor の外へ追加する定義例:

void printSeparator()
{
    print("-----\n");
}

void は結果の値を返さないことを表します。Action 内で printSeparator(); と書くと呼び出せます。

C++ 側に実装する native 関数は、組み込みガイドで扱います。Mana で宣言するだけでは、対応するゲーム側の処理は作られません。

次に読む

Priority で割り込みと復帰を扱うへ進みます。

Priority で割り込みと復帰を扱う

Priority(優先度)は、同じ Actor の中で、どの Action を優先するかを表します。数が大きいほど優先されます。今回は、案内役が会話の途中で警告を行い、その後で会話の続きへ戻ります。

Priority による割り込みと復帰

動かして確かめる

次はファイル全体です。Mana フォルダーの lesson.mn を置き換えて保存し、mana lesson.mn で実行してください。

const int kNormalPriority = 10;
const int kEmergencyPriority = 100;

actor Event
{
    action main()
    {
        await(kNormalPriority, Guide->talk());
        print("Event: Finished.\n");
    }
}

actor Guide
{
    action talk()
    {
        print("Guide: Talk begins.\n");
        request(kEmergencyPriority, self->warn());
        print("Guide: Talk resumes.\n");
    }

    action warn()
    {
        print("Guide: Watch out!\n");
    }
}

期待する出力:

Guide: Talk begins.
Guide: Watch out!
Guide: Talk resumes.
Event: Finished.

同梱の完成コードは mana examples/tutorial/09-priority.mn でも実行できます。

数字に名前を付ける

const int kNormalPriority = 10; は、変更しない整数に名前を付ける 定数の宣言です。const が変更しないことを表します。k で始めるのは教材の命名上の約束です。

ここまでは直接10を書いてきました。複数の優先度が登場したので、通常処理の10と緊急処理の100を、名前で区別しています。

高い Priority が終わると、元の続きへ戻る

self は、その処理を実行している Actor 自身です。Guide の talk から、同じ Guide の warn を高い Priority で依頼しています。

flowchart TD
    A["talk / Priority 10 : 会話を開始"] --> B["warn / Priority 100 を依頼"]
    B --> C["talk を中断して warn を実行"]
    C --> D["warn が終了"]
    D --> E["talk の続きへ復帰"]
Loading

ここでは request を使います。自分自身を awaitStart や await で待つと、実行時エラーになります。

この例は同じ Actor 内の割り込みを観察するものです。別 Actor への Request が、OS の割り込みのように任意の瞬間に走ることを意味しません。Actor を進める仕組みは 実行モデルで説明します。

高い、低い、同じを区別する

対象 Actor の状態に対する要求 基本的な扱い
現在より高い、未使用の Priority 現在の Action より優先する
現在より低い、未使用の Priority 実行できる状態になるまで保持する
使用中・予約済みと同じ Priority 新しい要求は受理しない

受理されるには、対象 Action が存在し、Actor が要求を受け付けていることなども必要です。完全な条件は Request リファレンスを参照してください。

一つの Actor で使用中の10と、別の Actor で使用中の10は別に管理されます。プログラム全体の実行順を、一つの番号表で決める仕組みではありません。

一つ変えてみる

完成コードの kEmergencyPriority を100から10へ変えてください。talk がすでに10を使用しているため、warn は受理されず、警告の出力がなくなります。

Guide: Talk begins.
Guide: Talk resumes.
Event: Finished.

試したら100へ戻してください。すべての Action に異なる Priority を付ける必要はありません。順番に完了させるなら同じ値を再利用し、割り込み関係が必要なところに優先度の段階を作ります。

次に読む

待機と同期を使い分けるで、ここまで使ってきた完了待ちの条件を詳しく確認します。

待機と同期を使い分ける

会話して門を開けるでは、await で順番を作りました。ここでは、開始可能になるのを待つ、すでに動いている Actor を待つ、自分の処理を一度譲る、という使い方を追加します。

request / awaitStart / await の違い

まずは完了待ちを振り返る

イベントの main 内の抜粋:

await(10, Guide->talk());
await(10, Gate->open());

この例は、要求が受理され、他の要求元による競合がない前提で、会話を終えてから門を開きます。

await が直接調べるのは、対象 Actor の現在の Priority が指定値より低くなったかです。要求一件ごとの完了通知を記録して待っているわけではありません。

三つの待機を区別する

以下の条件は、await 系では要求が受理された場合のものです。

命令 新しく要求するか 呼び出し側が先へ進める条件
request(p, Actor->action()) する 待機しない
awaitStart(p, Actor->action()) する 対象 Actor の現在の Priority が p 以下
await(p, Actor->action()) する 対象 Actor の現在の Priority が p 未満
join(p, Actor) しない 対象 Actor の現在の Priority が p 以下

p は説明のための仮の名前です。実際には10などの整数や定数を指定します。

awaitStart は、要求した Action が開始できる Priority まで進むのを待つ用途に使います。その Action の最初の文が実行済みであることまでは保証しません。 相手の処理結果が必要なら、開始可能になることと、その処理が終わることを区別してください。

join(0, Guide); は新しい会話を始めません。Guide の現在の Priority が0以下になるまで待ちます。main の Priority は0なので、これは Actor の全処理の終了を意味する条件でもありません。

要求が受理されない場合

awaitStart と await は、最初の要求が受理されなければ待たずに次へ進みます。空くまで要求を繰り返す命令ではありません。

例えば、対象 Actor の Priority 10 がすでに使用中なら、別の Action を10で依頼しても、その Action の実行は保証されません。待機から戻ったことだけを、その行動が成功した証拠として扱わないでください。

入門のイベントでは、一つの進行役が一つずつ要求し、終了を待ってから次を依頼することで、この競合を避けています。複数の Actor から同じ相手へ要求する設計では、依頼元と Priority の分担も決めます。

また、自分自身を対象とする awaitStart / await は、実行時エラーになります。

yield で実行を譲り、delay で秒数を待つ

動かして確かめる

次はファイル全体です。Mana フォルダーの lesson.mn を置き換えて保存し、mana lesson.mn で実行してください。

native void delay(float seconds);

actor Guide
{
    action main()
    {
        print("Guide: Step 1.\n");
        yield();
        print("Guide: Step 2.\n");
        delay(0.5);
        print("Guide: Step 3.\n");
    }
}

期待する出力:

Guide: Step 1.
Guide: Step 2.
Guide: Step 3.

同梱の完成コードは mana examples/tutorial/10-yield.mn でも実行できます。

yield() は現在の Action を終了せず、実行をいったん VM に返します。再び実行機会が来ると、その続きから進みます。

出力だけでは間隔は見えません。yield() は「1秒待つ」という命令ではなく、「1ゲームフレーム待つ」とも限りません。VM をいつ進めるかは、ホストアプリケーション側の呼び出し方によります。

長い繰り返しでは、yield() を使って他の Actor に実行機会を渡せます。実時間やアニメーションの終了待ちは、ゲーム側の更新・完了条件と組み合わせて設計します。

delay(0.5) は、VM の時間で0.5秒待ってから続きを実行します。C++ 側の組み込み関数 Delay は、Mana には小文字の delay という名前で登録されています。使用するファイルには native void delay(float seconds); と宣言します。

CLI と引数なしの VM::Run() は単調時計で計測した経過時間を使います。組み込み先では VM::Run(deltaSeconds) に経過秒数を渡すことで、ポーズや倍速を制御できます。Run(0.0) では時間は進みませんが、実行可能な命令は処理します。再開は期限以降の最初の実行機会になるため、更新間隔による遅れは生じます。割り込み中も VM の時間は進み、待機期限は維持されます。

delay(0.0) は待機せず続行します。負数や非有限値は実行時エラーです。以前の整数のフレーム数を受け取る delay とは互換性がありません。

選び方を確かめる

次の用途に合うものを考えてください。

  • 会話の終了後に門を開く:await
  • 通知を依頼し、進行役はその完了を待たずに進む:request
  • 新しい行動を依頼せず、対象の Priority が指定値以下になるのを待つ:join
  • 自分の処理を終了せず、一度実行を譲る:yield
  • VM の時間で指定秒数だけ待つ:delay

境界条件や関連する制御は Requestと 実行制御で調べられます。

次に読む

複数のファイルに分けるで、完成したイベントを整理します。

プログラムを複数のファイルに分ける

最初に作ったイベントを、動きを保ったまま二つのファイルへ分けます。新しい機能を足す前に、進行役と町の Actor を別々に編集できるようにします。

二つのファイルを作る

Mana フォルダーに lesson フォルダーを作り、その中へ次の二つを保存してください。

lesson/
├─ main.mn
└─ town.mn

town.mn のファイル全体:

actor Guide
{
    action talk()
    {
        print("Guide: Welcome!\n");
    }
}

actor Gate
{
    action open()
    {
        print("Gate: Open.\n");
    }
}

main.mn のファイル全体:

import "town.mn";

actor Event
{
    action main()
    {
        await(10, Guide->talk());
        await(10, Gate->open());
        print("Event: Finished.\n");
    }
}

入口のファイルを実行する

Mana フォルダーのターミナルで実行します。

mana lesson/main.mn

期待する出力:

Guide: Welcome!
Gate: Open.
Event: Finished.

同梱の main.mnと town.mnも使えます。

mana examples/tutorial/11-files/main.mn

import はソースを一緒に読み込む

import "town.mn"; は、別のソースをコンパイル対象へ取り込みます。既定のファイル読み込みでは、相対パスの基準は import を書いたファイルのあるフォルダーです。

この例では main.mn と同じ場所の town.mn を探します。ターミナルから指定する lesson/main.mn の基準が作業フォルダーであることと区別してください。

flowchart LR
    A["main.mn"] --> C["コンパイラ"]
    B["town.mn"] --> C
    C --> D["一つの Program Image"]
    D --> E["Mana VM"]
Loading

ファイルごとに別々の VM が動くわけではありません。town.mn の Actor も、同じプログラムの一部になります。

一つ変えてみる

town.mn の案内役の文字を変更し、保存してから mana lesson/main.mn を再実行してください。入口のファイルを変更しなくても、読み込まれる側の変更が反映されます。

次に town.mn を village.mn へ名前変更するなら、main.mn の import も同じ名前へ変更する必要があります。

import と include

通常のソース分割では import から始めてください。同じ解決先のソースを一度だけ取り込み、共通定義の重複読み込みを防ぎます。include は指定するたびに読み込みます。

詳しい規則は ソースファイルリファレンスにあります。

次に読む

ファイルを分けても、名前は自動でグループ化されません。namespace で名前を整理するで、名前の衝突を避ける方法を学びます。

namespace で名前を整理する

町にも別の場所にも Guide がいるとき、名前を区別する必要があります。**namespace(名前空間)**は、名前をグループに分ける仕組みです。

前の章の二つのファイルを、次の内容に置き換えます。

町の Actor を Town にまとめる

lesson/town.mn のファイル全体:

namespace Town
{
    actor Guide
    {
        action talk()
        {
            print("Guide: Welcome!\n");
        }
    }

    actor Gate
    {
        action open()
        {
            print("Gate: Open.\n");
        }
    }
}

Guide の完全な名前は Town::Guide、Gate は Town::Gate になりました。:: は名前空間を含む名前の区切りです。

使う側で名前を指定する

lesson/main.mn のファイル全体:

import "town.mn";

using Town;

actor Event
{
    action main()
    {
        await(10, Guide->talk());
        await(10, Gate->open());
        print("Event: Finished.\n");
    }
}

using Town; を書くと、Town 内の名前を短い形で参照できます。この例の Guide は Town::Guide を指します。

実行する

Mana フォルダーから実行します。

mana lesson/main.mn

期待する出力:

Guide: Welcome!
Gate: Open.
Event: Finished.

同梱の main.mnと town.mnも使えます。

mana examples/tutorial/12-namespace/main.mn

一つ変えてみる

using Town; を削除し、main の二つの依頼を次に置き換えてください。

await(10, Town::Guide->talk());
await(10, Town::Gate->open());

同じ出力になれば、完全な名前で参照できています。

記号 たどるもの
:: 名前空間。例:Town::Guide
-> Actor が持つ Action。例:Town::Guide->talk()

別の名前空間にも Guide があり、複数の using によってどちらか分からなくなる場合は、完全な名前で指定します。

ファイルと名前空間は別の整理

town.mn というファイル名だけでは Town は作られません。逆に、一つの名前空間を複数ファイルへ分けることもできます。

まずは役割でファイルを分け、名前が衝突する、あるいは所属を明示したくなったときに名前空間を加えると扱いやすくなります。詳細は Namespace リファレンスを参照してください。

ここから自分のイベントへ

チュートリアルでは、文字を表示するところから始め、会話と門の順序、変数による記憶、条件、繰り返し、関数、割り込み、待機、ファイルと名前の整理を学びました。

復習するなら、この町の Guide に 会話回数を追加し、条件分岐で初回と2回目の会話を変えてみてください。進行役から会話を2回依頼し、それぞれの出力を確かめます。

目的に応じて次へ進んでください。

Clone this wiki locally