Just some little pointers to help you use GameMaker to its fullest potential. Please take these points 1 by 1 and try to digest how they can help you. After using Game Maker since its release, attending University for Computer Science (yes I recommend going if possible), and working as a full time software developer these are some methodologies I have adopted. I know not everyone is going to agree but these are not ideas that I have come up with. They are principles used throughout the Software Development industry, and that are often forgotten in Game Maker due to the fact that GameMaker is built upon the idea that a new user can continuously blast text, often times aimlessly throughout their code until finally getting a result they expect. Without further ado, and try to keep an open mind if you're a seasoned GM user, here are a few tips and reasons why.... Implement Singletons – This is a pretty simple but effective one. In GameMaker there are often times that you need to have an object live between rooms, but you never want a second one of that object. For example lets say you wanted to create an object that starts a timer that keeps count of how long its been since you started from a specific room. You could create an object that is persistent called GameTimer, and have it draw to the Gui and increase its timer each step. Now we don’t want this GameTimer to be destroyed on room change so we set it to be persistent. This is all good, we place it in the room of where we want the timer to start. But now, what if we accidently place another one in the 40th room because we forgot, or by accident. We don’t play through all 39 of our previous rooms for playtest and release a demo. Now we will have overlapping timers at the top left. There is a simple solution. The Singleton. This is a parent object that it itself will never be created, but rather an object you can inherit from that will serve a couple purposes. A Singleton is a globally accessible object that there is only ever a single instance of. The first thing that we want to ensure is that a singleton is persistent, so in its Create Event we can simply say persistent = true. Easy, now any object that inherits from Singleton will be persistent (Even if you forget to check the box). Additionally we want to ensure that there can only be 1 of the GameTimer. Another simple solution. In the Singletons Create Event we can place the code. There you go, now if a second game timer tries to get created it will just destroy itself. You can now easily add new singleton objects by following these steps.
And its that simple. You can now have an object that will stay alive across room transitions, allow you to not have to worry about duplicate objects, and not have your code coupled to another object. More on this below. NOTES: "But GloomyToad, I could just create a global.gameTimer in my obj_Controller". And I would reply, yes you could. But lets examine the downsides. Number 1, your obj_Controller is probably created in the main menu, but you don’t want your timer to start here. So now you have to implement code in the step event or on room change to begin timer. In either case you will have to implement the code In addition you will have to have a second check in your step event before the timer begins counting down. Now you also need ANOTHER check in the draw event to only draw the timer when needed. Separating out the timer into a new object allows you to simply place it in the room where it should start, eliminate the Conditional checks, and if you have rooms where you don’t want it to count down and draw, you can simply deactivate the object! If you are done counting down, you can simply destroy it. More on this design in the next section. STOP USING THE OBJ_CONTROLLER OBJECT This is going to be controversial, and I understand that. If you are developing a small game or quick prototype this is fine to use. If you even think for a second that the game scope could grow or you might acquire additional team members then maybe consider other alternatives. Please go into this with an open mind, I know *obj_controller* has been the foundation of many games for as long as GM has been around, and even official tutorials showing this use. The docs and tutorials use this, because this design IS AIMED AT BEGINNERS. No I am not saying you're a beginner for using it. I used it up until about two years ago. It is an easy concept for beginners to understand, but unfortunately has lived on into the use of advanced users as well. I started with GM when it was first released and used this object for YEAAAARS, and I am here to tell you there are better ways. The problem: Creating a central Controller object seems like a good idea. Make it persistent, and all your codes right there easy to find.. at first. 6 months later your Controllers step event it 5000 lines long and you have wrapped Conditional checks around large chunks of code. Example: Now this does look fairly clean, but its just a tiny portion of what your step event may look like. I am really only showing some Input handling. Inside of this would be a mess of entangled code. On top of this you will have room transition code to turn off the lighting Booleans, and handling tons of logic throughout this event. New users may assume that the globals are created in this objects create event. Once again you're going to end up having many many variables in this object. Now 2 months down the road you decide you want to re write how your lighting works. Now you must go through this object and strip out “Draw Event” code, step event code, and initialization code. You will most certainly have other objects that have become dependent on some of this code as well. This will be very time consuming and in most cases, you will not even bother re-writing it. The work isn’t worth it. First off the above is just a quick example, of course you can be painstakingly careful and try to avoid any mess, but as scope grows and you keep adding to it. The mess is almost unavoidable. Maybe its not a mess to you. But if a new developer hops into your project down the road they may spend hours scratching their head figuring out what points where. Let me introduce the “Single Responsibility Principle”. (It is a piece of SOLID which is still very applicable to GM). Read about it more here http://www.doolwind.com/blog/solid-principles-for-game-developers/. This principle states that a “class” or in our case an “object” should only have one reason to change. The obj_controller has hundreds of reasons to change. Why you ask? Number 1: This keeps related code in a tight easy to read space. This is useful for not only yourself but if new developers coming along. If they want to learn about the lighting system they go to the LightingManager object. If they want to create new input behavior they can safely edit the InputManager without digging through hundreds of lines of code making sure that they have covered their tracks. Number 2: It allows you to easily track variables. You no longer need to use globals for so many things. You can use for instance. InputManager.leftKey and InputManager.inputType. This allows you to easily track down where the variables are initialized and where they belong. “But GloomyToad, isn’t it faster to reference global variables”. Yes, by micro seconds. If you are having FPS issues you should look elsewhere for optimization. Plus, I will go as far as to say that with the improved organization your game will be more performent in the long run. Number 3: It keeps code dependencies at a minimum, separate objects stay decoupled, and allows better usage of abstraction. You can change out systems effortlessly. Lets see a pretty advanced example here. Lets create an InputManager object that inherits from Singleton as shown in the first point. So now its available just like a global variable would be, so we can use variables local to the InputManager. But lets not stop here. Lets make the InputManager a “Parent” class, so we do not explicitly use it in our game. Instead we will create two separate input managers that will be children of this one. PCInputManager and ArtificialInputManager. This will look like... Lets start with the parent object for implementation. We create an InputManager that will be parent to any other InputManagers we want. Note: As of GMS2 2.1.3.273 you can set these in the variable definitions and not have to call event_inherited() from child objects. This is the create event for the top level InputManager. Now this is just an "interfacing" system. We now move to our PCInputManager Now for the implementation! Easy enough, now from any object in our game we can get the axis of input by using. Now the cool part. First lets implement our ArtificialInputManager. Instead of calculating these horizontalInput and verticalInputs in the step event we will use an alarm with a random time. Now in our game.. We could change the PCInputManager into an ArtificialInputManager and without changing any code our player would begin moving (With whatever physics you have implemented) on his own in random directions, because it doesn't care what kind of InputManager it is getting values from, it just cares that they are there. This is the power of Interfacing and what you can do by using Single Resonsibility, abstraction, and inheritance in GM. Yes this CAN be done in a single controller object.. But once again, you will have to have Conditional checks every step and the code would get messy very quickly. Because of inheritance you can reference either PCInputManager or ArtificialInputManager by the name InputManager alone. So any sub-classes can be interchanged without fuss as long as they provide the same variables as is needed by whomever is referencing them. Another simple reason to use this principle is simply that its easy to rewrite systems. For example the lighting system that I mentioned earlier. Instead of having to strip all of the code out of your Controller object you can create a new object and change the way lighting is implemented. Not getting rid of old code, or dirtying your existing code with comments. You simply have the old version as a separate object. Use Global Variables Sparingly This is another topic that has some disagreement. Using the above design principles it is easy to avoid using global variables as much. Now they are NOT BAD, but as stated several times. It is going to provide better context to what they do and where they are managed when referencing by an object InputManager.verticalInput > global.verticalInput RoomManager.previousRoom > global.previousRoom GuiManager.drawHealthBar > global.drawHealthBar It is easy to find where these variables are created and you can swap child object in and out as shown above. It keeps your code nice and tidy. Just try it and see how good it feels. Now an example of when I do use global variables. I use them in re-usable api's. For instance if I am writing a Networking API and have a net_Init() script. I need to initialize variables but I do not know what another user may call the object that initializes it. Here I will use a global variable. That way I can safely use these variables from other scripts that might be used outside of the object that called the Initialization script. Use #Macros Macros are not highly used or understood, but super useful. Macros are NOT variables. They are literally tags for text to be replaced throughout your code on compile. For example you can use. What this will actually do is find anywhere in your code where EQUALS is placed and replace it with the "=". The word EQUALS will not exist in your compiled code, just in the IDE. Now the IDE will scream at you for a syntax error but if you try to compile it will be successful and run. Because this is done during compile your Macros do not need to be declared in an object. Simply create a script called "Macros" and put your macros in it. No need to call the script. Some other uses. Use Enums and Not Strings This is kind of misleading. I mean use Enums instead of strings when checking types. For instance I see state machines like this. First of all, this is pretty slow. The computer must move through each individual character of your "state" and compare it with that character of the string you are checking against. That takes a bit of time in itself. The second issue is that its easy to make a "typeo". Yes, I meant to make a typo. The alternative is to use Alternative.Enums. An enum is kind of like a mini object that holds constant values. The values are constant because they are stored at compile time, which means they are static. Once again this means that you can place them in a script that does not need to be called from an object. They look like this. // Messaging Enums enum MoveState { falling, jumping, grounded } Now these get compiled and used as integers. By default the integers they represent are set from top to bottom starting at zero. So in this case MessageType.greeting == 0. You can optionally specify values. // Messaging Enums enum MoveState { falling = 0, jumping = 2, grounded = 1 } Usually this is not needed for most uses. Lets go back to the state machine and re-write then observe some things.. Couple Notes: Enums don't have to be in an event (I showed them with the machine to show you all the code at once). They are static so in a script by themselves is good enough. Also the red error is because I didn't show code between the if statements As you can see there are a few advantages. Number one is it shows up in auto complete while typing to remind you of your types. Number two is its much harder to make spelling mistakes. Finally, number 3 is that its much faster to compare real numbers than it is to compare strings! Enjoy.
These points are short summaries of points from my book (not yet finished) called Effective GameMaker. It is quite lengthy and covers many concepts not only applicable to GameMaker, but also general software design. If this article interests you at all then be looking out for it. Thank you guys for reading!
6 Comments
|
Logan SpencerSoftware Developer, Hobby Game Designer, Game Maker Enthusiast ArchivesCategories |