Difference between revisions of "DaVince scripting tutorial"
(→Conditions: 'if') |
(Fixed links) |
||
(23 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
+ | {{LegacyWarning}} | ||
+ | |||
This tutorial teaches you how to script with JavaScript, and how to apply this knowledge so you can make games in Sphere. I have tried to write the tutorial in such a way that beginners can easily step into the world of Sphere. There are also some tests available which you can take to practice the things you have learnt. | This tutorial teaches you how to script with JavaScript, and how to apply this knowledge so you can make games in Sphere. I have tried to write the tutorial in such a way that beginners can easily step into the world of Sphere. There are also some tests available which you can take to practice the things you have learnt. | ||
Line 21: | Line 23: | ||
=== This tutorial === | === This tutorial === | ||
− | Ah, Sphere... An awesome and flexible RPG | + | Ah, Sphere... An awesome and flexible game and RPG creation program... It includes a fully-fledged editor to make your own RPGs (or other kinds of games!), and a flexible, extensive scripting language. But how do you start learning and using this scripting language, and how does it work inside Sphere? This tutorial will help you on your way. Some of the tutorial's chapters are based on other, older tutorials. Some are entirely original. |
− | Some of the tutorial's chapters are based on other, older | + | |
− | + | In any case, I hope it will be useful to you! Have a good Sphere experience! :) | |
− | Questions? Suggestions? Other feedback? | + | Questions? Suggestions? Other feedback? [https://docs.google.com/document/d/14xpwoyKuSvIXZzRNbsGXVoxtZriS01rj82KjyOndC9o/edit#bookmark=id.oajpni3fz38l Contact me]! |
--DaVince | --DaVince | ||
Line 34: | Line 36: | ||
=== Where can I find the latest version of Sphere? === | === Where can I find the latest version of Sphere? === | ||
− | Sphere and its editor can be downloaded | + | Sphere and its editor can be downloaded [https://drive.google.com/folderview?id=0B-5bW82jK5Z5X2pkellDenZUeWM&usp=sharing&tid=0B-5bW82jK5Z5UGZaS0c1OFJZZDA here] (versions 1.5 and 1.6 are recommended). |
− | |||
− | + | Some work has been going into a much newer implementation of Sphere lately, known as TurboSphere. It's pretty good, but as of this writing not complete yet, so stick with a standard release for this tutorial. | |
− | If | + | If some things don't work (correctly), download version 1.5 or 1.6 and these problems will usually disappear. If you already have the latest version and you still encounter problems, you found a bug in either Sphere or your own code! :) |
+ | === Get used to the editor first! === | ||
+ | This tutorial teaches you how to script, not how to use the editor. There are some articles on this wiki and with Sphere's included documentation that can help you out there. | ||
== Sphere and Javascript == | == Sphere and Javascript == | ||
Line 57: | Line 60: | ||
You will get to know more about scripts later; I'll talk about where you can use scripts first now. | You will get to know more about scripts later; I'll talk about where you can use scripts first now. | ||
− | === | + | === Where are scripts used? === |
You can use scripts in different places in Sphere. These are the maps, triggers, persons and files. | You can use scripts in different places in Sphere. These are the maps, triggers, persons and files. | ||
− | |||
− | ''' | + | * '''Files''' are the most common place for script code and can be created in any text editor. JavaScript files always have the .js file extension. ''Script files are the most important part of a game'' as these are the main method to give instructions to Sphere, including what to start (and where). |
− | ''' | + | * '''Maps''' have a few internal scripts. You can find these in the Map Properties (menu: Map > Properties). They are useful to have the game control what happens when the map itself is opened or closed, or when/where the player-controlled character is entering or leaving the map. Maps also contain two different types of "entities": triggers and persons. |
− | |||
− | * | + | * '''Triggers''' are attached to a specific tile and have a script that is launched when the player walks over it. |
− | |||
− | |||
− | |||
− | |||
− | ''' | + | * '''Persons''' are entities on the map that have a name and sprite. Persons have 5 personal scripts to control their actions (like walking or talking), each used for a different situation: |
+ | ** ''On Create'', used to run code when the person is created. | ||
+ | ** ''On Destroy'', runs right before the person is destroyed. | ||
+ | ** ''On Activate (Touch)'', runs when the person is being touched by the player-controlled person. | ||
+ | ** ''''On Activate (Talk)'', runs when the player-controlled person faces the person and presses the TALK button (default = space). | ||
+ | ** ''On Generate Commands'', runs every frame the map engine is open (but only when the person isn't doing anything else). | ||
− | |||
− | + | The scripts in persons, triggers and maps are more of a bonus that make things easier in these very specific situations. More experienced coders tend to avoid making scripts inside maps and entities as much as they can, and instead try to do it all in the JS files. All code then stays in one clearly-defined place; nothing is hidden in some random place in a map. It's more flexible and you're less likely to leave mistaked or old code behind. | |
+ | To make a new script file in the default editor, click on the empty page icon. You'll get a text editor. This is Sphere's built-in script editor, probably the best one for Sphere scripts. You can also make your scripts in your own favourite text editor, just save them as .js files in the scripts directory of your game. | ||
− | |||
== The basics of scripting == | == The basics of scripting == | ||
===Comments=== | ===Comments=== | ||
− | A comment is the most simple thing to do in Sphere. | + | A comment is the most simple thing to do in Sphere. With comments, you can give an explanation of something with normal, human-readable text. Sphere itself plain skips over this text, so you can put in anything you like. A comment is mainly intended to help the reader of the code: you can put comments next to your actual code explaining what a complex line does, or ''why'' it does it. |
+ | |||
+ | A single comment line can be made by putting // in front of the line. | ||
+ | |||
+ | <syntaxhighlight>//This is a comment.</syntaxhighlight> | ||
− | |||
− | |||
− | + | A comment block of multiple lines can be made by putting /* in front and */ behind it. | |
− | /* | + | <syntaxhighlight> |
+ | /* This is a comment. */ | ||
/* This is also a comment. | /* This is also a comment. | ||
Line 97: | Line 101: | ||
*/ | */ | ||
− | This | + | This part is NOT a comment, because it does not have // in front and is not in a /* */ block! |
− | + | // Buuuuuuuuut... The above line *is* a comment from ^^ this spot onward! Notice the green highlighting. | |
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | |||
A comment can be put on the same line as a line of code, too. This is useful for explaining just that line: | A comment can be put on the same line as a line of code, too. This is useful for explaining just that line: | ||
− | Code(); | + | <syntaxhighlight>Code(); //Comment!</syntaxhighlight> |
+ | |||
− | If you're | + | If you're just beginning, it's useful to put comments next to every line of code of importance. That way, you can look it up later and read what a certain piece of code did again... |
===End a line of code with a semicolon=== | ===End a line of code with a semicolon=== | ||
− | This is the second simplest thing in JS's code | + | This is the second simplest thing in JS's code syntax (syntax = code "grammar"). In real life, you have to add a period (.) at the end of every sentence. You basically do the same in JavaScript. Well, it's slightly different, as you type a semicolon ; instead. |
− | <syntaxhighlight>code_goes_here; | + | <syntaxhighlight> |
+ | code_goes_here; | ||
different_bit_of_code_goes_here; | different_bit_of_code_goes_here; | ||
− | Code; Next_bit_of_code;</syntaxhighlight> | + | Code; Next_bit_of_code; |
− | + | </syntaxhighlight> | |
− | |||
+ | Remember this! If you forget to put a semicolon somewhere, Sphere might try to 'glue' one bit of code together with the next one, which may cause errors because it tries to interpret things differently than you intended. Just like how sentences glued together can sometimes be interpreted in multiple ways. | ||
== The basics on functions == | == The basics on functions == | ||
===Functions=== | ===Functions=== | ||
+ | [[File:DaVinceScripting-Function.png|250px|thumb |Why functions are useful.]] | ||
+ | Simply put, a function is a bunch of lines of code that you give an identifiable name. Then you can run that code any time by referencing that name. | ||
− | + | Example: you could make a function with the name ''TextBox''. It would run code to make a box appear on screen, display some text on top, wait for user input, etc. Then you can show this text box whenever you like by referencing the function name TextBox wherever you like. | |
+ | |||
+ | It's very useful to make your code manageable, and it prevents you from doing bad things like copying the same few lines of code over and over again in different places. That's bad because if you change or improve your code, you now have to change it everywhere else, too. Using a function prevents this: the code exists only in that one place. Plus your code takes up less lines (just call function TextBox in a single line somewhere instead of pasting 15 lines of text-displaying code). | ||
===The structure of a function=== | ===The structure of a function=== | ||
− | + | A '''function''' always starts with the word <tt>function</tt> so both Sphere and the reader of the script know we're going to make a function. You call such a word a ''keyword'': JavaScript uses it internally to identify what you're trying to do. In this case, we want to define a function. | |
− | + | After the keyword <tt>function</tt> comes the ''name'' of the function. When giving it a name, keep in mind only letters, numbers and the underscore _ are allowed. It is also not possible to start the name with a number. | |
− | |||
− | |||
* Valid function names: <tt>FunctionName</tt>, <tt>do_300_things</tt>, <tt>EatDonut</tt>. | * Valid function names: <tt>FunctionName</tt>, <tt>do_300_things</tt>, <tt>EatDonut</tt>. | ||
Line 132: | Line 142: | ||
After the name come an opening parenthesis and a closing parenthesis (). This is intended so you can provide extra data to the function (I'll explain in detail later). | After the name come an opening parenthesis and a closing parenthesis (). This is intended so you can provide extra data to the function (I'll explain in detail later). | ||
+ | <br>After that, you put an opening brace { which indicates that the code coming after the brace will belong to your function. | ||
+ | <br>After the brace, you put the code that would be run when you "run" the function. | ||
+ | <br>When you're done with this code, you close the function definition with the closing brace }. | ||
− | |||
− | + | === Defining a function === | |
+ | Every Sphere game must have a main script file, containing the main function with the name <tt>game</tt>. Let's use it for our example and define the function game: | ||
− | |||
− | |||
− | |||
<syntaxhighlight> | <syntaxhighlight> | ||
function game() | function game() | ||
{ | { | ||
//code comes here. | //code comes here. | ||
− | }</syntaxhighlight> | + | } |
− | That's what a function looks like, basically. And | + | </syntaxhighlight> |
+ | |||
+ | |||
+ | That's what a function looks like, basically. And <tt>function game()</tt> in particular is a function that you *need* to define to give Sphere a place to start out at. | ||
+ | |||
+ | Still, if you try to run this code, nothing will happen. Sphere will open and then immediately close. That's because we didn't put in any code yet. (A comment isn't any kind of real code.) | ||
− | |||
+ | === Calling a function === | ||
Let's add one very simple command, with a comment in front of it: | Let's add one very simple command, with a comment in front of it: | ||
Line 154: | Line 169: | ||
function game() | function game() | ||
{ | { | ||
− | + | MapEngine("map.rmp", 60); //Function call: opens the map engine with map.rmp | |
− | MapEngine("map.rmp", 60); | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | The above code also demonstrates what ''calling'' a function looks like. MapEngine() | + | This piece of code WILL do something: open Sphere's map engine with the file map.rmp, with a framerate of 60 frames per second. Still, if you want this code to work, make sure that the file map.rmp actually exists inside the maps directory of your game! |
+ | |||
+ | The above code also demonstrates what ''calling'' a function looks like. In this case, Sphere internally has a <tt>function MapEngine()</tt>. We told Sphere to run that code here by doing this: | ||
+ | <syntaxhighlight> MapEngine("map.rmp", 60);</syntaxhighlight> | ||
+ | |||
+ | [[Legacy:MapEngine|MapEngine]]() is actually an internal Sphere function, but ''if'' it were coded in JS by a normal user it would look like this: | ||
<syntaxhighlight> | <syntaxhighlight> | ||
function MapEngine(map, framerate) //map, framerate will be explained in the next chapter. | function MapEngine(map, framerate) //map, framerate will be explained in the next chapter. | ||
Line 195: | Line 213: | ||
Check these pages for more details: | Check these pages for more details: | ||
− | * [[CreatePerson]](person_name, spriteset, destroy_with_map) | + | * [[Legacy:CreatePerson|CreatePerson]](person_name, spriteset, destroy_with_map) |
− | * [[AttachCamera]](person_name) | + | * [[Legacy:AttachCamera|AttachCamera]](person_name) |
− | * [[AttachInput]](person_name) | + | * [[Legacy:AttachInput|AttachInput]](person_name) |
− | * [[MapEngine]](mapfile, framerate) | + | * [[Legacy:MapEngine|MapEngine]](mapfile, framerate) |
===A list of common functions=== | ===A list of common functions=== | ||
− | There are a lot more common Sphere functions, another bunch explained below. You can look up all the other Sphere functions here: [[ | + | There are a lot more common Sphere functions, another bunch explained below. You can look up all the other Sphere functions here: [[Legacy:Functions]]. They're also in the doc_functions.txt file that comes with Sphere (it's in the docs directory). |
NOTE BEFORE YOU USE THESE: Some functions have a period . in them. I'll talk about what this means in detail later. For now, just put the appropriate function that loads a resource in front. Example LoadFont("file").drawText(1, 1, "text"); | NOTE BEFORE YOU USE THESE: Some functions have a period . in them. I'll talk about what this means in detail later. For now, just put the appropriate function that loads a resource in front. Example LoadFont("file").drawText(1, 1, "text"); | ||
Line 211: | Line 229: | ||
! align="left" | Description | ! align="left" | Description | ||
|- | |- | ||
− | | [[FlipScreen]]() || Put everything you have drawn so far on the screen. When drawing and blitting, your stuff doesn't get drawn on the screen, but in memory (in the "backbuffer"). It will be brought to the screen when you use FlipScreen(). After that the backbuffer is emptied. Use this after you've blitted ''absolutely everything you wanted'', and use it only once per frame! | + | | [[Legacy:FlipScreen]|FlipScreen]() || Put everything you have drawn so far on the screen. When drawing and blitting, your stuff doesn't get drawn on the screen, but in memory (in the "backbuffer"). It will be brought to the screen when you use FlipScreen(). After that the backbuffer is emptied. Use this after you've blitted ''absolutely everything you wanted'', and use it only once per frame! |
|- | |- | ||
− | | [[LoadFont]]("file.rfn") || Loads a font file. Only the Sphere font format .rfn is supported, so make a Sphere font first. | + | | [[Legacy:LoadFont|LoadFont]]("file.rfn") || Loads a font file. Only the Sphere font format .rfn is supported, so make a Sphere font first. |
|- | |- | ||
− | | [[Font.drawText]](x, y, "text") || Write text on the screen with the assigned Font and on the assigned x/y coordinates (in pixels). Font should be a variable containing the font you loaded with LoadFont. | + | | [[Legacy:Font.drawText|Font.drawText]](x, y, "text") || Write text on the screen with the assigned Font and on the assigned x/y coordinates (in pixels). Font should be a variable containing the font you loaded with LoadFont. |
|- | |- | ||
− | | [[Font.drawTextBox]](x, y, width, height, offset, "text") || Write text on the screen with the given font on the given x/y coordinates. This is a bit different from Font.drawText, because you define an area to write the text into. When the text hits the maximum given width, it continues writing the rest of the text on the next line until the maximum height is reached. The offset adds an extra bit to the X position of the first line of your text. | + | | [[Legacy:Font.drawTextBox|Font.drawTextBox]](x, y, width, height, offset, "text") || Write text on the screen with the given font on the given x/y coordinates. This is a bit different from Font.drawText, because you define an area to write the text into. When the text hits the maximum given width, it continues writing the rest of the text on the next line until the maximum height is reached. The offset adds an extra bit to the X position of the first line of your text. |
|- | |- | ||
− | | [[LoadWindowStyle]]("file.rws") || Load a windowstyle file into memory. | + | | [[Legacy:LoadWindowStyle|LoadWindowStyle]]("file.rws") || Load a windowstyle file into memory. |
|- | |- | ||
− | | [[WindowStyle.drawWindow]](x, y, width, height) || Draw the windowstyle on coordinates x,y with the given width and height. | + | | [[Legacy:WindowStyle.drawWindow|WindowStyle.drawWindow]](x, y, width, height) || Draw the windowstyle on coordinates x,y with the given width and height. |
|- | |- | ||
− | | [[LoadImage]]("file.png") || Load an image file (bmp, png, gif, jpg, pcx, tga supported... But png is recommended for its size/quality ratio and transparency!). | + | | [[Legacy:LoadImage|LoadImage]]("file.png") || Load an image file (bmp, png, gif, jpg, pcx, tga supported... But png is recommended for its size/quality ratio and transparency!). |
|- | |- | ||
− | | [[Image.blit]](x, y) || Draws Image at the given x,y coordinates. ("Blit" is an old English word for "draw".) | + | | [[Legacy:Image.blit|Image.blit]](x, y) || Draws Image at the given x,y coordinates. ("Blit" is an old English word for "draw".) |
|- | |- | ||
− | | [[Image.zoomBlit]](x, y, zoom) || Draws the image at the given scale on the given coordinates. When using a value like 1.5 the image will be drawn 1.5 its original size. | + | | [[Legacy:Image.zoomBlit|Image.zoomBlit]](x, y, zoom) || Draws the image at the given scale on the given coordinates. When using a value like 1.5 the image will be drawn 1.5 its original size. |
|- | |- | ||
− | | [[LoadSound]]("file.mp3") || Load a sound file into memory. WAV, OGG and MP3 work best. MOD, IT, MID are supported but might have issues depending on what version of Sphere you use. | + | | [[Legacy:LoadSound|LoadSound]]("file.mp3") || Load a sound file into memory. WAV, OGG and MP3 work best. MOD, IT, MID are supported but might have issues depending on what version of Sphere you use. |
|- | |- | ||
− | | [[Sound.play]](boolean) || Play the loaded sound. The boolean argument is true or false. true == repeat, false == do not repeat. | + | | [[Legacy:Sound.play|Sound.play]](boolean) || Play the loaded sound. The boolean argument is true or false. true == repeat, false == do not repeat. |
|- | |- | ||
− | | [[Sound.stop]]() || Stop the sound. | + | | [[Legacy:Sound.stop|Sound.stop]]() || Stop the sound. |
|- | |- | ||
− | | [[RenderMap]]() || If you have the map engine open and you want to draw something on screen, the map will not be shown when you [[FlipScreen]]() it. So you'll have to draw a picture of the map yourself with RenderMap(), first. All the stuff you draw after it will then be displayed on that image of the map. | + | | [[Legacy:RenderMap|RenderMap]]() || If you have the map engine open and you want to draw something on screen, the map will not be shown when you [[Legacy:FlipScreen|FlipScreen]]() it. So you'll have to draw a picture of the map yourself with RenderMap(), first. All the stuff you draw after it will then be displayed on that image of the map. |
|- | |- | ||
− | | [[GetKey]]() || Pause script execution until you press any key on the keyboard. Useful for a really quick "wait for input" so you can test if something renders properly, for example. Doesn't take arguments. | + | | [[Legacy:GetKey|GetKey]]() || Pause script execution until you press any key on the keyboard. Useful for a really quick "wait for input" so you can test if something renders properly, for example. Doesn't take arguments. |
|- | |- | ||
− | | [[ChangeMap]]("file.rmp") || If the map engine is open, you can switch maps with this function. Use [[MapEngine]]() if the map engine wasn't open yet. | + | | [[Legacy:ChangeMap|ChangeMap]]("file.rmp") || If the map engine is open, you can switch maps with this function. Use [[Legacy:MapEngine|MapEngine]]() if the map engine wasn't open yet. |
|- | |- | ||
− | | [[Exit]]() || Quit the game. | + | | [[Legacy:Exit|Exit]]() || Quit the game. |
|- | |- | ||
− | | [[Abort]]("message") || Quit the game with an error message you can define yourself. Useful for debugging (it can let you know why something goes wrong somewhere, for example). | + | | [[Legacy:Abort|Abort]]("message") || Quit the game with an error message you can define yourself. Useful for debugging (it can let you know why something goes wrong somewhere, for example). |
|} | |} | ||
Line 253: | Line 271: | ||
var some_name; | var some_name; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
This is how you make a variable. It does NOT contain any kind of value yet. It has the name <tt>some_name</tt>, which means I would type <tt>some_name</tt> every time I needed its contents. (You can't do that yet, though, as it doesn't have any right now.) | This is how you make a variable. It does NOT contain any kind of value yet. It has the name <tt>some_name</tt>, which means I would type <tt>some_name</tt> every time I needed its contents. (You can't do that yet, though, as it doesn't have any right now.) | ||
Line 264: | Line 283: | ||
music = LoadSound("SomeFile.mp3"); | music = LoadSound("SomeFile.mp3"); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
This code declares a variable called <tt>music</tt>. Then we give it a value. The value we just gave it is <tt>LoadSound("SomeFile.mp3")</tt>, which is a function that loads a sound file an puts it (as a value of sorts) inside the variable. Now you can refer to the variable's name, <tt>music</tt>, to to get to its contents. In this case, that is the sound data retrieved from <tt>LoadSound("SomeFile.mp3")</tt>. | This code declares a variable called <tt>music</tt>. Then we give it a value. The value we just gave it is <tt>LoadSound("SomeFile.mp3")</tt>, which is a function that loads a sound file an puts it (as a value of sorts) inside the variable. Now you can refer to the variable's name, <tt>music</tt>, to to get to its contents. In this case, that is the sound data retrieved from <tt>LoadSound("SomeFile.mp3")</tt>. | ||
Line 270: | Line 290: | ||
music.play(true); | music.play(true); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
What you see is <tt>music.play(true)</tt>, but Sphere sort of reads it as <tt>LoadSound("SomeFile.mp3").play(true)</tt> because music is a variable containing (the result of) LoadSound("SomeFile.mp3"). | What you see is <tt>music.play(true)</tt>, but Sphere sort of reads it as <tt>LoadSound("SomeFile.mp3").play(true)</tt> because music is a variable containing (the result of) LoadSound("SomeFile.mp3"). | ||
Line 285: | Line 306: | ||
//Wait, the music isn't stopping! That's because the file is loaded from file twice, and so recognized as two different bits of data in memory. | //Wait, the music isn't stopping! That's because the file is loaded from file twice, and so recognized as two different bits of data in memory. | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
With a variable: | With a variable: | ||
Line 303: | Line 325: | ||
var music = LoadSound("SomeFile.mp3"); | var music = LoadSound("SomeFile.mp3"); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
The variable is declared and immediately given a value. | The variable is declared and immediately given a value. | ||
Line 312: | Line 335: | ||
haha = LoadSound("haha.mp3"); | haha = LoadSound("haha.mp3"); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
As you can see you can also change the value in the variable any time you like. It IS called a variable, after all. Its contents can vary, even from one moment to the other. | As you can see you can also change the value in the variable any time you like. It IS called a variable, after all. Its contents can vary, even from one moment to the other. | ||
+ | |||
===Variables and data types=== | ===Variables and data types=== | ||
Line 323: | Line 348: | ||
var player_name = "Pete Peterson"; | var player_name = "Pete Peterson"; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
This is useful for many different things. For example, story progression: if an event has happened: change the value of the <tt>story</tt> variable so Sphere knows that the event has happened. Or in the case of the string, you could remember a character's name that was entered by the user. | This is useful for many different things. For example, story progression: if an event has happened: change the value of the <tt>story</tt> variable so Sphere knows that the event has happened. Or in the case of the string, you could remember a character's name that was entered by the user. | ||
There are many more types of data that you can store, and they will be explained in detail soon. | There are many more types of data that you can store, and they will be explained in detail soon. | ||
+ | |||
===Adding and subtracting to variables=== | ===Adding and subtracting to variables=== | ||
− | How to add and subtract values from variables containing numbers? | + | How to add and subtract values from variables containing numbers? Like this! |
{| class="wikitable" | {| class="wikitable" | ||
Line 354: | Line 381: | ||
{| class="wikitable" | {| class="wikitable" | ||
− | | <syntaxhighlight>variable | + | | <syntaxhighlight>variable *= value</syntaxhighlight> || multiplies the variable's value with the defined one and puts it in the variable. |
|- | |- | ||
− | | <syntaxhighlight>variable = variable | + | | <syntaxhighlight>variable = variable * value</syntaxhighlight> || does the same. |
|- | |- | ||
| <syntaxhighlight>variable /= value</syntaxhighlight> || divides the variable's value with the defined one and puts it in the variable. | | <syntaxhighlight>variable /= value</syntaxhighlight> || divides the variable's value with the defined one and puts it in the variable. | ||
Line 369: | Line 396: | ||
<syntaxhighlight>game();</syntaxhighlight> | <syntaxhighlight>game();</syntaxhighlight> | ||
+ | |||
+ | |||
So with this you'd run whatever is in function game(). This particular function doesn't need to be called manually, because game() is automatically called when Sphere starts your game. But it's a good example on how to create your own functions. | So with this you'd run whatever is in function game(). This particular function doesn't need to be called manually, because game() is automatically called when Sphere starts your game. But it's a good example on how to create your own functions. | ||
Making your own function is pretty simple. Just look at how you made the game() function... You can make your own functions in the same way. | Making your own function is pretty simple. Just look at how you made the game() function... You can make your own functions in the same way. | ||
+ | |||
<syntaxhighlight> | <syntaxhighlight> | ||
function Name() | function Name() | ||
Line 378: | Line 408: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | |||
You can put code in functions, this code will be executed as soon as the function is called from somewhere else. As an example we'll try to make a function that executes all commands necessary to make a box containing text. | You can put code in functions, this code will be executed as soon as the function is called from somewhere else. As an example we'll try to make a function that executes all commands necessary to make a box containing text. | ||
+ | |||
<syntaxhighlight> | <syntaxhighlight> | ||
//First we'll declare a few variables outside of any function. | //First we'll declare a few variables outside of any function. | ||
Line 387: | Line 420: | ||
var font = GetSystemFont(); | var font = GetSystemFont(); | ||
− | //Opens the | + | //Opens the standard Sphere system font and puts it in the 'font' variable. |
− | |||
//Here's our own function named 'TextBox'. | //Here's our own function named 'TextBox'. | ||
Line 399: | Line 431: | ||
GetKey(); | GetKey(); | ||
}</syntaxhighlight> | }</syntaxhighlight> | ||
+ | |||
If you insert TextBox(); in your game function, everything in the TextBox() function will be run once you start the game. | If you insert TextBox(); in your game function, everything in the TextBox() function will be run once you start the game. | ||
Line 410: | Line 443: | ||
− | ===Function arguments=== | + | === Function arguments === |
There is something lacking about our textbox: you can add TextBox(); to your code, but the end result will always stay the same. Every time you call TextBox() you will see the text "Some text" appear on the screen, no matter what. But what if we wanted to display something else, like "Hello", "Line 2" or "Status: moody"? It's not smart to make four different functions just for that, that would be a waste of memory. It would duplicate almost the exact same code four times, defeating the purpose of having a function! So instead, we slightly change the TextBox() function. | There is something lacking about our textbox: you can add TextBox(); to your code, but the end result will always stay the same. Every time you call TextBox() you will see the text "Some text" appear on the screen, no matter what. But what if we wanted to display something else, like "Hello", "Line 2" or "Status: moody"? It's not smart to make four different functions just for that, that would be a waste of memory. It would duplicate almost the exact same code four times, defeating the purpose of having a function! So instead, we slightly change the TextBox() function. | ||
+ | |||
Examine the following code: | Examine the following code: | ||
+ | |||
<syntaxhighlight> | <syntaxhighlight> | ||
function TextBox(text) | function TextBox(text) | ||
Line 422: | Line 457: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
Look at the spot where the function is declared (the first line). Inbetween the parentheses there's something new now. This is NOT a function or variable, but an argument. Arguments (also named parameters) can pass values (any kind: numbers, strings etc) on to your function, resulting in the output varying depending on what you "fed" the function. Now we can put all our different text in the function and get different results: | Look at the spot where the function is declared (the first line). Inbetween the parentheses there's something new now. This is NOT a function or variable, but an argument. Arguments (also named parameters) can pass values (any kind: numbers, strings etc) on to your function, resulting in the output varying depending on what you "fed" the function. Now we can put all our different text in the function and get different results: | ||
+ | |||
<syntaxhighlight> | <syntaxhighlight> | ||
TextBox("Hello"); | TextBox("Hello"); | ||
Line 429: | Line 466: | ||
TextBox("Status: dead"); | TextBox("Status: dead"); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
As you can see we put our text in-between double apostophes ", in-between the braces () of the function call. The " are there to show Sphere that it's a string of text. More on that in chapter 5. | As you can see we put our text in-between double apostophes ", in-between the braces () of the function call. The " are there to show Sphere that it's a string of text. More on that in chapter 5. | ||
Line 439: | Line 477: | ||
* The result is different every time because it's dependant on what you inserted! | * The result is different every time because it's dependant on what you inserted! | ||
And here it is in physical form: | And here it is in physical form: | ||
+ | |||
<syntaxhighlight> | <syntaxhighlight> | ||
function a(argument) | function a(argument) | ||
Line 453: | Line 492: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | ===Multiple arguments in one function=== | + | |
+ | |||
+ | === Multiple arguments in one function === | ||
If you want to have more than one argument, simply seperate the argument names with a comma: | If you want to have more than one argument, simply seperate the argument names with a comma: | ||
<syntaxhighlight> | <syntaxhighlight> | ||
Line 464: | Line 505: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | |||
The function call would look like this: | The function call would look like this: | ||
− | TextBox(10, 15, "hahaha!"); | + | <syntaxhighlight>TextBox(10, 15, "hahaha!");</syntaxhighlight> |
+ | |||
This will make the text "haha" appear on x position 10 and y position 15. | This will make the text "haha" appear on x position 10 and y position 15. | ||
− | |||
− | |||
== Test your newly gained skills! == | == Test your newly gained skills! == | ||
Line 790: | Line 832: | ||
* <tt>Declare variable story with value 0</tt> | * <tt>Declare variable story with value 0</tt> | ||
− | Self-explanatory. | + | Self-explanatory. (If not, read about declaring variables again.) |
+ | |||
* <tt>Check whether variable 'story' contains value 0</tt> | * <tt>Check whether variable 'story' contains value 0</tt> | ||
Line 823: | Line 866: | ||
The == operator (that's what you call it) is just one of a set of comparisons you can use in conditions. It compares whether one number is equal to the other. But more types of comparisons exist, like checking if a value is higher or lower than the other. Here they are: | The == operator (that's what you call it) is just one of a set of comparisons you can use in conditions. It compares whether one number is equal to the other. But more types of comparisons exist, like checking if a value is higher or lower than the other. Here they are: | ||
− | a > b Compares whether value a is higher than value b. | + | {| class="wikitable" |
− | a < b Compares whether value a is lower than value b. | + | | <syntaxhighlight>a > b</syntaxhighlight> || Compares whether value a is higher than value b. |
− | a >= b Compares whether value a is higher than or equal to value b. | + | |- |
− | a <= b Compares whether value a is lower than or equal to value b. | + | | <syntaxhighlight>a < b</syntaxhighlight> || Compares whether value a is lower than value b. |
− | a != b Compares whether value a is anything but value b. | + | |- |
− | a === b | + | | <syntaxhighlight>a >= b</syntaxhighlight> || Compares whether value a is higher than or equal to value b. |
− | Some weird values can evaluate to true together, even if they're not of the same type or even value. For example, when you compare to the value true, almost any value will | + | |- |
− | A triple-equals sign === was made to solve this problem: it will not only compare the values but also the types of the items compared. If you do this, only | + | | <syntaxhighlight>a <= b</syntaxhighlight> || Compares whether value a is lower than or equal to value b. |
− | + | |- | |
+ | | <syntaxhighlight>a != b</syntaxhighlight> || Compares whether value a is anything but value b. | ||
+ | |- | ||
+ | | <syntaxhighlight>a === b</syntaxhighlight> || Strict compare. Also compares if the type is the same. See below for explanation. | ||
+ | |} | ||
+ | Some weird values can evaluate to true together, even if they're not of the same type or even value. For example, when you compare to the value true, ''comparing it to almost any value'' will make the complete comparison evaluate to true. (2 == true), (true == "a string") and (-400.6 == true) will all evaluate to ''true'', simply because these values are not false in the machine code (false is 0, more or less; true is plain everything else). However, you probably wouldn't treat those values as "true" but compare them properly. | ||
+ | |||
+ | A triple-equals sign === was made to solve this problem: it will not only compare the values but also the types of the items compared. If you do this, these types need to match. "true" is a boolean, and a comparison will now only evaluate as "true" when it's compared to "true" (so <tt>true === true</tt>). <tt>(2 === true)</tt> and <tt>("blah" === true)</tt> evaluate to false. Only (true === true) will evaluate the condition as true. Only (2 === 2) will evaluate as true, (2 === "2") won't (because that other "2" is a string, not a number). | ||
+ | |||
+ | So... Why would you compare true to true? Well... It all has to do with the values in variables. You can do <tt>var storyfinished = true;</tt> (storyfinished === true) would entirely make sense. | ||
+ | |||
You use all of these in the same way you used the double equals sign. | You use all of these in the same way you used the double equals sign. | ||
+ | <syntaxhighlight> | ||
if (a > b) { TextBox("a was higher."); } | if (a > b) { TextBox("a was higher."); } | ||
if (a != b) { TextBox("a and b are different."); } | if (a != b) { TextBox("a and b are different."); } | ||
Line 839: | Line 893: | ||
if (a === b) { TextBox("a is the exact same object with the exact same value as b."); } | if (a === b) { TextBox("a is the exact same object with the exact same value as b."); } | ||
//etc. | //etc. | ||
− | Side note Notice how I put the condition, opening brace, code and closing brace all on one line? Yes, this is allowed. You can do the same with functions, or other blocks of code that look like them: | + | </syntaxhighlight> |
− | function Something() { TextBox("Hi."); /* I'm a nested comment. */ Abort("The game is quit now."); } | + | |
− | + | ''Side note'' | |
+ | |||
+ | Notice how I put the condition, opening brace, code and closing brace all on one line? Yes, this is allowed. You can do the same with functions, or other blocks of code that look like them: | ||
+ | <syntaxhighlight>function Something() { TextBox("Hi."); /* I'm a nested comment. */ Abort("The game is quit now."); }</syntaxhighlight> | ||
+ | In fact, this is mostly why the semicolon ; is used: to easily recognize when one command ends and the next one begins, even on the same line. | ||
+ | |||
+ | Use this sparingly, though, as things can kind of become a mess if you do this. Each bit of code on a new line is much easier to read, especially when you have { stuff with tabs going on. | ||
===Loops: 'while' and 'for'=== | ===Loops: 'while' and 'for'=== | ||
====while==== | ====while==== | ||
Loops in Sphere! These are very useful for things like repeating code. One advantage is that you don't have to have many lines containing the same code over and over again. Example: | Loops in Sphere! These are very useful for things like repeating code. One advantage is that you don't have to have many lines containing the same code over and over again. Example: | ||
+ | |||
+ | <syntaxhighlight> | ||
var value = 0; | var value = 0; | ||
value++; | value++; | ||
Line 856: | Line 918: | ||
TextBox(value); | TextBox(value); | ||
//etcetera etcetera.... | //etcetera etcetera.... | ||
+ | </syntaxhighlight> | ||
It takes up way too much code if you want to add 1 to value and show a textbox with that value 100 times in a row. That's where the loop comes in: a loop makes it neat and short because you just enter the two lines (value++ and TextBox(value)) in it once, and then run it a hundred times! The loop takes a condition to determine when it should still repeat the code running inside. If the condition is false, the loop ends. | It takes up way too much code if you want to add 1 to value and show a textbox with that value 100 times in a row. That's where the loop comes in: a loop makes it neat and short because you just enter the two lines (value++ and TextBox(value)) in it once, and then run it a hundred times! The loop takes a condition to determine when it should still repeat the code running inside. If the condition is false, the loop ends. | ||
− | There are more kinds of loops. The while loop is the easiest one to start with, because it looks just like an if block. | + | |
+ | There are more kinds of loops. The ''while'' loop is the easiest one to start with, because it looks just like an ''if'' block. | ||
+ | |||
+ | <syntaxhighlight> | ||
var value = 0; | var value = 0; | ||
Line 868: | Line 934: | ||
//Code continues here when the loop is done. | //Code continues here when the loop is done. | ||
− | In this case, the loop is | + | </syntaxhighlight> |
+ | |||
+ | A <tt>while</tt> loop is basically like an ''if-statement'': it checks whether the given condition returns <tt>true</tt>. The only difference is that it repeats the code inside the braces when this is the case. In this case, the loop can be interpreted as: | ||
+ | <pre> | ||
+ | While (the value of var 'value' is lower than 100): | ||
+ | Add 1 to var value. | ||
+ | Call function TextBox(), which does something with the value. | ||
+ | Do the comparison again and run the above two lines again if still true. | ||
+ | </pre> | ||
+ | |||
+ | Things keep looping while variable <tt>value</tt> is still lower than <tt>100</tt> (because it's <tt>true</tt> that <tt>(value < 100)</tt> here). | ||
+ | |||
+ | When <tt>value</tt> becomes <tt>100</tt> or higher, the comparison made in the condition (<tt>value < 100</tt>) becomes <tt>false</tt> and the loop stops executing. The engine continues business as usual. | ||
+ | |||
+ | * The loop begins running because when the loop starts, var <tt>value</tt> is still 0, which is lower than 100. If <tt>value</tt> were to be 100 already, the loop would never even begin. | ||
+ | * The loop ends because it reaches a point where (value < 100) becomes false. Sphere will then exit the loop. | ||
+ | |||
+ | |||
+ | '''So why are loops so useful?''' | ||
+ | |||
+ | If we had NOT used a loop for this piece of code, we would have wasted around 200 lines of code: 100 for value++ and another 100 for TextBox(value). It would be a never-ending mess! But with the power of loops we minimized this to 5 lines, where those two lines just loop 100 times. Much, much better. | ||
+ | |||
+ | Also useful if you don't know how many times you want something to run! | ||
+ | |||
+ | '''Practical example''' (note: vars and functions are all made up for the example) | ||
+ | <syntaxhighlight> | ||
+ | while (player_actions_done < max_player_actions) | ||
+ | { | ||
+ | //Do stuff like ask the player to perform an action here | ||
+ | player_actions_done++; | ||
+ | } | ||
+ | //And afterwards this player's turn ends or something | ||
+ | </syntaxhighlight> | ||
− | + | To close, a common term used with loops! A single time that we run the loop is called an ''iteration''. The example code with the TextBox() goes through 100 iterations. The first iteration is the one where value goes from 0 to 1 to then '1' in a self-made TextBox. The 88th iteration would make the value go from 87 to 88 and display '88'. The last (final) iteration is run when value is 99 - when it's 100 the block of code is no longer run. | |
− | |||
− | |||
− | |||
− | |||
====for==== | ====for==== | ||
Line 880: | Line 974: | ||
A for loop looks a bit different. It's because you do multiple things at once, still in the loop. A for loop usually looks like this: | A for loop looks a bit different. It's because you do multiple things at once, still in the loop. A for loop usually looks like this: | ||
+ | <syntaxhighlight> | ||
for (var value = 0; value < 100; value++) | for (var value = 0; value < 100; value++) | ||
+ | </syntaxhighlight> | ||
+ | |||
There are three things here, and semicolons inbetween them! That's a bit different from the usual... But, it's not as difficult to grasp as you might think, and it actually makes the loop much more powerful... | There are three things here, and semicolons inbetween them! That's a bit different from the usual... But, it's not as difficult to grasp as you might think, and it actually makes the loop much more powerful... | ||
− | for ( | + | <pre>for (prepare variables here; putcondition here; action to variable on each iteration here)</pre> |
− | In the first part of a for loop, you place your variable that will usually 'control' the loop. This can be a new variable, or one that was created globally somewhere else. You should also give it a starting value (if you don't, your variable will effectively be useless for use in the loop). In the second part you place the condition, just like you would do in an if or a while loop, only this time you usually involve your special variable. In the third part you put code that affects the variable so it can end the loop at some point. The third part of the statement is run after the code inside loop was run; eg. after an iteration has finished. | + | * In the first part of a for loop, you place your ''variable'' that will usually 'control' the loop. This can be a new variable, or one that was created globally somewhere else. You should also give it a starting value (if you don't, your variable will effectively be useless for use in the loop). |
− | + | * In the second part you place the ''condition'', just like you would do in an if or a while loop, only this time you usually involve your special variable. | |
+ | * In the third part you put ''code that affects the variable'' so it can end the loop at some point. The third part of the statement is run after the code inside loop was run; eg. after an iteration has finished. | ||
− | + | '''Examples''' | |
+ | <syntaxhighlight> | ||
for (var blah = 0; blah < 25; blah++) | for (var blah = 0; blah < 25; blah++) | ||
{ | { | ||
TextBox("This loop started " + blah + " times so far..."); | TextBox("This loop started " + blah + " times so far..."); | ||
} | } | ||
+ | </syntaxhighlight> | ||
+ | <syntaxhighlight> | ||
TextBox("Countdown for liftoff! Ready..."); | TextBox("Countdown for liftoff! Ready..."); | ||
for (var blah = 10; blah > 0; blah--) | for (var blah = 10; blah > 0; blah--) | ||
Line 898: | Line 999: | ||
} | } | ||
TextBox("ZERO! We have a liftoff!"); | TextBox("ZERO! We have a liftoff!"); | ||
− | + | </syntaxhighlight> | |
+ | This loop won't show a text box with "0..." because the loop ends right after blah is decreasing in value from 1 to 0. So we can put our own, neater "ZERO!" at the bottom instead. | ||
+ | |||
+ | It's probably easier to try it out for yourself to really see how it works! | ||
====Practical example of combining loops with arrays==== | ====Practical example of combining loops with arrays==== | ||
Now for a different use of the loop that also shows its practical use in combination with a collection of data (in this case, an array). We're going back to the last example of the previous chapter, and we're going to rewrite it to use a loop. | Now for a different use of the loop that also shows its practical use in combination with a collection of data (in this case, an array). We're going back to the last example of the previous chapter, and we're going to rewrite it to use a loop. | ||
+ | |||
Goal: to create persons on the map using their names identified in the array. | Goal: to create persons on the map using their names identified in the array. | ||
Original version: | Original version: | ||
+ | |||
+ | <syntaxhighlight> | ||
var people = ["DaVince", "Petey Pirate", "Aegis", "Flikky", "Nidoran"]; | var people = ["DaVince", "Petey Pirate", "Aegis", "Flikky", "Nidoran"]; | ||
Line 912: | Line 1,019: | ||
CreatePerson(people[3], people[3] + ".rss", true); | CreatePerson(people[3], people[3] + ".rss", true); | ||
CreatePerson(people[4], people[4] + ".rss", true); | CreatePerson(people[4], people[4] + ".rss", true); | ||
+ | </syntaxhighlight> | ||
+ | |||
New version: | New version: | ||
+ | <syntaxhighlight> | ||
var people = ["DaVince", "Petey Pirate", "Aegis", "Flikky", "Nidoran"]; | var people = ["DaVince", "Petey Pirate", "Aegis", "Flikky", "Nidoran"]; | ||
var len = people.length; //array.length can be used to get the length of an array. | var len = people.length; //array.length can be used to get the length of an array. | ||
Line 921: | Line 1,031: | ||
CreatePerson(people[i], people[i] + ".rss", true); //people[i] is people[0], then people[1], then people[2]... | CreatePerson(people[i], people[i] + ".rss", true); //people[i] is people[0], then people[1], then people[2]... | ||
} | } | ||
− | It might not seem too worthwhile to write a loop for this small bit of code, but consider how much extra work you would have to do if you had 14 people... And what if two of them disappeared later? The original version is inflexible and relies on you knowing exactly what's in the array; the flexible version has the computer take care of that. This greatly reduces the chance of getting error messages as your program won't read entries that no longer exist, for example. It also greatly expands the possibilities of what you can do with the language, and with your game. | + | </syntaxhighlight> |
+ | It might not seem too worthwhile to write a loop for this small bit of code, but consider how much extra work you would have to do if you had 14 people... And what if two of them disappeared later? Or if you gained five more people? The original version is inflexible and relies on you knowing exactly what's in the array; the flexible version has the computer take care of that. This greatly reduces the chance of getting error messages as your program won't read entries that no longer exist, for example. | ||
+ | |||
+ | It also greatly expands the possibilities of what you can do with the language, and with your game. Whether you have a party of one or a party of five, the game can handle it in any situation and you can display a character list menu properly, for example. | ||
====do...while==== | ====do...while==== | ||
Line 927: | Line 1,040: | ||
There is also a third type of loop, but it's basically the same as a while loop and not many people use it. If you still want to know more about it, check out this page. | There is also a third type of loop, but it's basically the same as a while loop and not many people use it. If you still want to know more about it, check out this page. | ||
− | == Test | + | == Test Yourself Again == |
− | + | Take a breather. Try to incorporate everything that you learned into a game. One day, I might assist you with this by adding a test for you to do here. | |
− | |||
− | |||
− | |||
== Advanced functionality in functions == | == Advanced functionality in functions == | ||
Line 942: | Line 1,052: | ||
The inefficient way is nesting a condition inside another one. | The inefficient way is nesting a condition inside another one. | ||
+ | <syntaxhighlight> | ||
if (value > 0) | if (value > 0) | ||
{ | { | ||
Line 949: | Line 1,060: | ||
} | } | ||
} | } | ||
+ | </syntaxhighlight> | ||
You're running two conditions, here. But you're also running two if clauses. This takes up a tiny bit more processing power. The computer gets a tiny bit more to do, because it has to do this: | You're running two conditions, here. But you're also running two if clauses. This takes up a tiny bit more processing power. The computer gets a tiny bit more to do, because it has to do this: | ||
Line 958: | Line 1,070: | ||
* And then another one, because we had two conditions. | * And then another one, because we had two conditions. | ||
− | This can surely be done more | + | '''But more importantly, you're nesting more than you have to.''' Can you imagine when you have eight things to check against? Your code would be on the right side of the screen, and you'd have to keep track of which accolades {} belong to which condition! This makes things very unreadable and confusing. |
+ | |||
+ | This can surely be done more in a better way! Yup, it sure can: | ||
====The efficient way==== | ====The efficient way==== | ||
+ | <syntaxhighlight> | ||
if (value > 0 && value < 10) | if (value > 0 && value < 10) | ||
{ | { | ||
TextBox("The value is somewhere in-between 0 and 10."); | TextBox("The value is somewhere in-between 0 and 10."); | ||
} | } | ||
+ | </syntaxhighlight> | ||
What Sphere does now: | What Sphere does now: | ||
Line 977: | Line 1,093: | ||
Next to the &&, which is called the AND operator, there is the OR operator. This special operator will evaluate the entire result as true if any of the given conditions is true, no matter if all others are false. This can be useful if you want some common code to run under only some circumstances: | Next to the &&, which is called the AND operator, there is the OR operator. This special operator will evaluate the entire result as true if any of the given conditions is true, no matter if all others are false. This can be useful if you want some common code to run under only some circumstances: | ||
+ | <syntaxhighlight> | ||
if (cheat_mode || backflip_mode || the_player_kicks_ass) //If even just one of these three equals true... | if (cheat_mode || backflip_mode || the_player_kicks_ass) //If even just one of these three equals true... | ||
{ | { | ||
DoABackflip(); | DoABackflip(); | ||
} | } | ||
+ | </syntaxhighlight> | ||
The player might do a backflip if any of the three variables given are true. Doesn't matter if only one of them is true, or all three... As long as something is true, it'll run the code inside the block. | The player might do a backflip if any of the three variables given are true. Doesn't matter if only one of them is true, or all three... As long as something is true, it'll run the code inside the block. | ||
Line 986: | Line 1,104: | ||
The two types of operators can be combined. You could check if all elements in a set of conditions is true, and then compare that to another conditions that might override it and run the condition anyway. It's best to group these together by using extra parentheses. Example: | The two types of operators can be combined. You could check if all elements in a set of conditions is true, and then compare that to another conditions that might override it and run the condition anyway. It's best to group these together by using extra parentheses. Example: | ||
+ | <syntaxhighlight> | ||
if ((master_password_entered && voice_recognition_success && confirmed_twice) || evil_intruder_override) | if ((master_password_entered && voice_recognition_success && confirmed_twice) || evil_intruder_override) | ||
{ | { | ||
SelfDestruct(ship); | SelfDestruct(ship); | ||
} | } | ||
+ | </syntaxhighlight> | ||
The ship will ONLY self-destruct if the master password was entered, the voice recognition succeeded AND the user confirmed the self-destruct sequence twice. Only then. Oh, or if there's an evil intruder who somehow overrode the system. | The ship will ONLY self-destruct if the master password was entered, the voice recognition succeeded AND the user confirmed the self-destruct sequence twice. Only then. Oh, or if there's an evil intruder who somehow overrode the system. | ||
Line 996: | Line 1,116: | ||
Note that it also works with loops. | Note that it also works with loops. | ||
− | while (!done && | + | <syntaxhighlight> |
+ | while (!done && willpower > 0) //When you're not done yet AND you still have willpower... | ||
{ | { | ||
DontGiveUp(); | DontGiveUp(); | ||
} | } | ||
+ | </syntaxhighlight> | ||
===Leaving out the braces=== | ===Leaving out the braces=== | ||
This is another exception to the syntax rules in JavaScript, and sometimes a rather handy one: you can choose to leave out the braces. However, you can do this in only ONE case: when there's only one statement to make. The if-statement immediately closes after you put that finishing semicolon after your only line of code that's going to be run in the condition: | This is another exception to the syntax rules in JavaScript, and sometimes a rather handy one: you can choose to leave out the braces. However, you can do this in only ONE case: when there's only one statement to make. The if-statement immediately closes after you put that finishing semicolon after your only line of code that's going to be run in the condition: | ||
+ | <syntaxhighlight> | ||
if (condition) | if (condition) | ||
Statement(); | Statement(); | ||
if (next_condition) | if (next_condition) | ||
OtherStatement(); | OtherStatement(); | ||
+ | </syntaxhighlight> | ||
Of course, you can still format this in any way you like. This can make code much more compact: | Of course, you can still format this in any way you like. This can make code much more compact: | ||
+ | <syntaxhighlight> | ||
if (story == 1) Statement(); | if (story == 1) Statement(); | ||
if (story == 2) SecondPart(); | if (story == 2) SecondPart(); | ||
if (story == 3) ThirdPart(); | if (story == 3) ThirdPart(); | ||
if (story >= 4) Abort("End of the game!"); | if (story >= 4) Abort("End of the game!"); | ||
+ | </syntaxhighlight> | ||
Another exception exists where you can use several statements if you like. However, this method is less compatible; a few JavaScript keywords like return and break will NOT work if you do this: | Another exception exists where you can use several statements if you like. However, this method is less compatible; a few JavaScript keywords like return and break will NOT work if you do this: | ||
+ | <syntaxhighlight> | ||
if (condition) | if (condition) | ||
Statement1(), //Use a comma, not a semicolon, and you can keep feeding the thing code until a semicolon ; is encountered! | Statement1(), //Use a comma, not a semicolon, and you can keep feeding the thing code until a semicolon ; is encountered! | ||
story++, | story++, | ||
Statement2(); //End of the code to run for this condition. | Statement2(); //End of the code to run for this condition. | ||
+ | </syntaxhighlight> | ||
Note that it also works with loops. | Note that it also works with loops. | ||
− | while ( | + | <syntaxhighlight> |
+ | while (willpower > 0) | ||
DontGiveUp(); | DontGiveUp(); | ||
for (var i = 0; i < 1000; i++) | for (var i = 0; i < 1000; i++) | ||
TextBox("The loop has run " + i + " times now. WILL IT EVER END? Yes, at 999."); | TextBox("The loop has run " + i + " times now. WILL IT EVER END? Yes, at 999."); | ||
+ | </syntaxhighlight> | ||
===The "else" keyword=== | ===The "else" keyword=== | ||
Line 1,039: | Line 1,169: | ||
There are two types of ways to use else: | There are two types of ways to use else: | ||
− | * else if - there's a different, related condition. For example, story is 2. | + | * <tt>else if</tt> - there's a different, related condition. For example, story is 2. |
− | * Just else - All of the previous related conditions have evaluated to false. For example, story is 6 and there's no else if that checks for it to be 6. | + | * Just <tt>else</tt> - All of the previous related conditions have evaluated to false. For example, story is 6 and there's no else if that checks for it to be 6. |
Basically, using just else is like defining code that will run in all other cases. else if can only exist right after an if or another else if. else can only exist once, and that is after all other related conditions. | Basically, using just else is like defining code that will run in all other cases. else if can only exist right after an if or another else if. else can only exist once, and that is after all other related conditions. | ||
+ | <syntaxhighlight> | ||
var story = (any value) | var story = (any value) | ||
Line 1,054: | Line 1,185: | ||
else //Story could be 5, or 389, or -300 for all we know | else //Story could be 5, or 389, or -300 for all we know | ||
Abort("That story section does not exist, fool!"); | Abort("That story section does not exist, fool!"); | ||
− | + | </syntaxhighlight> | |
== Objects == | == Objects == | ||
− | + | Note: this stuff is kind of complicated. I sometimes explain several concepts at the same time. I also apply a bunch of the earlier knowledge, so make sure you know and control the other stuff before you begin. | |
I've mentioned objects before, now we'll go into more detail about them. An object is one of the more complicated things in Sphere, but they will allow you to do very powerful things. | I've mentioned objects before, now we'll go into more detail about them. An object is one of the more complicated things in Sphere, but they will allow you to do very powerful things. | ||
+ | |||
===What is an object?=== | ===What is an object?=== | ||
Line 1,065: | Line 1,197: | ||
Objects use names instead of numbers for your data. These "names" are properties if you're storing data and methods if you're storing functions. | Objects use names instead of numbers for your data. These "names" are properties if you're storing data and methods if you're storing functions. | ||
Objects can have different instances. | Objects can have different instances. | ||
+ | |||
===What is an instance?=== | ===What is an instance?=== | ||
Objects are very useful for one specific thing - creating clones of itself that contain different data, and possibly different behavior. When you start making your own object, you start out by shaping the "original" object, which is called the prototype. From here on, your prototype will serve as the way to create actual, usable objects out of your prototype. These new, copied objects are what you would call an instance of that object. Instances let you have similar object - with different values. | Objects are very useful for one specific thing - creating clones of itself that contain different data, and possibly different behavior. When you start making your own object, you start out by shaping the "original" object, which is called the prototype. From here on, your prototype will serve as the way to create actual, usable objects out of your prototype. These new, copied objects are what you would call an instance of that object. Instances let you have similar object - with different values. | ||
+ | |||
===An example of different instances=== | ===An example of different instances=== | ||
The best example to start out with is with something familiar: let us look back at Sphere's own, internal object. When you use LoadImage(), you actually create a new instance of the internal Sphere Image object. For example: | The best example to start out with is with something familiar: let us look back at Sphere's own, internal object. When you use LoadImage(), you actually create a new instance of the internal Sphere Image object. For example: | ||
+ | <syntaxhighlight> | ||
var a = LoadImage("a.png"); | var a = LoadImage("a.png"); | ||
var b = LoadImage("b.png"); | var b = LoadImage("b.png"); | ||
+ | </syntaxhighlight> | ||
Both a and b are Image objects, but they contain different images - that is, different data. | Both a and b are Image objects, but they contain different images - that is, different data. | ||
+ | |||
===Making your own prototype=== | ===Making your own prototype=== | ||
Now, Sphere's internals use functions like LoadImage(), but this is not actually "true" object-oriented programming, and it is not how we will make our own object prototype. But in this first step, it will almost appear as if we are! | Now, Sphere's internals use functions like LoadImage(), but this is not actually "true" object-oriented programming, and it is not how we will make our own object prototype. But in this first step, it will almost appear as if we are! | ||
+ | <syntaxhighlight> | ||
function MyObject() | function MyObject() | ||
{ | { | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | |||
There you go, we just made an object prototype. What? It looks just like making a function? That's exactly right - all functions are actually objects, except we don't normally use them as such! Let's take it a step further and see what's so different about objects. | There you go, we just made an object prototype. What? It looks just like making a function? That's exactly right - all functions are actually objects, except we don't normally use them as such! Let's take it a step further and see what's so different about objects. | ||
+ | <syntaxhighlight> | ||
function MyObject() | function MyObject() | ||
{ | { | ||
this.myproperty = 4; | this.myproperty = 4; | ||
} | } | ||
+ | </syntaxhighlight> | ||
+ | |||
Objects have properties. Inside properties, you store values. It's just like using var, but instead we use this. The reason for this is so we can access the value from outside the function. Not only that, but we tell Sphere that this variable-type thing is a default value that the prototype will give to each instance, after which it can be changed individually per instance. You could access the property from the outside as follows: | Objects have properties. Inside properties, you store values. It's just like using var, but instead we use this. The reason for this is so we can access the value from outside the function. Not only that, but we tell Sphere that this variable-type thing is a default value that the prototype will give to each instance, after which it can be changed individually per instance. You could access the property from the outside as follows: | ||
− | MyObject.myproperty | + | <syntaxhighlight>MyObject.myproperty</syntaxhighlight> |
+ | |||
But take note! We are still making an object prototype here, so it's better to leave the prototype alone. Why? Because if you change the prototype's properties' values, new instances would take over that new value too (but old ones would still be in their original state with the old values). This can become a mess, so be careful with it. Usually you will want your prototype to be well-defined inside itself, and nowhere else. | But take note! We are still making an object prototype here, so it's better to leave the prototype alone. Why? Because if you change the prototype's properties' values, new instances would take over that new value too (but old ones would still be in their original state with the old values). This can become a mess, so be careful with it. Usually you will want your prototype to be well-defined inside itself, and nowhere else. | ||
− | Object prototypes can also have functions. These are copied just like values would be, which is why you could make two instances of the same type (that means, derived from the same prototype) have similarly named functions that do different thing. A function inside an object is called a method - usually because the object is made to do certain related things, and these subfunctions would be the method to manipulate its data. | + | Object prototypes can also have functions. These are copied just like values would be, which is why you could make two instances of the same type (that means, derived from the same prototype) have similarly named functions that do different thing. A function inside an object is called a '''method''' - usually because the object is made to do certain related things, and these subfunctions would be the method to manipulate its data. |
+ | |||
+ | <syntaxhighlight> | ||
function MyObject() | function MyObject() | ||
{ | { | ||
Line 1,107: | Line 1,252: | ||
} | } | ||
} | } | ||
+ | </syntaxhighlight> | ||
+ | |||
The above added method adds 1 to our property, so every time it's called, myproperty would increase by 1. | The above added method adds 1 to our property, so every time it's called, myproperty would increase by 1. | ||
− | + | ||
===Making an instance of the object=== | ===Making an instance of the object=== | ||
− | + | Also known as ''instantiating''. To do it, use the <tt>new</tt> keyword. I'll also demo how you can set values from the instance's properties. | |
+ | |||
+ | <syntaxhighlight> | ||
+ | var some_instance = new MyObject(); | ||
+ | Abort(some_instance.myproperty); //The engine quits, displaying a 4 | ||
+ | </syntaxhighlight> | ||
+ | It's useful because each instance can have different values from the other. '''This is one of the real strengths of objects.''' | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | var first = new MyObject(); | ||
+ | first.myproperty = 6; //Change the value of myproperty in just this instance | ||
+ | var second = new MyObject(); //New instance, its myproperty is that prototype value of 4 | ||
+ | |||
+ | Abort(first.myproperty + " and also " + second.myproperty); //The engine quits, displaying "6 and also 4" | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | ====Practical example==== | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | //Let's define a new object prototype for this example | ||
+ | function Character() | ||
+ | { | ||
+ | this.name = "default"; | ||
+ | this.hp = 100; | ||
+ | this.mp = 50; | ||
+ | this.equipment = []; | ||
+ | } | ||
+ | |||
+ | //Now let's make a few player characters | ||
+ | var players = []; | ||
+ | players[0] = new Character(); //New instance of Character | ||
+ | players[0].name = "Jon"; //This value is only changed for this instance | ||
+ | players[0].hp = 200; //Same deal | ||
+ | |||
+ | players[1] = new Character(); //Another instance of Character | ||
+ | players[1].name = "Jacques"; //This value is only changed for THIS instance, Jon with 200 hp is still Jon with 200 hp | ||
+ | players[1].hp = 40; //Same, etc. | ||
+ | players[1].mp = 9000; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | Now we have two player characters with very different stats, all thanks to an object prototype we made for managing players! | ||
+ | <br>Note that I made this object have some very generic properties, which has advantages. It's flexible, it can be used for enemy characters too: | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | //Now let's make a few player characters | ||
+ | var enemies = []; | ||
+ | enemies["mage"] = new Character(); | ||
+ | enemies["mage"].name = "Mage"; | ||
+ | enemies["mage"].mp = 2000; | ||
+ | enemies["mage"].equipment = ["wizard robe", "wizard hat"]; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | === Passing arguments to the prototype === | ||
+ | What I've just shown is powerful, but it's also very long. A bunch of lines just to change some character stats? Luckily, arguments can be used not in just functions, but also in prototypes! | ||
+ | |||
+ | Let's enhance our Character object with it to shorten the code above. | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | function Character(charname, initialhp, initialmp) | ||
+ | { | ||
+ | this.name = charname; | ||
+ | this.hp = initialhp; | ||
+ | this.mp = initialmp; | ||
+ | this.equipment = []; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | Now, instantiating becomes much shorter. | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | var players = []; | ||
+ | players[0] = new Character("Jon", 200, 50); //.name becomes Jon, .hp becomes 200, .mp becomes 50 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | Note that .equipment wasn't a part of the prototype's arguments, so it has to be treated like before. | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | players[0].equipment = ["cap", "T-shirt", "shorts"]; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | This is a good thing to do. During instantiation it's best to only put the things you'd most commonly change as arguments. So for a character, that would be the name and basic stats. Otherwise it becomes a long mess of arguments you don't even fill in half of the time. | ||
+ | |||
+ | |||
+ | === Using objects in combination with each other === | ||
+ | You could use many different objects to really start managing your data in a nice way. For example, let's add a data structure to store the equipment in. | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | function Equipment(name, type, description, hpeffect) | ||
+ | { | ||
+ | this.name = name; //The first this.name is this object's name property. The non-this name is the argument in function Equipment. | ||
+ | this.type = type; | ||
+ | this.description = description; | ||
+ | this.hpeffect = hpeffect; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | <syntaxhighlight> | ||
+ | var all_equipment_ever = []; //Some global variable somewhere maybe | ||
+ | all_equipment_ever["cap"] = new Equipment("Cap", "headwear", "This neat cap ups your HP by ten.", 10); | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | Now that we've invented some neat equipment, we can improve the Character object to make use of it. | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | function Character(charname, initialhp, initialmp) | ||
+ | { | ||
+ | this.name = charname; | ||
+ | this.hp = initialhp; | ||
+ | this.mp = initialmp; | ||
+ | this.equipment = []; | ||
+ | |||
+ | //Let's make a method to apply the stats of equipped equipment | ||
+ | this.equip = function(what) | ||
+ | { | ||
+ | var theitem = all_equipment_ever[what]; | ||
+ | //Ex. if the what argument is "cap", theitem would contain all_equipment_ever["cap"]. Which is that object instance we made earlier. | ||
+ | |||
+ | this.equipment.push(theitem); | ||
+ | //array.push() is a function in JS to append new array items at the end of an array. | ||
+ | //So now all_equipment_ever["cap"] is pushed at the end of the empty equipment array, making this.equipment = [all_equipment_ever["cap"]] | ||
+ | |||
+ | this.hp += theitem.hpeffect; //This would add the HP that the item is supposed to give you to this character's HP | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | Let's use the equip method in practice. It allows us to do this: | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | var players = []; | ||
+ | players[0] = new Character("Jon", 200, 50); | ||
+ | players[0].equip("cap"); | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | And boom. The instance of Character (players[0]) now has an instance of Equipment (all_equipment_ever["cap"]) stored in its .equipment property, adding 10 to his .hp thanks to that Equipment instance's .hpeffect. | ||
+ | <br>...Uh, let's reword that sentence by using the object's property names. | ||
+ | |||
+ | |||
+ | '''Jon is now wearing a cap. It causes his HP to go up by 10.''' | ||
+ | |||
+ | |||
+ | {| border="1" style="border-collapse:collapse;" | ||
+ | ! align="left" | Readable line | ||
+ | ! align="left" | Complicated line | ||
+ | ! align="left" | Associated code | ||
+ | |- | ||
+ | | '''Jon''' || Instance of Character || <syntaxhighlight>players[0]</syntaxhighlight> | ||
+ | |- | ||
+ | | '''is now wearing a cap.''' || now has an instance of Equipment stored in its .equipment property || <syntaxhighlight>.equipment = [all_equipment_ever["cap"]]</syntaxhighlight> | ||
+ | |- | ||
+ | | '''It causes his HP to go up by 10.''' || adding 10 to his .hp thanks to that Equipment instance's .hpeffect || <syntaxhighlight>this.hp += theitem.hpeffect;</syntaxhighlight> | ||
+ | |} | ||
+ | I hope this clarifies it all. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ==== Unequipping, to finish things off ==== | |
+ | Note that of course we'd need an unequip() method which removes the equipment from the Character's equipment array and also removed those added stats again. Here it is, as a bonus: | ||
+ | <syntaxhighlight> | ||
+ | this.unequip = function(what) | ||
+ | { | ||
+ | var theitem = all_equipment_ever[what]; //Get the item | ||
+ | var itemindex = this.equipment.indexOf(theitem); //Get the numerical position of the item in the array | ||
+ | this.equipment.splice(itemindex, 1); //Remove 1 item at this position from the array (so that would be our item) | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | See [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Methods_of_Array_instances Methods of Array instances] for info on indexOf() and splice(), and what their arguments mean. | ||
+ | |||
+ | ==Tutorial TODO list== | ||
+ | * Try..catch | ||
+ | * Tests | ||
+ | * Create some clarifying pics if necessary | ||
[[Category:Tutorials]] | [[Category:Tutorials]] | ||
+ | [[Category:Sphere 1]] |
Latest revision as of 15:24, 4 August 2017
Warning: Legacy code ahead
This page has been written for the legacy Sphere 1.x API. While this API is supported in miniSphere, it is generally recommended to use the new Sphere 2 API.
You can mix old and new API functions, but please be aware that Sphere 2 objects are different from Sphere 1 objects, so they cannot be used interchangeably.
This tutorial teaches you how to script with JavaScript, and how to apply this knowledge so you can make games in Sphere. I have tried to write the tutorial in such a way that beginners can easily step into the world of Sphere. There are also some tests available which you can take to practice the things you have learnt.
Resource pack
Here's a very basic resource pack so you can get started right away. Includes:
- a simple map (rmp file)
- a sprite (rss file)
- a font (rfn file)
- some sounds
File names are in Dutch, but it should be clear what's what.
There are more resources on the Spherical Downloads Repository (coming soon).
Contents
- 1 Warning: Legacy code ahead
- 2 Resource pack
- 3 About
- 4 Sphere and Javascript
- 5 The basics of scripting
- 6 The basics on functions
- 7 Variables and more on functions
- 8 Test your newly gained skills!
- 9 More on variables
- 10 Conditions and loops
- 11 Test Yourself Again
- 12 Advanced functionality in functions
- 13 Objects
- 14 Tutorial TODO list
About
This tutorial
Ah, Sphere... An awesome and flexible game and RPG creation program... It includes a fully-fledged editor to make your own RPGs (or other kinds of games!), and a flexible, extensive scripting language. But how do you start learning and using this scripting language, and how does it work inside Sphere? This tutorial will help you on your way. Some of the tutorial's chapters are based on other, older tutorials. Some are entirely original.
In any case, I hope it will be useful to you! Have a good Sphere experience! :)
Questions? Suggestions? Other feedback? Contact me! --DaVince
Sphere
Sphere is a program designed to create RPGs in the style of early Final Fantasy games without too much effort. However, you'll be able to create almost any other kind of 2D game you can imagine if you're skilled enough. It uses a scripting language (JavaScript, which is NOT JAVA!) that "makes everything happen". This, of course, means that you will have to learn this scripting language in order to be able to create a game in Sphere. Scripting isn't always easy, especially for programming/scripting newbies, so I'll try to keep things simple and clear.
Where can I find the latest version of Sphere?
Sphere and its editor can be downloaded here (versions 1.5 and 1.6 are recommended).
Some work has been going into a much newer implementation of Sphere lately, known as TurboSphere. It's pretty good, but as of this writing not complete yet, so stick with a standard release for this tutorial.
If some things don't work (correctly), download version 1.5 or 1.6 and these problems will usually disappear. If you already have the latest version and you still encounter problems, you found a bug in either Sphere or your own code! :)
Get used to the editor first!
This tutorial teaches you how to script, not how to use the editor. There are some articles on this wiki and with Sphere's included documentation that can help you out there.
Sphere and Javascript
Sphere uses SpiderMonkey, a special library that makes JavaScript available in applications like Sphere. Sphere itself then adds lots of functions to this implementation of JavaScript so you can actually use it to create a game. JavaScript is a browser scripting language in origin, specially designed for the use in webpages, but as Sphere proves it can be used for other things perfectly fine too. Web JS consists of three parts: the Core, the Client and the Server. Sphere only uses the Core, the other two are actually parts that belong to Web JS.
Configuration and information on scripts
Every Sphere game must have a script where it can start out "reading" (actually interpreting) what to do. The start script to use can be defined in the Game Setings window, accessible by double-clicking on "Game Settings" in a project's main window. You can't define a script if you don't have a script yet, of course, so make one first. Give this first script a good, identifiable name, like start.js or main.js. The script doesn't need to have a specific name like main.js - you can give it any valid name you can think of, "valid" meaning using only characters you can use in a filename. However, it's recommended to keep the name simple and preferably without any special characters (even spaces), so you can quickly and easily reference this script in your code. The start script will be the first script that's read by Sphere's game engine when you start the game. If you want to let Sphere open other scripts next to this one, you can use specific functions in the main script to include these scripts (by using RequireScript()).
You do not need to have more than one script, you could just as well put all of the game's code in the one single start script. But using multiple script files makes everything more clear and easier to find and use. Think about it: seperate scripts for battles, menus, text boxes, storyline... It also allows you to use specific scripts in multiple games, if you make sure to make everything in that script work independently from other scripts... Very nice if you made a nice menu or item system that you want to re-use in a different game. Sphere looks for the function game() inside your startup script when the game is launched. This is the only automatically started (rather, "called") function and gives you a point to start with. All other functions will have to be called by hand. More about functions further down in this document.
You will get to know more about scripts later; I'll talk about where you can use scripts first now.
Where are scripts used?
You can use scripts in different places in Sphere. These are the maps, triggers, persons and files.
- Files are the most common place for script code and can be created in any text editor. JavaScript files always have the .js file extension. Script files are the most important part of a game as these are the main method to give instructions to Sphere, including what to start (and where).
- Maps have a few internal scripts. You can find these in the Map Properties (menu: Map > Properties). They are useful to have the game control what happens when the map itself is opened or closed, or when/where the player-controlled character is entering or leaving the map. Maps also contain two different types of "entities": triggers and persons.
- Triggers are attached to a specific tile and have a script that is launched when the player walks over it.
- Persons are entities on the map that have a name and sprite. Persons have 5 personal scripts to control their actions (like walking or talking), each used for a different situation:
- On Create, used to run code when the person is created.
- On Destroy, runs right before the person is destroyed.
- On Activate (Touch), runs when the person is being touched by the player-controlled person.
- ''On Activate (Talk), runs when the player-controlled person faces the person and presses the TALK button (default = space).
- On Generate Commands, runs every frame the map engine is open (but only when the person isn't doing anything else).
The scripts in persons, triggers and maps are more of a bonus that make things easier in these very specific situations. More experienced coders tend to avoid making scripts inside maps and entities as much as they can, and instead try to do it all in the JS files. All code then stays in one clearly-defined place; nothing is hidden in some random place in a map. It's more flexible and you're less likely to leave mistaked or old code behind.
To make a new script file in the default editor, click on the empty page icon. You'll get a text editor. This is Sphere's built-in script editor, probably the best one for Sphere scripts. You can also make your scripts in your own favourite text editor, just save them as .js files in the scripts directory of your game.
The basics of scripting
Comments
A comment is the most simple thing to do in Sphere. With comments, you can give an explanation of something with normal, human-readable text. Sphere itself plain skips over this text, so you can put in anything you like. A comment is mainly intended to help the reader of the code: you can put comments next to your actual code explaining what a complex line does, or why it does it.
A single comment line can be made by putting // in front of the line.
//This is a comment.
A comment block of multiple lines can be made by putting /* in front and */ behind it.
/* This is a comment. */
/* This is also a comment.
But, it stays comment on the next line, too.
And it will keep on doing that until the blocks gets closed with the asterisk and the slash.
*/
This part is NOT a comment, because it does not have // in front and is not in a /* */ block!
// Buuuuuuuuut... The above line *is* a comment from ^^ this spot onward! Notice the green highlighting.
A comment can be put on the same line as a line of code, too. This is useful for explaining just that line:
Code(); //Comment!
If you're just beginning, it's useful to put comments next to every line of code of importance. That way, you can look it up later and read what a certain piece of code did again...
End a line of code with a semicolon
This is the second simplest thing in JS's code syntax (syntax = code "grammar"). In real life, you have to add a period (.) at the end of every sentence. You basically do the same in JavaScript. Well, it's slightly different, as you type a semicolon ; instead.
code_goes_here;
different_bit_of_code_goes_here;
Code; Next_bit_of_code;
Remember this! If you forget to put a semicolon somewhere, Sphere might try to 'glue' one bit of code together with the next one, which may cause errors because it tries to interpret things differently than you intended. Just like how sentences glued together can sometimes be interpreted in multiple ways.
The basics on functions
Functions
Simply put, a function is a bunch of lines of code that you give an identifiable name. Then you can run that code any time by referencing that name.
Example: you could make a function with the name TextBox. It would run code to make a box appear on screen, display some text on top, wait for user input, etc. Then you can show this text box whenever you like by referencing the function name TextBox wherever you like.
It's very useful to make your code manageable, and it prevents you from doing bad things like copying the same few lines of code over and over again in different places. That's bad because if you change or improve your code, you now have to change it everywhere else, too. Using a function prevents this: the code exists only in that one place. Plus your code takes up less lines (just call function TextBox in a single line somewhere instead of pasting 15 lines of text-displaying code).
The structure of a function
A function always starts with the word function so both Sphere and the reader of the script know we're going to make a function. You call such a word a keyword: JavaScript uses it internally to identify what you're trying to do. In this case, we want to define a function.
After the keyword function comes the name of the function. When giving it a name, keep in mind only letters, numbers and the underscore _ are allowed. It is also not possible to start the name with a number.
- Valid function names: FunctionName, do_300_things, EatDonut.
- Invalid function names: 123WayToGo, EatDonut&Sandwich, Can't-do-this
After the name come an opening parenthesis and a closing parenthesis (). This is intended so you can provide extra data to the function (I'll explain in detail later).
After that, you put an opening brace { which indicates that the code coming after the brace will belong to your function.
After the brace, you put the code that would be run when you "run" the function.
When you're done with this code, you close the function definition with the closing brace }.
Defining a function
Every Sphere game must have a main script file, containing the main function with the name game. Let's use it for our example and define the function game:
function game()
{
//code comes here.
}
That's what a function looks like, basically. And function game() in particular is a function that you *need* to define to give Sphere a place to start out at.
Still, if you try to run this code, nothing will happen. Sphere will open and then immediately close. That's because we didn't put in any code yet. (A comment isn't any kind of real code.)
Calling a function
Let's add one very simple command, with a comment in front of it:
function game()
{
MapEngine("map.rmp", 60); //Function call: opens the map engine with map.rmp
}
This piece of code WILL do something: open Sphere's map engine with the file map.rmp, with a framerate of 60 frames per second. Still, if you want this code to work, make sure that the file map.rmp actually exists inside the maps directory of your game!
The above code also demonstrates what calling a function looks like. In this case, Sphere internally has a function MapEngine(). We told Sphere to run that code here by doing this:
MapEngine("map.rmp", 60);
MapEngine() is actually an internal Sphere function, but if it were coded in JS by a normal user it would look like this:
function MapEngine(map, framerate) //map, framerate will be explained in the next chapter.
{
//Code to make the map engine to work here.
}
The original MapEngine function actually consists of internal Sphere code, not scripted in JS, but in the programming language Sphere itself has been written in (C++). You can't change this without changing Sphere itself.
Example of some simple Sphere functions
I'm going to show you and explain a few of the most used functions in Sphere. Every line of code will be explained.
function game()
{
CreatePerson("my_name", "character.rss", false);
//Creates a new person. gives him the name my_name and loads the spriteset file character.rss for his looks.
//'false' indicates he should NOT be destroyed when the game switches maps. That means he'll keep existing even on other maps.
AttachCamera("my_name");
//Makes sure that the camera is always pointed at the person called my_name.
AttachInput("my_name");
//Bind the default input to the person my_name. This means that when you press an arrow key, he'll move, stuff like that.
MapEngine("map.rmp", 60);
//We'll open map.rmp with a framerate of 60 frames per second (fps).
}
Check these pages for more details:
- CreatePerson(person_name, spriteset, destroy_with_map)
- AttachCamera(person_name)
- AttachInput(person_name)
- MapEngine(mapfile, framerate)
A list of common functions
There are a lot more common Sphere functions, another bunch explained below. You can look up all the other Sphere functions here: Legacy:Functions. They're also in the doc_functions.txt file that comes with Sphere (it's in the docs directory).
NOTE BEFORE YOU USE THESE: Some functions have a period . in them. I'll talk about what this means in detail later. For now, just put the appropriate function that loads a resource in front. Example LoadFont("file").drawText(1, 1, "text");
Function | Description |
---|---|
[[Legacy:FlipScreen]|FlipScreen]() | Put everything you have drawn so far on the screen. When drawing and blitting, your stuff doesn't get drawn on the screen, but in memory (in the "backbuffer"). It will be brought to the screen when you use FlipScreen(). After that the backbuffer is emptied. Use this after you've blitted absolutely everything you wanted, and use it only once per frame! |
LoadFont("file.rfn") | Loads a font file. Only the Sphere font format .rfn is supported, so make a Sphere font first. |
Font.drawText(x, y, "text") | Write text on the screen with the assigned Font and on the assigned x/y coordinates (in pixels). Font should be a variable containing the font you loaded with LoadFont. |
Font.drawTextBox(x, y, width, height, offset, "text") | Write text on the screen with the given font on the given x/y coordinates. This is a bit different from Font.drawText, because you define an area to write the text into. When the text hits the maximum given width, it continues writing the rest of the text on the next line until the maximum height is reached. The offset adds an extra bit to the X position of the first line of your text. |
LoadWindowStyle("file.rws") | Load a windowstyle file into memory. |
WindowStyle.drawWindow(x, y, width, height) | Draw the windowstyle on coordinates x,y with the given width and height. |
LoadImage("file.png") | Load an image file (bmp, png, gif, jpg, pcx, tga supported... But png is recommended for its size/quality ratio and transparency!). |
Image.blit(x, y) | Draws Image at the given x,y coordinates. ("Blit" is an old English word for "draw".) |
Image.zoomBlit(x, y, zoom) | Draws the image at the given scale on the given coordinates. When using a value like 1.5 the image will be drawn 1.5 its original size. |
LoadSound("file.mp3") | Load a sound file into memory. WAV, OGG and MP3 work best. MOD, IT, MID are supported but might have issues depending on what version of Sphere you use. |
Sound.play(boolean) | Play the loaded sound. The boolean argument is true or false. true == repeat, false == do not repeat. |
Sound.stop() | Stop the sound. |
RenderMap() | If you have the map engine open and you want to draw something on screen, the map will not be shown when you FlipScreen() it. So you'll have to draw a picture of the map yourself with RenderMap(), first. All the stuff you draw after it will then be displayed on that image of the map. |
GetKey() | Pause script execution until you press any key on the keyboard. Useful for a really quick "wait for input" so you can test if something renders properly, for example. Doesn't take arguments. |
ChangeMap("file.rmp") | If the map engine is open, you can switch maps with this function. Use MapEngine() if the map engine wasn't open yet. |
Exit() | Quit the game. |
Abort("message") | Quit the game with an error message you can define yourself. Useful for debugging (it can let you know why something goes wrong somewhere, for example). |
Variables and more on functions
Variables
When you're coding and need the exact same bit of data several times, you'll find that it is pretty annoying to type things like "I am a string" LoadFont("file") over and over again. Luckily there is is this little thing called a variable, which you can use to store these things (and values, text and a lot more!). Because of this you will only need to type the name of the variable to get what's stored inside it, which could be our LoadFont("file") or whatever! Well, that's just one of many advantages of it.
var some_name;
This is how you make a variable. It does NOT contain any kind of value yet. It has the name some_name, which means I would type some_name every time I needed its contents. (You can't do that yet, though, as it doesn't have any right now.)
In any case, what I just did is called declaring a variable. This means that I reserved some precious computer memory for our variable. The keyword var is only needed once, and that is when you're declaring a new variable. After that you only need to type the variable name, and the JS parser will figure out it's a variable.
Now, let's declare a different variable and assign a value to it.
var music;
music = LoadSound("SomeFile.mp3");
This code declares a variable called music. Then we give it a value. The value we just gave it is LoadSound("SomeFile.mp3"), which is a function that loads a sound file an puts it (as a value of sorts) inside the variable. Now you can refer to the variable's name, music, to to get to its contents. In this case, that is the sound data retrieved from LoadSound("SomeFile.mp3").
music.play(true);
What you see is music.play(true), but Sphere sort of reads it as LoadSound("SomeFile.mp3").play(true) because music is a variable containing (the result of) LoadSound("SomeFile.mp3").
Advantages:
- It's quicker to type music instead of LoadSound("SomeFile.mp3") every time you need the song.
- It also saves memory and loading time because the actual sound file is loaded only once into the variable music. It's not reloaded for every time we need the music.
Regarding point 2, using just LoadSound("SomeFile.mp3") all the time would load the file over and over every time, making your game use way too much memory.
Not just that, but if you load the same song twice, it's still identified as two different songs in memory. This means you wouldn't be able to use stuff like the .stop() function to stop music if you don't put it in a variable! Example:
LoadSound("SomeFile.mp3").play();
LoadSound("SomeFile.mp3").stop();
//Wait, the music isn't stopping! That's because the file is loaded from file twice, and so recognized as two different bits of data in memory.
With a variable:
var music;
music = LoadSound("SomeFile.mp3");
music.play();
music.stop(); //It stops playing properly, because thanks to the variable name "music", Sphere knows what song (data) to stop.
Quick variable declaration
There is another, faster way to declare a variable and assign a value to it at once. You can do it like this:
var music = LoadSound("SomeFile.mp3");
The variable is declared and immediately given a value.
var haha = LoadFont("haha.rfn");
haha = LoadFont("eh.rfn");
haha = LoadSound("haha.mp3");
As you can see you can also change the value in the variable any time you like. It IS called a variable, after all. Its contents can vary, even from one moment to the other.
Variables and data types
You can store all kinds of stuff in a variable! For example, you can use them to hold numbers or text for later use. These different types of data are called exactly that - a data type. Example:
var story = 1;
var player_name = "Pete Peterson";
This is useful for many different things. For example, story progression: if an event has happened: change the value of the story variable so Sphere knows that the event has happened. Or in the case of the string, you could remember a character's name that was entered by the user.
There are many more types of data that you can store, and they will be explained in detail soon.
Adding and subtracting to variables
How to add and subtract values from variables containing numbers? Like this!
variable++ |
adds 1 to your variable. |
variable = variable + 1 |
does the same. |
variable-- |
subtracts 1 from your variable. |
variable = variable - 1 |
does the same. |
variable += value |
adds the value to your variable. |
variable = variable + value |
does the same. |
variable -= value |
subtracts the value from your variable. |
variable = variable - value |
does the same. |
variable = value |
changes your variable to the value. |
variable *= value |
multiplies the variable's value with the defined one and puts it in the variable. |
variable = variable * value |
does the same. |
variable /= value |
divides the variable's value with the defined one and puts it in the variable. |
variable = variable / value |
does the same. |
Of course, you could always do something like variable = variable - 4 if you like. But note that the shorter versions (++, +=, -=, --) are preferred, for readability reasons (they're also shorter to type!).
Creating a function
As you probably know by now, Sphere reads the commands you insert. These commands are called functions... game(), for example, is a function too, so it could be started the same way as for example a FlipScreen(): by typing its name and the braces behind it.
game();
So with this you'd run whatever is in function game(). This particular function doesn't need to be called manually, because game() is automatically called when Sphere starts your game. But it's a good example on how to create your own functions.
Making your own function is pretty simple. Just look at how you made the game() function... You can make your own functions in the same way.
function Name()
{
//Code comes here.
}
You can put code in functions, this code will be executed as soon as the function is called from somewhere else. As an example we'll try to make a function that executes all commands necessary to make a box containing text.
//First we'll declare a few variables outside of any function.
//This makes them global, which means they will exist in any function, even if you didn't declare them there.
var window = GetSystemWindowStyle();
//Declare the variable 'window' and put the standard Sphere window style in it.
var font = GetSystemFont();
//Opens the standard Sphere system font and puts it in the 'font' variable.
//Here's our own function named 'TextBox'.
function TextBox()
{
//Code goes inbetween the braces {}.
window.drawWindow(5,5,310,100);
font.drawText(5,5,"Some text");
FlipScreen();
GetKey();
}
If you insert TextBox(); in your game function, everything in the TextBox() function will be run once you start the game.
function game()
{
TextBox();
}
Function arguments
There is something lacking about our textbox: you can add TextBox(); to your code, but the end result will always stay the same. Every time you call TextBox() you will see the text "Some text" appear on the screen, no matter what. But what if we wanted to display something else, like "Hello", "Line 2" or "Status: moody"? It's not smart to make four different functions just for that, that would be a waste of memory. It would duplicate almost the exact same code four times, defeating the purpose of having a function! So instead, we slightly change the TextBox() function.
Examine the following code:
function TextBox(text)
{
window.drawWindow(5,5,310,100);
font.drawText(5,5,text);
FlipScreen();
GetKey();
}
Look at the spot where the function is declared (the first line). Inbetween the parentheses there's something new now. This is NOT a function or variable, but an argument. Arguments (also named parameters) can pass values (any kind: numbers, strings etc) on to your function, resulting in the output varying depending on what you "fed" the function. Now we can put all our different text in the function and get different results:
TextBox("Hello");
TextBox("Line 2");
TextBox("Status: dead");
As you can see we put our text in-between double apostophes ", in-between the braces () of the function call. The " are there to show Sphere that it's a string of text. More on that in chapter 5.
The stuff that we insert inbetween the parentheses is saved into an argument, we gave this argument the name 'text'. Arguments behave like variables, but only inside their own function. They don't exist outside the function. (The same actually counts for variables made inside a function.) Anyway, what Sphere will do is give assign whatever value you put inbetween parentheses to this argument, and then use it inside the function.
If that wasn't clear enough, imagine that Sphere is doing this: (it doesn't happen literally, but you can compare it with this anyway:)
- Take the text that was found when the function is called and put it in our argument 'text'.
- Walk through the function and look for other places using this argument 'text'.
- Replace it with what we got through the function call.
- The result is different every time because it's dependant on what you inserted!
And here it is in physical form:
function a(argument)
{
Abort(argument);
}
//And to call it:
a("This text is used by the Abort() function inside the a() function.");
function a("something!") //Don't write code like this! Only put something that looks like a variable. This is just an example on what Sphere might do internally.
{
Abort("something!");
}
Multiple arguments in one function
If you want to have more than one argument, simply seperate the argument names with a comma:
function TextBox(x, y, text)
{
window.drawWindow(x, y, 310,100);
font.drawText(x, y, text);
FlipScreen();
GetKey();
}
The function call would look like this:
TextBox(10, 15, "hahaha!");
This will make the text "haha" appear on x position 10 and y position 15.
Test your newly gained skills!
I'm gonna be making an entirely new test. Working on it right now. But feel free to experiment with the functions you've learned! ;)
More on variables
There's a few types of values available in Sphere, each one of them is intended for different things. This means that you can put different types of content in variables. In this chapter you'll learn about most of these types of values.
1. Numbers
You already knew this one. It is an often used type of value. You can put numbers in variables so you can refer to them, change them, or do maths with them. var blah = 4;
2. Text
You know this one already, too, if you have been paying close attention to the last chapter. You can use regular sentences, words, letters and characters with this type of content. You call this a string. Strings are used to store and use text in your program. Strings are recognizable by the fact that they have double quotes put around them (like "this"). This is done to clearly define to the system that it is a text string, not a number or anything else. You can also use single quotes if it's more convenient at the time, but take care with single quotes if you have "regular" text: words like it's use a single quote too, so they would end the string. Strings are also typically useful to define file and directory names.
var hi = "Hello!";
var project_file = "game.sgm";
There is a way to "connect" seperate strings together, in fact, it doesn't even have to be a string to be able to connect it to a string. Connecting (or combining) two strings (or compatible types) is what you would call concatenating. There is a very simple way to concatenate: use the plus sign as if you were "adding" two values. Run the following code in Sphere:
var string = "I am a string" + " with a second string concatenated";
string = string + " and a third string concatenated";
string += " and even a fourth one!" +
" And a fifth one on the next line, because the plus character allows this.";
Abort(string);
Sphere will show something like this:
I am a string with a second string concatenated and a third string concatenated and even a fourth one! And a fifth one on the next line, because the plus character allows this.
Strings can also contain some special characters you can use for formatting the text when it's put on screen:
- \n makes a new line.
- \t adds a tab (or rather, 5 spaces).
- \\ is for adding a \ to the text. :P
- \" is used so you can safely add a double quote to your string without indicating to the engine that you finish the string.
If you used a single quote ' to formulate your string in stead of a double quote, you can use a regular " inside this single-quote string, but you will then have to define "textual" single quotes like this: \'
Try running the following code to see how you can use these:
Abort("Hi.\nI'm on a new line.\nMe too.\n\tI'm on a new line with a tab in front.\nC:\\path\\to\\something.txt\nAnd \"I\" am in \"quotes\".");
The result will be something like this:
Hi.
I'm on a new line.
Me too.
I'm on a new line with a tab in front.
C:\path\to\something.txt
And "I" am in "quotes".
3. Sphere functions
Sphere has a few built-in functions that can return values, meaning they give you values back when calling them. For example, GetPersonX(name) gives you a numerical value of the X position of a person on the map, GetScreenWidth() gives you the screen width, stuff like that. When you use such a function as a value in a variable, Sphere will put the resulting returned value in the variable. var blah = GetScreenWidth(); In this case, you're not really putting the function in a variable. You're putting whatever value it gives back to you in it instead. For example, GetScreenWidth() could return the number 320 to you, so var blah would then contain the number 320 as a value.
You can actually store the actual function inside a variable, if you wish. This will create what you would call a reference to this function: you're basically creating a new identifying name to call the function. To re-reference a function, just remove the parentheses () at the end: var GetSW = GetScreenWidth; var blah = GetSW(); //GetSW() actually calls the same code as GetScreenWidth() would now. There are a lot of functions in Sphere that return a value to whatever called it. Below are a few that you might want to study and practice using.
- GetPersonX(person_name) - Returns the x position (as a number) of person_name on the map.
- GetPersonY(person_name) - Same, but returns the y position.
- GetScreenWidth() - Returns the width of the game screen (as a number).
- GetScreenHeight() - Returns the screen height.
- GetPersonList() - returns an array filled with strings of the person names on this map.
- GetVersionString() - Returns the current Sphere version (as a string).
4. Sphere's special internal objects
You can put objects in variables. Objects are a kind of function too, but they can do much more internally, like store and retreive sub-values and even sub-functions inside. They also can have "changing" states: two objects can be the same on the outside, but store very different things. For example, you can have two instances of an Image object, one containing a small icon and the other a big cloud graphic. Objects are complex, so I'll leave the explanation at that for now and go more in-depth later in the tutorial. Anyway! Sphere has a bunch of internal objects that can load images, music, basically all basic file resources. These objects can be made (instantiated) and stored inside variables by calling their appropiate LoadWhatever() functions, like LoadImage(file) or LoadFont(file). These then offer some internal "sub"-functions (or subvalues) to get or change further information, like image.width and image.height, or Font.getHeight().
var blah = LoadFont("fontfile.rfn");
var fontheight = blah.getFontHeight();
blah.drawText(10, fontheight, "Hahaha!"); //y position of this text depends on the height of the largest character in the font you loaded.
blah contains an instance of Sphere's internal Font object, which has methods (subfunctions, indicated with a separating dot) to further do things with this object, like draw it on the screen.
The following is a list of functions that returns an instance of any special Sphere object, like images and fonts.
- LoadFont(file) - Makes and prepares a font object and returns it (to whatever called it).
- LoadImage(file) - Makes and prepares an image object and returns it (to whatever called it).
- LoadSound(file) - Makes and prepares a sound object and returns it.
- LoadAnimation(file) - Makes and prepares an animation object and returns it.
- LoadSpriteset(file) - Makes and prepares a spriteset object and returns it.
- LoadSurface(file) - Makes and prepares a surface object and returns it.
- LoadWindowStyle(file) - Makes and prepares a windowstyle object and returns it.
- OpenFile(file) - Makes and prepares a file object (for saving game data or settings) and returns it.
---
- CreateColor(red, green, blue, alpha) - Makes a color object with the values you gave it.
- GrabImage(x, y, width, height) - Returns an image object that contains the contents of whatever is on screen in the zone you specified.
- GrabSurface(x, y, width, height) - Same as GrabImage(), but it returns a surface object, which has different kinds of functions. Better suited for manipulating the image, or drawing it in a special way (like warping it all over the screen).
All of the above functions actually make different kinds of objects, which have their own specialized functions for that type of file. For example, a Sound object has .play() and .stop() functions in it (these are methods), and the Image object has a .blit() function (or method), etcetera. You can create your own objects too.
5. New functions
You can also put NEW functions in variables, like this:
var blah = function(parameters)
{
//Code comes here.
}
This is basically a different way to declare a function. It's functionally equivalent to this:
function blah(parameters)
{
//Code comes here.
}
This might seem useless, it has several uses:
Objects use it to create methods (subfunctions, example is the getHeight() method in Font.getHeight()), because the syntax to make these is a bit different. You can make anonymous functions. These are normal functions that do not have a name. An example of doing this is when you pass a function as an argument:
function CallMe(argument)
{
argument(); //I'm sure that the argument actually contains a function! Let's call it.
}
//...
CallMe(function() { Abort("something happens in here."); });
That's a lot of braces and parentheses! But all we do on the last line is create a function on the spot, on a single line, and not assign any identifiable name to it. I could also have done the following for the last line, but then you wouldn't call it an anonymous function anymore:
var somefunction = function()
{
Abort("something happens in here.");
}
CallMe(somefunction);
Still, it does the same thing; pass a function to another function, showing that functions can be allowed as "values" to pass to other things just as well as a number or a string.
6. true and false
true and false are keywords in JavaScript that are used when you only need to know two "states" about something. Some examples: Has an event happened? (you could call it a flag when used like this, you toggle the flag as you finish the event.)
var talked_to_old_man = false;
Should my textbox draw a window?
var draw_a_window = true;
//And in a textbox function somewhere:
if (draw_a_window) //The condition evaluates to "true" because draw_a_window is true...
{
windowstyle.drawWindow(x, y, w, h);
}
Do I want to debug my game and show extra values on the screen?
var DEBUG = true;
//Anywhere else in code:
if (DEBUG) { /* Do something related to testing your game */ }
Do I want to check if something finished in order to end a loop?
var finished = true;
while (finished == false)
{
//Do whatever until I set finished to true somewhere in this loop.
}
An alternative way of checking for false is by putting an exclamation mark in front of the condition (or variables in the condition), like this:
while (!finished)
Do I want to check if something simply is true or false, or do I want to make it true or false?
if (!sound.isPlaying()) //Check if sound is not playing (sound.Playing == false).
{
sound.play(true); //The true here is an argument to sound.play, which answers Sphere's question: should the music loop?
}
As you can see, it has many small but important uses. You'll encounter these two values a lot.
Last thing, remember that false is (sort of) equivalent to 0, and true is (sort of) equivalent to almost any other value. This means that a condition like (value == false) could be true when value equals 0! This can come in handy in places like loops.
7. undefined
undefined is a keyword in Sphere (or rather, JavaScript) that lets you enter nothing as a value! For example, some function's argument could be optional, and use some other default value if it's not set, so in this case you could pass the undefined value as an argument. Or maybe you wish to erase a variable's previous value.
var blah = 4;
blah = undefined; //Woops, setting 4 was a mistake.
Or maybe you wish to declare a variable, but not set a value yet... But then again, in that case you could just not set a value: JS will automatically enter undefined as the default value for a variable not given a value.
var blah;
Abort(blah); //Sphere will quit, printing out "undefined".
8. Arrays
Arrays are somewhat different from the other variables mentioned so far. The big difference lies in that you can put multiple values in it, and these values can be of various types (even more arrays)! Declaring the array To make an array, you can type one of the following:
var blah = new Array();
- OR -
var blah = [];
I recommend using [] because it's shorter to type, though it doesn't really make a difference. The first way is a nice example of instantiating an object, though, so you might want to come back here later when you're learning about objects.
Storing values in the array
Okay, so now you have an Array. You can store your values in it in two different ways.
The first way to do it is by putting values in it right when you declare the array.
var blah = new Array(1, 6, 4, "Theo", "etcetera", new Array("subarray!"), 88);
- OR -
var blah = [1, 6, 4, "Theo", "etcetera", ["subarray!"], 88];
This is how you declare the array and immediately put some values in it. An array is like a list of items where you can store a value, each seperated with a comma. All items in this list also have a number, starting from 0, which can be used to identify the seperate items, or places in the array.
In the above array:
- The number 1 is stored in spot 0 of the array.
- The number 4 is stored in spot 2 of the array.
- The string "etcetera" is stored in spot 4 of the array.
- Another array (containing the string "subarray!" on spot 0) is stored in spot 5 of its parent (or containing) array.
Confusing? Read over it again, then continue. The second way to put values in an array will make things a bit more clear. :)
The second way to store values in an array is by already having declared the array, and then assigning values to spots in the array:
var array = []; //or new Array();
array[number] = a_new_value;
For example, I'll now change some of the values in the array blah we made before:
blah[0] = "new value!"; //Spot 0 contained the value 1 before, now it contains "new value!".
blah[1] = 7; //And this used to be 6
blah[4] = "Pete"; //And this used to be "etcetera"
blah[7] = "new value"; //Spot 7 comes after the last spot (spot 6, which contains 88), so basically we created a new spot to store a value in.
blah[9] = [1, 2, 3]; //Another subarray with three items is added to the blah array. Spot 8 will contain an undefined value.
This way you can change the values (or make new values) on different places of your array. The old values (if there are any) are replaced, just like in a normal variable.
Since we didn't touch spots 2 and 3, they'll keep their original values, which were 4 and "Theo". That means that our array now consists of the following values:
["new value!", 7, 4, "Theo", "Pete", ["subarray!"], 88, "new value", undefined, [1, 2, 3]]
Now how do we use these values? Well, that's pretty simple, actually. Just refer to your array and add straight brackets right after it containing the number of the spot you want to reach. For example:\
var nn = blah[3]; //nn now contains the value "Theo".
TextBox(blah[4]); //TextBox will print out the text "Pete".
Abort(blah[9]); //Sphere will quit, showing the text "[1, 2, 3]" (the array gets interpreted as a string, showing its internal values).
So what are arrays useful for? Well, for example, they can be useful for an inventory:
var inventory = ["Potion", "Antidote", "Empty bottle"];
But most importantly, they can be used to store similar data together and perform mass actions on them. Like naming all characters in your game (or just the party) and then generating a person on the map for each one of them:
var people = ["DaVince", "Petey Pirate", "Aegis", "Flikky", "Nidoran"];
//Create persons using their above name for both the identifying name and the spriteset file to load (like DaVince.rss or Nidoran.rss).
CreatePerson(people[0], people[0] + ".rss", true);
CreatePerson(people[1], people[1] + ".rss", true);
CreatePerson(people[2], people[2] + ".rss", true);
CreatePerson(people[3], people[3] + ".rss", true);
CreatePerson(people[4], people[4] + ".rss", true);
Now, the above might not seem like it has any advantage ("Why not just do CreatePerson("DaVince", "DaVince.rss", true)?").
You see, the above code can be made much simpler and quicker using loops, explained in the next chapter.
You'll find that arrays can have lots of uses. I use them a lot, and some native Sphere functions also use them, so you'd better get to know them! ;)
Conditions and loops
If, while and for
This chapter will become a bit more difficult and confusing because we'll have to break some of the Javascript syntax rules that you learned before. For instance, we won't add a semicolon after every line any more, and sometimes we add multiple semicolons in one line! Luckily this only happens on a few special occasions, so once you know this exception from the rule it's easy to recognize.
Conditions: 'if'
You might have thought at least once about having an event happen in the game ONLY when something else had happened first. Well, you can do this. It's called a condition, as you set a condition for making it happen.
Conditions basically check if something is true or not. For example, it can check if the variable blah contains value 4, and if it is, it will evaluate this as true. You use the if keyword to make this check, in the following style:
if (blah == 4)
Now for a complete example:
var story = 0;
if (story == 0) //No semicolon ; at the end!
{
//Hey, story is 0! This code runs.
story = 1;
} |
Declare variable story with value 0
Check whether variable 'story' contains value 0
Open a block of code that runs when this is true
Set story to 1; now this condition can't run again
Close the block of code for the condition |
Explanation:
- Declare variable story with value 0
Self-explanatory. (If not, read about declaring variables again.)
- Check whether variable 'story' contains value 0
This line of code has TWO equals signs (=) in it. A double equals sign (==) tells JavaScript that this is a comparison: we're comparing the value inside story and the value 0 to each other. Specifically, == tells JavaScript to check if these are EQUAL to each other.
A single = does not run a comparison, but assigns a value to a variable. I did this in the chapter about variables: I assigned a value to a variable by putting an = in-between: var a = 6. But in conditions and other comparisons, we shouldn't use a single = because we're not assigning values to variables; we're just comparing them!
- Open a block of code that runs when this is true
A condition (or if-statement) uses braces {} in which you enter the code that's going to run when the condition passes, just like how a function runs code between the braces when it is called. This is called a block of code. The opening brace { is put after the if-statement. The code inbetween the braces will then run when the condition evaluates as true (so when story == 0). Else, it will simply skip the block of code and continue regular code execution after the block (ending at } ).
- Set story to 1; now this condition can't run again
Now that we've done everything we wanted to do in case story was 0, let's change the value of story to 1. This ensures that the condition will not be run again, so the same event won't happen twice (unless you set story back to 0 somewhere and make the game run the condition again).
- Close the block of code for the condition
The block of code containing what should be run when the condition is true is closed. The conditional code has finished.
As you can see, the structure of a condition is almost the same as when you're creating a function. The big difference lies in when and how it is used: a function will run whenever it's called, but a condition will only run whenever it's encountered AND when its conditions all evaluate to 'true'.
(Evaluate, in JS's case, basically means to "solve" a problem by simplifying it. 1+1==2 would evaluate to true since one and one makes two, and 2 IS indeed equal to 2. Otherwise it will evaluate to false.)
If it helps, here's one more explanation compared to real life. Things like story == 0 are things that you could call a statement. Basically, you make a statement to the engine that story would have the same value as 0. JS then evaluates this statement and determines if this is true, or false. Kind of like a math teacher evaluating your test answers. They'll also put crosses and circles on your test paper to indicate if your statement that 1 + 1 = 3 is true or false.
Comparing values
The == operator (that's what you call it) is just one of a set of comparisons you can use in conditions. It compares whether one number is equal to the other. But more types of comparisons exist, like checking if a value is higher or lower than the other. Here they are:
a > b |
Compares whether value a is higher than value b. |
a < b |
Compares whether value a is lower than value b. |
a >= b |
Compares whether value a is higher than or equal to value b. |
a <= b |
Compares whether value a is lower than or equal to value b. |
a != b |
Compares whether value a is anything but value b. |
a === b |
Strict compare. Also compares if the type is the same. See below for explanation. |
Some weird values can evaluate to true together, even if they're not of the same type or even value. For example, when you compare to the value true, comparing it to almost any value will make the complete comparison evaluate to true. (2 == true), (true == "a string") and (-400.6 == true) will all evaluate to true, simply because these values are not false in the machine code (false is 0, more or less; true is plain everything else). However, you probably wouldn't treat those values as "true" but compare them properly.
A triple-equals sign === was made to solve this problem: it will not only compare the values but also the types of the items compared. If you do this, these types need to match. "true" is a boolean, and a comparison will now only evaluate as "true" when it's compared to "true" (so true === true). (2 === true) and ("blah" === true) evaluate to false. Only (true === true) will evaluate the condition as true. Only (2 === 2) will evaluate as true, (2 === "2") won't (because that other "2" is a string, not a number).
So... Why would you compare true to true? Well... It all has to do with the values in variables. You can do var storyfinished = true; (storyfinished === true) would entirely make sense.
You use all of these in the same way you used the double equals sign.
if (a > b) { TextBox("a was higher."); }
if (a != b) { TextBox("a and b are different."); }
if (a <= b) { TextBox("a is lower, or the same as b."); }
if (a === b) { TextBox("a is the exact same object with the exact same value as b."); }
//etc.
Side note
Notice how I put the condition, opening brace, code and closing brace all on one line? Yes, this is allowed. You can do the same with functions, or other blocks of code that look like them:
function Something() { TextBox("Hi."); /* I'm a nested comment. */ Abort("The game is quit now."); }
In fact, this is mostly why the semicolon ; is used: to easily recognize when one command ends and the next one begins, even on the same line.
Use this sparingly, though, as things can kind of become a mess if you do this. Each bit of code on a new line is much easier to read, especially when you have { stuff with tabs going on.
Loops: 'while' and 'for'
while
Loops in Sphere! These are very useful for things like repeating code. One advantage is that you don't have to have many lines containing the same code over and over again. Example:
var value = 0;
value++;
TextBox(value);
value++;
TextBox(value);
value++;
TextBox(value);
value++;
TextBox(value);
//etcetera etcetera....
It takes up way too much code if you want to add 1 to value and show a textbox with that value 100 times in a row. That's where the loop comes in: a loop makes it neat and short because you just enter the two lines (value++ and TextBox(value)) in it once, and then run it a hundred times! The loop takes a condition to determine when it should still repeat the code running inside. If the condition is false, the loop ends.
There are more kinds of loops. The while loop is the easiest one to start with, because it looks just like an if block.
var value = 0;
while (value < 100)
{
value++;
TextBox(value);
}
//Code continues here when the loop is done.
A while loop is basically like an if-statement: it checks whether the given condition returns true. The only difference is that it repeats the code inside the braces when this is the case. In this case, the loop can be interpreted as:
While (the value of var 'value' is lower than 100): Add 1 to var value. Call function TextBox(), which does something with the value. Do the comparison again and run the above two lines again if still true.
Things keep looping while variable value is still lower than 100 (because it's true that (value < 100) here).
When value becomes 100 or higher, the comparison made in the condition (value < 100) becomes false and the loop stops executing. The engine continues business as usual.
- The loop begins running because when the loop starts, var value is still 0, which is lower than 100. If value were to be 100 already, the loop would never even begin.
- The loop ends because it reaches a point where (value < 100) becomes false. Sphere will then exit the loop.
So why are loops so useful?
If we had NOT used a loop for this piece of code, we would have wasted around 200 lines of code: 100 for value++ and another 100 for TextBox(value). It would be a never-ending mess! But with the power of loops we minimized this to 5 lines, where those two lines just loop 100 times. Much, much better.
Also useful if you don't know how many times you want something to run!
Practical example (note: vars and functions are all made up for the example)
while (player_actions_done < max_player_actions)
{
//Do stuff like ask the player to perform an action here
player_actions_done++;
}
//And afterwards this player's turn ends or something
To close, a common term used with loops! A single time that we run the loop is called an iteration. The example code with the TextBox() goes through 100 iterations. The first iteration is the one where value goes from 0 to 1 to then '1' in a self-made TextBox. The 88th iteration would make the value go from 87 to 88 and display '88'. The last (final) iteration is run when value is 99 - when it's 100 the block of code is no longer run.
for
There is a different, more useful but also more difficult to learn type of loop. This is the for loop. A for loop looks a bit different. It's because you do multiple things at once, still in the loop. A for loop usually looks like this:
for (var value = 0; value < 100; value++)
There are three things here, and semicolons inbetween them! That's a bit different from the usual... But, it's not as difficult to grasp as you might think, and it actually makes the loop much more powerful...
for (prepare variables here; putcondition here; action to variable on each iteration here)
- In the first part of a for loop, you place your variable that will usually 'control' the loop. This can be a new variable, or one that was created globally somewhere else. You should also give it a starting value (if you don't, your variable will effectively be useless for use in the loop).
- In the second part you place the condition, just like you would do in an if or a while loop, only this time you usually involve your special variable.
- In the third part you put code that affects the variable so it can end the loop at some point. The third part of the statement is run after the code inside loop was run; eg. after an iteration has finished.
Examples
for (var blah = 0; blah < 25; blah++)
{
TextBox("This loop started " + blah + " times so far...");
}
TextBox("Countdown for liftoff! Ready...");
for (var blah = 10; blah > 0; blah--)
{
TextBox(blah + "...");
}
TextBox("ZERO! We have a liftoff!");
This loop won't show a text box with "0..." because the loop ends right after blah is decreasing in value from 1 to 0. So we can put our own, neater "ZERO!" at the bottom instead.
It's probably easier to try it out for yourself to really see how it works!
Practical example of combining loops with arrays
Now for a different use of the loop that also shows its practical use in combination with a collection of data (in this case, an array). We're going back to the last example of the previous chapter, and we're going to rewrite it to use a loop.
Goal: to create persons on the map using their names identified in the array. Original version:
var people = ["DaVince", "Petey Pirate", "Aegis", "Flikky", "Nidoran"];
//Create persons using their above name for both the identifying name and the spriteset file to load (like DaVince.rss or Nidoran.rss).
CreatePerson(people[0], people[0] + ".rss", true);
CreatePerson(people[1], people[1] + ".rss", true);
CreatePerson(people[2], people[2] + ".rss", true);
CreatePerson(people[3], people[3] + ".rss", true);
CreatePerson(people[4], people[4] + ".rss", true);
New version:
var people = ["DaVince", "Petey Pirate", "Aegis", "Flikky", "Nidoran"];
var len = people.length; //array.length can be used to get the length of an array.
//In this case, it's 5 (the highest spot to contain a value is in 4, meaning there's 5 items that were given a value (including the undefined ones)).
for (var i = 0; i < len; i++) //Basically, we go through each and every item that's in the array, and run the code below, using them.
{
CreatePerson(people[i], people[i] + ".rss", true); //people[i] is people[0], then people[1], then people[2]...
}
It might not seem too worthwhile to write a loop for this small bit of code, but consider how much extra work you would have to do if you had 14 people... And what if two of them disappeared later? Or if you gained five more people? The original version is inflexible and relies on you knowing exactly what's in the array; the flexible version has the computer take care of that. This greatly reduces the chance of getting error messages as your program won't read entries that no longer exist, for example.
It also greatly expands the possibilities of what you can do with the language, and with your game. Whether you have a party of one or a party of five, the game can handle it in any situation and you can display a character list menu properly, for example.
do...while
There is also a third type of loop, but it's basically the same as a while loop and not many people use it. If you still want to know more about it, check out this page.
Test Yourself Again
Take a breather. Try to incorporate everything that you learned into a game. One day, I might assist you with this by adding a test for you to do here.
Advanced functionality in functions
There's much more that you can do with if-statements than you have learnt so far. For example, you can put several conditions on your if-statement at once; you can group related if-statements together for efficiency and readability; you can even leave out the braces around the code to execute in some cases. This chapter will explain you why and how.
Combining several conditions: && and ||
You can combine several conditions - that is, comparisons - to make an overall more complicated if clause. More complicated, but also more precise. For example, (value > 0) can only check if your value is higher than 0, but combined with a (value < 10), we suddenly only have the possibility to be the numbers 1 to 9 to have a condition evaluate to true! This can be very useful, and sometimes even required, to get the desired effect.
The inefficient way
The inefficient way is nesting a condition inside another one.
if (value > 0)
{
if (value < 10)
{
TextBox("The value is somewhere in-between 0 and 10.");
}
}
You're running two conditions, here. But you're also running two if clauses. This takes up a tiny bit more processing power. The computer gets a tiny bit more to do, because it has to do this:
- Compare a value,
- then run the code inside,
- which again compares a value,
- then runs the code inside that,
- and then Sphere has to "end" that block of code.
- And then another one, because we had two conditions.
But more importantly, you're nesting more than you have to. Can you imagine when you have eight things to check against? Your code would be on the right side of the screen, and you'd have to keep track of which accolades {} belong to which condition! This makes things very unreadable and confusing.
This can surely be done more in a better way! Yup, it sure can:
The efficient way
if (value > 0 && value < 10)
{
TextBox("The value is somewhere in-between 0 and 10.");
}
What Sphere does now:
- Compare a value,
- and a second one,
- evaluate whether the total result is true.
- If so, run the code inside,
- and then "end" that block of code.
Actually, I think it might be less steps, because computers have special ways to compare multiple grouped conditions efficiently. Still, one step less than the previous thing with my explanation. The code complexity has decreased, and you have saved some precious processor power, which can then be used towards more useful things, like advanced AI or whatever.
Next to the &&, which is called the AND operator, there is the OR operator. This special operator will evaluate the entire result as true if any of the given conditions is true, no matter if all others are false. This can be useful if you want some common code to run under only some circumstances:
if (cheat_mode || backflip_mode || the_player_kicks_ass) //If even just one of these three equals true...
{
DoABackflip();
}
The player might do a backflip if any of the three variables given are true. Doesn't matter if only one of them is true, or all three... As long as something is true, it'll run the code inside the block.
The two types of operators can be combined. You could check if all elements in a set of conditions is true, and then compare that to another conditions that might override it and run the condition anyway. It's best to group these together by using extra parentheses. Example:
if ((master_password_entered && voice_recognition_success && confirmed_twice) || evil_intruder_override)
{
SelfDestruct(ship);
}
The ship will ONLY self-destruct if the master password was entered, the voice recognition succeeded AND the user confirmed the self-destruct sequence twice. Only then. Oh, or if there's an evil intruder who somehow overrode the system.
Of course, even if you don't use such a complicated combination of options, it's still a lot more convenient than putting a condition alongside a condition inside a condition inside a condition. Note that it also works with loops.
while (!done && willpower > 0) //When you're not done yet AND you still have willpower...
{
DontGiveUp();
}
Leaving out the braces
This is another exception to the syntax rules in JavaScript, and sometimes a rather handy one: you can choose to leave out the braces. However, you can do this in only ONE case: when there's only one statement to make. The if-statement immediately closes after you put that finishing semicolon after your only line of code that's going to be run in the condition:
if (condition)
Statement();
if (next_condition)
OtherStatement();
Of course, you can still format this in any way you like. This can make code much more compact:
if (story == 1) Statement();
if (story == 2) SecondPart();
if (story == 3) ThirdPart();
if (story >= 4) Abort("End of the game!");
Another exception exists where you can use several statements if you like. However, this method is less compatible; a few JavaScript keywords like return and break will NOT work if you do this:
if (condition)
Statement1(), //Use a comma, not a semicolon, and you can keep feeding the thing code until a semicolon ; is encountered!
story++,
Statement2(); //End of the code to run for this condition.
Note that it also works with loops.
while (willpower > 0)
DontGiveUp();
for (var i = 0; i < 1000; i++)
TextBox("The loop has run " + i + " times now. WILL IT EVER END? Yes, at 999.");
The "else" keyword
You can connect if-statements together if you like. What if there's three related cases? Maybe var story is 1... Maybe it's 2. Maybe it's neither! You can use the else keyword to connect these three different conditions together. This offers two advantages:
To people reading the code, it's recognizable as a group of related statements;
When one condition evaluates as true, all others will automatically skip, not even bothering to evaluate. This saves processing power (your PC needs to do less).
There are two types of ways to use else:
- else if - there's a different, related condition. For example, story is 2.
- Just else - All of the previous related conditions have evaluated to false. For example, story is 6 and there's no else if that checks for it to be 6.
Basically, using just else is like defining code that will run in all other cases. else if can only exist right after an if or another else if. else can only exist once, and that is after all other related conditions.
var story = (any value)
if (story == 0)
RunStorySectionOne();
else if (story == 1)
RunSecondSection();
else if (story >= 2 && story <= 4) //If story is 2, 3 or 4
RunThirdSection();
else //Story could be 5, or 389, or -300 for all we know
Abort("That story section does not exist, fool!");
Objects
Note: this stuff is kind of complicated. I sometimes explain several concepts at the same time. I also apply a bunch of the earlier knowledge, so make sure you know and control the other stuff before you begin.
I've mentioned objects before, now we'll go into more detail about them. An object is one of the more complicated things in Sphere, but they will allow you to do very powerful things.
What is an object?
An object is a type of variable in which you can store many things - values, functions, and even more objects. However, an object is not like an array because of two main reasons: Objects use names instead of numbers for your data. These "names" are properties if you're storing data and methods if you're storing functions. Objects can have different instances.
What is an instance?
Objects are very useful for one specific thing - creating clones of itself that contain different data, and possibly different behavior. When you start making your own object, you start out by shaping the "original" object, which is called the prototype. From here on, your prototype will serve as the way to create actual, usable objects out of your prototype. These new, copied objects are what you would call an instance of that object. Instances let you have similar object - with different values.
An example of different instances
The best example to start out with is with something familiar: let us look back at Sphere's own, internal object. When you use LoadImage(), you actually create a new instance of the internal Sphere Image object. For example:
var a = LoadImage("a.png");
var b = LoadImage("b.png");
Both a and b are Image objects, but they contain different images - that is, different data.
Making your own prototype
Now, Sphere's internals use functions like LoadImage(), but this is not actually "true" object-oriented programming, and it is not how we will make our own object prototype. But in this first step, it will almost appear as if we are!
function MyObject()
{
}
There you go, we just made an object prototype. What? It looks just like making a function? That's exactly right - all functions are actually objects, except we don't normally use them as such! Let's take it a step further and see what's so different about objects.
function MyObject()
{
this.myproperty = 4;
}
Objects have properties. Inside properties, you store values. It's just like using var, but instead we use this. The reason for this is so we can access the value from outside the function. Not only that, but we tell Sphere that this variable-type thing is a default value that the prototype will give to each instance, after which it can be changed individually per instance. You could access the property from the outside as follows:
MyObject.myproperty
But take note! We are still making an object prototype here, so it's better to leave the prototype alone. Why? Because if you change the prototype's properties' values, new instances would take over that new value too (but old ones would still be in their original state with the old values). This can become a mess, so be careful with it. Usually you will want your prototype to be well-defined inside itself, and nowhere else.
Object prototypes can also have functions. These are copied just like values would be, which is why you could make two instances of the same type (that means, derived from the same prototype) have similarly named functions that do different thing. A function inside an object is called a method - usually because the object is made to do certain related things, and these subfunctions would be the method to manipulate its data.
function MyObject()
{
this.myproperty = 4;
this.myMethod = function()
{
this.myproperty++;
}
}
The above added method adds 1 to our property, so every time it's called, myproperty would increase by 1.
Making an instance of the object
Also known as instantiating. To do it, use the new keyword. I'll also demo how you can set values from the instance's properties.
var some_instance = new MyObject();
Abort(some_instance.myproperty); //The engine quits, displaying a 4
It's useful because each instance can have different values from the other. This is one of the real strengths of objects.
var first = new MyObject();
first.myproperty = 6; //Change the value of myproperty in just this instance
var second = new MyObject(); //New instance, its myproperty is that prototype value of 4
Abort(first.myproperty + " and also " + second.myproperty); //The engine quits, displaying "6 and also 4"
Practical example
//Let's define a new object prototype for this example
function Character()
{
this.name = "default";
this.hp = 100;
this.mp = 50;
this.equipment = [];
}
//Now let's make a few player characters
var players = [];
players[0] = new Character(); //New instance of Character
players[0].name = "Jon"; //This value is only changed for this instance
players[0].hp = 200; //Same deal
players[1] = new Character(); //Another instance of Character
players[1].name = "Jacques"; //This value is only changed for THIS instance, Jon with 200 hp is still Jon with 200 hp
players[1].hp = 40; //Same, etc.
players[1].mp = 9000;
Now we have two player characters with very different stats, all thanks to an object prototype we made for managing players!
Note that I made this object have some very generic properties, which has advantages. It's flexible, it can be used for enemy characters too:
//Now let's make a few player characters
var enemies = [];
enemies["mage"] = new Character();
enemies["mage"].name = "Mage";
enemies["mage"].mp = 2000;
enemies["mage"].equipment = ["wizard robe", "wizard hat"];
Passing arguments to the prototype
What I've just shown is powerful, but it's also very long. A bunch of lines just to change some character stats? Luckily, arguments can be used not in just functions, but also in prototypes!
Let's enhance our Character object with it to shorten the code above.
function Character(charname, initialhp, initialmp)
{
this.name = charname;
this.hp = initialhp;
this.mp = initialmp;
this.equipment = [];
}
Now, instantiating becomes much shorter.
var players = [];
players[0] = new Character("Jon", 200, 50); //.name becomes Jon, .hp becomes 200, .mp becomes 50
Note that .equipment wasn't a part of the prototype's arguments, so it has to be treated like before.
players[0].equipment = ["cap", "T-shirt", "shorts"];
This is a good thing to do. During instantiation it's best to only put the things you'd most commonly change as arguments. So for a character, that would be the name and basic stats. Otherwise it becomes a long mess of arguments you don't even fill in half of the time.
Using objects in combination with each other
You could use many different objects to really start managing your data in a nice way. For example, let's add a data structure to store the equipment in.
function Equipment(name, type, description, hpeffect)
{
this.name = name; //The first this.name is this object's name property. The non-this name is the argument in function Equipment.
this.type = type;
this.description = description;
this.hpeffect = hpeffect;
}
var all_equipment_ever = []; //Some global variable somewhere maybe
all_equipment_ever["cap"] = new Equipment("Cap", "headwear", "This neat cap ups your HP by ten.", 10);
Now that we've invented some neat equipment, we can improve the Character object to make use of it.
function Character(charname, initialhp, initialmp)
{
this.name = charname;
this.hp = initialhp;
this.mp = initialmp;
this.equipment = [];
//Let's make a method to apply the stats of equipped equipment
this.equip = function(what)
{
var theitem = all_equipment_ever[what];
//Ex. if the what argument is "cap", theitem would contain all_equipment_ever["cap"]. Which is that object instance we made earlier.
this.equipment.push(theitem);
//array.push() is a function in JS to append new array items at the end of an array.
//So now all_equipment_ever["cap"] is pushed at the end of the empty equipment array, making this.equipment = [all_equipment_ever["cap"]]
this.hp += theitem.hpeffect; //This would add the HP that the item is supposed to give you to this character's HP
}
}
Let's use the equip method in practice. It allows us to do this:
var players = [];
players[0] = new Character("Jon", 200, 50);
players[0].equip("cap");
And boom. The instance of Character (players[0]) now has an instance of Equipment (all_equipment_ever["cap"]) stored in its .equipment property, adding 10 to his .hp thanks to that Equipment instance's .hpeffect.
...Uh, let's reword that sentence by using the object's property names.
Jon is now wearing a cap. It causes his HP to go up by 10.
Readable line | Complicated line | Associated code |
---|---|---|
Jon | Instance of Character | players[0] |
is now wearing a cap. | now has an instance of Equipment stored in its .equipment property | .equipment = [all_equipment_ever["cap"]] |
It causes his HP to go up by 10. | adding 10 to his .hp thanks to that Equipment instance's .hpeffect | this.hp += theitem.hpeffect; |
I hope this clarifies it all.
Unequipping, to finish things off
Note that of course we'd need an unequip() method which removes the equipment from the Character's equipment array and also removed those added stats again. Here it is, as a bonus:
this.unequip = function(what)
{
var theitem = all_equipment_ever[what]; //Get the item
var itemindex = this.equipment.indexOf(theitem); //Get the numerical position of the item in the array
this.equipment.splice(itemindex, 1); //Remove 1 item at this position from the array (so that would be our item)
}
See Methods of Array instances for info on indexOf() and splice(), and what their arguments mean.
Tutorial TODO list
- Try..catch
- Tests
- Create some clarifying pics if necessary