August 2015


Style Credit

Expand Cut Tags

No cut tags
Sunday, August 23rd, 2015 05:45 pm
[ profile] davidn recently re-released his game Crystal Towers 2 on Steam, but as the game had been quite a few years in the making before release initially, and then a few more years before it was Greenlit on Steam, he decided to go through the game and give it an overhaul, making it Crystal Towers 2 XL, including improvements to the controls, graphics, and even the UI. (Pretty much, he wanted to add widescreen support and then he realized that the graphics needed to be updated because of the increased viewing window, and while he's at it, might as well use a new font, and etc, etc, etc...)

I had played the game a few years back, enjoying it massively, but now that I've been thinking a lot recently about game design and working in Game Maker, I have been thinking a little more critically about how games are made and I ended up making a little analysis of the first few stages, primarily in how each Challenge is designed and what needed to be set up in preparation for the level.

I know that David had, at one point, used a spreadsheet to ensure that the game was solvable, as he had added so many different items and pickups during the creation process that it must have been very difficult to keep track in his head, and I'm seeing that having a design document is critical to knowing when to stop adding new features ;) The amount of different ideas in this game are staggering, ranging from simple spell collection, to a combo system for bouncing off of enemies, to gathering materials for synthesis, to collecting enough gems to unlock new stages. (David, when you first thought of this game, how many stages were planned and how many ideas ended up being inserted later?)

When comparing his game to my own experiences, Game Maker has a drag-and-drop system to help make game creation easier, but it also has a detailed coding system underneath the drag-and-drop environment, In playing through some levels, I found myself looking at it from the view of a programmer and wondered how I would create it in Game Maker, as Crystal Towers 2 was created in a different app, Multimedia Fusion, which probably has similar coding techniques. David suggested that I write some out so we could compare how it was actually handled/created.

For those who don't know the game, it plays like a mixture of Sonic (brightly-coloured environments with the ability to move really fast throughout the Stages) and Mario64 (multiple Challenges in each Stage that further unlock other Stages), with a Metroid mix as well (gathering items to help get to other areas). For example, the first Stage is Walnut Creek A, which has 8 different Challenges. Here is a sampling of the challenges that comes to mind:

- Get the Crystal Orb: This is a simple "get from Point A to Point B" which introduces you to each stage before the other Challenges are presented. From a programmer's viewpoint, I can envision the entire stage is loaded and running (and that includes the enemies and their movement patterns), but only the view/camera surrounding your character is visible.
- Get a combo worth x amount of points or more: A simple "If [combo integer variable]>=x then [Win Condition]", but the combo program itself is complex, registering when you bounce off one enemy onto another without touching the ground. Thinking about this, I know that some enemies can't be jumped on and others can (this would typically be made with Parent enemies) but I get this feeling every type of enemy is programmed separately, and there may be some sharing of code depending on its behaviours (Tower Crawlers and Ladyblargs, for example, have a unique attack when you approach them).
- Grab x shards in 30 seconds: Keeping track of the amount of shards picked up in the stage with a countdown timer. This leads me to believe a number of variables are turned on/off in each Stage to define each Challenge - in this one, the Timer is enabled along with a Shard Counter as the Win Condition.
- Don't touch the bees!: I've noticed that the stage has little flies buzzing about (he has many graphical touches, such as destroying dandelion heads as you walk by them), but now the bugs are a little bigger, bee-shaped, and move haphazardly (but keep themselves restrained to a certain area). This would theoretically be "replace [this asset: tiny flies, no damage to your character] with [this asset: buzzing bees, which instantly kill your character]"
- Find the Rainbow Gem: This one simply has a Gem hidden somewhere in the level (not at the normal End point) and you have to locate it. See my thoughts on "Point A, B, and C" below. This also displayed a note at the start of the stage for the player that mentions you might not be able to find it until you find a new spell in another stage. Once you realize this once, you don't have to be reminded ever again - a nice touch. :)
- Don't kill any enemies: You just can't kill anything. This one doesn't kill you outright if you fail, but it does result in an "X" shown above the character's head, and the Gem is faded-out, so you can continue to go about the stage if you're so inclined and try to map out a successful path for the next try. For this one, I assume the condition is "If [Enemies Destroyed]>0, then [Lose Condition]".
- Don't collect any shards: This one is even more difficult than not killing enemies (just tell a person who's collected everything in every videogame NOT to collect the basic collectibles), but from a programming standpoint, it's almost identical - just substitute [enemies Destroyed] with [Shards Collected].
- Your pocket has a hole and you lose gems constantly - don't let it drop to zero!: After the first few seconds to let the player collect a few Shards reduce [Shard Count] by 1 every second. The little visual of a gem falling out of his pocket with every warning "beep" is a nice touch/reminder.
- Destroy all Thunderflies: From Game Maker's standpoint, all the instances of the same enemy are easily tracked, so it has a simple "check if [all] instances of [this particular enemy] are [destroyed]. If they are, [Win Condition]." Your character also has a compass pointing you towards the closest Thunderfly - this can be done by calculating "Find the nearest instance of Thunderfly, calculate angle from Player to the Thunderfly, then draw an arrow a short distance away from the player, directing them towards the Thunderfly". (This compass is also used in the Music Castle hub to point towards open Stages, very useful.)
- Get to the beginning of the level: This one starts you at Point B and you have to get to Point A, but from a programming standpoint I have to wonder if the programming is any different than simply writing "Point B is the Start, and Point A is the End". As the stage is exactly the same otherwise, this means that the Start and End Points are variable and cannot be a constant point in the Stage. I realize that this could be programmed in two ways: Every challenge in a stage has a defined Start and End point and this one simply has them redefined, or alternately, there is a Point A, B, and C (i.e. the Rainbow Gem) preset in the Stage and every Stage assigns Start to Point A and End to Point B (or Point C if the Rainbow Gem challenge is running).
- Collect all the shards on the way down: This one was a hell of a challenge, just saying. I'm assuming this set of shards that were collected are simply counted as a set, and once the player touches the ground, it reports the amount of shards missed. But that means that these shards are counted both as regular shards in all the other Challenges, but as a special set in this particular Challenge, so I would hazard a guess that these shards were initially labelled as all the other shards, but then had a second reference programmed in for this particular Challenge.
- Don't touch the ground for more than 12 seconds: Whaaat, you have a way to detect if the character is touching the ground? I'm gonna have to learn how that one works in Game Maker. It must be collision detection with the ground, but managing to keep a timer running for that one is tricky (though now that I think about it, every step/frame is 1/30 of a second, so the timer might just be relying on counting frames...)

