Getting started with Extension Development
From FusionWiki
Extension Development is a time consuming but very rewarding thing to do. Until recently, most of the development time was spent adding actions, conditions and expressions and sorting out the extension's connection to MMF, rather than actually making the extension do cool things and working on the fun parts. rSDK is an attempt to make it a whole lot easier, so developers can concentrate on getting the actual code done rather than messing up exporting functions to MMF and spending hours trying to fix it.
This article is a simple step-by-step tutorial aimed at someone who at least knows the basics of C++, and knows quite a bit about MMF. Even if you don't know much C++ at all, extension development is actually a great way to learn, since Clickteam's extension development kit helps you do a lot of things which are normally much harder in C++, and the way MMF works means you don't have to set out coding an entire game- you can try out coding with the flexibility and ease of use of MMF to support you.
To follow this tutorial, you will need:
- Microsoft Visual C++ 6.0 or higher - free download
- A basic knowledge of C++
- Some good ideas and inspiration
Setting up the template
First, you want to download rSDK from http://clickteam.info/looki/Extensions/rSDK.zip. Download and extract it to it's own folder. All your extensions can use the same copy of the SDK (you don't need to extract a seperate SDK for every extension).
In your rSDK folder, you will see five more folders:
- Inc - This folder contains some C/C++ "header" files which do not change for different extensions. These are global to any extension you build with rSDK, and very important to it's functionality. You shouldn't need to touch the files in this folder.
- Lib - This folder contains the MMF2 library file. This, along with some header files in the Inc folder, makes up the "glue" which makes your extension work with MMF.
- Extensions - In a brand new SDK, this folder just contains a single folder, called "Template". In a few weeks it will be full of your own extensions which you are developing ;)
To start developing an extension, you want to go to the Extensions folder. You will see a folder called "Template"- this is the skeleton of an extension, which you will build upon for every extension you create. Create a copy of it, and rename your copy to the name of the extension you want to create. Now you can open up your newly created folder.
Open the "DSW" file in Visual Studio. If you are not using Visual C++ 6.0 (which was used to create the template), Visual Studio will offer to convert it for you. Let it do so.
Understanding the SDK
Now you will be presented with a friendly IDE (Integrated Development Environment) for you to work in to develop your extensions. If you are using Visual C++ 6.0, click the FileView tab in the bottom left. If you are using Visual Studio .NET, click the Solution Explorer tab.
Expand the rSDK project, and you will see a list of folders- something like:
- Source Files
- Header Files
- Resource Files
- MMF Headers
- External Dependencies
You don't really want to touch the MMF Headers, as they are global to every extension and you could quite easily mess up your SDK by modifying them. Expand the Source Files section. A number of files will be there:
- Main.cpp - This is where your actions, conditions and expressions live, along with their code.
- Edittime.cpp - This contains routines specific to edittime (when the extension is being used within MMF, and not while the application is running).
- Runtime.cpp - This contains routines specific to runtime (the opposite of edittime).
- General.cpp - This contains routines specific to both runtime and edittime.
- Ext.def - Don't touch me (yet).
You don't want to start on the source files yet, however. Expand the header files section. There will be around 6 files in there:
- Common.h - This is where you include header files your extension needs (and some other things).
- Data.h - This is where the RUNDATA and EDITDATA structures live. They are used to hold data your extension needs to keep either at edittime or between actions, conditions and expressions (and hold some other useful structures which you can use to interact with MMF's edittime or runtime).
- FlagsPrefs.h - This is where the OEFLAGS and the OEPREFS are kept. You can uncomment these to make MMF treat your extension differently, and a full list of them is available at the end of the article.
- Information.h - This is where the information that makes your extension unique is held, like the name, author and identifier.
- Menu.h - This is where the menus used in the event editor are stored.
- Resource.h - Don't touch this one.
Implementing the extension
First head over to Information.h. You will want to change most of the basic object details. The ObjectRegID was something I wanted to set up with Clickteam, but it seems Sphax's Fusion Updater has made it unnecessary. Change the identifier string to any 4 characters which you don't think anyone else has used. When Information.h is done, you can head over to Main.cpp.
Main.cpp is where the actual code that makes your extension tick will live. In the template, there are some very simple actions, conditions and expressions there already as examples. There is a condition which checks if two numbers are equal, a "true" condition (extension developers usually call them "triggered" conditions), an action which activates the triggered condition, an action which does nothing at all, and two expressions to calculate sum and product. I am using a slightly older version of the SDK than the current one, so chances are these will be a bit different in yours.
The way Main.cpp is set up may seem a little daunting at first, but it is a vast improvement on the old SDK. If you look at the sample condition:
CONDITION(
/* ID */ 0,
/* Name */ "%o: %0 == %1",
/* Flags */ EVFLAGS_ALWAYS|EVFLAGS_NOTABLE,
/* Params */ (2,PARAM_NUMBER,"First number",PARAM_NUMBER,"Second number")
) {
int p1 = Param(TYPE_INT);
int p2 = Param(TYPE_INT);
return p1 == p2;
}
ID
The ID is unique to each function and must match the one in Menu.h. Conditions, actions and expressions have a different set of IDs (so condition ID 0 is not the same as action ID 0). The main purpose of it is so you can make a menu item point to a function, but it is also used to identify triggered conditions.
Name
The name is what is displayed in the event editor. It can look a bit daunting at first, but let's take a look at it:
%o: %0 == %1
The %o displays an icon or text depending on the user's event editor preferences to represent the object which owns the condition. You don't need to use this for actions. %0, %1, %2, %3 and so on (%A, %B, %C come after %9) display the different parameters. So if our extension was called "Empty object" and we gave the parameters 2 and 4, it would display as
Empty object: 2 == 4
Flags
The flags are quite straightforward- you can specify multiple flags by joining them with a | (pipe character). EVFLAGS_ALWAYS means that it is not a true event and should be checked every loop. EVFLAGS_NOTABLE means that you can negate it. For a "true" or "triggered" event it is fine to have the flags as 0. An event can still be triggered by a condition even if the condition is not a triggered condition, but this can cause confusion in some cases, and the condition may run when not triggered (depending on if it returned true or false and if it was negated) so it is best to set the condition flags to 0 for triggered conditions.
I don't know of any flags for actions, although you will want to remember EXPFLAG_STRING and EXPFLAG_DOUBLE for expressions (you use these when returning string or float types along with ReturnString and ReturnFloat).
Params
The parameters are written as the number of parameters, followed by the type and name of each parameter. PARAM_NUMBER means the parameter is a numeric expression, PARAM_STRING means it is a string expression. For expressions, you use EXPPARAM_LONG and EXPPARAM_STRING- there is a list of all the parameter types at the end of this post.
After the parameters, we have the actual routine (this is where any C++ knowledge you have could come in handy). The function Param(Type) (or ExParam for expressions) retrieves a parameter from MMF. They are retrieved in order, so the first call of Param will get the first parameter, the second the second, and so on. Param(TYPE_INT) will get an integer parameter, Param(TYPE_STRING) will get a string parameter, and TYPE_FLOAT will get a float parameter. There are other types, available by using TYPE_GENERIC and casting the return value to the needed type. A condition must return a boolean value (true or false) so we just return p1==p2 (true of p1 is the same as p2). Note that in expressions, you always have to get all parameters via ExParam() before returning.
For your extension, you can delete all of the template functions and start adding your own. When you have all of your functions done (remember to change the IDs) you can proceed to Menu.h.
In Menu.h, you basically make a menu by using ITEM(ID,"Text of the item") with the ID being the same as the function you want it to point to. Submenus and separators are also possible, and they are demonstrated in the example.
Building
When it's all ready, build it and then copy the built MFX file to MMF's extensions folder. By default, rSDK's template builds to C:\Program Files (x86)\Multimedia Fusion Developer 2\Extensions\rSDK.mfx. You should change this in your project properties to build to your own MMF2 directory. To change where the extension builds to for each build type, you have to go to "Project -> rSDK Properties" in the VC++ menu. From there, open up the Configuration Properties tab, and click on the Linker tab. On the right, you can change where it builds the mfx in the Output File field. To change what build type (Debug, Release, Run_Only, ect) you are editing, you can click the drop down box next to "Configuration: ". Note that this doesn't change what build type you are building, it simply displays and lets you edit it's properties. To change which build type you want to build as, you need to change it in the toolbar at the top of VC++.
If it works, excellent- you have just made your first extension! If it doesn't, post in the Extension Development forum and you are sure to get some help.
OEFLAGS
M_OEFLAG_DISPLAYINFRONT
The object will be displayed in front of all the other objects in the editor. You should use this flag when your object is displayed as a window so that the editor reflects the display that you get within the application.
M_OEFLAG_BACKGROUND
The object will be displayed in the middle of the background objects. This flag should be used with caution. Use it only for objects that are not updated very often and that don't move. Whenever the object changes, the entire screen will be redrawn. The Picture object is an example of an object that uses the M_OEFLAG_BACKGROUND routine. It should not be used with ====M_OEFLAG_SPRITES, M_OEFLAG_QUICKDISPLAY, etc.
M_OEFLAG_BACKSAVE
The background of the object will be saved. The zone saved is computed from the coordinates of the object and the hoImgXHot, hoImgYHot, hoImgWidth, hoImgHeight fields returned by the HandleRunObject routine.
M_OEFLAG_RUNBEFOREFADEIN
The object will be created before the fade-in transition. This flag is set by the user through the Create Before Fade-In Transition property.
M_OEFLAG_MOVEMENTS
Gives the object a movement property.
M_OEFLAG_ANIMATIONS
Appends a sprite-based animation property to the object. If an object has both the M_OEFLAG_ANIMATIONS and M_OEFLAG_SPRITE flags, the display will be automatically be handled by MMF, meaning you do not need to define any drawing routine.
M_OEFLAG_WINDOWPROC
Set this if your object needs to intercept window messages. MMF will call a WindowProc routine in the extension object. See Windows for more information.
M_OEFLAG_VALUES
Appends a value property to the object which contains 26 alterable values and 10 alterable strings.
M_OEFLAG_SPRITES
Tells MMF to handle the object as a sprite. If the M_OEFLAG_ANIMATIONS flag is also set, MMF will use a true sprite to display the object and you don't need to write a line of code. Only M_OEFLAG_BACKSAVE will have an effect.
M_OEFLAG_INTERNALBACKSAVE
If this flag is set and M_OEFLAG_BACKSAVE flag is also set, MMF will call the extension background saving routines (which MUST be defined). SaveBackground must save the background into a buffer and RestoreBackground must restore the saved buffer at the same coordinates. KillBackground must erase the buffer from memory. If you use the SaveRect / RestoreRect / KillRect functions of mmfs2.dll, you can use the hoBackSave field from the headerObject structure- or you can use a surface to store the background.
M_OEFLAG_SCROLLINGINDEPENDANT
The object will not follow the playfield when it scrolls. Therefore, the object will remain static on the display, like a Score or Lives object.
M_OEFLAG_QUICKDISPLAY
Displaying an object as a true sprite can slow down your application a great deal. If your object is slow to display (for example, you have created a large text object using many Windows text functions) each time another sprite is displayed in front of the object, the object must be erased and redrawn. This will cause your application to run very slowly. To prevent your object from being redrawn every time, use the M_OEFLAG_QUICKDISPLAY flag. This flag can only be used in conjunction with the M_OEFLAG_SPRITE flag. Such a sprite is removed from the main sprite list, but drawn just on top of the backdrop. When a sprite comes in front, it is not redrawn. Therefore, you can have a big text displayed under moving sprites at full speed. M_OEFLAG_QUICKDISPLAY objects can or cannot save the background depending on the M_OEFLAG_BACKSAVE and M_OEFLAG_INTERNALBACKSAVE flags. One thing these flags don't do properly is handle the background saving when they cross each other. Some parts of the second object can or cannot be saved as part of the background saved area, and vice versa. The solution is simply not to put M_OEFLAG_QUICKDISPLAY objects on top of each other.
M_OEFLAG_NEVERKILL
The object will not be destroyed if it goes too far outside the edges of the playfield.
M_OEFLAG_NEVERSLEEP
The object will never be deactivated.
M_OEFLAG_MANUALSLEEP
Usually, an object is deactivated if it's too far from the display window- it still exists, but it is removed from the display window. The decision of whether or not to deactivate this object is usually done by MMF. If the object is not controlled by a player, and is not part of a collision detection, then it can be deactivated. If not, then it will stay alive even if it's far from the display area. This behaviour does not apply when this flag is set.
M_OEFLAG_TEXT
Adds a "Text" menu in the actions, conditions and expressions. This menu contains conditions like "Is Bold", "Is Italic" etc., actions like "Set font name", "Set italic", "Set bold" etc. If you want to implement this flag (if your object is a text object), you need to define four functions- GetRunObjectFont, SetRunObjectFont, GetRunObjectTextColor and SetRunObjectTextColor. These functions are called at runtime to change/retrieve the font and font colours used in your extension. Some examples of objects which use this flag are Button, Clock, Edit, List and ListView.
Parameter types
For actions and conditions
PARAM_CLICK
The user is asked to choose between a left, middle, or right click and whether the click should be a single or double click. The result can be gotten with Param(TYPE_INT), LOWORD(myParam) representing left (0), middle (1) or right (2), and the myParam&PARAMCLICK_DOUBLE being true for double clicks.
PARAM_COLOUR
The user is asked to choose a colour from the colour palette. The colour’s value can be gotten using Param(TYPE_GENERIC) and the colour macros.
PARAM_NUMBER
The user is asked to enter a number. This is the same as PARAM_EXPRESSION. The number can be gotten using Param(TYPE_INT).
PARAM_STRING
The user is asked to enter a string. The string’s value can be gotten using (char *)Param(TYPE_STRING).
PARAM_FILENAME
The user is asked to select a file using the file selector. The filename can be gotten using (char *)Param(TYPE_STRING).
PARAM_JOYDIR
The user is asked to choose a direction and/or fire button using the joystick. The result can be gotten using Param(TYPE_INT) and then checking for myParam&JOYSTICK_UP, JOYSTICK_DOWN, JOYSTICK_LEFT, JOYSTICK_RIGHT, JOYSTICK_FIRE1, JOYSTICK_FIRE2, JOYSTICK_FIRE3, JOYSTICK_FIRE4.
PARAM_KEY
The user is asked to press a key. The resulting key can be gotten using Param(TYPE_INT).
PARAM_NEWDIRECTION
The user is asked to enter a direction (0-31). The resulting direction can be gotten with Param(TYPE_INT), and then each of the 32 bits will be set to reflect the choices in the direction selector. Bit 0 is left, bit 8 is up, bit 16 is right and bit 24 is down, for example (myParam&(1<<8)!=0) would be true if direction 8 was selected.
PARAM_PLAYER
The user is asked to choose a player (0-3). The resulting player number can be gotten with Param(TYPE_INT).
PARAM_POSITION
The user is asked to choose a position on the frame. The resulting position can be gotten with Param(TYPE_INT), HIWORD(myParam) containing the X position and LOWORD(myParam) containing the Y position
PARAM_SPEED
The user is asked to input a speed value. The resulting speed (0-100) can be gotten with Param(TYPE_INT).
PARAM_TIME
The user is asked to enter a time value using the seconds/minutes selector. The resulting time in 1/1000s of a second can be gotten with Param(TYPE_INT).
PARAM_ZONE
The user is asked to define a zone on the frame. The resulting zone can be gotten using SRECT * MyZone=(SRECT *)Param(TYPE_GENERIC); with MyZone.left, MyZone.top, MyZone.right and MyZone.bottom representing the position of each side of the zone
For conditions only
PARAM_COMPARISON
The user is asked to enter a comparison consisting of an expression and a comparison operator (like equal, greater than). The condition should return a value to be compared to the value that the user enters internally by MMF2.
PARAM_CMPSTRING
The user is asked to enter a string expression and a comparison operator. The condition should return a pointer to a string to be compared to the value that the user enters internally by MMF2.
PARAM_CMPTIME
The user is asked to enter a time and a comparison operator. The condition should return a time in milliseconds to be compared to the value that the user enters internally by MMF2.
For expressions only
EXPPARAM_LONG
The user is asked to enter a numeric expression, the result of which can be gotten with ExParam(TYPE_INT).
EXPPARAM_STRING
The user is asked to enter a string expression, the result of which can be gotten with ExParam(TYPE_STRING).
Note: if you're having trouble getting float parameters, read this thread: http://www.clickteam.com/epicenter/ubbthreads.php?ubb=showflat&Number=197027#Post197027
Troubleshooting
Whenever I try to build the rSDK template, I get an error
1>c:\rSDK\Inc\rTemplate.h(41) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int 1>c:\rSDK\Inc\rTemplate.h(81) : see reference to class template instantiation 'rVector<T>' being compiled
If you are getting this error, then open rTemplate.h by double-clicking it in the log. Go to line 41, it should look like this:
inline operator = (rVector<T> &O) {
Now, replace it with this:
inline void operator = (rVector<T> &O) {
Rebuild, everything should work fine now.
Whenever I nest expressions, the application crashes
In certain cases, you can't nest your expressions, e.g. Fruit$("Your Object",Apple("Your Object")).
This is due to a bug in rSDK's EXPRESSION macro. Werbad has released an updated version of it. His explanation of the bug is as follows:
"The problem in rSDK is that the nested expression causes the first expression to use the GetFirstExpressionParameter function twice."
Replace the macro by the following code. You can go to its definition by right-clicking "EXPRESSION" and clicking "Go To Definition". It is located at line 359 in rTemplate.h.
// Macro for an expression in main.cpp
#define EXPRESSION(num, name, flags, params) \
long WINAPI DLLExport ExpressionFunc##num(LPRDATA rdPtr, long param1); \
long IExpressionFunc##num(LPRDATA, long); \
ExtFunction ExpressionClass##num((LPEXPRESSION)ExpressionFunc##num, flags, name, param_list##params); \
long WINAPI DLLExport ExpressionFunc##num(LPRDATA rdPtr, long param1) { \
long (* cur)(LPRDATA rdPtr,short param1,long type) = rdPtr->rRd->P_GetExpressionParameter; \
rdPtr->rRd->P_GetExpressionParameter=G_GetFirstExpressionParameter; \
long ret = IExpressionFunc##num(rdPtr,param1); \
rdPtr->rRd->P_GetExpressionParameter = cur; \
return ret; \
} inline long IExpressionFunc##num(LPRDATA rdPtr,long param1)
