Yup, pretty cool tutorial.
About your comment regarding the "class cScrMap : iScrMap { /* code */ }" thing:
It's a declaration of the class that represents the map (cScrMap), and inherits stuff (like functions) from another class (iScrMap).
A class is basically a user-defined type: but, what that means in essence is that it's a way to group some data and functions related to that data together. And it really is a type, just like int, double, bool, etc. (except it's usually somewhat more complicated).
So you could have (and in SOMA, you
do have) classes that represent the map, the player, monsters, lights, sound entities, or things like geometrical vectors, or even some more abstract stuff like data associated with an event, and more.
And like with other types, you once you have a class, you can create variables of that class (type).
(There's a slight technical difference in that you need to know when to use the "@" character in declarations, but the basic principle is the same.)
Just as you would declare integers using:
Code:
int a;
int b;
// etc...
You would declare custom types (classes) using:
Code:
className variable1; // or rather: className@ variable1;
className variable2; // (but forget about that @ thing for now)
// etc...
The values stored in different variables are known as different
instances of the class. We also call them
objects (of the class). So, one class (custom type), but many different instances (objects) of that class - which means that a monster class can represent many different monsters, or one switch class can represent different door switches on the level. Classes define the shared behavior (functions), and once you create objects, you can parameterize them with different data (e.g. hook up each switch object to a different door, etc.)
For an example of how to use classes and objects, take a look at this code snippet from the first level (slightly modified):
PHP Code:
for(int i = 0; i < mvSounds.length(); ++i)
{
cSlideShowSound@ nextSound = mvSounds[i];
if(mfSlideShowTimer < nextSound.mfStartTime)
{
// (some stuff here omitted...)
}
}
Here, cSlideShowSound is a class that (I guess) represents a sound in some kind of a slide show. Then, nextSound is a variable of the type cSlideShowSound, that's initialized to the next sound within the mvSounds array.
BTW, mvSounds is just an array of objects of type cSlideShowSound, declared earlier as:
array<cSlideShowSound> mvSounds;
Basically, just a sequence of sound objects in memory, kinda like this:
[sound0][sound1][sound2][sound3][sound4]
You use arrayName[index] to access the element at a given index, where "index" is just a number that marks the position in the array - first element being 0, second 1, third 2, etc...
So, what the code above does, is it loops through each possible index in the mvSounds array, and then it initializes the nextSound object to the element at index i.
Then it checks if some timer value is less then the start time associated with that particular sound.
That's what the nextSound.mfStartTime does - it returns data associated with that particular sound object (that was set to some value somewhere earlier, either by a scripter, or by the game itslef).
Data and functions that belong to an object/class are called class members (member variables, member functions). And you use the . syntax so access them.
objectVariableName.memberVariableName
objectVariableName.memberFunctionName(... parameters, if any ...)
For example,
mvSounds.length()
calls a member function that returns the length of the mvSounds array.
You can also do something like this:
int soundsArrayLength = mvSounds.length();
So, it's just like calling a regular function - except that it has this extra bit in front.
Finally - a note about two different ways to work with classes / objects. First, you can use existing classes, and objects of those classes - and that's what I've been talking about so far.
Second, you can create your own class definitions. Within the class itself, you call it's own (or inherited) functions without specifying an object - just your standard function call.
And that's exactly what you are doing with cScrMap.
Except that these map classes are somewhat special, since (I think) that it's the engine that finds the correct one and creates an instance - end executes the code in your map class by calling some functions that it expects to find in there.
The " : iScrMap" bit is related to something called inheritance. In short, when your class inherits from another class (known as base class, or parent class), it "inherits" stuff that's defined in the parent class.
So, this means that if you have an object of type cScrMap, you can use the member access syntax (the ".") to call functions declared in iScrMap (or, if scripting "within" the cScrMap class itself - as you normally do with user-map classes, you can just call the base class function as any other "normal" function).
Speaking of that, iScrMap just defines two functions, and one member variable:
PHP Code:
class iScrMap
{
cLuxMap@ mBaseObj;
void SetupBaseInterface(cLuxMap @aObj){@mBaseObj = aObj;}
cLuxMap@ GetBase(){ return mBaseObj;}
}
This is done as a means to provide the user-created cScrMap class with the "core" map object exposed from the engine itself (here, mBaseObj - that should be accessed using the GetBase() function).
This "core" map object is of type cLuxMap, that can be found in hps_api.hps.
It exposes a bunch of useful functions that allow you to manipulate the map in some way.
However, FG has implemented a bunch of these helper functions that work with these classes and objects internally, and so effectively hide these details, making the scripting feel more like it did for Amnesia.
For example - there's this function that you can call in your map class like this: Map_SetDisplayNameEntry("some text");
But here's the implementation of that function:
PHP Code:
void Map_SetDisplayNameEntry(const tString&in asNameEntry)
{
cLuxMap@ pMap = cLux_GetCurrentMap();
pMap.SetDisplayNameEntry(asNameEntry);
}
Internally, it works on a cLuxMap instance.
Which means that you can achieve the same thing by doing this instead:
GetBase().SetDisplayNameEntry("some text");
Phew! That took waaay longer than I anticipated
.
But I hope it made some things more clear then before (although, I'm sure it's also confusing since there's a loot of new stuff here - but, it'll make more sense in time).
P.S. I guess this should probably end up somewhere in my Scrapbooks section...