Eufloria > Eufloria Classic Mods

Level Programming Guides

(1/2) > >>

annikk.exe:
This topic is locked to keep it clean.
On each post, there is a link to the original discussion thread.
If you wish to discuss the guides, or ask a question, please post in the original thread!

Contents


* Guide To Making Levels For Absolute Beginners


* Getting Started
* Modifying values
* Add an asteroid
* Win/Lose condition, misc

* Troubleshooting Your Designs


* Introduction
* What have you just been working on?
* Finding error messages
* Analyse what your code is doing
* Comment out suspect parts
* Use Developer Mode
* Write down in words what your code ought to be doing
* Use the console to test values in-game
* Use a test MessageBox
* Assign asteroid names to the values of your variables
* Ask on the forums for help
* Sleep on it

* Intermediate Coding Guide


* while GameRunning() do
* for loops
* Arrays
* math.random
* math.sin, math.cos
* Build your own functions!

* Common Coding Tasks


* Send seedlings after a delay
* Perform tasks after different amounts of time
* Determine whether a number is odd or even
* Calculate the distance between the centers of two asteroids
* Calculate the distance between the closest edges of two asteroids
* Calculate how much send distance is required for Asteroid 1 to send Asteroid 2
* Intersection of two lines (2D)
* Intersection of line and plane (3D)
* Convert LevelDraw to ScreenDraw
* Concatenate variables/text in a MessageBox

* Annikk's Mechanics


* Read this first
* Infected AI
* Gravity/Collisions
* Parallax Scrolling
* Downloads


* Reserved

annikk.exe:

Click Here for Original Discussion Thread

Overwhelmed by the thought of trying to code your own levels?
Don't worry, Fluffy is here to halp.
Follow this guide and you'll be making levels in no time!
(If you actually try everything suggested here for yourself, it will take you about 40-60 mins to learn how to make levels.)

To start with we will create a very simple level, and then learn by adjusting values and playing the level each time to see what has changed.




1.
Click Start




2.
Click on "Run".




3.
Type "notepad.exe" into the box as shown, and click OK.




4.
Notepad opens.




5.
Enter the text shown below into Notepad.
If you would prefer to copy & paste the text (recommended) instead of typing it out yourself, click the "Spoiler" button below:

(click to show/hide)
--- Code: ---function LevelSetup()

-- Set Global Values
Globals.G.Asteroids=(0)
Globals.G.EnemyFactionsMin=(1)
Globals.G.EnemyFactionsMax=(1)


-- Asteroid 0 - starting asteroid
a = AddAsteroidWithAttribs(0,0, 0.7,0.6,0.5)
a.Owner = 1
a.TreeCap = 1
a:SetRadius(250)
a.SendDistance = 2500

a:AddSeedlings(90)


-- Asteroid 1
a = AddAsteroidWithAttribs(2000,0, 0.3,0.3,0.3)
a.Owner = 2
a.TreeCap = 2
a:SetRadius(450)
a.SendDistance = 2500

a:AddSeedlings(100)

end



function LevelLogic()

end

--- End code ---




6.
Now click "File" in the top left of Notepad, and choose "Save As".




7.
We need to make sure we save it in the right folder.
The picture below shows the correct location of the "Maps" folder.
Use the drop-down box at the top of the "Save As" window to navigate to the right folder.




8.
Give your level a name, and make sure that it ends with ".lua"
Set the file type to All Files.
Set Encoding to ANSI.

Then click the Save button.  Now it's time to check out our level.


9.
Load up Eufloria, choose "Custom Levels" from the main menu, and your new level should be waiting there.
Click on it to try playing it!




10.
When it loads up, this is what it should look like.




11.
If you send a scout to the asteroid next door, you should see that there are enemy seedlings orbiting it.





If you got this far, congratulations - you are through the biggest barrier, which is getting started.  It will be plain sailing from here!






Now that we've made a simple level, lets experiment with it by changing some values.



12.
To start with, lets change the number of seedlings that the Player starts with from 90 up to 500.  Make the change indicated below:




13.
Now that we've made the change, it is important that we remember to save the file.  Otherwise our changes won't be apparent when we try to play the level.




14.
Now go back to Eufloria and try loading up the level like before.  You should start with loads of seedlings this time!




15.
Next lets try to change the size of the Player's asteroid.  Make the change indicated below, and don't forget to save the level afterwards by clicking "File" and "Save", like in step 13.




16.
Now load the game up, and lo and behold your asteroid has swollen to epic proportions!




17.
Next lets try changing the maximum number of trees allowed on the Player's asteroid.  Make the change indicated below - make sure you change the right asteroid!  Then save the level again.




18.
Load it up in Eufloria and now we can plant up to 20 trees :>





Now try changing some of the other values yourself.  I would suggest to only change one thing at a time - that way if you accidentally break the level, it's easy to know what to undo to fix the problem.



19.
Hopefully by now you should be getting an idea of how this works.
You can kind of tell what each line means... it doesn't take a genius to work out that "SendDistance" is how far the asteroid can send seedlings.

However, one command is not so obvious, and that is the AddAsteroidWithAttribs command, which as you might guess is used to create asteroids and set their basic attributes.  Lets take a moment to look at that line, because it's a good example to teach us about the other commands as well.



Lets deal with the bits in brackets first.

There are 5 values seperated by commas.

The first two values are the X and Y coordinates of the asteroid - it tells the game where on the map the asteroid is located.  These have to be integers (whole numbers).

This map hopefully gives you an idea of what sort of values you can use.






20.
The next three values are the Energy, Strength, and Speed of the asteroid.



You can use any number between 0 and 1.  So you could have 0.5, or you could have 0.55555 or whatever you like really.




21.
Now that we've dealt with the brackets, lets look at the bit at the start:



The command starts with an "a = ".  The "a" is sort of like the name of the asteroid that we are creating.
Then we can use the various functions and property commands to tell the game what the asteroid is like.

This is what a.TreeCap is doing - we refer to the asteroid called "a", and then we tell the game that "a" has a Tree Cap of...whatever we set it to.






22.

You might notice that we use "a" a second time to make the second asteroid:



It doesn't matter if we use it twice in a row like that.  When we use it to make the second asteroid, we are just telling the game to "forget" which asteroid "a" is, because now this asteroid is called "a".  We just overwrite the old value by declaring a new one.

Then we can set the properties for the second asteroid using the same commands like before, such as "a.TreeCap" and "a:AddSeedlings()" and so on.

Note: You can give each asteroid a unique name if you wish, rather than just re-using "a" all the time.  This can actually be useful if you want to refer to them in your scripts later on.
fluffy = AddAsteroidWithAttribs is perfectly acceptable.  :>  fluffy.TreeCap and other commands will work without problems.


"a." (with a dot) is used to set properties, and "a:" (with a colon) is used for functions.  You must be careful not to mix them up, or the level won't load.
A complete list of the different commands you can use for asteroid properties can be found in the LUA Scripting Reference Sticky.




23.
Finally, the complete command looks like this:

NAME = AddAsteroidWithAttribs(Horizontal Position,Vertical Position,Energy,Strength,Speed)




24.
Now to try adding a new asteroid to your level.

You need to make sure you put it within the bounds of the function LevelSetup().  That is, it must appear after the LevelSetup() command, and before the end command, as shown.



Lets make an asteroid by typing in the code for it where the red arrow indicates.

First we should make a comment to remind ourselves later on that this is Asteroid 2 (remember we had asteroid 0, 1, and this will be asteroid 2).
So type in "-- Asteroid 2" like this:






25.
Now lets type the command to add an asteroid, and pick some values for the new asteroid.
I'll use coordinates 2000, -2000 and energy 0.8, strength 0.6, speed 0.9.






26.
To save some time, I'll copy and paste the commands I used on the previous asteroids.






27.
Now I'll change the values around a bit to make the asteroid the way I want it:






28.
Now lets save the level, load it up, and check out the new roid! :D







29.
There are some other nifty commands you can use in LevelSetup.
A really handy one is SetBackdropColour() which lets you change the background colour using 3 comma-seperated RGB values between 0 and 255.
Lets make the background red!






30.
And the fruits of our work so far....







31.
Maybe you noticed by now that the asteroid positions don't seem to be quite right.  Because of their large size the game is squashing them apart.
To prevent this behaviour, add this line to each of your asteroids:






32.
Save the level and try playing it.  Now the asteroids are positioned exactly where we specified them in our coordinates.  :>






33.
We have been studying the section called function LevelSetup(), which deals with the creation of the level and all the initial conditions.

Now it's time to have a look at the function LevelLogic() section.  This section lets you make things happen during play, such as scripted events.