As an aside, I noticed that the enemies all follow specific instructions, but I also noticed that each enemy's placement requires different movement ranges depending on where it's placed in the stage (i.e. some enemies have a short path to follow where others have a longer path), so I have to wonder if the enemies have knowledge of the ledges and work automatically upon placement, or if they are all hand-tailored for each instance.

So after this analysis, I have a feeling that most of Crystal Towers 2 is not generated through AI scripting or tricks, but rather, hand-coded using good old-fashioned paper for notetaking, every enemy meticulously placed with their specific instructions, and essentially is a labour of love. How close was I, David? ;)

When you look at it from not the viewer of a gamer, but the view of a programmer, you have to appreciate it even more. :)

While I'm on the subject of newly released programs, [ profile] ravenworks also recently released his first app, Squishtoon! I've had a chance to see it in action and it's extremely powerful yet easy to work with!
Monday, August 24th, 2015 03:03 pm (UTC)
Thanks for the analysis :) Let's see what I can do to answer it...

I really did plan out Crystal Towers 2 as I went along (or 'badly', in other words). I came up with the three levels in each environment with seven missions per level, for example, without doing the necessary mathematics to realize that that would mean 231 rainbow gems to collect if I had eleven environments, and looking back I'm surprised that I just went ahead with it and didn't realize it should probably be cut back.

