Nice to see such animo for this subject
Well structured codebases are infinitely better maintainable.
They remain manageable after many additions and modifications.
When all parts are separate
- Something that was used for one thing can be reused for something else
- Each part can be independently tested and easily debugged
- It becomes easier to focus on a particular task
Warning: This is going to be a huge post.
But if you're interested in organising code in GML, it might be worth the reading effort.
i like to start new projects and copy over code, organizing it in the process.
I guess that could work. But it also brings problems, unless you:
- Work on one project at a time
- Consider all previous projects as complete and never to be worked on again
Otherwise, you'd have to:
- Copy-and-paste the changes you made to the copied code from the latest project
- Re-adjust the whole older project to the new organization
Or work in an old version of your code, risking:
- Having to make thing that were already made in the new version
- Creating a separate branch (in case of cool new stuff)
- Creating "merge conflicts" for the next new project
Also when you fix a bug, you need to fix it many times for all projects where you used that code.
To prevent such issues, I always put all application logic in separated extension modules.
If I work on an older project, I can easlily import all new functionality I need.
When I start a new project, I can easily import the required functionality I made before.
Design Patterns and Principles are important, and I certainly have some I've developed when working with GM, though I haven't written them down as explicit rules or anything. One of the things that I've started to do is to avoid Macros/Constants as such. I've always found the interface to them lacking - and it is hard to keep things organized with it. (...) In their place I recommend enums.
I've always used constants until I recently found out about and adopted these enums.
Their naming is quite odd: an enum
is usually something entirely different.
I sometimes use true enums, but implement them as array of string values.
A GML "enum" is more like a struct
with constant numerical values.
That's really nice to have; they are like constants, but with a namespace
Not sure this is the kind of organisation you mean, but all of this helps me a lot. It gets me beyond 20,000 lines of code and I can still find things. I do notice though that development is slower than in the beginning, but that can also just be because many small things do need to be fixed and this is simply more work than adding new stuff.
Those are definitely the organisational topics I was talking about.
You've got some great discussion-worthy subjects in there.
This scheme shows all rooms (and all possible room transitions), all objects and where they are created, all global variables, all instance variables, all constants, all events, all actions, and the logical if-then structure of some routines. Many scripts and resources are not shown.
It looks like.. a level!
But in all seriousness, the diagram looks pretty good.
Not many projects have such a diagram available...
If they had, I think most would be much more tangled up than this one.
As you can see, it's... not... really... structured...
Thing is, I don't see how it can be structured. After all, there are variables that many parts of the game need. Almost every ingame object needs to know which upgrades the player has researched.
I always try to keep objects as self-contained as possible.
A practical way to do this is to think about how it goes in reality.
In the real world, a house does not know anything about the renovations it has had.
It "knows" only its current state.
The state of the house is influenced by the renovator, but does not depend on it.
When the renovation business disappears, the house does not return to its old state.
The same principle could go with ingame objects.
If the ingame objects would not depend on
, but receive from
the upgrades, they become more self-sufficient and versatile.
The word "level" is a dangerous term, because it can mean anything. And you're in for trouble if you use the same word for two different things. So I try to avoid the word, and use words like "plot", "world", "estate" or "storey" for the level you're in (depending on the project), "tier" for the upgrade level of a structure, "occasion" for the current level number in the campaign, etc.
This, I cannot quite comprehend.
Naming things is already one of the hard parts of software development: Why make it even harder?
I use plenty of words that have several slightly different meanings.
For example, the word index.
Whether it be a sprite index, a channel-, buffer- or font index, they're all indices.
They mean something slightly different, but in the end they all indicate the location of a resource.
Depending on the prefix, they mean different things:
In case of a sprite index, it indicates the location of a sprite.
In case of a buffer index, it indicates the location of a buffer.
It can be the same with level
; it's a progress indicator.
For a campaign level, its an indicator for the progress of the player through the campaign.
For an upgrade level, its an indicator for the progress of the upgrades for that entity.
Sure, their meaning slightly changes based on the context.
But GML, like most other languages, is quite capable of dealing with that.
What I keep finding difficult is thinking about the responsibility of objects over other objects. That's probably why my scheme is such a mess, but which object is responsible for the creation and destruction of a certain object? Ideally, one should define this for every object.
There's a whole range of design patterns for the creation of objects
; you could look into those.
If you want one object to be responsible for the creation of one (or several) other object(s), you might want to consider that object a Factory
I think I'd implement Factories as scripts, for objects in gamemaker cannot have methods other than their events, but I guess that's an implementation detail.
Alternatively, there's repositories
They are mostly used in webdevelopment, to provide methods to find, load and persist objects in a database.
I'm not sure yet as to how exactly, but I think the cancept can be used in GameMaker.
When properly implemented, repositories can bring great benefits.
They provide a single point of access to your objects.
This makes it easy to use one object instead of another, without the need to make changes all over the place.
One use case for that is to substitute a dummy object to a unit test.
Another is when you decide to rename an object, or use an entirely new one in its place:
Instead of needing to change every instance_create call, you change only one repository.
Even more important, it helps separating the business logic layer from the application layer.
What do I mean by "business logic", while most of us aren't running businesses?
Business logic is what I'd call each part of your code that determines what when to use.
The application layer is the code that defines how that is done.
There's also the presentation layer, defining how these things are shown.
These concepts are common in Multilayered architectures
Multilayered or multitiered systems completely seperate the "what to do" from the "how to do it" and the "how to present it".
GameMaker already separates the presentation layer from the others.
This separation is encouraged by the draw events.
It could be separated even better, of course, by using objects that do rendering only.
I think GameMaker projects have a lot to gain by separating the "what" from the "how" as well.
When application logic is properly separated from business logic:
- All application logic can be easily reused
- The business logic is greatly reduced
When the application logic is properly encapsulated in .gex extension files, the amount of code in the .gmx of the game is decreased enormously.
One object, one responsibility
This is hard. Turmoil has an invisible object "bill" that does everything related to money. It determines the starting money, it can take loans, and the stats object pulls the financial data from it.
Sounds more like a bank
Everything related to the bill starts with bill. bill_borrow, bill_can_repay, bill_repay, bill_landlease_value_remaining, those kinds of things.
In the case you describe, I would probably have made an object type Bank.
A Bank instance would provide access to the following features:
- Manage debit accounts of its customers (the players)
- Make payments from one account to another
- Provide loans and charge interest
The Bank would provide access to these services.
But the Bank is only a storefront.
Business layer logic.
Each seperate feature would be in its own module.
You'd have a Bank Account extension module, responsible for:
- Defining what a bank account is
- Creating bank accounts
- Retrieving the account balance
- Updating the account balance
- Destroying bank accounts
Those are just the basic CRUD
Then there's a Bank Payments extension module, responsible for:
- Making a payment from one Bank Account to another
- Providing a check to see if a transaction can be made
- Charging transaction fees
- Keeping a record of previous transactions
The Bank Payments extension module would depend on the Bank Account extension module.
And of course, the Bank Loan extension module, responsible for:
- Providing loans, possibly with collateral
- Providing a mechanism to determine if the maximum loan
- Accepting loan repayments and charging interest
- Retrieving information about the loan
- Checking if loan repayments are behind
- Calling a business-layer script if a loan is unpaid
The Bank Loan extension module would depend on the Bank Payments extension module.
The Bank object now accesses the different bank_ functions in three separate extension modules.
These would now have a double prefix, for example:
- bank_account_create(bank, player)
- bank_payment_make(account_from, account_to, amount)
- bank_loan_create(bank, player, amount, collatteral)
- bank_loan_repay(loan, account, amount)
Now lets say for a next game, I'd need only the money accounts and a payment mechanism, but no loans.
I'd simply include the bank accounts and the bank payments, but not the bank loans.
If for this game, I'd need savings accounts, I'd make an additional Bank Savings extension module.
In case I decide to also use bank savings in the previous game, I include the savings module.
Easily extensible and cleanly separated.
Not sure how one would go about unit testing in game maker. Is this possible? Right now i'm using various debug commands, cheats n such to make it easier to test the systems, but this is not ideal.
There's limitations, but unit testing is possible, sure.
Unit testing means that each separate part of the code is tested separately.
What I'm doing right now, is writing a script for each test case.
It returns an array of length 3, with:
- The actual result of the testing operation
- The expected result of the test case
- A description of what should have happened
In a testing environment (the .gmx I use for a module) I then:
- Run all the scripts
- Compare the expected value with the actual result
- Display the descriptions where the comparison failed
An example of such test cases is for_all_tests.gml
, which I wrote for my for_all extension.
I'm currently working on a unit testing module to facilitate and improve such tests.
For one of the points, specifically "Assume nothing about the executing instance, pass instances through arguments.", I would mostly agree, though I have encountered a situation, mainly in my dialogue system, where it would help to assume somethings about the instance
It's tempting, I know
But assumptions, more often than not, lead to errors.
Sometimes you still want to use variables of an instance from a scripts.
The usual way to handle this, is to use interfaces
Interfaces are like contracts for objects.
They make sure that, for example, an object has some required properties.
Usually, they also define a set of methods which a class must define.
Since gamemaker does not support class methods, the latter would not make sense.
Interfaces might be aproximated in GML, though only for properties (variables).
In a script, you'd limit your input to instances that implement an interface.
That way, you can use instance variables which are defined in the interface without assuming anything.
which brings me to folder structure. Many interesting ways this could be handled, and I'm not sure how other people have done it, but i'll give my two cents on how I handle this. I'd love to hear how other people handle this.
Since all my application layer logic is properly tucked away
in extension modules, they don't show up in the folder structure.
If they would show up - they might in the future - I'd put them in a root folder "Application logic" under scripts.
Under `scripts` I only have those scripts that are Business logic.
These are the scripts that define what should happen on what moment.
They are often the scripts that are called by various extension functions.
Those are also named "callbacks
Lets take the Bank Loan example from before.
The Bank Loan mechanism is properly encapsulated in an extension module.
This mechanism is not, however, responsible for enforcing whatever happens if the borrower does not pay up.
Such consequences are typical business layer logic, and should not be defined in the extension module.
I'd therefore make the Bank Loan mechanism call a script when such a thing happens.
That script would show up in the .gmx file of the game.
It might transfer ownership of the collateral from the borrower to the lender.
In another game, the script called on that occasion would tell the bank to raise an army to punish the borrower.
Whatever the decision, it's a business layer decision, for it is entirely game-specific.
In a .gmx file that is for a game (as opposed to one used for an extension), I use a structure like this for scripts:
- Callbacks (with a subfolder for each module that uses callbacks)
- Events (with a subfolder for each object type that uses such scripts in their events)
My objects folder has a different structure for every game type, but often still very small for the size of the game.
I regard them as classes, and often only make objects for what some people would consider parents.
Their names do not start with an (ugly) "obj_" prefix, but with a Capital.
For example, in an RPG game with NPC's, I might have:
To act as parent for:
But I would not have a class for each NPC.
Instead, I'd load their data from a file, or have their properties generated.
In the RTS I'm building, there will be hundreds of different units, buildings and other interactive instances.
Around a dozen objects will be sufficient all of them.
This is because of yet another separation I make: The separation between code and content.
The name of a character is content, not code.
If an NPC has a specific dialoge, that's content.
So instead of creating a class for every character, I make a general class `Character` and provide it with content.
Through all these separations, I end up with a very small amount of scripts and objects in my game file.
Any organisation through folder structure is pure bonus at that point.TL;DR
- Separate the "what" from the "how"
- Separate the code from the content
- Re-use as much as possible
- End up with less code
Edited by Stratadox, 13 December 2015 - 03:31 PM.