34.
To begin with, lets make a Message Box appear after 5 seconds of game time has passed.
The messagebox could contain a greeting, and instructions for the player.


Add this code:
(click to show/hide)
--- Code: --- -- Greet the player
Timer = GetGameTime() + 5


while GetGameTime() < Timer do

coroutine.yield()
end

Pause()
MessageBox("Take the asteroid to the east to win.  Don't lose yours.")
WaitDialog()
Unpause()

--- End code ---




Load up the level to test it out:





35.
So what happened there?  Lets think about this for a minute before we move on.

a.
Well, first we created a variable called Timer (case sensitive!).

We set it to Game Time + 5, and since it is the first thing the game evaluates once it's running, Timer must have a value of 0:05 seconds.


b.
Then we used something called a While loop to create a delay.

We told the game, while the game time is less than Timer, just keep repeating these commands.


c.
The part which tells it the loop has ended is the coroutine.yield() command.

This command tells the game the loop has ended and the game should go back up and check if the conditions of the While statement are still being met.

We don't have any commands in between our While statement and our coroutine.yield().  So basically the game just does nothing for 5 seconds. :>


d.
Once the 5 seconds are up, the While loop exits at the end statement.



e.
Then next thing that happens is that the game pauses, a Message Box appears, and when the player clicks OK, the game unpauses again.





Look at the commands and read over this part a few times if you need to.  It is helpful if you can conceptualise what is going on here.




36.
The level is a bit easy at the moment.  Lets add a scripted event where a bunch of enemy seedlings spawn on Asteroid 1.
I'll make mine 20 seconds after the Message Box appears, and I guess I'll add about... 400 seedlings.
You might notice I've started adding comments as well, to remind myself what each part does later on.



Did you notice we used the :AddSeedlings command in LevelSetup() too?  Most of the commands used there are also usable here.  In this case, we weren't able to refer to it by a name like "a" because "a" had been replaced with a different asteroid, so instead we just tell the game which asteroid we mean with GetAsteroid(ID).  Hence, GetAsteroid(1):AddSeedlings(400) is the command to add 400 seedlings to asteroid 1.




37.
Lets save the level and see if the the enemies spawn correctly. :>







38.
If you would like to make it even more interesting, you can add the following line after:

GetAsteroid(1):SendSeedlingsToTarget(2,400,GetAsteroid(0))

This sends seedlings from Asteroid 1, belonging to player 2... it sends 400 of them, to Asteroid 0.





39.
At the moment, if the player takes over all the asteroids, the game just continues running.  There is no way to "win".

We will create a winning condition and a losing condition, so that the game can actually be won or lost by the player.

Add this code:
(click to show/hide)
--- Code: --- gamewon = 0

while gamewon == 0 do

coroutine.yield()
end

if gamewon == 1 then

Pause()
MessageBox("You have won.")
WaitDialog()
Unpause()

Quit(true)
end

if gamewon == 2 then

Pause()
MessageBox("You have lost.")
WaitDialog()
Unpause()

Quit(false)
end

--- End code ---



There are three new things here.

Quit(true) means the game ends and the player won.
Quit(false) means the game ends and the player lost.

If and then statements.  These check if something is true, and if so, does something.
So if the variable gamewon is equal to 1, then the player is told they have won and the game ends in victory.
But if the variable gamewon is equal to 2, it skips the first If statement and proceeds to the second If statement... resulting in the player being told they have lost, and the game ending in defeat.

You might also notice that in some places we use a single "=", and in other places we use the double: "==".
A single equals sign "=" is used to set values, whereas the double "==" is used to compare values.




40.
Now that we have created the winning and losing code, we need to have some way to trigger the victory or defeat.
For this example, we will say that if Player 1 gets asteroid 1, the player wins (gamewon = 1).  And if Player 2 gets Asteroid 0, the player loses (gamewon = 2).

The easiest way to check this is to use an entirely new function - seperate entirely from function LevelLogic - this new one is called function OnAsteroidTaken, and we place it below everything else.

Add this code:
(click to show/hide)
--- Code: ---function OnAsteroidTaken(id, owner)


if id == 1 and owner == 1 then
gamewon = 1
return
end

if id == 0 and owner == 2 then
gamewon = 2
return
end

end
--- End code ---





41.
function OnAsteroidTaken is triggered every time an asteroid changes owner.
We can put If statements in there to check the asteroid and the owner, and if appropriate, change the gamewon variable to either 1 or 2.

Once we change the gamewon variable to something other than 0, the While loop that we made in our function LevelLogic() for the victory/defeat code ends.  Straight after that loop ends, the gamewon variable is evaluated to see whether the player lost or won.  Go and look again at the code in step 39 and make sure you understand how this works.


Now you should understand the mechanism of our victory/defeat detection system.


Lets give it a try:









42.
Now you know enough to begin making your own levels.
I hope you will read the LUA Scripting Reference as well and see all the crazy things it's possible to change in this game, and let your imagination run wild :>


Happy coding,
-Fluffy

Click Here for Original Discussion Thread

annikk.exe:
Troubleshooting Your Designs

Click Here for Original Discussion Thread




1.  Introduction
2.  What have you just been working on?
3.  Finding error messages
4.  Analyse what your code is doing
5.  Comment out suspect parts
6.  Use Developer Mode
7.  Write down in words what your code ought to be doing
8.  Use the console to test values in-game
9.  Use a test MessageBox
10. Assign asteroid names to the values of your variables
11. Ask on the forums for help
12. Sleep on it








1. Introduction

So you made it through the beginners guide!  ...or maybe you didn't.  I can't possibly hope to cover every individual bug you might encounter, so instead I will cover the basic tools and techniques used to troubleshoot a map.

Understand that everyone gets bugs.  If you made it through all the examples in the beginners guide without a single bug along the way, you are definitely the exception rather than the rule!
Bugs are something you will constantly have to deal with, so it makes sense to get comfortable with troubleshooting them early on.



So.  You've got a bug.  Lets say for example your map doesn't load at all.  Where do you start?




2. What have you just been working on?

If you've just added 2 new lines of code, and now the level won't load anymore, the chances are high that the lines you just entered caused the problem.  Go and take another look at them.  Is there a typo?

Are you referring to any variables that haven't been assigned a value yet?  Did you use a dot instead of a colon or vice versa?  EG did you write GetAsteroid(0).AddSeedlings(100) instead of GetAsteroid(0):AddSeedlings(100)?  The difference is small but it can be sufficient to prevent the level from loading.

Look for common mistakes and correct any you find.




3. Finding error messages

Suppose your map won't load and you can't spot which part of your code is wrong.



Eufloria will typically give you an error message.  Read what this says!!  It will often give a number, this is the line number where the code causing the error is located so be sure to look for that.
If your text editor does not display line numbers, I suggest you download Notepad++.  It's free and awesome.

Eufloria will sometimes give you specific information about what caused the bug, sometimes even mentioning a specific variable that is causing the problem.  Then again, sometimes it does not give specific information, but is more generalised.

There are more error messages too - not just the popup error messages you see when your level fails to load.  Sometimes your level will load but stop working instantly, so any active scripting won't proceed.  In this case, you will probably be able to tell something is not right, but you have no clues as to what the problem is because the level managed to get past the initial loading, so there is no messagebox with the error message.

In this case, it's a good idea to check the console to look for the error message there.



