-
-
Notifications
You must be signed in to change notification settings - Fork 4
Tutorial
🌐 English · 日本語
- Mana Tutorial
- Actor and Action
- Requesting an Action
- Talk, then open the gate
- Remembering state with variables
- Changing behaviour with conditions
- Repeating work
- Grouping work into functions
- Interrupting and resuming with Priority
- Choosing between waiting and synchronisation
- Splitting a program into several files
- Organising names with namespace
You build a small event in which a guide talks and a gate opens. You finish a first version in the third chapter, then learn about state, conditions, loops and synchronisation. At the end you organise the same event into several files and namespaces.
You start once you can run your first Mana program. No programming experience is assumed.
- Actor and Action
- Requesting an Action
- Talk, then open the gate
- Remembering state with variables
- Changing behaviour with conditions
- Repeating work
- Grouping work into functions
- Interrupting and resuming with Priority
- Choosing between waiting and synchronisation
- Splitting a program into several files
- Organising names with namespace
- Code marked "whole file" can be saved and run as it is. Replace the previous chapter's code with it rather than adding to it.
- Code marked "excerpt", "replacement" and so on goes into the place the lesson points to.
- Commands go into the terminal, and Mana code goes into the editor.
- Check each chapter's expected output before moving on to "Change one thing".
- Printed numbers and
yield()do not necessarily mean waiting a number of seconds. The lessons use the output to observe how the work flows.
You can also run the examples from the list of finished code. Each example is an independent program; they are not meant to be imported together.
To look up syntax, see the Language Reference. To get the ideas straight, see Mana concepts.
This page separates two ideas: defining a behaviour, and running it.
This is the whole file. Save it in the Mana folder as lesson.mn and run mana lesson.mn from the terminal you set up on the setup page. Replace the previous chapter's code with the whole file rather than adding to it.
actor Guide
{
action main()
{
print("Guide: Ready.\n");
}
action talk()
{
print("Guide: Welcome!\n");
}
}
Expected output:
Guide: Ready.
You can also run the finished code that comes with Mana from the Mana folder with this command.
mana examples/tutorial/02-actor.mn
Guide is one Actor with two Actions, main and talk. The VM asks for main to run at startup, but talk does not run just because it is defined.
flowchart TD
A["Guide : the guide"] --> B["main : runs at startup"]
A --> C["talk : talks"]
actor and action are words whose meaning Mana defines. Guide and talk are names the author chose. main has the special meaning of being used at startup.
One Actor can define several Actions. Split them by purpose: a gate might have open and close, and a guide talk and warn.
In the next chapter, the controller that runs the whole event also becomes an Actor. A single source file can define several Actors, each with its own work.
If several Actors have a main, each of them runs at startup. There is no mechanism that picks a single main for the whole file. To decide the order between Actors, use the requests and waiting you learn from the next chapter on.
Change the text in main to Guide: Waiting., then save and run. Changing the text in talk does not show up in the output yet.
Next, you make that talk run.
Continue with Requesting an Action.
The event controller Event asks the guide Guide to talk. The instruction for asking is request.
This is the whole file. Save it in the Mana folder as lesson.mn and run mana lesson.mn from the terminal you set up on the setup page. Replace the previous chapter's code with the whole file rather than adding to it.
actor Event
{
action main()
{
print("Event: Request.\n");
request(10, Guide->talk());
}
}
actor Guide
{
action talk()
{
print("Guide: Welcome!\n");
}
}
Expected output:
Event: Request.
Guide: Welcome!
You can also run the finished code that comes with Mana from the Mana folder with this command.
mana examples/tutorial/03-request.mn
Excerpt from the code above:
request(10, Guide->talk());
The values passed to an instruction inside the brackets are called arguments. Several arguments are separated by ,.
| Part | Meaning |
|---|---|
10 |
The Priority. A larger number is higher |
Guide->talk() |
The Action talk of Guide
|
-> |
Points from the Actor on the left to its Action on the right |
10 is neither a number of seconds nor a repeat count. These lessons use 10 to begin with, and interrupting by priority comes in a later chapter.
This example compiles even though Guide is defined after Event. The compiler looks up names across the whole program.
request sends a request to the other Actor and carries on with the caller's work without waiting for it to finish. Moving on to the next line does not prove that the other Action has finished.
Also, if the same Priority is already in use or reserved on an Actor, a new request at that Priority is not accepted. Writing two requests one after the other does not guarantee that the two jobs run in order. The details come in Priority.
To open the gate after the conversation ends, you need an instruction that waits for completion. The next chapter uses it.
Rename talk to greet. If you change both the definition action talk() and the target Guide->talk(), you get the same output.
If you change only one of them, the code refers to a name that does not exist. Read the compiler's diagnostics and make the names match.
Continue with Talk, then open the gate.
Here you finish your first event. The guide talks, the gate opens once the talking is over, and finally the event reports that it has finished.
There are no characters or gates on screen yet. You follow the event through the order of the output.
This is the whole file. Save it in the Mana folder as lesson.mn and run mana lesson.mn from the terminal you set up on the setup page. Replace the previous chapter's code with the whole file rather than adding to it.
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");
}
}
Expected output:
Guide: Welcome!
Gate: Open.
Event: Finished.
You can also run the finished code that comes with Mana from the Mana folder with this command.
mana examples/tutorial/04-event.mn
await(10, Guide->talk()); requests the conversation and waits for it to complete before moving on. As with request, you give a priority and an Action.
In this example nothing else sends requests to the same Actors and the Priority used is free, so the work completes in order.
sequenceDiagram
participant E as Event
participant G as Guide
participant D as Gate
E->>G: Request talk
G->>G: Print Welcome!
G-->>E: When done, Event resumes
E->>D: Request open
D->>D: Print Open.
D-->>E: When done, Event resumes
E->>E: Print Finished.
It is Event that waits. The whole Mana VM does not stop, so Guide and Gate, which it is waiting for, can get on with their work.
| What you want | Instruction to start with |
|---|---|
| Ask, and carry on without waiting for the other side to finish | request |
| Carry on after the requested work has finished | await |
In fact, await waits on a condition about the target Actor's Priority. If the request is not accepted, it carries on without waiting. When you bring in several requesters or interrupts, also check the conditions in Waiting and synchronisation.
Swap the two await lines in Event. Save and run, and the gate opens first, then the guide talks.
Once you have put them back, add another request for the conversation after the gate. The output becomes "talk → gate → talk → finished". The second request is made after the first conversation has finished, so the same Priority can be used again.
An Actor cannot use await on itself. Putting the work in a different Action does not make it a different Actor, and it fails with an error at run time.
What Gate->open() does is print text, so no real door is drawn or animated yet. When you connect this to a game, you link that part to processing on the C++ side. First, let's add counts and conditions to this event.
Continue with Remembering state with variables.
You talk to the guide twice and print how many times you have talked. A variable stores the value.
This is the whole file. Replace lesson.mn in the Mana folder with it, save, and run it with 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);
}
}
Expected output:
Talk count: 1
Talk count: 2
You can also run the finished code that comes with Mana with mana examples/tutorial/05-variables.mn.
int mTalkCount; declares a variable that holds an integer. int is the type, the kind of value it holds, and mTalkCount is the variable's name. The init Action sets its first value to 0.
mTalkCount = mTalkCount + 1; is an assignment that adds 1 to the current value and stores the result. Unlike an equation in maths, it computes the value on the right and puts it into the left. The first time it goes from 0 to 1, the second time from 1 to 2.
In print("Talk count: %d\n", mTalkCount);, %d marks where the integer passed after it is printed. \n is a line break.
Printing a variable shows how far the work has got and how the value has changed.
This variable is declared inside Guide and outside its Actions, so it is an Actor variable. The Actions belonging to Guide can refer to it directly, while other Actors cannot modify it directly.
Starting the name with m is a convention that makes it easy to tell it is an Actor member; the language does not require it.
The variable keeps its value after the conversation's Action ends. But if you quit the program and start it again, it starts from 0 again. Nothing has been saved to a file.
Because the state belongs to Guide, it stays encapsulated with the Actions that use it. For more details, see the variables reference.
A variable declared inside an Action is a local variable used by that work.
Example that replaces the body of talk:
int count = 0;
count = count + 1;
print("Talk count: %d\n", count);
Here each conversation starts from count = 0, so both times it prints 1.
The following are examples of declarations written inside an Action.
int count = 3;
float distance = 1.5;
bool hasKey = true;
string message = "Welcome!";
| Type | Value it stores |
|---|---|
int |
An integer |
float |
A number that can have a fractional part |
bool |
One of two values, true or false
|
string |
A string of text |
Go back to the finished code and add a line to Event that requests a third conversation. If it prints up to Talk count: 3, you have confirmed that the state is kept.
In Changing behaviour with conditions, you branch on a value.
You add a condition to the first event: open the gate only if you have the key.
This is the whole file. Save it in the Mana folder as lesson.mn and run mana lesson.mn from the terminal you set up on the setup page. Replace the previous chapter's code with the whole file rather than adding to it.
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");
}
}
Expected output:
Guide: Welcome!
Gate: Open.
You can also run the finished code that comes with Mana from the Mana folder with this command.
mana examples/tutorial/06-conditions.mn
bool hasKey = true; represents having the key. true is the value for "holds" and false for "does not hold".
if (hasKey) runs the first block only when that value holds. If it does not, the else block runs instead. Both never run.
flowchart TD
A["The conversation ends"] --> B{"Do you have the key?"}
B -->|true| C["Open the gate"]
B -->|false| D["Say to find the key"]
Change the initial value of hasKey to false, then save and run.
Guide: Welcome!
Event: Find the key.
Here you set whether you have the key yourself, in the code. Nothing is reading the player's actions or inventory automatically.
With the conversation count from the previous chapter, writing mTalkCount == 0 checks whether it is 0.
| Written as | Meaning |
|---|---|
a == b |
Equal |
a != b |
Not equal |
a < b / a <= b
|
Less than / less than or equal |
a > b / a >= b
|
Greater than / greater than or equal |
= assigns and == compares. Writing hasKey = true changes the value, so keep it apart from checking a condition.
Example inside an Action:
bool hasKey = true;
bool isOpen = false;
if (hasKey && !isOpen)
{
print("Ready to open.\n");
}
&& holds when both hold, || when at least one holds, and ! flips whether it holds. The condition above means "you have the key and it is not open yet".
Go back to the finished code of the previous chapter and replace the body of talk with the following.
if (mTalkCount == 0)
{
print("Guide: Welcome!\n");
}
else
{
print("Guide: Welcome back!\n");
}
mTalkCount = mTalkCount + 1;
The first time it says Welcome!, the second time Welcome back!. Notice that it compares first and increases the count afterwards.
Continue with Repeating work.
You print 3, 2, 1 and then open the gate. while repeats work of the same shape.
This is the whole file. Save it in the Mana folder as lesson.mn and run mana lesson.mn from the terminal you set up on the setup page. Replace the previous chapter's code with the whole file rather than adding to it.
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");
}
}
Expected output:
Remaining: 3
Remaining: 2
Remaining: 1
Gate: Open.
You can also run the finished code that comes with Mana from the Mana folder with this command.
mana examples/tutorial/07-loops.mn
while (remaining > 0) repeats the block as long as what remains is greater than 0. It subtracts 1 each time, so it eventually reaches 0 and leaves the loop.
| Value when the condition is checked | What happens |
|---|---|
| 3 | Print 3, change it to 2 |
| 2 | Print 2, change it to 1 |
| 1 | Print 1, change it to 0 |
| 0 | Skip the block and go on to opening the gate |
This is not a countdown in seconds. The loop itself has no way to wait for time to pass.
If you set the initial remaining to 5, it prints from 5 down to 1. If you set it to 0, it opens the gate without printing a single number. Predict the result before you run it.
Example that replaces the whole main of the finished code:
for (int i = 0; i < 3; i++)
{
print("Step: %d\n", i);
}
await(10, Gate->open());
Inside the brackets of for come "what to do first; the condition for continuing; what to do after each round". i++ increases the value by 1. Here it prints 0, 1 and 2, then opens the gate.
If you delete remaining = remaining - 1; from the finished code, the condition never changes and the loop never ends. This is called an infinite loop. You don't need to try it, but if it happens by mistake, press Ctrl+C in the terminal to stop it.
Also be careful with a loop that just keeps waiting, assuming another Actor will change a variable eventually. In Mana this involves handing other work a chance to run. You learn yield() in Waiting and synchronisation.
break ends a loop partway through, and continue skips the rest of the current round. do-while runs the work at least once before checking the condition.
For details, including the dedicated loop syntax, go to the statements reference. For now, being able to use while and for with an exit condition is enough.
Continue with Grouping work into functions.
You give a name to the calculation of how many more keys are needed, and use it by that name. Work that takes input and returns a result can be defined as a function.
This is the whole file. Save it in the Mana folder as lesson.mn and run mana lesson.mn from the terminal you set up on the setup page. Replace the previous chapter's code with the whole file rather than adding to it.
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);
}
}
Expected output:
Missing keys: 2
You can also run the finished code that comes with Mana from the Mana folder with this command.
mana examples/tutorial/08-functions.mn
Broken down, int remainingKeys(int required, int owned) means the following.
| Part | Meaning |
|---|---|
The first int
|
Returns an integer as its result |
remainingKeys |
The function's name |
int required |
Argument that receives the number needed |
int owned |
Argument that receives the number you have |
Calling remainingKeys(3, 1) passes 3 to required and 1 to owned. return gives back the result and ends that call. Here it returns 2, the result of 3 - 1, which goes into missing.
If you have at least as many keys as needed, it reaches return 0; first, so the subtraction below never runs.
Change the call to remainingKeys(3, 5). The output is Missing keys: 0.
If you add this function to the earlier event, you can use remainingKeys(3, 5) == 0 as the if condition and open the gate when you have enough keys.
A function does a calculation or similar inside the work that called it, and returns the result to the caller. An Action is something an Actor does, and it is what a Request targets.
| Purpose | How these lessons write it |
|---|---|
| Calculate how many keys are needed | remainingKeys(3, 1) |
| Ask the gate to open | await(10, Gate->open()) |
Example definition added outside the Actors:
void printSeparator()
{
print("-----\n");
}
void means it returns no result. Write printSeparator(); inside an Action to call it.
native functions, implemented on the C++ side, are covered in the integration guide. Declaring one in Mana does not create the matching processing in the game.
Continue with Interrupting and resuming with Priority.
A Priority says which Action comes first within the same Actor. The larger the number, the higher the priority. In this chapter the guide gives a warning in the middle of a conversation, then goes back to the rest of the conversation.
This is the whole file. Replace lesson.mn in the Mana folder with it, save, and run it with 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");
}
}
Expected output:
Guide: Talk begins.
Guide: Watch out!
Guide: Talk resumes.
Event: Finished.
You can also run the finished code that comes with Mana with mana examples/tutorial/09-priority.mn.
const int kNormalPriority = 10; declares a constant: a name for an integer that does not change. const says it does not change. Starting the name with k is a naming convention of these lessons.
Until now you wrote 10 directly. Now that there are several priorities, names tell apart 10 for normal work and 100 for emergencies.
self is the Actor that is running the current work. From Guide's talk, it requests the same Guide's warn at a higher Priority.
flowchart TD
A["talk / Priority 10 : the conversation starts"] --> B["Request warn / Priority 100"]
B --> C["talk is suspended and warn runs"]
C --> D["warn ends"]
D --> E["talk carries on where it stopped"]
This uses request. Waiting on your own Actor with awaitStart or await causes a runtime error.
This example is for watching an interrupt within one Actor. It does not mean that a Request to another Actor runs at an arbitrary moment, like an operating-system interrupt. How Actors are advanced is explained in the execution model.
| Request, compared with the target Actor's state | Basic handling |
|---|---|
| A higher Priority that is not in use | Runs ahead of the current Action |
| A lower Priority that is not in use | Kept until it can run |
| The same Priority as one in use or reserved | The new request is not accepted |
To be accepted, the target Action must also exist and the Actor must be accepting requests, among other things. For the full conditions, see the Request reference.
A 10 in use on one Actor and a 10 in use on another Actor are managed separately. There is no single numbered table that decides the order of execution for the whole program.
Change kEmergencyPriority in the finished code from 100 to 10. talk is already using 10, so warn is not accepted and the warning disappears from the output.
Guide: Talk begins.
Guide: Talk resumes.
Event: Finished.
Change it back to 100 once you have tried it. You do not need a different Priority for every Action. To complete work in order, reuse the same value, and use different levels only where one Action needs to interrupt another.
In Choosing between waiting and synchronisation, you look closely at the completion waits you have been using.
In Talk, then open the gate, you made an order with await. This chapter adds more ways to wait: until the work can start, for an Actor that is already running, and handing over your own turn for a moment.
Excerpt from the event's main:
await(10, Guide->talk());
await(10, Gate->open());
Provided the requests are accepted and no other requester competes, this opens the gate after the conversation ends.
What await actually checks is whether the target Actor's current Priority has fallen below the given value. It does not record a completion notice for each request and wait for it.
For the await instructions, the conditions below apply when the request is accepted.
| Instruction | Makes a new request? | When the caller can carry on |
|---|---|---|
request(p, Actor->action()) |
Yes | It does not wait |
awaitStart(p, Actor->action()) |
Yes | The target Actor's current Priority is p or lower |
await(p, Actor->action()) |
Yes | The target Actor's current Priority is lower than p
|
join(p, Actor) |
No | The target Actor's current Priority is p or lower |
p is a placeholder name for this explanation. In real code you give an integer such as 10, or a constant.
awaitStart is for waiting until the target has reached a Priority at which the requested Action can start. It does not guarantee that the first statement of that Action has already run. If you need the result of the other work, keep "it can start" and "it has finished" apart.
join(0, Guide); does not start a new conversation. It waits until Guide's current Priority is 0 or lower. main runs at Priority 0, so this condition does not mean that all of the Actor's work has finished either.
If the initial request is not accepted, awaitStart and await carry on without waiting. They do not keep requesting until the Priority is free.
For example, if Priority 10 is already in use on the target Actor, requesting a different Action at 10 does not guarantee that the Action runs. Don't treat returning from a wait on its own as proof that the behaviour succeeded.
The beginner's event avoids this conflict by having one controller make one request at a time, waiting for each to finish before asking for the next. In a design where several Actors send requests to the same Actor, also decide which requester uses which Priority.
Also, awaitStart / await targeting your own Actor causes a runtime error.
This is the whole file. Replace lesson.mn in the Mana folder with it, save, and run it with mana lesson.mn.
actor Guide
{
action main()
{
print("Guide: Step 1.\n");
yield();
print("Guide: Step 2.\n");
}
}
Expected output:
Guide: Step 1.
Guide: Step 2.
You can also run the finished code that comes with Mana with mana examples/tutorial/10-yield.mn.
yield() hands execution back to the VM for a moment without ending the current Action. When it gets another chance to run, it carries on from where it left off.
The output alone does not show the gap. yield() is not an instruction to "wait one second", and it does not necessarily mean "wait one game frame" either. When the VM advances depends on how the host application calls it.
In a long loop, you can use yield() to give other Actors a chance to run. Waiting for real time or for an animation to finish is designed together with the game's updates and completion conditions.
Think about which fits each purpose.
- Open the gate after the conversation ends:
await - Ask for a notification, and have the controller carry on without waiting for it:
request - Wait for the target's Priority to drop to a given value or lower, without asking for anything new:
join - Hand over your turn once without ending your own work:
yield
Edge cases and related controls are covered in Request and Execution control.
In Splitting a program into several files, you organise the finished event.
You split the first event you built into two files without changing what it does. Before adding new features, you make it possible to edit the controller and the town's Actors separately.
Create a lesson folder in the Mana folder and save the following two files in it.
lesson/
├─ main.mn
└─ town.mn
Whole file town.mn:
actor Guide
{
action talk()
{
print("Guide: Welcome!\n");
}
}
actor Gate
{
action open()
{
print("Gate: Open.\n");
}
}
Whole file main.mn:
import "town.mn";
actor Event
{
action main()
{
await(10, Guide->talk());
await(10, Gate->open());
print("Event: Finished.\n");
}
}
Run it from the terminal in the Mana folder.
mana lesson/main.mn
Expected output:
Guide: Welcome!
Gate: Open.
Event: Finished.
You can also use the main.mn and town.mn that come with Mana.
mana examples/tutorial/11-files/main.mn
import "town.mn"; brings another source into what is compiled. With the default file loading, a relative path is resolved from the folder of the file that contains the import.
In this example it looks for town.mn in the same place as main.mn. Keep this apart from lesson/main.mn given in the terminal, which is resolved from the working folder.
flowchart LR
A["main.mn"] --> C["Compiler"]
B["town.mn"] --> C
C --> D["One Program Image"]
D --> E["Mana VM"]
Each file does not get its own VM. The Actors in town.mn become part of the same program.
Change the guide's text in town.mn, save, and run mana lesson/main.mn again. The change in the imported file shows up without touching the entry file.
If you then rename town.mn to village.mn, you also need to change the import in main.mn to the same name.
For ordinary splitting of source, start with import. It brings in a source that resolves to the same file only once, which prevents shared definitions from being read twice. include reads the file every time it is written.
The detailed rules are in the source files reference.
Splitting into files does not group names automatically. In Organising names with namespace, you learn how to avoid names clashing.
When there is a Guide in the town and another somewhere else, you need to tell the names apart. A namespace is a way of putting names into groups.
Replace the two files from the previous chapter with the following.
Whole file lesson/town.mn:
namespace Town
{
actor Guide
{
action talk()
{
print("Guide: Welcome!\n");
}
}
actor Gate
{
action open()
{
print("Gate: Open.\n");
}
}
}
The full name of Guide is now Town::Guide, and Gate is Town::Gate. :: separates the parts of a name that includes a namespace.
Whole file lesson/main.mn:
import "town.mn";
using Town;
actor Event
{
action main()
{
await(10, Guide->talk());
await(10, Gate->open());
print("Event: Finished.\n");
}
}
With using Town;, you can refer to names inside Town in their short form. The Guide in this example means Town::Guide.
Run it from the Mana folder.
mana lesson/main.mn
Expected output:
Guide: Welcome!
Gate: Open.
Event: Finished.
You can also use the main.mn and town.mn that come with Mana.
mana examples/tutorial/12-namespace/main.mn
Delete using Town; and replace the two requests in main with the following.
await(10, Town::Guide->talk());
await(10, Town::Gate->open());
If you get the same output, you are referring to them by their full names.
| Symbol | What it follows |
|---|---|
:: |
A namespace. Example: Town::Guide
|
-> |
An Action of an Actor. Example: Town::Guide->talk()
|
If another namespace also has a Guide and several using declarations make it unclear which one is meant, give the full name.
The file name town.mn alone does not create Town. The other way round, one namespace can be split across several files.
It is easiest to split files by role first, then add namespaces when names clash or you want to make clear where something belongs. For details, see the Namespace reference.
In the tutorial you started by printing text, then learned the order of the conversation and the gate, remembering with variables, conditions, loops, functions, interrupts, waiting, and organising files and names.
For a review, add the conversation count to this town's Guide and use a condition to change what it says the first and second time. Have the controller request the conversation twice and check each output.
Where to go next depends on what you want to do.
- Understand how Actors advance: Mana concepts
- Look up syntax and rules: Language Reference
- Connect to a real game: integration guide
このマニュアルは shun126/Mana の documents/wiki/ から自動生成しています。Wiki を直接編集しても次の公開で上書きされるため、修正はリポジトリへの Pull Request でお願いします。
This manual is generated from documents/wiki/ in shun126/Mana. Edits made on the Wiki itself are overwritten on the next publish, so please send changes as pull requests to the repository.