SPHERE SCRIPTING TUTORIAL PART 2

Quests and Menus

  • Rhuan
  • 03/13/2011 04:45 AM
  • 6140 views
This continues on from part 1, please start there, some of the examples in this will expect you to have a font loaded in a variable called font (as in the previous part) and some will expect you to have the whole of the Talk function from the previous part available.

In this part two we look at how you go about having things change after events happen, the important scripting/programming concept of scope, and how to make a menu.

Quests and Conditions
What if Bob tells you to talk to Jeff, but then you want him to say something different when you've talked to Jeff? We need something to do the job of RPG Maker's switch system, there are many many ways we could do this the simplest I can think of is to imitate RPG Maker's switch system almost exactly with an array of booleans:
var switches = new Array();
switches[0] = false;
switches[1] = false;
switches[2] = false;//...

(Note that arrays start from entry 0 not entry 1) You could put this in your main script, then in Bob's OnActivateTalk you could add switches = true; and then in Jeff's you could have:
if (!switches[0])
{
  Talk("Jeff", "Go and talk to Bob.");
}
else
{
  Talk("Jeff", "So Bob has explained why you have no chance, are you going to give up now?");
}

I chose to do this in a way that may appear to be backwards to teach something else, namely that ! means not in this sort of context, so if switches is true, you get the else, whereas if it's false you get the first message.

You can develop this further, having Bob and Jeff send you back and forth using more and more entries in the switch array, once an array is created in sphere you can assign values to any entry in it up to almost any length you're likely to ever consider using. Note that you should define an entry before you use it, i.e. before you do:
if (switches[5])
{
  //something...
}

You should somewhere have switches = false; or switches = true;

Of course arrays don't have to just contain booleans. In various other scripting and programming languages an array can only feature lots of the same type of data, however in sphere it can feature many different types, so if you want to use the one array to handle all of your quests, but say you ahve a quest that involves two people sending you back and forth and you don't want to check a load of booleans, you could use a number instead, start it at 0 and add one to it each time you visit the next person you're meant to in the quest.

At this point I think I need to explain how to use conditions of various sorts. You can repeat if statements as follows:
if(a)
{

}
else if (b)
{

}
else if (c)
{

}
else
{

}

Also, there are various different forms of comparison you can do in an if:
if(a == 5) note the ==, a single = will result in a being set to 5 and the if always being true, whereas == compares a with 5
if (a != 5) != means not equal to, so if a is 4 or 2 or -1 or "fred" the if will be true
if (a == 7 && b == 23) && means "and", you can use it to have lots of conditions, you can string as many ands as you like along in one if, and the if will only be true if all of those conditions hold
if (a == 7 || b = 23) || means "or" so here the if will always be true as we have b = 23 instead of b == 23 :P, when you have multiple statements with || between them you need one or more of them to be true for the if to run

You may be thinking that having a complicated quest will involve far too many else if's, to the rescue cometh the switch and case statement:
switch (thing_to_check)
{
  case (0):
  {
    //action a
    break;
  }
  case (1):
  {
    //action b
    break;
  }
  case (2):
  {
    //action c
  }
  case (3):
  {
    //action d
    break;
  }
  default:
  {
    //something else
  }
}

Note firstly the colons on the end of each case statement these are completely necessary, and colons not semi-colons, secondly the break; statements, if you don't put a break at the end of a case it should drop down into the lower cases, currently case 2 should result in action c and action d but not the default as case 3 ends in a break. The basic idea is you provide a variable in the brackets at the start of the switch thing_to_compare you then write several cases, is the variable's value is equal to the value of a specific case the code for that case will run (and any further code until a break or the end of the switch), you can have as many cases as you like, the default is an optional you can put on the end to run if none of the others have.

Note that if a variable b is a number that b += 2 or any other number will add that number to b, you can use another variable b += a, which will not change a but will change the value of b to it's current value + a, note that b + a, can be used to provide information to something else (specifically the value of b + the value of a) but it will not change b or a. A short hand for adding one to a variable is ++ so: ++b; will increase b by 1, though for quest purposes you could just set it to the new value each time.