The things that were planned out from the start:
- Levels around a hub, accessed by collecting orbs, gems and keys
- The idea of having objectives within each level
- The gradual collection of spells to grant abilities for passing obstacles
The things that I made up as I went along:
- Everything else: i.e.
-- The actual level environments that would exist in the game
-- The entire synthesizer quest, collecting items from enemies and using them to craft items
-- The online score system
-- The combo display at the bottom of the screen and its role in the missions
-- The fact that the game would feature bosses at all
-- How the game would end
-- What magic would actually entail

It got out of hand very quickly, teaching me that the most valuable thing to decide when first planning out a game is where to stop.

You can actually read my first thoughts on the LJ that I started for keeping notes - it starts here, and it's interesting to read back on what I was thinking without the knowledge of what this would grow into. As you can read there, the missions are set up by specifying a string in an external file - the game looks for a key with the name [Level name]Mission[Number of mission selected], such as IronworksAMission7=BACKWARDS, and adjusts the level based on that.
Thursday, August 27th, 2015 04:04 am (UTC)
You made this game incredibly ambitious, but considering the other games you've made, this seems feasible for your standards. Have you had anyone think that "David X Newton" is hiding a team of people rather than being a single individual?

Wow, I never even considered the math of 231 gems, myself, there must have been a point when you realized that you maybe said to yourself "I've bitten off more than I can chew". But you never scaled the game back, did you? It appears that you always just added new features as you went along.

A quick look into the data files makes me realize that the music files are actually playable in ModPlug Tracker. (I may look at your music to learn some of my own stuff...) Does MMF support that natively?

(Also, I noticed the Instrument List is untouched... does this mean you can still get The Drums of Mayonnaise if you did some adjustments? ^^)
Monday, August 24th, 2015 03:38 pm (UTC)
Fusion handles all the game's memory considerations for me, so I don't have to do anything in the way of deciding what parts of the level are loaded - I think the way it handles it is that all of the level is in memory, but that certain objects are "turned off" if they're far from the visible window (they don't move and things like collisions aren't handled on them because there's no point).

I genuinely can't remember exactly how the number of points for a combination is calculated. There are two variables - let's call them ComboSize and ComboPoints. ComboSize counts every time a combo-rewarding thing happens (bouncing on an enemy, hitting a spring as long as you haven't hit one already in this combo, etc) and ComboPoints counts up the total score value. Once the player lands on the ground again, ComboPoints is multiplied by ComboSize, but it's weighted somehow so that you don't attain ludicrous scores with it - I can't remember how.

There are actually very few shared properties in these objects, partly because Fusion doesn't do subclassing all that well but mostly because of my bad planning. Most enemies are just set up completely separately as "If player jumps on this object, then destroy it and add 1 to combosize, add its value to combocount" etc.

The way the missions work in general is that there's a "Mission" object that holds several variables that can be used differently depending on the challenge. It reads in the mission string that I described in the post above, and parses it to get any variables out. Some of them like BACKWARDS don't have any variables, but in the "grab X shards in Y seconds", for example, the mission would be written as GEMS_TIME_40_30. On finding that string, the Mission object looks at the start to see "GEMS_TIME_", then it knows that it needs to use the next two numbers to define how many gems and how much time for that mission. It then stores these in a couple of variables, which are then manipulated depending on what mission is active - in this case, we know to subtract from the timer every time a frame is drawn, and to add to the counter whenever a gem is destroyed.

You're close with the bees - they're actually the same object as the flies, just with a different animation and there's a flag set so that the event "If the player touches an insect and this flag is on" will now reduce the player's life to 0. I sort of regret making it that simple, as I didn't make the random movement of the insects remotely fair on the player - the idea was that you had to find a route that had fewer bees to have a chance of survival, but I don't think this came across.
Thursday, August 27th, 2015 04:17 am (UTC)
I really wonder just how different MMF is from GM:S, but I have a feeling that it shares a great deal of similar ideas and functions. For example, I put together a room of enemies where the view-window would just scroll upwards, but I realized that things were happening outside of my view, as enemies which were programmed to speed up the longer they existed would be showing up much earlier than I intended, as they were already moving despite being outside the view!

(I also just discovered that there is a "On Destroy" event in GM:S which would make things much easier for creating enemy "explosions" since I've been programming them into every enemy I created ^^;)

I may have to look into how GM:S handles external files. Was there a reason you used an external file for the missions rather than scripting them inside the game itself?

I noticed how haphazard the bees motions were, and I actually was doing exactly what you were suggesting -- finding other paths! I mean, that's what we would do in real life, after all...
Monday, August 24th, 2015 03:39 pm (UTC)
Your Point A, B, and C theory is correct :) Each level has several different invisible objects placed in it:
- Start point
- BackwardsStart point
- SpecialStart point
- Level Ender
- BackwardsGem
- SpecialGem

This makes it easy to define the location of the gem and start point for each level. Normally, the player will start at the start point and the gem will be placed where it is by default (it's called "Level Ender" internally). If the current mission is BACKWARDS, then the player instead gets placed at BackwardsStart and the gem is placed at BackwardsGem. In FIND_THE_GEM missions, the player remains at Start but the gem is placed at SpecialGem. And in several missions like the infamous helicopter bounce on Luminous Tower A or the mushroom combo one in Walnut Creek B, I start the player somewhere completely different at SpecialStart to put them straight into a mission that isn't part of the main level.

As a side note, any mission can be played on any level (theoretically) - I could go into the data files and change a mission in any level to BACKWARDS, and the game would know how to handle it - just put the player at the BackwardsStart point and the end-of-level crystal at the BackwardsGem point. There might have been instances where I forgot to place these objects correctly, but in general the system is entirely flexible.

I think the actual event for the "don't destroy any enemies" mission is really a hack, it checks to see whether the bounce sound you get when you destroy an enemy is playing or something :) What there should really be is a counter that increments whenever an enemy is destroyed, and it should check that instead - the issue is that as I mentioned, most of the enemies are separate unconnected objects so you can't easily check for one being destroyed on a global scale. Shards collected is more sensible, checking that no gems are destroyed.

Everything is correct on the "hole in pocket" and "destroy all of this type of enemy" theories :)