The console is accessed by pressing the Tilde key.  (`)
The tilde key is the key to the left of "1" on most keyboards.


When you open the console, any recent error messages will be displayed there.  As with the above, it will sometimes be specific, other times general.. sometimes it mentions a line number, other times it does not.

Note: Occasionally an error message gets "stuck" in the console.  To check if there is a stuck error message, just type something into the console and press enter.  You can type anything you like, eg type in "fish" and press enter.  You should see an error message in response, because obviously Eufloria doesn't know any "fish" command...  if there was a stuck error message, you will also see this appear now.




4. Analyse what your code is doing

Try to think about all the possible values your variables might be assigned.  Will they ever run into a situation where a number is divided by zero?  If that case arises, you have a crash on your hands.



Try to spot logic flaws in your code.  If you have a condtional statement like "if x > y then", make sure that this condition is met when it's appropriate.  Think everything through, and take your time.  Avoid changing things randomly and hoping for the best - that almost never works.  Instead, work on your understanding of what is happening in the code, and let that guide your changes.




5. Comment out suspect parts

We covered commenting briefly in the Beginner's Guide.  If you put a double dash (--) at the start of a line, you "comment it out".  This means anything on that line written after the double-dashes will no longer be run and instead is just treated as a comment.
The aim of this troubleshooting approach is to comment out large sections of your code until you've stripped the level back to a state where it will load again.



Then, un-comment your code a few lines at a time until you discover the point where it no longer works.
This helps you to pinpoint which part of the code is causing the issue.




6. Use Developer Mode

Pressing CTRL-D during gameplay toggles Developer Mode on and off.
Once you have activated Developer Mode, you can use the Function keys F1-F12 to activate different useful functions.  F4 and F12 are the most useful ones.



Spend 2 minutes learning what these do, and your level designing will be that much easier going forward.




7. Write down in words what your code ought to be doing

Sometimes the simple act of writing things down helps you to realise where your code and your intentions differ.



Don't underestimate the usefulness of this exercise for both planning and troubleshooting!




8. Use the console to test values in-game

If your level loads but produces unexpected results, this can often be the best investigative tool.
Suppose you are trying to draw a line on the screen, and the line's location is to be based on the position of an asteroid.
Maybe you have an "angle" variable which is calculated, and then used in a subsequent calculation to figure out the coordinates of the line.

At certain points, you know that "angle" ought to be within a certain range.  For example, at the start of the level you expect (from your plans) that "angle" will have a value of about 50.

Test whether it really does have a value of 50!  Load up your level and open the console.
Then type the following: print (angle)
Press enter and the console will show the value of the "angle" variable.



Maybe it turns out to be 12484382834832483248324.12321 - then you KNOW something isn't right!  Time to study the way "angle" is being calculated...




9. Use a test MessageBox

Eufloria stops running your level code if it encounters an error.
It's possible to exploit this behaviour to see "how far through the code does it get before reaching the error?"
If you have a long section of code and you know at a certain point it is failing, try adding the following line at an opportune point:


--- Code: ---MessageBox("Fish")
--- End code ---

Now run the level.  If you see a Fish messagebox, you know that the code ran fine up until the message box....  so you know the code above it must be fine, and the code below must contain the error.  Go move the Fish command further down a bit, and then try again.  Repeat in this manner until you find the line that is causing the problem.






10. Assign asteroid names to the values of your variables

If you have a variable that you want to monitor closely during testing, one neat trick is to place a command like this inside your LevelLogic's While loop:


--- Code: ---GetAsteroid(0).Name = MyVariable
--- End code ---

Where "MyVariable" is the variable you want to monitor.
Now when the game is running, the name of the asteroid will constantly change to reflect the value of MyVariable.  This allows you to see the values fluctuating in real time while the code is running, and can help a great deal in spotting problems.




11. Ask on the forums for help

That's what we're here for!  You could be the greatest coder in the world, sometimes it just takes another pair of eyes to spot the problem.



When asking for help, please ALWAYS post the level file you are working on.  Don't just post the part of the code you think is causing the problem.  Maybe you don't think the other stuff is relevant but people - including me - have assumed that before, and then later discovered some of that "irrelevant" code was actually the root cause of the problem.  Learn from my mistake and just attach the whole .lua file.  :>




12. Sleep on it

Hey, soldier.  Been working on this for a while?

Is it 3am?



4am?

If it's really late and you've been working on this for ages, sometimes the best thing to do is to just call it a night and get some sleep.  When you wake up, your mind will be more focused, and last night's impossible barriers become mere toy puzzles in the face of your well-rested intellect.  Going to sleep is not giving up.. it's a tactical retreat.  :>


Click Here for Original Discussion Thread

annikk.exe:


Click Here for Original Discussion Thread

Hi coders!




Required Knowledge
This guide is intended for people who have covered the beginner's guide, done a little basic map-making of their own to get used to things, and now are ready to move on and learn about the structural commands and techniques needed for more advanced scripting.

Scope
This guide looks almost exclusively at function LevelLogic() - it is assumed that a suitable LevelSetup() already exists with appropriate asteroids added, etc.  It will cover the syntax of various important structural commands, various commands from the maths library, and usage examples.

Could be heavy going..!
Yea, I know you're eying the size of the scrollbar nervously.  This IS a long document.  However, I have split this guide into numbered chunks, each of which should take no longer than 20 minutes to read and thoroughly understand.  Please, give section 1 a read through, and hopefully you will see that it's not all that difficult.  :>  I promise to regale you with my distinctive brand of humour along the way.  Or something.




Contents:


* while GameRunning() do
* for loops
* Arrays
* math.random
* math.sin, math.cos
* Build your own functions!


1.  While GameRunning() do

i)

--- Code: ---function LevelLogic()

while GameRunning() do

-- later on we'll put some commands here!

coroutine.yield()
end
end
--- End code ---


The commands above create a loop that is run continuously, at a rate of 60 times per second, until the game ends.

This specific type of While loop is useful for all sorts of scripting, and I recommend using it as a base for any advanced scripting level.  It's the only while loop you will need in your code - in fact I'd suggest you should avoid using other While loops altogether.

Any code you place after this loop's end will never run.

--- Code: ---function LevelLogic()

while GameRunning() do

-- later on we'll put some commands here!

coroutine.yield()
end

-- CODE PLACED HERE WILL NEVER RUN

end
--- End code ---
That is because the condition of the while loop is "while the game is running".  The game is always running!  :>  The game is only considered to have ended when the player quits, or a Quit() command is triggered, causing the player to return to the menu.


In the beginner's guide, we looked at how you can use the OnAsteroidTaken(id,owner) function to trigger a Victory or Loss.
Now we'll look at another method of doing this using a while GameRunning() do loop, which will work much better alongside our other scripts.



ii)

Suppose we want the player to win if they've taken over the whole galaxy, and lose if they've lost all their asteroids.
First lets make a While GameRunning() do loop and populate it with comments to remind ourselves what each bit of code needs to do, and where it needs to be inserted.



--- Code: ---function LevelLogic()

while GameRunning() do
-- if player 1 has conquered whole galaxy then
-- Quit(true)

-- if player 1 has lost all asteroids then
-- Quit(false)

coroutine.yield()
end
end
--- End code ---



iii)
So how to tell if the player has conquered the whole galaxy?


One way would be to check if the number of asteroids the player owns is equal to the number of asteroids in the level.
If it is, that means the player must own every asteroid - fulfilling the victory condition.


--- Code: --- if GetEmpire(1):GetNumOwnedAsteroids() == 20 then
Pause()
MessageBox("You have won")
WaitDialog()
Unpause()
Quit(true)
end
--- End code ---

This would work fine if the number of asteroids in the level is indeed 20.

But what if you decide later on to go back and add more asteroids?  If there are 25 asteroids in the level but the If statement is only looking for 20, then the player will win when there are 5 asteroids still to conquer.
So although this would work, it's not ideal because it means every time we add or remove asteroids from the level we also have to change the number of asteroids the victory condition is checking for.

So what other method could we use that avoids this problem?



iv)
Well, maybe we could check how many asteroids are owned by the other empires.
If this map only has empire 2 in it, then maybe we could check it like this:


--- Code: --- if GetEmpire(2):GetNumOwnedAsteroids() == 0 then
Pause()
MessageBox("You have won")
WaitDialog()
Unpause()
Quit(true)
end
--- End code ---

Then when Empire 2 has no asteroids left, player 1 would win.  Doing it like this would mean subsequent changes to the number of asteroids in the level would not break the win condition.  :>

But there's a potential problem with this too.  Suppose there are 20 asteroids and the player conquers 5 of them, then goes straight on to conquer all 5 of Empire 2's asteroids.  That leaves 10 asteroids uncolonised, ie they belong to the greys (empire 0).

The player would win at this point even though they've only conquered half the galaxy - because Empire 2 has 0 asteroids left.





This behaviour isn't bad, but the behaviour we were actually looking for is "if the player has conquered the whole galaxy"...



v)
If we really want the player to have to take over the whole galaxy to score a win, we would need to make a modification to our If statement to include Empire 0:


--- Code: --- if GetEmpire(2):GetNumOwnedAsteroids() == 0 and GetEmpire(0):GetNumOwnedAsteroids() == 0 then
Pause()
MessageBox("You have won")
WaitDialog()
Unpause()
Quit(true)
end
--- End code ---

That's better.  :>  We're saying If Empire 2 doesn't have any asteroids left, and Empire 0 doesn't have any asteroids left, then the player wins.
Now this will mean the player must conquer every asteroid in the level in order to win.


vi)
Ok but what if you have Empire 3, too?  What then?  The statement in its current form doesn't check for Empire 3 at all, so if there is an Empire 3 in the level then the player might win even when Empire 3 still owns some asteroids!

We could make another modification to our If statement to also take Empire 3 into account:


--- Code: --- if GetEmpire(2):GetNumOwnedAsteroids() == 0 and GetEmpire(3):GetNumOwnedAsteroids() == 0 and GetEmpire(0):GetNumOwnedAsteroids() == 0 then
Pause()
MessageBox("You have won")
WaitDialog()
Unpause()
Quit(true)
end
--- End code ---




So there's our victory condition.



vii)
What about the loss condition?





Well, in our comments we said that the player should lose "if the player has lost all asteroids".

So we can represent that like this:


--- Code: --- if GetEmpire(1):GetNumOwnedAsteroids() == 0 then
Pause()
MessageBox("You have lost")
WaitDialog()
Unpause()
Quit(false)
end
--- End code ---

Looking at this code, it seems it should work no matter how many asteroids or empires we add or remove.  :>
The loss condition is a lot simpler to make than the win condition..





viii)
Lets return to the template that we wrote out at the start:

--- Code: ---function LevelLogic()

while GameRunning() do
-- if player 1 has conquered whole galaxy then
-- Quit(true)

-- if player 2 has lost all asteroids then
-- Quit(false)

coroutine.yield()
end
end
--- End code ---



ix)
...and populate it with the Win Condition we made in step vi)....

--- Code: ---function LevelLogic()

while GameRunning() do
-- win condition
if GetEmpire(2):GetNumOwnedAsteroids() == 0 and GetEmpire(3):GetNumOwnedAsteroids() == 0 and GetEmpire(0):GetNumOwnedAsteroids() == 0 then
Pause()
MessageBox("You have won")
WaitDialog()
Unpause()
Quit(true)
end

-- if player 2 has lost all asteroids then
-- Quit(false)

coroutine.yield()
end
end
--- End code ---



x)
... and the Lose Condition from step vii).  :>

--- Code: ---function LevelLogic()

while GameRunning() do
-- win condition
if GetEmpire(2):GetNumOwnedAsteroids() == 0 and GetEmpire(3):GetNumOwnedAsteroids() == 0 and GetEmpire(0):GetNumOwnedAsteroids() == 0 then
Pause()
MessageBox("You have won")
WaitDialog()
Unpause()
Quit(true)
end

-- lose condition
if GetEmpire(1):GetNumOwnedAsteroids() == 0 then
Pause()
MessageBox("You have lost")
WaitDialog()
Unpause()
Quit(false)
end

coroutine.yield()
end
end
--- End code ---


Now we've made a working Win/Lose condition using a while GameRunning() do loop.  :>  This will set us up for scripting some advanced behaviours.






This data tunnel is what advanced coding actually looks like...






2.  For loops



i)
The for loop is an essential tool when it comes to coding.
A typical for loop might look like this:


--- Code: ---for i = 0,9 do

-- some code goes here

end
--- End code ---

As you can see, it too needs an "end" to close it off.
What are the numbers about though?  And what's that "i =" doing there?


ii)
This type of loop counts from the first number (0) to the second number (9), and runs the code within the loop for each number counted.

The example above would start with the counter called "i" on "0", and then run all the code inside the loop.  Then, it would increment the counter by one, and run through all the code again.  Then it increments "i" again, runs the code again, and so on.
After the code has been run for the 10th time (ie "i" = 9) then the loop will stop and exit at the end



iii)
The first and most obvious use of a for loop is to run a piece of code multiple times.  For example, the code below would produce 3 message boxes:

--- Code: --- for i = 0,2 do
MessageBox("Hallo Fluffy")
end
--- End code ---




iv)
The second thing that for loops are useful for, is to change values for lots of different entities that are numbered.  For example, asteroids.  :>
Asteroids are numbered according to their ID.  Suppose we had 100 asteroids and wanted to change the Send Distance of all of them at once.  Here is how we could do it:


--- Code: --- for i = 0,99 do
GetAsteroid(i).SendDistance = 5000
end
--- End code ---

What will happen is that each time the loop runs through, "i" is increased by 1, and then when we refer to "GetAsteroid(i)" it's simply getting whichever asteroid ID currently corresponds to the value of "i".

So on the first run through of the loop, the command effectively reduces to this:

--- Code: --- GetAsteroid(0).SendDistance = 5000
--- End code ---

On the second pass of the loop, it would resolve to this:

--- Code: --- GetAsteroid(1).SendDistance = 5000
--- End code ---

...and so on.



v)
Thus, this:

--- Code: --- for i = 0,99 do
GetAsteroid(i).SendDistance = 5000
end
--- End code ---

...is just a (much) shorter way of writing this:

--- Code: ---GetAsteroid(0).SendDistance = 5000
GetAsteroid(1).SendDistance = 5000
GetAsteroid(2).SendDistance = 5000
GetAsteroid(3).SendDistance = 5000
GetAsteroid(4).SendDistance = 5000
GetAsteroid(5).SendDistance = 5000
GetAsteroid(6).SendDistance = 5000
GetAsteroid(7).SendDistance = 5000
GetAsteroid(8).SendDistance = 5000
GetAsteroid(9).SendDistance = 5000
GetAsteroid(10).SendDistance = 5000
GetAsteroid(11).SendDistance = 5000
GetAsteroid(12).SendDistance = 5000
GetAsteroid(13).SendDistance = 5000
GetAsteroid(14).SendDistance = 5000
GetAsteroid(15).SendDistance = 5000
GetAsteroid(16).SendDistance = 5000
GetAsteroid(17).SendDistance = 5000
GetAsteroid(18).SendDistance = 5000
GetAsteroid(19).SendDistance = 5000
GetAsteroid(20).SendDistance = 5000
GetAsteroid(21).SendDistance = 5000
GetAsteroid(22).SendDistance = 5000
GetAsteroid(23).SendDistance = 5000
GetAsteroid(24).SendDistance = 5000
GetAsteroid(25).SendDistance = 5000
GetAsteroid(26).SendDistance = 5000
GetAsteroid(27).SendDistance = 5000
GetAsteroid(28).SendDistance = 5000
GetAsteroid(29).SendDistance = 5000
GetAsteroid(30).SendDistance = 5000
GetAsteroid(31).SendDistance = 5000
GetAsteroid(32).SendDistance = 5000
GetAsteroid(33).SendDistance = 5000
GetAsteroid(34).SendDistance = 5000
GetAsteroid(35).SendDistance = 5000
GetAsteroid(36).SendDistance = 5000
GetAsteroid(37).SendDistance = 5000
GetAsteroid(38).SendDistance = 5000
GetAsteroid(39).SendDistance = 5000
GetAsteroid(40).SendDistance = 5000
GetAsteroid(41).SendDistance = 5000
GetAsteroid(42).SendDistance = 5000
GetAsteroid(43).SendDistance = 5000
GetAsteroid(44).SendDistance = 5000
-- ok i can't be bothered writing them all out.
-- hopefully you get the idea...
-- ...
GetAsteroid(97).SendDistance = 5000
GetAsteroid(98).SendDistance = 5000
GetAsteroid(99).SendDistance = 5000
--- End code ---



vi)
Finally, just so you're aware, you don't have to use "i" for the counter.  You can use whatever name you want.  For example:


--- Code: --- for fluffy = 0,99 do
GetAsteroid(fluffy):AddSeedlings(fluffy)
end
--- End code ---

That would put 0 seedlings on asteroid 0, 1 seedling on asteroid 1, 2 seedlings on asteroid 2, etc.
It doesn't matter that we called the counter "fluffy" instead of "i".  It will still work just fine.  :>




This explosion is HUGE....




3.  Arrays



i)
Arrays are special variables that have lots of different "slots" for storing values, rather than just being one big slot like the variables we've seen so far.
Another way to think about arrays is to imagine them as numbered lists.




ii)
Lets look at some array syntax.


Before you can use an array, you must first declare it like this:

--- Code: --- fluffy = {}
--- End code ---
Note the unusual shaped brackets.  This creates an empty array called fluffy.

The usual place to put this command is anywhere in function LevelSetup(), but for now, to keep everything together, I'll put mine at the very top of function LevelLogic().



--- Code: ---function LevelLogic()

-- declare the array
fluffy = {}


while GameRunning() do
-- some commands go here later
coroutine.yield()
end
end
--- End code ---




iii)
Now that we've initialised the fluffy array, we can start assigning values to the slots in it.  But how to refer to the different slots in fluffy?

The answer is with some square brackets tagged on the end like this: fluffy[0]
That would refer to slot 0 of fluffy.

From now on when we refer to the array, we always refer to the slot number that we are interested in as well.
By convention, we usually start with slot 0.




So we can use commands like these:


--- Code: --- fluffy[0] = 2
fluffy[1] = 4
fluffy[2] = 7
fluffy[3] = 9
fluffy[4] = 5
--- End code ---





iv)
Then, when we want to know about the value in a slot, we can test it using the same square brackets as above.  For example:


--- Code: --- if fluffy[0] == 2 then
MessageBox("w00t!")
end
--- End code ---


So what practical use is this?
Well, as I discovered, it turns out there are all sorts of reasons why you might want to store things in an array like this.
To illustrate why, and also to show you a good example of using an array to do useful work, lets implement an example advanced behaviour using some arrays.

What we will do is create a mechanic whereby the player's lasermine can fly from one asteroid to another, and each asteroid they fly to changes the background to a different colour.

So when the game starts, the player's lasermine is orbiting the first asteroid, and the background is black.  When it flies to a nearby second asteroid, the background turns red.  When it flies to the next asteroid, the background turns blue, and so on.

We will store the colours in three arrays named red, green and blue because those are nice logical names that we can plug into our SetBackDropColour command later on.


v)
In this example lets assume there are 5 asteroids in the level, numbered 0, 1, 2, 3 and 4.
At the top of our function LevelLogic(), before the start of the while loop, I'll initialise the arrays and assign some values to them.


--- Code: ---function LevelLogic()

-- initialise arrays
red = {}
green = {}
blue = {}

-- assign values for the different slots in the arrays
red[0] = 0
green[0] = 0
blue[0] = 0

red[1] = 255
green[1] = 0
blue[1] = 0

red[2] = 0
green[2] = 255
blue[2] = 0

red[3] = 0
green[3] = 0
blue[3] = 255

red[4] = 127
green[4] = 0
blue[4] = 127




while GameRunning() do

-- later on we'll put some commands here!

coroutine.yield()
end

end
--- End code ---




vi)
Now we'll just need to make some code in our while loop to check if the player has a mine at any of the asteroids.
Here we can combine the for loops we learned about earlier with the arrays we've just initialised.


--- Code: ---function LevelLogic()

-- initialise arrays
red = {}
green = {}
blue = {}

-- assign values for the different slots in the arrays
red[0] = 0
green[0] = 0
blue[0] = 0

red[1] = 255
green[1] = 0
blue[1] = 0

red[2] = 0
green[2] = 255
blue[2] = 0

red[3] = 0
green[3] = 0
blue[3] = 255

red[4] = 127
green[4] = 0
blue[4] = 127


while GameRunning() do

-- Check all the asteroids in turn
for i = 0,4 do
-- if an asteroid has a player mine on it...
if GetAsteroid(i):GetNumMines(1) > 0 then
-- set the backdrop colour to the values for slot i in the red, green and blue arrays
SetBackdropColour(red[i], green[i], blue[i])
end
end

coroutine.yield()
end

end
--- End code ---


And that is how we can use arrays to do useful things for us.
You can download a playable level file containing this example script and check out the functionality for yourself.  Click here to download.






vii)
Before we finish up with arrays, lets look at how you might set the values for hundreds of slots in an array using a formula placed inside a for loop.

Imagine the example above, but instead of 5 colour-changing asteroids, we have 500 colour-changing asteroids!
Clearly setting these manually would take a very long time.

Therefore it becomes necessary to think up some way to set the colours for all 500 at once.

Lets take the red array as our example, and figure out some way to generate different values for all 500 slots.


--- Code: ---for i = 0,500 do
red[i] = ?
end
--- End code ---

Any ideas?  Well, the very most simple thing we could do is to make the slot [i] equal to the value i itself.


--- Code: ---for i = 0,500 do
red[i] = i
end
--- End code ---

Then slot 0 would contain the value 0, slot 230 would contain the value 230, and so on.



viii)
That's a bit boring though - and besides, Asteroid 500 would have the corresponding red value of 500, and that's more than the maximum 255 that SetBackdropColour is expecting.
Maybe we can use some maths to make a more interesting series of values!


--- Code: ---for i = 0,500 do
red[i] = 1 / i
end
--- End code ---

That should be pretty cool.  Imagine what this would work out as, for all the different values of i.
If i = 2, then the calculation would be 1 / 2 = 0.5
If i = 5, then the calculation would be 1 / 5 = 0.2
If i = 10, then the calculation would be 1 / 10 = 0.1
If i = 100, then the calculation would be 1 / 100 = 0.01



ix)
There's a problem with this though.
What do you think happens when i = 0?

If i = 0, then the calculation would be 1 / 0 = ........

Dividing by zero makes Eufloria crash.  It's because if you divide something by zero, the answer is always infinity.




To combat this problem, we can make a change:


--- Code: ---for i = 0,500 do
red[i] = 1 / (i + 1)
end
--- End code ---


Why do you think we need "(i + 1)" and not simply "i" on its own?

We add 1 to i.  That way on the first cycle it works out as 1 / 1, which is just a nice harmless 1 instead of an infinity.  :>
The curly brackets just mean that part of the calculation will be worked out first, as a seperate chunk - before the result is used in the rest of the calculation.



x)
This calculation will then give us an array called red that has all its slots filled with values between 0 and 1.
This is ideal mathematically, because it means we can just multiply in the range that we need.  In this case, SetBackdropColour expects values between 0 and 255.
So we just multiply each value by 255:


--- Code: ---for i = 0,500 do
red[i] = 1 / (i + 1)
red[i] = red[i] * 255
end
--- End code ---


Then, using some similar example calculations as we used above...

If i = 0, then the calculation would be (1 / (0 + 1)) * 255 = 1 * 255 = 255
If i = 1, then the calculation would be (1 / (1 + 1)) * 255 = 0.5 * 255 = 127
If i = 4, then the calculation would be (1 / (4 + 1)) * 255 = 0.2 * 255 = 51
If i = 9, then the calculation would be (1 / (9 + 1)) * 255 = 0.1 * 255 = 26
If i = 99, then the calculation would be (1 / (99 + 1)) * 255 = 0.01 * 255 = 3

Hopefully you should be able to see from this selection of examples that this gives us a nice smooth gradiant of different values.  The asteroids with the lowest ID numbers will be reddest.  :>



Multiple arrays can be combined to represent a matrix.  We'll cover matrix usage in a future Advanced Guide.


It's up to you to find your own formulas to assign values for your arrays.  The maths sections below will help you with this.






4.  math.random
i)
The command math.random(a,b) generates a random number between a and b.






So for example, the following command would mean that the variable "MyRandomNumber" is assigned a value between 1 and 6.


--- Code: ---MyRandomNumber = math.random(1,6)
--- End code ---

This is the equivalent of rolling a six-sided dice.  :>


ii)
So what use is this?
Well, it's useful for all sorts of things.  One good example is randomising the stats of your asteroids.  This can make the level different every time it is played, adding variety.

Suppose we have an asteroid like this one...


--- Code: ---a = AddAsteroidWithAttribs(1500,1200, 0.5,0.5,0.5)
a.owner = 1
a.TreeCap = 3
a.radius = 200
a.SendDistance = 2000
a.Moveable = false

a:AddSeedlings(20)
--- End code ---


We could make some of the stats random, like this:



--- Code: ---a = AddAsteroidWithAttribs(1500,1200, 0.5,0.5,0.5)
a.owner = 1
a.TreeCap = math.random(2,5)
a.radius = math.random(100,450)
a.SendDistance = 2000
a.Moveable = false

a:AddSeedlings(math.random(10,50))
--- End code ---

Now the asteroid will have a different treecap, radius and number of seedlings each time we play.  :>



iii)
How else could we improve this code?

Well, we could randomise the send distance, too.
That would be like this:


--- Code: ---a.SendDistance = math.random(1500,2500)
--- End code ---

But the problem with that approach is that we lose the relationship between an asteroid's radius, and its Send Distance.  It's visually pleasing to have asteroids with send distances proportional to their size.



So maybe we could do something else instead, that preserves the relationship between size and send distance...


--- Code: ---a.radius = math.random(100,450)
a.SendDistance = a.radius * 10
--- End code ---

This means that if the asteroid spawns with a radius of 100, its send distance would be 1000; if it spawns with radius 250, its send distance would be 2500, and so on.
This technique allows us to model the send distance after the asteroid size, even when we don't know in advance exactly what size the asteroid will be.




iv)
What else could we do to make it even more random?

Well, we could try adjusting the X and Y coordinates of the asteroid.
So our a = AddAsteroidWithAttribs(1500,1200, 0.5,0.5,0.5) becomes this:


--- Code: ---a = AddAsteroidWithAttribs(math.random(-1500,1500),math.random(-1200,1200), 0.5,0.5,0.5)
--- End code ---

Negative numbers like those in the line above work fine in math.random.  :>

But, there is a problem with randomising asteroid positions, and that is that there's a chance asteroids may spawn on top of each other!
This clearly will not do, so to fix it we can change the Moveable command from false to true.


--- Code: ---a.Moveable = true
--- End code ---

Then, if asteroids spawn on top of each other, the game will move them apart for us.  :>



v)
Lets take a look at what we've got so far:


--- Code: ---a = AddAsteroidWithAttribs(math.random(-1500,1500),math.random(-1200,1200), 0.5,0.5,0.5)
a.owner = 1
a.TreeCap = math.random(2,5)
a.radius = math.random(100,450)
a.SendDistance = a.radius * 10
a.Moveable = true

a:AddSeedlings(math.random(10,50))
--- End code ---

That's pretty damn random!

We might get a tiny asteorid with treecap 5 and 50 seedlings to the northeast, or we might get a large asteroid with treecap 2 and 10 seedlings to the south...  And so forth.

There's one more thing I want to show you while we're looking at math.random, and that is the question of how to randomise asteroid attributes effectively.




vi)
Consider this command:


--- Code: ---a = AddAsteroidWithAttribs(math.random(-1500,1500),math.random(-1200,1200), math.random(0,1),math.random(0,1),math.random(0,1))
--- End code ---

Ok so it's a long one, but basically if you study that, you should get that the asteroid attributes should be between 0 and 1.
Well, that's correct.  But there's a problem that you would quickly discover if you were to use this type of command on one of your asteroids.  See if you can figure out what it is.  :>  You'll be doing well if you manage, because I haven't explicitly mentioned it yet...

In the mean time, lets reorganise that line so it's much easier to read:



--- Code: ----- generate X and Y
x = math.random(-1500,1500)
y = math.random(-1200,1200)

-- generate stats
energy = math.random(0,1)
strength = math.random(0,1)
speed = math.random(0,1)

-- add the asteroid
a = AddAsteroidWithAttribs(x,y, energy,strength,speed)
--- End code ---

That's better.  Did you spot what the problem could be?








vii)
The problem we will face is that the asteroid's attributes will be set to either total maximum (1) or nothing whatsoever (0).
There's no in between!

To have nice stats for our asteroids, we actually wanted values between 0 and 1 - not 0 or 1 themselves!
More generally, we can say that the math.random(a,b) command generates an integer (a whole number) between A and B.

So suppose we used math.random(1,5)

Values we might get from that command:


* 1
* 2
* 3
* 4
* 5
Values we definitely WOULD NOT get from that command:


* 0
* 7
* 1.5
* 3.141592653
* -3

Hopefully this is crystal clear to you now.  Assuming so, lets press on and solve the asteroid attributes problem!



viii)
If we really need random values like 0.3, we are going to have to find a way to make them out of integers.
Here's one way:


--- Code: ----- generate stats
energy = math.random(0,10) / 10
strength = math.random(0,10) / 10
speed = math.random(0,10) / 10
--- End code ---

So lets follow through what happens here.
Take energy for our example.  First, a random number between 0 and 10 is generated; lets say we generate a 6.
Then, that 6 is divided by 10.  What's 6 divided by 10?  It's 0.6!
So then the asteroid would have 60% energy.




ix)
The problem with that approach, once again, is that simply randomising the asteroid's attributes sometimes results in tiny asteroids with awesome stats, and huge asteroids with rubbish stats.
This breaks the relationship we talked about earlier between the size of an asteroid, and its quality.

What do we really need, then?
Lets think about this.  It's important to put the solution into words (and perhaps diagrams) before we try to code it.

We need asteroids to have their stats correspond with their size.  But we don't simply want flat values for energy, strength and speed - we want the proportions of each stat to be random.
So for example, we want an average sized asteroid to have stats like 0.3,0.7,0.5 - NOT 0.5,0.5,0.5.

I know a good solution to this problem, but instead of me telling you what it is yet again, I'm going to leave this problem open-ended and see if you guys can work it out.  :>  If you've read this far down the guide you already know everything you need to know in terms of commands needed to code it.




5.  math.sin, math.cos
i)
Maybe you remember these dreaded symbols from school.  If you're anything like me, the "sin" and "cos" buttons on my calculator taunted me back in maths class with their mystery.  Nobody ever really bothered to explain to me what they actually are.  I mean, what are they?  Sure, you put numbers into them and a result magically comes out, but how?

Incidentally, I had forgotten just about everything I used to know about their usage when I started teaching myself to code in lua.  I had to figure this out on my own from researching it on the internets.  So even if you've never heard these terms before in your life, don't worry.  I'll help you to understand them.  :>

The first half of this section will focus on helping you to conceptualise what sine and cosine are.
The second half will focus on using the calculations in your scripts to do useful (and often spectacular!) work.



ii)
The commands might be used inside of a while GameRunning() do loop, as follows:


--- Code: ---x = math.sin(GetGameTime())
y = math.cos(GetGameTime())
--- End code ---

...or to make it slightly clearer, lets assign the game time to a variable first:


--- Code: ---time = GetGameTime()
x = math.sin(time)
y = math.cos(time)
--- End code ---

We feed a value into the math.sin or math.cos (in this case, time).
Then we record the result to a variable, in this case x and y respectively.

What sort of values will we get for x and y as the Game Time proceeds?



iii)
It will always be a value between -1 and 1.  So it might be 0.33351, or it might be -0.778391, or it might even be 0.  It all depends on what value is fed into it.
If sin is plotted on a graph, it produces a wavy line:



This wavy line repeats itself for as long as the value fed in (eg GetGameTime()) increases.
The graph for cos is almost exactly the same.  The only difference is that it starts with the line in a different position.  Don't worry about this for now, it will all become clear (hopefully!)



iv)
Lets proceed to conceptualising how this "value between -1 and 1" is calculated.


Imagine you are a bird hovering above a field.

In the field below you is a circular train track.

A train drives in circles round the track.




The distance the train has travelled so far is what we will feed into our sin and cos to produce the values between -1 and 1.

math.sin(DistanceTravelled) is equal to wherever on the vertical axis the train would be after travelling that far.
math.cos(DistanceTravelled) is equal to wherever on the horizontal axis the train would be after travelling that far.


So for example, if math.cos(DistanceTravelled) = -1, the train must be on the west side of the track.


Lets look at another diagram to try to visualise this better:





v)
That's the whole deal.
Sin and cos are simply representations of each axis when imagining travelling round and round in a circle.

So if the train had gone round the circle exactly once, the output would be the same as if it had gone round the circle exactly twice, or exactly 3 times.

Please download and run the level file attached to this post called "sincos example.lua".  Click here to download
Play the level in Eufloria and you can see another example of what sin and cos do.  Check the names of each asteroid to see what they represent.



vi)
The commands math.sin() and math.cos() both expect a number inside their brackets, as we've seen.
This input is technically thought to be radians, rather than train tracks or seconds of game time, but in reality you can use any unit of measurement you want and it will be considered as the radians-equivalent.  Time is a very common thing to use as the input for sin and cos, because it continually counts up at a steady pace, making it ideal for achieving regular pulsing motions.

Now lets look at some useful things we can do with this pulsing effect.




vii)
First of all lets build our usual wrapper for this to go into:


--- Code: ---function LevelLogic()
while GameRunning() do

-- some code goes here later!


coroutine.yield()
end
end
--- End code ---


Now we'll record the game time to a variable called time...


--- Code: ---function LevelLogic()
while GameRunning() do

-- record the time
time = GetGameTime()


coroutine.yield()
end
end
--- End code ---


Now lets create a variable called.... oh... I don't know, I guess we'll go with something generic like input.



--- Code: ---function LevelLogic()
while GameRunning() do

-- record the time
time = GetGameTime()

-- calculate a value for "input" by taking the math.sin of time
input = math.sin(time)

coroutine.yield()
end
end
--- End code ---


A good start.  Now the variable called input will pulse between -1 and 1 as the game progresses.

1

-1

In this graph, the X-axis represents game time.  The Y-axis represents the value we'll get for input.



viii)
Now that we've got our pulsing variable, lets figure out something that it could control.

How about the radius of an asteroid?
We could make an asteroid grow bigger, then smaller... then bigger, then smaller...

For that, values between -1 and 1 are no good.  We need values between, say, 50 and 500!
Time to do some maths to change input to our desired range of values.


--- Code: ----- input is a value between -1 and 1

input = input + 1
-- now input is a value between 0 and 2

input = input / 2
-- now input is a value between 0 and 1

input = input * 450
-- now input is a value between 0 and 450

input = input + 50
-- now input is a value between 50 and 500!
--- End code ---

I did it in some slow steps there to make it easy to follow, but to make things a bit neater, I could just bundle all of that into one line like this:


--- Code: ---input = (((input + 1) / 2) * 450) + 50
--- End code ---



iv)
Now lets fire that line into the rest of our code:



--- Code: ---function LevelLogic()
while GameRunning() do

-- record the time
time = GetGameTime()

-- calculate a value for "input" by taking the math.sin of time
input = math.sin(time)
-- now input is between -1 and 1

input = (((input + 1) / 2) * 450) + 50
-- now input is between 50 and 500


coroutine.yield()
end
end
--- End code ---

Finally, lets change the radius of Asteroid 0 using our "input".



--- Code: ---function LevelLogic()
while GameRunning() do

-- record the time
time = GetGameTime()

-- calculate a value for "input" by taking the math.sin of time
input = math.sin(time)
-- now input is between -1 and 1

input = (((input + 1) / 2) * 450) + 50
-- now input is between 50 and 500

GetAsteroid(0).radius = input


coroutine.yield()
end
end
--- End code ---


And there we have it.  Download the level file "pulse example.lua" and play it in Eufloria to see this code in action.  Click here to download.




6.  Build your own functions!
i)
This is it.  This is the part where you realise you are actually inside the matrix.



Well, I guess we'll start at the beginning.  What is a function?


A function is a container for code.
You put code inside the container, and then you "call" the function from elsewhere.

This very general description likely makes little sense to you for now.  Don't worry!   Lets look at some examples.



ii)
For our first example, lets imagine it's the start of the game and you want to bring up a MessageBox to greet the player.
Your function LevelLogic() might look like this:



--- Code: ---function LevelLogic()

Pause()
MessageBox("Welcome to happyfuntimeland.  Is nice here.")
WaitDialog()
Unpause()
end
--- End code ---

Four lines of code just to bring up a message on the screen!
It's not a problem for now really, but when you have a LevelLogic() that is several hundred lines long, it can start to become a nightmare to keep track of where everything is.


Lets put those 4 commands into a function.
We place this text below everything else, including the "end" of your LevelLogic().


--- Code: ---function GreetPlayer()
Pause()
MessageBox("Welcome to happyfuntimeland.  Is nice here.")
WaitDialog()
Unpause()
end
--- End code ---




iii)
Now that we've moved that code into its own function, we will need to "call" it.



--- Code: ---function LevelLogic()

GreetPlayer()

end


function GreetPlayer()
Pause()
MessageBox("Welcome to happyfuntimeland.  Is nice here.")
WaitDialog()
Unpause()
end
--- End code ---


Ok so what happens is that when the level loads, LevelLogic() will run the command "GreetPlayer()".  This will then run the commands in the function with the corresponding name.  It's practically as if the compiler copies and pastes the code inside our new function in place of anywhere we put our "GreetPlayer()" command.




iv)
What about other occasions when we need a Message Box?
If we know that we'll always pause the game when these messages appear, then the only thing that changes each time is the content of the message.

We could have lots of different functions, with names like "GreetPlayer()", "PlayerLoseMessage()", "PlayerWinMessage()", and so on.  But that would mean writing out loads of functions and cluttering up our code again, when really we only need one function to do this.

How can we display different messages with just the one function, you ask?
Well, what we do is that we pass a variable into the function.

From now on when we use our "GreetPlayer()" command, the command will expect a variable or value of some kind in the brackets.
Like this:



--- Code: ---function LevelLogic()

DisplayMessage("Welcome to happyfuntimeland.  Is nice here.")

end


function DisplayMessage(message)
Pause()
MessageBox(message)
WaitDialog()
Unpause()
end
--- End code ---



v)
So lets walk through what happens there.

First, the level loads and LevelLogic() starts running.  Straight away, it runs the DisplayMessage command which activates the DisplayMessage function.  It also passes a value into the function, in this case the value is "Welcome to happyfuntimeland.  Is nice here."
The function receives this value, and stores it in a variable called "message".
Then, the game is paused, and a MessageBox is displayed, containing the contents of the variable called "message".
When the MessageBox is clicked, it will Unpause the game.



vi)
If you followed all that, you should be able to figure out what this code would do:



--- Code: ---function LevelLogic()

DisplayMessage("Welcome to happyfuntimeland.  Is nice here.")
DisplayMessage("Chase rabbits down holes!!")
DisplayMessage("It's fun and the rabbits enjoy it.")

end


function DisplayMessage(message)
Pause()
MessageBox(message)
WaitDialog()
Unpause()
end
--- End code ---

It would display 3 message boxes one after the other, all with different messages!

This is already just 9 lines of code.  Doing it the "normal" way would be 12 lines.  Hopefully you can see how, if you have a lot of message boxes, this would save you a lot of space!



viii)
You can also call functions from within functions.

For example, consider this code:


--- Code: ---function LevelSetup()

energy = {}
strength = {}
speed = {}
radius = {}
senddist = {}
x = {}
y = {}

for i = 0,math.random(5,15) do
energy[i] = math.random(1,10) / 10
strength[i] = math.random(1,10) / 10
speed[i] = math.random(1,10) / 10
radius[i] = math.random (100,500)
senddist[i] = radius[i] * 10

x[i] = math.random(-5000,5000)
y[i] = math.radom(-5000,5000)

a = AddAsteroidWithAttribs(x[i],y[i],energy[i],strength[i],speed[i])
a.radius = radius[i]
a.SendDistance = senddist[i]
a.Moveable = true
end

end
--- End code ---


We could split it up like this....


--- Code: ---function LevelSetup()

InitArrays()

for i = 0,math.random(5,15) do
RandomiseAttributes(i)
PlaceTheRoids(i)
end

end

function InitArrays()
energy = {}
strength = {}
speed = {}
radius = {}
senddist = {}
x = {}
y = {}
end

function RandomiseAttributes(RoidID)

energy[RoidID] = math.random(1,10) / 10
strength[RoidID] = math.random(1,10) / 10
speed[RoidID] = math.random(1,10) / 10
radius[RoidID] = math.random (100,500)
senddist[RoidID] = radius[i] * 10

x[RoidID] = math.random(-5000,5000)
y[RoidID] = math.radom(-5000,5000)


end


function PlaceTheRoids(RoidID)

a = AddAsteroidWithAttribs(x[RoidID],y[RoidID],energy[RoidID],strength[RoidID],speed[RoidID])
a.radius = radius[RoidID]
a.SendDistance = senddist[RoidID]
a.Moveable = true

end
--- End code ---


... and then like this.  

--- Code: ---function LevelSetup()

GenerateLevel()

end





function GenerateLevel()
InitArrays()
for i = 0,math.random(5,15) do
RandomiseAttributes(i)
PlaceTheRoids(i)
end
end

function InitArrays()
energy = {}
strength = {}
speed = {}
radius = {}
senddist = {}
x = {}
y = {}
end

function RandomiseAttributes(RoidID)

energy[RoidID] = math.random(1,10) / 10
strength[RoidID] = math.random(1,10) / 10
speed[RoidID] = math.random(1,10) / 10
radius[RoidID] = math.random (100,500)
senddist[RoidID] = radius[i] * 10

x[RoidID] = math.random(-5000,5000)
y[RoidID] = math.radom(-5000,5000)


end


function PlaceTheRoids(RoidID)

a = AddAsteroidWithAttribs(x[RoidID],y[RoidID],energy[RoidID],strength[RoidID],speed[RoidID])
a.radius = radius[RoidID]
a.SendDistance = senddist[RoidID]
a.Moveable = true

end
--- End code ---

Note how some functions are called from within another function.  You can "nest" functions as deep as you like in this way.



viii)
Have you realised what's going on yet?

You really are somewhere down the rabbit hole, Alice...  because function LevelSetup() and function LevelLogic() are themselves just functions which are being called by the Eufloria engine.



You've been inside The Matrix all along!


Click Here for Original Discussion Thread


annikk.exe:
Click Here for Original Discussion Thread

Figured I'd start a thread where I (and others) give examples of program code that is likely to be used often.

Let me know if you guys have any requests!
Also let me know if you spot a mistake, or you tried some of this code and it didn't work, even in an otherwise empty level file.

0. Contents

1. Send seedlings after a delay
2. Perform an arbitary number of different tasks after different amounts of time
3. Determine whether a number is odd or even
4. Calculate the distance between the centers of two asteroids
5. Calculate the distance between the closest edges of two asteroids
6. Calculate how much send distance is required for Asteroid 1 to be able to send seedlings to Asteroid 2
7. Calculate the X and Y coordinates at which the line formed by points: [Lx1, Ly1] [Lx2, Ly2] intersects with the line formed by [Lx3, Ly3] [Lx4, Ly4]
8. Calculate the X, Y and Z coordinates at which a line, denoted by any 2 points with coordinates [Lx1, Ly1, Lz1] [Lx2, Ly2, Lz2] intersects with a plane, denoted by any 3 coordinates on that plane; [Px1, Py1, Pz1] [Px2, Py2, Pz2] [Px3, Py3, Pz3]
9. Convert a set of 2D LevelDraw Coordinates into a set of 2D ScreenDraw Coordinates
10.  Concatenate one or more variables and/or one or more strings of text into a single MessageBox



1. Send seedlings after a delay

--- Code: ---function LevelLogic()
-- create a "latch" variable, so that the seedlings are only sent once.
once = false


-- Run this loop continuously
while GameRunning() do

-- check if enough game time has passed yet
if GetGameTime() > 3 and once == false then

-- 3 seconds have passed - Asteroid 0 should now send 50 seedlings belonging to Empire 2 to Asteroid 1.
GetAsteroid(0):SendSeedlingsToTarget(50,2,GetAsteroid(1))

-- flip the latch
once = true

end

coroutine.yield()
end
end
--- End code ---



2. Perform an arbitary number of different tasks after different amounts of time

--- Code: ---function LevelLogic()
-- create an "incident" variable.  This will be used to store a note of which event is due to happen next.
incident = 0

-- create a "next" variable, this will record what the Game Time should be when the next incident must be run
next = 0

-- Run this loop continuously
while GameRunning() do

-- check if it's time for the next event
if GetGameTime() > next then

-- It's time, so run the action function for the current incident
Action(incident)

-- increment the incident counter by 1
incident = incident + 1

end

coroutine.yield()
end
end

function Action(incident)

if incident == 0 then
GetAsteroid(0):AddSeedlings(10)
next = GetGameTime() + 10
elseif incident == 1 then
GetAsteroid(0):AddSeedlings(20)
next = GetGameTime() + 5
elseif incident == 2 then
GetAsteroid(0):PlantDysonTree(1)
next = GetGameTime() + 25
elseif incident == 3 then
GetAsteroid(0):SendSeedlingsToTarget(50,1,GetAsteroid(1))
next = GetGameTime() + 10
elseif incident == 4 then
SetBackdropColour(0,0,80)
next = GetGameTime() + 15
else
-- no further incidents defined
end
end
--- End code ---



3. Determine whether an integer is odd or even

--- Code: ---if integer % 2 == 0 then -- if the integer divided by 2 has a remainder of 0, then
--the integer is even
else
-- the integer is odd
end
--- End code ---


4. Calculate the distance between the centers of two asteroids

--- Code: ---function LevelLogic()

-- first lets assign the values to some variables
roid1X = GetAsteroid(1).position.X
roid1Y = GetAsteroid(1).position.Y
roid2X = GetAsteroid(2).position.X
roid2Y = GetAsteroid(2).position.Y

-- next, calculate the diffence between their X coordinates
dx = roid2X - roid1X

-- and their Y coordinates
dy = roid2Y - roid1Y

-- dx and dy are like 2 sides of a right angled triangle.
-- If you recall pythagoras, the 3rd side (hypotenuse) can be calculated with a^2 + b^2 = c^2
-- So dx^2 + dy^2 = DistanceBetweenAsteroids^2

distance = math.sqrt((dx * dx) + (dy * dy))

end
--- End code ---



5. Calculate the distance between the closest edges of two asteroids

--- Code: ---function LevelLogic()

-- first lets assign the values to some variables
roid1X = GetAsteroid(1).position.X
roid1Y = GetAsteroid(1).position.Y
roid2X = GetAsteroid(2).position.X
roid2Y = GetAsteroid(2).position.Y

-- next, calculate the diffence between their X coordinates
dx = roid2X - roid1X

-- and their Y coordinates
dy = roid2Y - roid1Y

-- dx and dx are like 2 sides of a right angled triangle.
-- If you recall pythagoras, the 3rd side (hypotenuse) can be calculated with a^2 + b^2 = c^2
-- So dx^2 + dy^2 = DistanceBetweenAsteroids^2

dist = math.sqrt((dx * dx) + (dy * dy))

-- dist is the distance between the asteroid centers.  To get the actual distance between their surfaces, we must subtract both radii from dist
distance = dist - GetAsteroid(1).radius - GetAsteroid(2).radius

end
--- End code ---


6. Calculate how much send distance is required for Asteroid 1 to be able to send seedlings to Asteroid 2

--- Code: ---function LevelLogic()

-- first lets assign the values to some variables
roid1X = GetAsteroid(1).position.X
roid1Y = GetAsteroid(1).position.Y
roid2X = GetAsteroid(2).position.X
roid2Y = GetAsteroid(2).position.Y

-- next, calculate the diffence between their X coordinates
dx = roid2X - roid1X

-- and their Y coordinates
dy = roid2Y - roid1Y

-- dx and dx are like 2 sides of a right angled triangle.
-- If you recall pythagoras, the 3rd side (hypotenuse) can be calculated with a^2 + b^2 = c^2
-- So dx^2 + dy^2 = DistanceBetweenAsteroids^2

dist = math.sqrt((dx * dx) + (dy * dy))

-- dist is the distance between the asteroid centers.  To get the minimum required SendDistance, we measure from the center of Asteroid 1 (startpoint) to the edge of Asteroid 2 (destination)
distance = dist - GetAsteroid(2).radius

end
--- End code ---


7. Calculate the X and Y coordinates at which the line formed by points: [Lx1, Ly1] [Lx2, Ly2] intersects with the line formed by [Lx3, Ly3] [Lx4, Ly4]

--- Code: ----- calculate A, B and C for both lines (standard form)
A1 = Ly2-Ly1
B1 = Lx1-Lx2
C1 = A1*x1+B1*y1
A2 = Ly4-Ly3
B2 = Lx3-Lx4
C2 = A3*x3+B3*y3

-- detect if they will ever intersect..
det = A1*B2 - A2*B1

if det == 0
--Lines are parallel

else
-- intersection coordinates give as:

intersectX = ((B2*C1) - (B1*C2)) / det
intersectY = ((A1*C2) - (A2*C1)) / det

end
--- End code ---



8. Calculate the X, Y and Z coordinates at which a line, denoted by 2 points with coordinates [Lx1, Ly1, Lz1] [Lx2, Ly2, Lz2] intersects with a plane, denoted by 3 coordinates on that plane; [Px1, Py1, Pz1] [Px2, Py2, Pz2] [Px3, Py3, Pz3]

--- Code: --- -- get 2 vectors along the plane
-- PV1x stands for Plane Vector #1, X-coordinate
PV1x = Px2 - Px1
PV1y = Py2 - Py1
PV1z = Pz2 - Pz1

PV2x = Px3 - Px1
PV2y = Py3 - Py1
PV2z = Pz3 - Pz1

-- now find the cross product, which is a line perpendicular to the plane
-- PV1 X PV2 = normal vector!
i = (PV1y * PV2z) - (PV1z * PV2y)
j = (PV1z * PV2x) - (PV1x * PV2z)
k = (PV1x * PV2y) - (PV1y * PV2x)

-- for the line, calculate tx, ty, tz
tx = Lx2 - Lx1
ty = Ly2 - Ly1
tz = Lz2 - Lz1

-- calculate t
num = ((i * (Px1 - Lx1)) + (j * (Py1 - Ly1)) + (k * (Pz1 - Lz1)))
denom = ((i * (Lx2 - Lx1)) + (j * (Ly2 - Ly1)) + (k * (Lz2 - Lz1)))
t = num / denom

-- calculate coordinates of the intersection point
intersectX = (t * tx) + Lx1
intersectY = (t * ty) + Ly1
intersectZ = (t * tz) + Lz1
--- End code ---


9. Convert a set of 2D LevelDraw Coordinates into a set of 2D ScreenDraw Coordinates

--- Code: ----- where lvlX is the x-coordinate of the point in Leveldraw that must be converted, and lvlY is the y-coordinate.

screenX = (lvlX * GetCameraScale()) - (GetCameraX() * GetCameraScale()) + (GetScreenWidth() - (GetScreenWidth() / 2))
screenY = (lvly * GetCameraScale()) - (GetCameraY() * GetCameraScale()) + (GetScreenHeight() - (GetScreenHeight() / 2))
--- End code ---


10.  Concatenate one or more variables and/or one or more strings of text into a single MessageBox

--- Code: ---c1 = 5
d1 = 0
omg1 = "cats"

MessageBox(c1 .. d1)
-- "05"

MessageBox("Hello " .. omg1)
-- "Hello cats"

MessageBox("You have " .. c1 .. " " .. omg1 .. " and " .. d1 .. " dogs.  Clearly you must prefer " .. omg1 .. ".")
-- "You have 5 cats and 0 dogs.  Clearly you must prefer cats."
--- End code ---

Click Here for Original Discussion Thread

Navigation

[0] Message Index

[#] Next page

Go to full version