Before beginning this tutorial, you should be comfortable with the material from the basic tutorial. The logic is more complex here, and there are more technical details to remember, so there are a lot more opportunities to make mistakes. This is also where you'll need to start thinking like a computer.
While loops
If you want your script to run continuously, or if it should wait for something to happen, you need a loop. A loop is a piece of code that runs over and over again as long as a specified condition is met.
There are three types of loops in ZScript. The simplest of these is the while loop. The syntax is easy enough:
while(<condition>) { <body> }The condition determines whether the loop will run. The condition can be a variable, a function call, or any other expression that can be interpreted as a Boolean value. The body, the code in between the braces, is the code that will be run if the condition is met. Note that there's no semicolon after the while statement. More on that shortly.
When the game reaches the loop, it first evaluates the condition. If it's false, it skips right past the loop. If it's true, it runs the code in the loop's body. When it reaches the closing brace of the body, it evaluates the condition again. If it's still true, it repeats; otherwise, it moves on.
// This will move Link to the right as long as A is held. // This won't wait for A to be pressed; if the button isn't already // being held when the loop is reached, it won't run. while(Link->InputA) { Link->X += 1; Waitframe(); // In most cases, a while loop's body should include Waitframe() } // You can use the Boolean literals true and false, as well. Using while(true) // is very common, since many scripts are intended to run indefinitely. // This will disable the B button by unpressing it every frame. while(true) // The condition will always be true, so this loop will repeat forever { Link->InputB = false; Waitframe(); } // This won't do anything. while(false) // The condition will never be true, so the body won't be run at all { Link->Z = 32; Waitframe(); } // Once a loop's body starts executing, it'll go all the way to the end, // even if the condition becomes false partway through. while(Link->InputA) { Link->InputA = false; // The lines after this one will run Link->HP -= 1; Waitframe(); // Remember that Link->InputA will be reset the next frame, // so this loop may run repeatedly } // You can nest one loop inside another. // Refill Link's HP when A is held, repeating every time the button is pressed. // It would actually make more sense to use a conditional statement than an inner // loop in this case, but those won't be covered until the next section. while(true) { // If A isn't being held when the inner loop is reached, it will be skipped. // Since it's inside another loop, the game will check repeatedly. while(Link->InputA) { Link->HP += 1; Waitframe(); } Waitframe(); // In this case, each loop needs its own Waitframe() }What happens if you use a semicolon after the while statement?
while(Link->InputA); { Link->HP += 1; Waitframe(); } // That's legal, but it's equivalent to this: while(Link->InputA) { // Do nothing } Link->HP += 1; Waitframe();Obviously, that's not what you would intend. The functional lines aren't in the loop's body at all. Worse than that, though, is that the body is empty. An empty while loop is a very bad thing.
It's vital to be understand that a script runs from one Waitframe() to the next in a single frame. The game engine only does one thing at a time; once it starts running a script, it won't move on to something else until the script ends or it reaches Waitframe() or Waitdraw(). One implication of this is that an infinite loop with no Waitframe() will hang the game. An infinite loop isn't just something like while(true). while(Link->InputA) is also infinite in that situation, because the button stays pressed for the entire frame. If the game doesn't advance, it will never update the input variables, so the condition will never become false. The good news is that if you do accidentally do this, you'll be able to figure it out pretty quickly when you run the script.
That's it for now. Thinking in terms of loops and per-frame updates takes some getting used to, but it's vital for complex scripts.
Here are some sample scripts that incorporate while loops.
// This script will draw a line connecting Link and the FFC. ffc script ConnectingLine { void run() { while(true) { // The line will flash because a random color is selected each frame Screen->Line(0, this->X + 8, this->Y + 8, Link->X + 8, Link->Y + 8, Rand(16), 1, 0, 0, 0, 128); Waitframe(); } } }
// This script will move the FFC toward Link, aiming again every two seconds. import "std.zh" ffc script ReaimRepeatedly { void run() { while(true) // This will repeat indefinitely { // Aim toward Link this->Vx = (Link->X - this->X) / Distance(Link->X, Link->Y, this->X, this->Y); this->Vy = (Link->Y - this->Y) / Distance(Link->X, Link->Y, this->X, this->Y); // Wait two seconds, then the loop will repeat Waitframes(120); } } }
// This script will drain Link's MP as long as B is held. // (Again, nested loops aren't normally the way this would be done.) ffc script MPDrainOnB { void run() { while(true) { while(Link->InputB) { Link->MP -=1; Waitframe(); } Waitframe(); // Why are two Waitframes needed? If the inner loop didn't have a Waitframe call, // it would be a hanging infinite loop when B was pressed. If the outer loop didn't // have one, it would hang when B was not pressed. } } }And a couple of practice exercises.
- Write a script that keeps the FFC's position opposite Link's. If Link is, say, one tile down from the top-left corner, the FFC will be one tile up from the bottom-right. This only takes a few lines; just figure out the mathematical relationship between the two's positions.HintSpoiler
- Draw a circle around Link that follows him as he moves.Spoiler
Conditional statements
As you can probably guess from the name, a conditional statement, or if statement, allows you to run code conditionally. That is, it only runs if a certain requirement is met. In its basic form, the syntax is like that of a while loop:
if(<condition>) { <body> }Just like a while loop, the body will be run if the condition is true and skipped if it's false. The difference is that it will only run once.
There's no semicolon after the if statement.
// Move five pixels to the right if A is pressed if(Link->InputA) { this->X += 5; } // This refills Link's HP and MP if he has the White Sword. if(Link->Item[I_SWORD2]) { Link->HP = Link->MaxHP; Link->MP = Link->MaxMP; } // You'll often want to put a conditional inside a loop. // This refills half a heart each time A is pressed. while(true) { if(Link->PressA) { Link->HP += 8; } Waitframe(); } // Again, you can use true or false for the condition. Unlike with loops, there's generally // no reason to do this. You might do it temporarily for debugging, but it's pointless otherwise. // Increase Link's HP by 16. if(true) { Link->HP += 16; }An if statement can be extended with an else clause. This lets you run code when the condition is false.
if(<condition>) { <body 1> } else { <body 2> }Body 1 will run if the condition is true, and body 2 will run if it's false. Only one of these will run; if the condition is initially true, body 2 won't run even if body 1 makes the condition false.
The else clause is optional, but the if is not. You can't have an else without an if. You also can't have more than one else for a single if.
// Move Link one tile to the left if A is pressed, and one tile to the right if it's not if(Link->InputA) { Link->X -= 16; } else { Link->X += 16; }In addition to that, you can combine else and if into else if to handle multiple conditions.
if(<condition 1>) { <body 1> } else if(<condition 2>) { <body 2> } else if(<condition 3>) { <body 3> } else { <body 4> }If condition 1 is true, body 1 will run. If condition 1 is false and condition 2 is true, body 2 will run. If conditions 1 and 2 are false but 3 is true, body 3 will run. If all three conditions are false, body 4 will run. You can repeat this with as many conditions as you like.
These are mutually exclusive, and the earlier statements take priority. If all three conditions are true, only body 1 will be run. Think of else if as meaning "if every condition so far is false and this new condition is true..."
// Move Link one tile to the left if A is held, and one tile to the right is B is held but not A if(Link->InputA) { // If the player is holding both A and B, this is the block that will run Link->X -= 16; } else if(Link->InputB) { Link->X += 16; }Some sample scripts...
// This script will heal Link while the B button is held. ffc script BButtonHeal { void run() { while(true) { if(Link->InputB) { Link->HP +=1; } Waitframe(); } } }
// This script will reverse the left and right buttons. ffc script SwitchLeftAndRight { void run() { while(true) { // If you don't see why it's done this way, try writing it differently // and work out what happens when both left and right are pressed if(Link->InputLeft) { // If both buttons are pressed, nothing changes if(Link->InputRight) { // Do nothing } // If only left is pressed, unpress it and press right instead else { Link->InputRight = true; Link->InputLeft = false; } } // If only right is pressed, unpress it and press left instead else if(Link->InputRight) { Link->InputLeft = true; Link->InputRight = false; } Waitframe(); } } }Practice exercises:
- Write a script that makes the FFC move right if left is pressed and left if right is pressed.Spoiler
- Write a global script that refills Link's MP when both L and R are held.HintSpoiler
Edited by Saffith, 22 December 2014 - 03:43 PM.