For collecting all the shards on the way down... I can't remember. I know that there's no special property set of those gems in the way you're thinking - I think what I might do is check for the player entering a certain area of the level (the ground at the bottom of the drop), and check to see if any gems exist in a zone above that when it happens. It's an inflexible way of doing it because the zones are defined in the game's "code" (hard-coding, something which should be avoided), but it works ;)

And for not touching the ground, I just check if the player is on the ground (which I need to know anyway to determine if I'm meant to be playing a walking animation or a jumping one, etc) and subtract from an internal timer based on that.

The enemies are constrained by invisible boundary objects, which you can just about see in this zoomed-out map: . This is kind of a manual way of setting them up, but it means they don't have to detect ledges and walls themselves, which can be complicated and uses up processing time. (Although some enemies are more free-roaming and can go up ramps and so on, and those sometimes do check automatically to see if they've hit a wall or are about to walk off a ledge.)

Thank you for all of this, it's very interesting to see it looked at from outside :)
Thursday, August 27th, 2015 05:09 am (UTC)
Wow, I was right about the A-B-C idea? I feel brighter for getting it! It seems like the easiest way to script the game -- and as you said, that also allows you to move things around easier as necessary, so long as all the levels have those invisible points.

I like your workaround/hack! It's a smart way to bypass the problem of other things (that aren't enemies) getting destroyed and therefore causing a Lose Condition. Smart!

The "collect the shards on the way down" seems very logical when you put it that way, because I noticed that all the other shards in the area are still present, so it's not like you created a specific set of shards or a different area for this mission. Now you've got the mind-gears working on how I would code something like that in Game Maker. :)

Invisible Boundary objects -- oh, that's just cool! So you don't have to edit the enemies individually, you just have to place the boundaries and they know to stop when they get there. again, that's really smart when it comes to reducing the amount of processing time needed.

I also realized that while you reworked the graphics for XL, you also give credit to J Freude on your site. What did he help with?

Thanks for your responses. I really want to play through this to enjoy it, but I'm enjoying it doubly so because I can appreciate all the work that goes into it :)