I personally like direct to the point threads, this is not some breastfed tutorial thread, this is for like-minded technical people who want to improve their GUI scripting skills, listing out a basic set of rules that I myself follow to keep my scripting smart and consistent.
No bullsh!t, just straight to the point radioactive baby formula for the masses.
The three basic rules of Scripting smartly (Using the GUI Trigger Editor).
DO - Use Functions.
DO - Use Records.
DO - Use While Loops instead of For Loops, and use loops whenever possible.
Otherwise this can be translated into ...
DO NOT - Create a map with 500 triggers.
DO NOT - Create a map with 800 global variables.
DO NOT - Use For Loops, especially while nested, ever.
If you did any of the three above, I sincerely suggest it is time for a re-write and to do things properly this time, the sooner you start the sooner you will finish and you do not want to wait until this get too complicated, you will thank me later.
Some Additional Things.
LABEL THINGS, have prefixes for things such as Action Definitions that will allow you to find them faster later.
LEARN ARITHMETIC, you know, the 1+2=3 stuff in primary school. For most people it is common knowledge but quite a few people expect to be able to script without any mathematic skills.
Algebra in a nutshell, x=1, y=2, z=? ... x+y=z, therefore Z must be 3. Ta da.
Use LOOPs whenever you can, you creating a dialog for each player? Do not create a dialog for each player, create one dialog for everyone to share, then update it using a loop.
(There are always exceptions, when in doubt use common sense.)
Variables
A variable can be anything, hence the name, you can change what it is at anytime. There are many many different types, but the two you will use most often as Boolean (True or False) and Integer (1,2,3 etc).
Players ARE Integers.
This means each player is given a unique number in the game, between 1 and 14. Neutral is 0, and Hostile is 15 (This is a total of 16 slots).
Always put variables into Records, do not create a huge list of Global Variables outside records.
Functions
Parameters are information you put in.
Return is the information you receive back.
ALWAYS create local variables = parameters of the same name, do not use parameters in coding. Why? So you can copy and paste code obviously.
There are two types of functions that I will quickly touch on, Functions return a value, Action Definitions do not, they are simply a trigger without an event.
Below is a simple function that I would like you to recreate, it does not need any global variables.
NameOfPlayerinColourOptions:FunctionReturnType:TextParametersplayer=0<Integer>GrammarText:NameOfPlayerinColour(player)HintText:(None)CustomScriptCodeLocalVariablesplayer=player<Integer>text=NoText<Text>ActionsVariable-Settext=(Combine("<c val="", (Text((Convert color (Color((Current player player color))) to string))), "">",(Nameofplayerplayer),"</c>"))General-Returntext
Creating the Function shown above
Right click on the trigger list, create new function, name it "Name of Player in Colour".
Change the return type to text.
Create a parameter called player, of an integer type.
Create two variables, one called player set to the parameter player, the other called text of text type.
Then set Text as Text Combine Multiple (you will need five combines).
<c val="
Convert String to Text ( Convert Colour to String ( Convert Player Colour to Colour ( ( player variable ) - current colour ) ) )
">
Name of Player ( player variable )
</c>
Then Return text
To see this function in action, you should create a new Action - Text Message and then go down to Name of Player in Colour (not Name of Player) and set the player as 1. It should show your name in your colour when you test the map, make sure the text message is set to Chat Area.
So instead of you having to waste time every time you want to show a player's name in their colour having to create a text multiple, then <c val, then colour etc etc, you can just use this one function. Every technical thing you do more than once should be in a function to save you time!
Action Definitions
Action Definitions are basically a trigger without an event, use these for everything, think of them like Folders but only for triggers. For instance, my Map Initialization in Tofu has one line.
You will see I label them different codes for what they do, in this case ti_ stands for Tofu Initialization, this is common sense, it makes finding them very fast.
For trouble shooting this also allows you to turn off things one at a time to see what is going wrong.
Action Definitions can also use Parameters, so you can create an Action Definition that updates a dialog for a specific player.
Records
People explained to me that records are like a folder you put variables in, but no one every told me how to create one. Stupid in hindsight? Correct.
In Wc3 I labelled variables like player_kills(15) player_deaths(15) etc. But when you reach over a couple of hundred variables, in my case 800, then finding them becomes quite slow. Records allow you to streamline this process and keep everything neat and tidy.
Creating a Record
Right click in the trigger pane and click Create new Record, name it Player Data.
Straight under that Right Click in the trigger pane and click Create new Variable, name it Player and set the type to - Record (Player Data), make it an array set to 15.
Wow that was easy wasn't it? Damn everyone who never told me you needed to create a variable to use a record. At least I am telling you, feel special? <3
Now create two variables inside the Player Data Record, named kills and deaths of type integer.
Now go outside to any trigger and do Set Variable, there you will see it has the record called Player, click that then set the Player to 1, and then kills.
So instead of having variables such as:
player_deaths[15]player_kills[15]
You have a nice little record that moves the [15] to after the player and before whatever it is.
Player[15].deathsPlayer[15].kills
w00t w00t.
You can put records inside records, but they must be above the master record which stores the variables of type record, for instance.
Remember, the "master" record must always be BELOW the ones you want to put inside it.
While Loops, Never ever use For Loops
Using while loops rather than for loops dramatically decreases your script size, decreases the overload and increases map performance. 9 out of the top 10 mappers agree that you should never ever use for loops, the other one is a noob, you don't want to be a noob do you?
X is the loop, Y is the starting value, and Z is the cut off value.
Alright, so For Loops basically give you;
For Each Integer (x) from (y) to (z) do blah.
While loops take a few seconds longer to set up. You need three lines to set up a While Loop. I will use the same variable names (x,y,z) below as I did above so you can see where it fits in.
Understand? The amounts, x,y,z are still there! Just in a different format.
So you need to set the loop variable X before the while loop to the starting amount. The IF condition is the cut off amount, in this case while X is less than or equal to Z do blah, and then at the very end of the while loop you need a Modify X + 1.
While loops are also handy as you can do a very simple timer on them, and have an extra condition say playersready=false, that when set to true will finish the timer.
This one counts down because the modify is now negative. There are a few different schools of thought about using wait timers, I do not personally see the harm in them, and think in moderation, say used at the beginning of the game to countdown a selection dialog box is fine.
Limit your Triggers
The only time you need a trigger is when you need an event to fire one. You should not have multiple triggers for each player, instead use one trigger for everyone and either use Triggering Player or a loop to get the information you need. The more triggers you have firing the more overhead you create and the sooner you will hit that limit. Tofu will perhaps become one of the most polished maps ever seen, but I believe most people will be shocked at how simply and efficient it is written, with next to no triggers, and the payload mainly consisting of functions.
I have seen some maps (ie Phantom Mode) have 14 triggers to allow people to write a command such as -players. You only need one trigger, with multiple events, that uses trigger playing to get the player. This whilst not effecting the game so much, is bad practice in keeping clean code.
Also using Action Definitions allows you to Create Threads when needed, especially with loops, to reduce the overhead of the running script.
Laziness
Copying and pasting code is bad. Reading someone else's code out of a map that has been unprotected is bad. Not purely because because of ethical reasons, but because chances are they have really badly written code. You will be amazed at how badly written most of the top 20 maps are, and whilst you do not need perfect coding to create a successful map, you should want your map to 'be all that it can be'.
Open Source vs Unprotecting
Whilst I do not want Tofu open sourced, I know that without a doubt that it will be available unprotected, and whilst I do not really have a problem with that because that it will only be available in Galaxy, and people will not be able to easily edit it in GUI until there is a parser available, there is nothing that I have done that you cannot do with some time. I am not hiding secrets, I have no problem with sharing my knowledge to those willing to learn, but if you are even so lazy as to not write your own code from an example given above, and would rather copy and paste it into your map, then it doesn't matter :) Cause your map will only ever be as good as your ability, and you are shooting yourself in the ass, nananana ... <3
TL;DR
Read it all.
Suggestions / Agreements / Disagreements
Spam me below and I will update this post if appliciable.
Using while loops rather than for loops dramatically decreases your script size, decreases the overload and increases map performance. 9 out of the top 10 mappers agree that you should never ever use for loops, the other one is a noob, you don't want to be a noob do you?
for loops got fixed by blizzard a few versions back.
they are well usable now :)
but didnt know that either some weeks ago.
overall a nice guide that will be helpful for alot of beginners but also advanced mappers.
"ALWAYS create local variables = parameters of the same name, do not use parameters in coding. Why? So you can copy and paste code obviously." and then "Copying and pasting code is bad."
Consistency is also good practice for any coder...
Besides, I've always thought that copy/pasting code from other places and modifying it is a good way to learn. Sure it won't be optimal, but if it's a matter of doing something poorly or not doing it at all, I know what I'd choose. And once you've learnt enough you can edit the bad code. Opening the campaign maps can be a good idea when you want to do "that thing in that mission". It was very helpful for me to figure out how to make abilities with charges and pickups.
As for Records, I don't find them worth the effort. Player[15].deaths instead of player_deaths[15]? Big deal. Sort your variables into folders if you have a lot of them and use a naming scheme and allows you to skip to a certain letter to find your variables.
As for Records, I don't find them worth the effort. Player[15].deaths instead of player_deaths[15]? Big deal. Sort your variables into folders if you have a lot of them and use a naming scheme and allows you to skip to a certain letter to find your variables.
its very useful. think of creating a new map initially supporting max players (so each player array should have size 16), but later you decide to make it max 8 players. if you have a record storing all player variable arrays u just change the size of the record to 8. if you have the variables in a folder you have to change the size to 8 for EACH single variable, what would result in alot of work depending on how many variables you got, since you cant use constants to define array sizes in gui.
"ALWAYS create local variables = parameters of the same name, do not use parameters in coding. Why? So you can copy and paste code obviously." and then "Copying and pasting code is bad."
Copying other people's code is bad, copying your own code is just plain common sense :P
It was merely directed at people who copy other peoples code and then never understand how it works, it is better to say have the code in a text format and write it yourself so you understand everything behind it. Like in shop class (woodwork), you don't grab someone else's wood duck and say it is yours, even if you have the measurements, you gotta cut it yourself.
Besides, I've always thought that copy/pasting code from other places and modifying it is a good way to learn. Sure it won't be optimal, but if it's a matter of doing something poorly or not doing it at all, I know what I'd choose. And once you've learnt enough you can edit the bad code. Opening the campaign maps can be a good idea when you want to do "that thing in that mission". It was very helpful for me to figure out how to make abilities with charges and pickups.
I did that as well when I first started scripting back in 2003, but I believe I learnt more through trial and error than I ever did reading other people's code. Do not confuse looking at Data (spells/abilities) and the Trigger Editor. Scripting is easy to pick up but extremely hard to master, and bad code is what breaks 90% of maps - you hardly ever see bad data breaking a well played map.
As for Records, I don't find them worth the effort. Player[15].deaths instead of player_deaths[15]? Big deal. Sort your variables into folders if you have a lot of them and use a naming scheme and allows you to skip to a certain letter to find your variables.
I did that, but Tofu was sitting at over 800 global variables, Records take maybe 5 seconds to set up? If you set them up at the beginning of a project rather than say me, who spent a couple of painstaking days changing all global variables to records, and all for loops to while loops. My three rules of common sense in the trigger editor do not need to be used, they are merely guidelines to help people improve their scripting, and I personally believe Records are one of the best things Blizzard ever did for Sc2 compared to Wc3, along with native local variables in the GUI.
Clean code is paramount to everything else. If the kitchen is clean it is easy to spot the roaches, aka, bugs.
well, while loops might be a little bit faster, the question is
1) is it even noticeable? (no)
2) is it worth the additional work in gui?
everyone has to decide that for himself. for loops were just unusable bevore the fix since they totally f*cked up when nested.
if you look at a for loop in galaxy it looks like this:
the first three columns just set up the start, the end and the increment, what costs nearly zero performence.
then it continues with a while loop checking if the counter variable (i) is within bounds, if yes, it gets incremented and the actions are run. (if increment is < 0 it checks if its bigger the min, if its > 0 it checks if its smaller than max.)
if(lv_i==autoF4604CB0_ae){break;}
also costs nearly zero performance since its just a integer comparison.
so in fact the only performance loss you might encounter are 1-2 integer comparisons per loop execution, what should be not noticable in any way. but yes, for perfect code do while loops.
for me personally its exactly the other way around. in the past i used while loops only, now i start working with for loops since they are well usable imo, especially if you have nested loops its saves some time when working.
There should be some difference performance wize with pick each integer and for each variable or whatever its called....
"Ah I never knew that, but for best habit forming one should always still use While loops."
Why the fuck should they? For loops are super convinient for some cases if they can match performance of while loops (atleast faster to make and probly better to maintain too).
For Gui, the fixed for loops are fine. If someone wants "perfect code", he should use plain galaxy script anyway (and in galaxy, there are only while loops ;) )
Also, there is no such thing as perfect code. In my opinion, its perfectly fine to sacrifice a little performance for readability and easy configuration.
exactly.
also i think perfect code is overrated. galaxy is kinda fast and its much more important that you understand your code and that its easy to work with it.
You will be amazed at how badly written most of the top 20 maps are
Actually, I'm not. I'd be amazed if the top 20 maps were actually good, with a few exceptions such as Mafia which would surprise me if it had bad code.
Anyway, on topic, nice little guide/suggestion :) Didnt know for loops got fixed yet O.o I will probably start using records in future maps :)
As I said before, it is for better habit forming. Whilst it has been established that For Loops have been 'fixed', if you start a career in programming by learning the basics scripting Starcraft II maps, it is better to be use to using While Loops rather than For Loops.
The difference between While and For is about 2-5 seconds of clicking, and a While loop gives you more control over the loop when you get into more advanced coding, where a For loop does not give you that control. Even though a For loop is technically just a While loop in galaxy, I believe it is better to educate the masses to do something correctly rather than easy, after all, if you are smart you would have a blank trigger where you would have all your mostly used functions (such as a while loop set up) that you can copy and paste into any script.
Perfect code is overrated, my code is not perfect. But what we want as a map making community is to raise the bar of people's creation, having good clean efficient code allows bugs to be spotted by other people (mentors) with greater ease. Your comment that "For Loops are faster to make and probably easier to maintain" really shows a lot about your knowledge, or lack there of. A few extra seconds setting up a loop is not going to add another months development time to your project, it is in this essense of laziness, that bad habits are formed and as a result, bad code exists.
This is a very subjective matter; as long as you are aware of the consequences, its perfectly fine in my eyes.
Its good, that you make sure, people know that for loops are not the most efficient performance- wise, but the decision to use them or not is not yours. While a few extra seconds of clicking might not delay your project by much, a for loop instead of a while will not be noticeable, either, especially if used in a one shot trigger or whatever.
Also, maintaining code is a very delicate matter. When I trigger spells, I usually set up a configurations header, with several variables and functions holding the damage and stuff. Sure, performance-wise it would be better to use the numbers directly, but if you want to change the balance, you always have to search the correct fields from the code. I gladly sacrifice some performance in the form of some additional function calls for better configurability. In this case, its not even lazyness, but adding additional code, which potentially slows down the game, just for better maintainability.
(There are always exceptions, when in doubt use common sense.)
As I said in the first paragraph, these are just a set of rules that I follow, perhaps I should expand on it, something along the lines of "These are just guidelines that I believe will help you improve your general scripting ability"?.
This is just a first draft, and is of course open to debate as I encouraged, which is why I explained that it was for developing good habits, nothing more. Will having a map full of global variables instead of records slow it down? Nope. Will having a map full of For loops rather than While loops 'noticably' slow it down? Probably not, and will a map which has a few hundred triggers compared to using functions slow it down? Well actually yes. So two out of the three are subjective, this is not a thread saying omg, you must do what I say or else, but as mentioned before, teaching people to improve their scripting.
Nearly everything I script is dynamic, ie, using variables, I have not noticed any performance issues over using direct numbers - because of the reason you mentioned, for better maintainability. I wish to expand upon that in more detail but thought a seperate thread would be suffice.
Suggest you add a section about variable, function, and action naming conventions. For example, for variables I tend to use camelCased C-like conventions based on type, such as:
intMyInteger
txtMyText
imgMyImage
etc
Based on what I've read and the fact that I've used nested For loops many times with good performance, I suggest you remove the section about using While loops over For loops. You're simply suggesting a more difficult path afaik.
That naming convention could be quite handy, I however group things the way they are used, for instance dialog1_button dialog1_art dialog1_text, this works especially well in combination with Records. I am probably going to write up another section naming conventions, as they are especially personal, and there is not really any one convention better than the other, just personal preference.
I am going to rewrite the loop section to mainly cover the differences between while and for loops.
DogmaiSEA is about 80% correct. The reason why I don't say 100% is because he is not explaining why.
I will explain using Java as an example. Using the "break" command is considered bad code. Why? Because it screws with the compiler, and can be easily avoided. If you ALWAYS know that you want something to be a certain value, for loops are GOOD. If that is the case, they should be paired with constants. (In fact DogmaiSEA, I think constants would be a valuable thing to add to your programming rules. Especially for GUI stuff)
In this case, no, I do not agree with only using while loops. The reason is simply that for loops were specifically made for this sort of purpose! HOWEVER, he is right about the while loops being faster on some occasions. For something like GUI development, I do this.
Function
Do
While (A variable)
Do
While (Another variable)
Function variable, constructing the particular UI that you need in that case. Then you get the speed of the while loop without having to write 50 billion of them. Catch my drift?
But in general for loops are not the devil. For loops with break (or continue) statements are.
Okay good insight on the For loops with Breaks. I am doing that sometimes in my code...maybe I should switch those to While loops for performance.
What is the best way to profile one's code? By profile I mean figure out what parts of the code are slower than the rest? There are all sorts of profiling tools for common languages like C, Java, etc, but I'm not sure if there are any tools available to SC2 modders? I mean, let's just get right to the bottom of this and figure out what takes longer to run and when...
Not sure as how to do it with Galaxy, although copying the code into a C# compiler would work with only a couple of minor changes.
I know that with Java, using break can actually be faster at times, as it all depends on when you use it. Labeled loops used with break are not too bad, as the compiler doesn't have to figure out what to break for itself. Also if you do the clean-up for the compiler when break is called, you are fine.
Example:
//Controls when to terminate
boolean continueVar;
forLoop1:
for(int i = 1; i < 100; i++)
{
if (continue == true)
{
break;
}
}
That's not good. Instead, do this something like this.
Example:
//Controls when to terminate
boolean continueVar;
forLoop1:
for(int i = 1; i < 100; i++)
{
if (continue == true)
{
//Start a thread that cleans up this particular loop.
break;
}
}
The reason why you want to do this yourself, and not put it on the compiler, is because the compiler doesn't initially know what kind of cleanup to call. I believe the compiler's cleanup code is a couple thousand lines of assembly that all need to be checked before the break statement can be called. In C and C variations, this is called by a thread. Eventually, this thread will be completed and all data can be restored. This is why there is so much more data leaks in C than in Java. In Java, this code is not called in a thread. It is all run before the program continues to run, making sure that there are no data leaks. In Java, all this does is slow the program down slightly. The cleanup system terminates everything that needs to be, and the program continues in less than 1/100 of a second (because it is an assembly code, not a class). In C#, this is never 100% cleaned up properly. The way C# cleans up actually makes using this thread take longer.
Here is the point: Using break in a for loop is okay, as long as you tell the complier not to do cleanup, and to do it yourself. By labeling, the compiler doesn't have to figure out what to break out of. By using your own cleanup, only a couple lines need to be run, instead of a couple thousand.
As for the time difference between using a for loop properly and a while loop properly, this has been discovered to be virtually undetectable. The difference is less than 1/100 of a second. As long as they are done properly, using whichever is more comfortable is what you should do. After all, if you can't complete a project, it doesn't matter how slowly it runs ;P
Rollback Post to RevisionRollBack
Great to be back and part of the community again!
To post a comment, please login or register a new account.
Common Sense 101 (Triggering Edition)
I personally like direct to the point threads, this is not some breastfed tutorial thread, this is for like-minded technical people who want to improve their GUI scripting skills, listing out a basic set of rules that I myself follow to keep my scripting smart and consistent.
No bullsh!t, just straight to the point radioactive baby formula for the masses.
The three basic rules of Scripting smartly (Using the GUI Trigger Editor).
Otherwise this can be translated into ...
If you did any of the three above, I sincerely suggest it is time for a re-write and to do things properly this time, the sooner you start the sooner you will finish and you do not want to wait until this get too complicated, you will thank me later.
Some Additional Things.
LABEL THINGS, have prefixes for things such as Action Definitions that will allow you to find them faster later.
LEARN ARITHMETIC, you know, the 1+2=3 stuff in primary school. For most people it is common knowledge but quite a few people expect to be able to script without any mathematic skills.
Algebra in a nutshell, x=1, y=2, z=? ... x+y=z, therefore Z must be 3. Ta da.
Use LOOPs whenever you can, you creating a dialog for each player? Do not create a dialog for each player, create one dialog for everyone to share, then update it using a loop.
(There are always exceptions, when in doubt use common sense.)
Variables
A variable can be anything, hence the name, you can change what it is at anytime. There are many many different types, but the two you will use most often as Boolean (True or False) and Integer (1,2,3 etc).
Players ARE Integers.
This means each player is given a unique number in the game, between 1 and 14. Neutral is 0, and Hostile is 15 (This is a total of 16 slots).
Always put variables into Records, do not create a huge list of Global Variables outside records.
Functions
Parameters are information you put in.
Return is the information you receive back.
ALWAYS create local variables = parameters of the same name, do not use parameters in coding. Why? So you can copy and paste code obviously.
There are two types of functions that I will quickly touch on, Functions return a value, Action Definitions do not, they are simply a trigger without an event.
Below is a simple function that I would like you to recreate, it does not need any global variables.
Creating the Function shown above
Right click on the trigger list, create new function, name it "Name of Player in Colour".
Change the return type to text.
Create a parameter called player, of an integer type.
Create two variables, one called player set to the parameter player, the other called text of text type.
Then set Text as Text Combine Multiple (you will need five combines).
Then Return text
To see this function in action, you should create a new Action - Text Message and then go down to Name of Player in Colour (not Name of Player) and set the player as 1. It should show your name in your colour when you test the map, make sure the text message is set to Chat Area.
So instead of you having to waste time every time you want to show a player's name in their colour having to create a text multiple, then <c val, then colour etc etc, you can just use this one function. Every technical thing you do more than once should be in a function to save you time!
Action Definitions
Action Definitions are basically a trigger without an event, use these for everything, think of them like Folders but only for triggers. For instance, my Map Initialization in Tofu has one line.
Which in turn is another list of Action Definitions etc (I will only show the top three lines cause it's a long list.
You will see I label them different codes for what they do, in this case ti_ stands for Tofu Initialization, this is common sense, it makes finding them very fast.
For trouble shooting this also allows you to turn off things one at a time to see what is going wrong.
Action Definitions can also use Parameters, so you can create an Action Definition that updates a dialog for a specific player.
Records
People explained to me that records are like a folder you put variables in, but no one every told me how to create one. Stupid in hindsight? Correct.
In Wc3 I labelled variables like player_kills(15) player_deaths(15) etc. But when you reach over a couple of hundred variables, in my case 800, then finding them becomes quite slow. Records allow you to streamline this process and keep everything neat and tidy.
Creating a Record
Right click in the trigger pane and click Create new Record, name it Player Data.
Straight under that Right Click in the trigger pane and click Create new Variable, name it Player and set the type to - Record (Player Data), make it an array set to 15.
Wow that was easy wasn't it? Damn everyone who never told me you needed to create a variable to use a record. At least I am telling you, feel special? <3
Now create two variables inside the Player Data Record, named kills and deaths of type integer.
Now go outside to any trigger and do Set Variable, there you will see it has the record called Player, click that then set the Player to 1, and then kills. So instead of having variables such as:
You have a nice little record that moves the [15] to after the player and before whatever it is.
w00t w00t.
You can put records inside records, but they must be above the master record which stores the variables of type record, for instance.
Inside the Player Data record I have at the top.
Remember, the "master" record must always be BELOW the ones you want to put inside it.
While Loops, Never ever use For Loops
Using while loops rather than for loops dramatically decreases your script size, decreases the overload and increases map performance. 9 out of the top 10 mappers agree that you should never ever use for loops, the other one is a noob, you don't want to be a noob do you?
X is the loop, Y is the starting value, and Z is the cut off value.
Alright, so For Loops basically give you;
For Each Integer (x) from (y) to (z) do blah.
While loops take a few seconds longer to set up. You need three lines to set up a While Loop. I will use the same variable names (x,y,z) below as I did above so you can see where it fits in.
Understand? The amounts, x,y,z are still there! Just in a different format.
So you need to set the loop variable X before the while loop to the starting amount. The IF condition is the cut off amount, in this case while X is less than or equal to Z do blah, and then at the very end of the while loop you need a Modify X + 1.
While loops are also handy as you can do a very simple timer on them, and have an extra condition say playersready=false, that when set to true will finish the timer.
A very basic while wait timer.
This one counts down because the modify is now negative. There are a few different schools of thought about using wait timers, I do not personally see the harm in them, and think in moderation, say used at the beginning of the game to countdown a selection dialog box is fine.
Limit your Triggers
The only time you need a trigger is when you need an event to fire one. You should not have multiple triggers for each player, instead use one trigger for everyone and either use Triggering Player or a loop to get the information you need. The more triggers you have firing the more overhead you create and the sooner you will hit that limit. Tofu will perhaps become one of the most polished maps ever seen, but I believe most people will be shocked at how simply and efficient it is written, with next to no triggers, and the payload mainly consisting of functions.
I have seen some maps (ie Phantom Mode) have 14 triggers to allow people to write a command such as -players. You only need one trigger, with multiple events, that uses trigger playing to get the player. This whilst not effecting the game so much, is bad practice in keeping clean code.
Also using Action Definitions allows you to Create Threads when needed, especially with loops, to reduce the overhead of the running script.
Laziness
Copying and pasting code is bad. Reading someone else's code out of a map that has been unprotected is bad. Not purely because because of ethical reasons, but because chances are they have really badly written code. You will be amazed at how badly written most of the top 20 maps are, and whilst you do not need perfect coding to create a successful map, you should want your map to 'be all that it can be'.
Open Source vs Unprotecting
Whilst I do not want Tofu open sourced, I know that without a doubt that it will be available unprotected, and whilst I do not really have a problem with that because that it will only be available in Galaxy, and people will not be able to easily edit it in GUI until there is a parser available, there is nothing that I have done that you cannot do with some time. I am not hiding secrets, I have no problem with sharing my knowledge to those willing to learn, but if you are even so lazy as to not write your own code from an example given above, and would rather copy and paste it into your map, then it doesn't matter :) Cause your map will only ever be as good as your ability, and you are shooting yourself in the ass, nananana ... <3
TL;DR
Read it all.
Suggestions / Agreements / Disagreements
Spam me below and I will update this post if appliciable.
for loops got fixed by blizzard a few versions back.
they are well usable now :)
but didnt know that either some weeks ago.
overall a nice guide that will be helpful for alot of beginners but also advanced mappers.
"ALWAYS create local variables = parameters of the same name, do not use parameters in coding. Why? So you can copy and paste code obviously." and then "Copying and pasting code is bad."
Consistency is also good practice for any coder...
Besides, I've always thought that copy/pasting code from other places and modifying it is a good way to learn. Sure it won't be optimal, but if it's a matter of doing something poorly or not doing it at all, I know what I'd choose. And once you've learnt enough you can edit the bad code. Opening the campaign maps can be a good idea when you want to do "that thing in that mission". It was very helpful for me to figure out how to make abilities with charges and pickups.
As for Records, I don't find them worth the effort. Player[15].deaths instead of player_deaths[15]? Big deal. Sort your variables into folders if you have a lot of them and use a naming scheme and allows you to skip to a certain letter to find your variables.
Thank you for posting this, Couldn't have phrased it better myself. I do have a personal vendetta against inefficient code >_<
Dear Moderator,
Please sticky this? I believe it's really important for the evolution of triggering in this community to reach new heights.
its very useful. think of creating a new map initially supporting max players (so each player array should have size 16), but later you decide to make it max 8 players. if you have a record storing all player variable arrays u just change the size of the record to 8. if you have the variables in a folder you have to change the size to 8 for EACH single variable, what would result in alot of work depending on how many variables you got, since you cant use constants to define array sizes in gui.
Copying other people's code is bad, copying your own code is just plain common sense :P
It was merely directed at people who copy other peoples code and then never understand how it works, it is better to say have the code in a text format and write it yourself so you understand everything behind it. Like in shop class (woodwork), you don't grab someone else's wood duck and say it is yours, even if you have the measurements, you gotta cut it yourself.
I did that as well when I first started scripting back in 2003, but I believe I learnt more through trial and error than I ever did reading other people's code. Do not confuse looking at Data (spells/abilities) and the Trigger Editor. Scripting is easy to pick up but extremely hard to master, and bad code is what breaks 90% of maps - you hardly ever see bad data breaking a well played map.
I did that, but Tofu was sitting at over 800 global variables, Records take maybe 5 seconds to set up? If you set them up at the beginning of a project rather than say me, who spent a couple of painstaking days changing all global variables to records, and all for loops to while loops. My three rules of common sense in the trigger editor do not need to be used, they are merely guidelines to help people improve their scripting, and I personally believe Records are one of the best things Blizzard ever did for Sc2 compared to Wc3, along with native local variables in the GUI.
@Mille25: Go
Clean code is paramount to everything else. If the kitchen is clean it is easy to spot the roaches, aka, bugs.
Ah I never knew that, but for best habit forming one should always still use While loops.
well, while loops might be a little bit faster, the question is
1) is it even noticeable? (no)
2) is it worth the additional work in gui?
everyone has to decide that for himself. for loops were just unusable bevore the fix since they totally f*cked up when nested.
if you look at a for loop in galaxy it looks like this:
the first three columns just set up the start, the end and the increment, what costs nearly zero performence.
then it continues with a while loop checking if the counter variable (i) is within bounds, if yes, it gets incremented and the actions are run. (if increment is < 0 it checks if its bigger the min, if its > 0 it checks if its smaller than max.)
also costs nearly zero performance since its just a integer comparison.
so in fact the only performance loss you might encounter are 1-2 integer comparisons per loop execution, what should be not noticable in any way. but yes, for perfect code do while loops.
for me personally its exactly the other way around. in the past i used while loops only, now i start working with for loops since they are well usable imo, especially if you have nested loops its saves some time when working.
what about pick each integer loops?
There should be some difference performance wize with pick each integer and for each variable or whatever its called....
"Ah I never knew that, but for best habit forming one should always still use While loops."
Why the fuck should they? For loops are super convinient for some cases if they can match performance of while loops (atleast faster to make and probly better to maintain too).
For Gui, the fixed for loops are fine. If someone wants "perfect code", he should use plain galaxy script anyway (and in galaxy, there are only while loops ;) )
Also, there is no such thing as perfect code. In my opinion, its perfectly fine to sacrifice a little performance for readability and easy configuration.
@Kueken531: Go
exactly.
also i think perfect code is overrated. galaxy is kinda fast and its much more important that you understand your code and that its easy to work with it.
Actually, I'm not. I'd be amazed if the top 20 maps were actually good, with a few exceptions such as Mafia which would surprise me if it had bad code.
Anyway, on topic, nice little guide/suggestion :) Didnt know for loops got fixed yet O.o I will probably start using records in future maps :)
As I said before, it is for better habit forming. Whilst it has been established that For Loops have been 'fixed', if you start a career in programming by learning the basics scripting Starcraft II maps, it is better to be use to using While Loops rather than For Loops.
The difference between While and For is about 2-5 seconds of clicking, and a While loop gives you more control over the loop when you get into more advanced coding, where a For loop does not give you that control. Even though a For loop is technically just a While loop in galaxy, I believe it is better to educate the masses to do something correctly rather than easy, after all, if you are smart you would have a blank trigger where you would have all your mostly used functions (such as a while loop set up) that you can copy and paste into any script.
Perfect code is overrated, my code is not perfect. But what we want as a map making community is to raise the bar of people's creation, having good clean efficient code allows bugs to be spotted by other people (mentors) with greater ease. Your comment that "For Loops are faster to make and probably easier to maintain" really shows a lot about your knowledge, or lack there of. A few extra seconds setting up a loop is not going to add another months development time to your project, it is in this essense of laziness, that bad habits are formed and as a result, bad code exists.
@DogmaiSEA: Go
This is a very subjective matter; as long as you are aware of the consequences, its perfectly fine in my eyes.
Its good, that you make sure, people know that for loops are not the most efficient performance- wise, but the decision to use them or not is not yours. While a few extra seconds of clicking might not delay your project by much, a for loop instead of a while will not be noticeable, either, especially if used in a one shot trigger or whatever.
Also, maintaining code is a very delicate matter. When I trigger spells, I usually set up a configurations header, with several variables and functions holding the damage and stuff. Sure, performance-wise it would be better to use the numbers directly, but if you want to change the balance, you always have to search the correct fields from the code. I gladly sacrifice some performance in the form of some additional function calls for better configurability. In this case, its not even lazyness, but adding additional code, which potentially slows down the game, just for better maintainability.
Indeed, it is why there is the line;
(There are always exceptions, when in doubt use common sense.)
As I said in the first paragraph, these are just a set of rules that I follow, perhaps I should expand on it, something along the lines of "These are just guidelines that I believe will help you improve your general scripting ability"?.
This is just a first draft, and is of course open to debate as I encouraged, which is why I explained that it was for developing good habits, nothing more. Will having a map full of global variables instead of records slow it down? Nope. Will having a map full of For loops rather than While loops 'noticably' slow it down? Probably not, and will a map which has a few hundred triggers compared to using functions slow it down? Well actually yes. So two out of the three are subjective, this is not a thread saying omg, you must do what I say or else, but as mentioned before, teaching people to improve their scripting.
Nearly everything I script is dynamic, ie, using variables, I have not noticed any performance issues over using direct numbers - because of the reason you mentioned, for better maintainability. I wish to expand upon that in more detail but thought a seperate thread would be suffice.
@DogmaiSEA: Go
Overall great write-up of Best Practices.
Suggest you add a section about variable, function, and action naming conventions. For example, for variables I tend to use camelCased C-like conventions based on type, such as:
Based on what I've read and the fact that I've used nested For loops many times with good performance, I suggest you remove the section about using While loops over For loops. You're simply suggesting a more difficult path afaik.
@jcraigk: Go
That naming convention could be quite handy, I however group things the way they are used, for instance dialog1_button dialog1_art dialog1_text, this works especially well in combination with Records. I am probably going to write up another section naming conventions, as they are especially personal, and there is not really any one convention better than the other, just personal preference.
I am going to rewrite the loop section to mainly cover the differences between while and for loops.
DogmaiSEA is about 80% correct. The reason why I don't say 100% is because he is not explaining why.
I will explain using Java as an example. Using the "break" command is considered bad code. Why? Because it screws with the compiler, and can be easily avoided. If you ALWAYS know that you want something to be a certain value, for loops are GOOD. If that is the case, they should be paired with constants. (In fact DogmaiSEA, I think constants would be a valuable thing to add to your programming rules. Especially for GUI stuff)
In this case, no, I do not agree with only using while loops. The reason is simply that for loops were specifically made for this sort of purpose! HOWEVER, he is right about the while loops being faster on some occasions. For something like GUI development, I do this.
Function
Do
While (A variable)
Do
While (Another variable)
Function variable, constructing the particular UI that you need in that case. Then you get the speed of the while loop without having to write 50 billion of them. Catch my drift?
But in general for loops are not the devil. For loops with break (or continue) statements are.
Great to be back and part of the community again!
@TacoManStan: Go
Okay good insight on the For loops with Breaks. I am doing that sometimes in my code...maybe I should switch those to While loops for performance.
What is the best way to profile one's code? By profile I mean figure out what parts of the code are slower than the rest? There are all sorts of profiling tools for common languages like C, Java, etc, but I'm not sure if there are any tools available to SC2 modders? I mean, let's just get right to the bottom of this and figure out what takes longer to run and when...
@jcraigk
Not sure as how to do it with Galaxy, although copying the code into a C# compiler would work with only a couple of minor changes.
I know that with Java, using break can actually be faster at times, as it all depends on when you use it. Labeled loops used with break are not too bad, as the compiler doesn't have to figure out what to break for itself. Also if you do the clean-up for the compiler when break is called, you are fine.
Example:
//Controls when to terminate
boolean continueVar;
forLoop1:
for(int i = 1; i < 100; i++)
{
if (continue == true)
{
break;
}
}
That's not good. Instead, do this something like this.
Example:
//Controls when to terminate
boolean continueVar;
forLoop1:
for(int i = 1; i < 100; i++)
{
if (continue == true)
{
//Start a thread that cleans up this particular loop.
break;
}
}
The reason why you want to do this yourself, and not put it on the compiler, is because the compiler doesn't initially know what kind of cleanup to call. I believe the compiler's cleanup code is a couple thousand lines of assembly that all need to be checked before the break statement can be called. In C and C variations, this is called by a thread. Eventually, this thread will be completed and all data can be restored. This is why there is so much more data leaks in C than in Java. In Java, this code is not called in a thread. It is all run before the program continues to run, making sure that there are no data leaks. In Java, all this does is slow the program down slightly. The cleanup system terminates everything that needs to be, and the program continues in less than 1/100 of a second (because it is an assembly code, not a class). In C#, this is never 100% cleaned up properly. The way C# cleans up actually makes using this thread take longer.
Here is the point: Using break in a for loop is okay, as long as you tell the complier not to do cleanup, and to do it yourself. By labeling, the compiler doesn't have to figure out what to break out of. By using your own cleanup, only a couple lines need to be run, instead of a couple thousand.
As for the time difference between using a for loop properly and a while loop properly, this has been discovered to be virtually undetectable. The difference is less than 1/100 of a second. As long as they are done properly, using whichever is more comfortable is what you should do. After all, if you can't complete a project, it doesn't matter how slowly it runs ;P
Great to be back and part of the community again!