You should now be able to see how to make something that involves sending someone back and forth and back and forth, switch and case statements and some variable that you keep increasing. If you have a switches array it could be an entry in said array defined initially as 0 rather than as false.

Scope
This may seem like a lengthy discussion about something abstract, but it is important. Variable scopes are the bane of many beginning programers, and in fact of some more experienced ones. The basic idea is that when you define a variable, when you do:
var bob = 2;
That variable is given a scope, an area in which it is accessible. if you define a variable inside a function, it's scope will be the function and it will not be accessible outside of the function, a variable defined outside of any function, for instance at the top of a script, is called global it can be accessed and altered anywhere, including inside of any function, any alteration of it inside a function lasts when the function ends.

There are a couple of reason for not beginning your main script by defining every single variable you're intending to use, one is that variable names in the same scope have to be unique, so you may find that you need a very large number of names, the other is that it would mean having a lot of data that is only needed for short periods of time for specific purposes floating around all the time. If you create a variable inside a function it is deleted when the function ends, which makes things a lot cleaner, it also simply doesn't exist as far as other functions are concerned (unless those other functions are defined inside the first function but you shouldn't generally need to define functions inside functions), this means that you can have half the functions in your game all use bits of temporary information under the variable name temp that they each define, and even if one of these functions calls one of the other ones the temp of the first function will not get in the way of the temp that the second function uses.

There is an additional problem with keeping too much data loaded at a given time and that is running out of memory, sphere has a relatively (when compared with how much memory computers have) low limit on how much scripting memory is available and a small bit is used by each variable currently in existence.

Making a Menu
Generally people want their game to start with a menu screen of some sort. Normally with some equivalent of New Game, Load Game and Quit. Further people tend to want an in game menu as well for managing items and equipment and so on. So, how does one go about making a menu?

If we break it down a menu needs:
1. A list of of selectable options.
2. A way of storing which option is currently selected.
3. A method of changing which option is selected and confirming a choice.
4. It must continue until a selection has been confirmed.

For the first an Array is the obvious answer:
var options = new Array();
options[0] = "New Game";
options[1] = "Load Game";
options[2] = "Quit";

For the second a simple variable:
var selection = 0;

For the third, we will need to use the GetKey() function, and note that as well as waiting for a key press it can be used to ascertain which key was pressed:
var finished = false; //the menu is not finished as the choice is not yet made
switch(GetKey())
{
  case(KEY_UP):
  {
    --selection;//subtract one from the selection
    if(selection == -1)//compensate for going off the bottom of the list
    {
      selection = 2;
    }
    break;
  }
  case(KEY_DOWN):
  {
    ++selection;//add one
    if(selection == 3)//compensate for the top of the list
    {
      selection = 0;
    }
    break;
  }
  case(KEY_ENTER):
  {
    finished = true;//declare that the choice has been made
  }
}


For the fourth point above what we need is a loop, there are a few different types of loop one can use in sphere, for this job we will use a while loop:
while (some_condition)
{
  //do this over and over
}

The condition is handled the same way that a condition for an if is handled, the contents of the loop keeps running until a choice is made.

One thing I have not yet done is discuss drawing the menu. We could do this in an utterly simplistic fashion, merely using draw text and a highlight for the currently selected option, to save repetition, I will introduce another type of loop as I do this, the for loop:
for (declaration; condition; increment)
{
  //do stuff
}
The for loop runs it's contents until the condition is met the key difference is that it expects you to declare a variable in it's start and increment it on each run, so you use it to run a piece of code say 3 times:
for (var i = 0; i < 3; ++i)//declare i as 0, then run three times, once with i as 0 once with 1 once with 2
{
  if(selection == i)
  {
    font.setColorMask(CreateColor(0, 0, 255));//change the text colour for the selected option
  }
  else
  {
    font.setColorMask(CreateColor(255, 255, 255));//set the colour back to normal
  }
  font.drawText(5, 5 + 10 * i, options[i]);
}
Putting all this together we could get the following simplistic menu:
var options = new Array();
options[0] = "New Game";
options[1] = "Load Game";
options[2] = "Quit";

var selection = 0;
var finished = false; //the menu is not finished as the choice is not yet made
var i; //declare i so it isn't being re-created each time the menu's main loop runs through

while (!finished)//run as long as finished isn't true
{
  for (i = 0; i < 3; ++i)//set i to 0, then run three times, once with i as 0 once with 1 once with 2
  {
    if(selection == i)
    {
      font.setColorMask(CreateColor(0, 0, 255));//change the text colour for the selected option
    }
    else
    {
      font.setColorMask(CreateColor(255, 255, 255));//set the colour back to normal
    }
    font.drawText(5, 5 + 10 * i, options[i]);
  } 
  FlipScreen();
  switch(GetKey())
  {
    case(KEY_UP):
    {
      --selection; //subtract one from the selection
      if(selection == -1)//compensate for going off the bottom of the list
      {
        selection = 2;
      }
      break;
    }
    case(KEY_DOWN):
    {
      ++selection;//add one
      if(selection == 3)//compensate for the top of the list
      {
        selection = 0;
      }
      break;
    }
    case(KEY_ENTER):
    {
      finished = true; //declare that the choice has been made
    }
  }
}

Talk("Menu Speaking", "You chose " + options[selection]);

This will however look very plain, one thing you could do to make things look nicer would be to draw a box behind the window, just add a box.drawWindow with appropriate co-ordinates before the for loop. Another would be to make use of:
var  picture = LoadImage("name_of_some_image_file");//open a picture

picture.blit(0, 0); //draw the picture at 0, 0

If you load the picture before the menu's loop starts, and then blit/draw it before the for loop each time you can have a background picture for your menu.

But what about having a snazzier menu, you could use pictures instead of text, or you could have text drawn in a more interesting layout, as one example, this is a ring menu, note it uses the left and right arrows instead of up and down, also note that there is very little in this that I haven't shown you already:

var options = new Array();//set up the options
options[0]  = "New Game";
options[1]  = "Start Game";
options[2]  = "Quit";
  
var angle = new Array();//where on the ellipse should the option be drawn
angle[0]  = 0;
angle[1]  = 8;
angle[2]  = 16;
  
var target = new Array();//where is the option moving to
target[0]  = 0;
target[1]  = angle[1];
target[2]  = angle[2];
  
var colour = new Array();//highlight colours
colour[0]  = 150;
colour[1]  = 0;
colour[2]  = 0;
 
var selection = 0; //what option is selected
var finished  = false; //is the menu done?
var temp      = new Array(); //an array for temporary data
  
while (!finished)
{
  for (var i = 0; i < 3; ++i)//draw the options in a loop
  {
    temp[i] = (20 * Math.cos((angle[i]/12)*Math.PI));//use a bit of Trig to work out a coordinate
    font.setColorMask(CreateColor(255 - colour[i], 255 - colour[i], 255, 135 + 6 * temp[i]));//highlighting and fading
    font.drawText(100 + (60 * Math.sin((angle[i]/12)*Math.PI)), 100 + temp[i], options[i]);//draw an option
    if(angle[i] != target[i])//move options from where they are to where they're meant to be
    {
      if (temp[3])
      {
        angle[i] ++;
        if (angle[i] == 24)
        {
          angle[i] = 0;
        }
      }
      else
      {
        if (angle[i] == 0 && angle[i] != target[i])
        {
          angle[i] = 24;
        }
        angle[i] --;
      }
    }
  }
  FlipScreen();//display what we've drawn
  if (AreKeysLeft())//as this menu has movement it mustn't be stuck waiting for a keypress
  {
    switch(GetKey())//handle a key press
    {
      case(KEY_LEFT):
      {
        temp[0]   = target[0];//adjust where the options should move to
        target[0] = target[1];
        target[1] = target[2];
        target[2] = temp[0];
        colour[selection] = 0; //get rid of highlighting
        -- selection; //adjust the actual selection
        if (selection < 0)
        {
          selection = 2;
        }
        colour[selection] = 150; //bring in highlighting
        temp[3] = true; //say which way the menu should rotate
        break;
      }
      case(KEY_RIGHT): //all the same just in the opposite direction
      {
        temp[0]   = target[2];
        target[2] = target[1];
        target[1] = target[0];
        target[0] = temp[0];
        colour[selection] = 0;
        ++ selection;
        if  (selection > 2)
        {
          selection = 0;
        }
        colour[selection] = 150;
        temp[3] = false;
        break;
      }
      case(KEY_ENTER): //we're done...
      {
        finished = true;
      }
    }
  }
}
Talk("Menu Speaking", "You chose " + options[selection]);

In making a complete game it's usual to make a menu function that can be used for your various different menus, like we had a talk function for all the different talking, I will show something of how a menu function can be made in the next tutorial as I go on to discuss in game menus, stats and items.

Posts

Pages: 1
Creation
An avid lover of Heartache 101
1446
I won't be using Sphere but I really appreciate you spending some of your time to share this at RMN. Thank you!
author=Creation
I won't be using Sphere but I really appreciate you spending some of your time to share this at RMN. Thank you!


I on the other hand intend to, so this tutorial is all the more appreciated.
Cheers dude.
author=Maxximum
author=Creation
I won't be using Sphere but I really appreciate you spending some of your time to share this at RMN. Thank you!
I on the other hand intend to, so this tutorial is all the more appreciated.
Cheers dude.
Excellent.

Part 3 should be up tomorrow or maybe in about 5 hours times.
Further to my last comment I must apologise, part 3 is only half done, and I now have too much to do before I go away for a few days, I won't be able to work on it again till Wednesday. I'm very sorry if anyone was waiting on it with baited breath.
Before I ask I need to point out that I havent had time to read through all of the tutorials yet, so far I just glanced over them to get a rough idea of what to expect. So if your tutorial alrady anwsers my question just let me know its in there somewhere and Ill figure the rest out on my own :D

How much built in functionality does sphere have other than the resource editors. Im talking default systems (menus, text boxes, ect.) here. I get the feeling that besides built in functions that handle resource manipulation and "gluing" everything together, everything else is up the creator. I dont actually have a problem with that since, in the long run, it gives me a lot more freedom. If I am missing something though, it would be good to know, since messing with some default systems would probably smoothen the learning curve.
(I'm sititing in front of a computer for the last time before my trip away, so any further questions I won't be able to answer untill wednesday)

If you work through the two parts of the tutorial that I've so far written you'll see that I teach how to make simple text boxes and menus, as sphere does not have that sort of functionality built in.

It has functions for displaying text (either word-wrapped or in a line) it has a function for drawing a box, it has a function for displaying an image, it has a function for playing sound, it has a built in map engine, but yeah, it is largely a grounds up job, however when this tutorial is complete it will take you from nothing to making a battle system.

Sphere has the default javascript functions (minus the specificly web orientated stuff) and then on top of that it has the functions listed ina file called api.txt that comes with sphere: docs/development/api.txt if it's not in there and it isn't some innate feature of javascript sphere probably can't do it directly, if you know what you're doing though you can code almost anything.... (hence where the tutorial comes in).

(I'm tired hence writing too much...)
author=Rhuan
Sphere has the default javascript functions (minus the specificly web orientated stuff) and then on top of that it has the functions listed ina file called api.txt that comes with sphere: docs/development/api.txt if it's not in there and it isn't some innate feature of javascript sphere probably can't do it directly, if you know what you're doing though you can code almost anything.... (hence where the tutorial comes in).

Perfect, thats exactly the kind of info I was looking for.
Thanks!
I'm really sorry for having not got part 3 up yet, it's not finished yet, I've been giving most of my time to preparing for my university final exams and completing my final year project. Goos excuses though they may be I gave my word and I've broken it, so I'm sorry, and I will try and have part 3 up soon.
Pages: 1