When I started coding on Marble Odyssey, I worked in a pretty straight ahead manner. Much of the code I had never had to write before, this is my first video game. How do you chain together many many elements to make a "game"? For instance, animating lights, particles, materials and so on. Sometimes something comes out of left-field, something your not quite expecting to be tough, and for me, one of those things is the PAUSE feature. Think about it. Marble Odyssey uses a physics based engine for ALL game-play. None of it is faked, and so, when you pause the game, those physics just need to STOP. Even more important, when you un-pause the game everything that was moving before, needs to move again. So you need to not just stop physics, but also store the state of ALL physical objects, and re-instate that state on un-pause. See, not that trivial... Next, lets say you are animating some elements, like the intensity of a light, the Emission(glow) of a material, or the position of an object. Again, those animations MUST stop on a dime, and then re-start when you un-pause. This means you must not only write all your animations, but also write them in a way that can handle being stopped. EVERYTHING has to be written in a pause capable manner. Bear this in mind also, a single effect in-game, can often take the animation of a number of elements to bring them to life. For instance, an explosion could consist of a sphere that grows in size. That sphere would have a glowing material on it that gets brighter and then fades away as the explosion happens. That glowing material really needs a light to go with it, to make the explosion appear to emmit light. And if course you need sounds effects for that explosion as well eh? Oh yeah, AND particles of course! ALL of these elements must be pausable, and pause when they are requested to. So, how are they requested? Well, there are two scenarios, and these kind of force you into two approaches. CoRoutines in Unity, are loops, and you can use these loops to change values over time to produce an animation. So the first approach is you keep an eye on if the game is paused in ANY CoRoutine loop that can happen in game. If the game is not paused, the values progress, the animation happens. If pause is in effect, you skip updating and you get a pause in the animation. Great! Next, what about Audio or Particles. In Unity you don't manually mange the playing of an audio clip or a particle system frame by frame. These play themselves once kicked off and then stop at their natural end, or loop. So, you then send out events from the Game Manager on Pause/Un-Pause, and ALL objects have to manually pause all Audio or Particles that were playing, and un-pause these when Pause is released. And of course all of this is in addition to storing the physics properties of ALL physically simulated objects in-game. You see, you really DO need to think quite a lot about PAUSE and getting it to work in your game engine.
So, how do you go about writing AI for a marble? Well, the first thing I did was look into the tools that Unity offered out of the box. Now it has a pathfinding feature, and an AI agent to follow that path. But.. in truth, I could only really use the pathfinding element of that feature set. Why? Well, it kind of assumed that any agent would need to TURN towards a nav point on a path and then move towards it. Now, that's not quite how our marbles move. So I could make use of Unity's feature to create a path between where the bad guy IS and where it wants to be. And when it does this, it creates a set of nav points, and if you move towards each of these in turn, then you will in time reach your target. But the question begs, where should the baddy be heading for? Does it know about the player? And if it does know about the player, how does it go about getting to them, and then, what about attacking. Try and think about all of those points, as I did, and it suggests a number of problems. Internal Mental State Each Baddy needs a state model. This means, on a basic level that they have a given mental state, like resting, and then something will cause the baddy to change to another mental state, like a pursuit, or attack. So I decided to create a model for each Baddy type, that reflected all of the mental states that they might be in. Then this model has a kind of stack, of arousal, with the baddy going to a higher level when they sense the player, or dropping back if the player escapes. The Patroller As a patrolling type of baddy would be obviously useful, and would probably be the most flexible, I hit this first. It would obviously have a patrolling state, which could escalate into pursuit IF it detected the player. And if it caught up with the player, it could go into Attack mode. Exactly what the attack would be, I'd think about later. Senses Now, how does a baddy sense you? They can see, and I suppose, hear? Seeing depends on gaze, so I had to write a system that mimicked sight, including line-of-sight, field-of-view distance, and view-blocking structures. Then I tuned this sense to be less than perfect. Its easie to code AI to be perfect, and the real skill is in slowing them down, and making them fallible, like the player. Once this was working, I added hearing. Which turned out to be something of a pain. Sounds played in-game are heard by the player through speakers or headphones. But they have no real existence in the AI world. So next you have to create sound events for key sounds, have the baddy possibly hear these and again tune these so hearing works in a realistic manner. A Sum that is more than its parts When these are all put together, we get the illusion of a GHOST IN THE MACHINE . Our little Baddy marble appears to be alive. It does it's patrol, looking around for you, and listening for odd sounds. And if it hears you, or sees you, it gets miffed and heads off in pursuit. Once it gets within attack range, it switches to attack mode, and then Boom! Yes Boom, the first attack is an energy bomb, as this was the easiest attack type I could think to code as a starting point. All of this took a good month or so to write, with each stage building towards a believable result. I was very worried the result would be poor. But in the end, it turned out to actually be pretty decent. It's not perfect, the baddies make mistakes, but I actually like this, it makes them fallible, even endearing to a degree. The key thing is, it WORKS as a whole system, and its flexible enough to be built upon. Now that we have SOME baddies in-game, the focus switches back to many other elements, but the baddy will receive more art and coding love in the months ahead.
So, I reached a juncture with the game, I had shown it to a number of players, and these generally were fans of another marble based game (which shall remain nameless..). I received quite a lot of feedback, and much of it felt pretty negative. However, reading between the lines, I kind of felt there was this common theme to the feedback. They said Marble Odyssey is not good because of feature X , and that's because feature X needs to be more like the other game . And believe me, I heard this a number of times. How the marble jumped was wrong, how fast it moved, it was too slow, the marble did not turn fast enough, how heavy it felt, puzzle levels were not a good thing, faster levels were. You get my drift So, it got me to thinking and my thoughts posed this pretty fundamental question to myself and the future path of the development of the game. Should I listen to these points, tweak away, and in so doing make my game into that other game? Of course, the answer was NO! That game already existing, no one needed a knock off of it. Sure, I could absorb some of its features. As we all had of Marble Madness. But a new game was needed, not a shallow copy! So, if I'm not going to make my game more similar, then I had to make it LESS similar. Vive la difference! So, I thought about it, and some levels having a slower pace, was IMHO a good thing, variety and all that. And that other game had zero bad guys. So.. I set about thinking on how bad guys were going to work in Marble Odyssey, and I also had a quiver of fear. You see, I'm a late developer in terms of coding, and here I was thinking about writing my first AI. Gulp! But, then, I had never written a game, and Marble Odyssey is IS a working game, so, maybe writing AI would work out?
Early in the design of Marble Odyssey, we decided to follow a look that would be more photo-realistic than not. The reason for this was, we wanted our marbles to be a reasonable representation of the real thing. Now real marbles are often made of clear glass, with refraction. We know from our VFX experience that reflections are SUPER dependent on their environment. This meant we had to create or source at least one, or more high-quality Reflection maps, for use in our environments. Being an Indie game, it means that certain aspects of Marble Odyssey have had to be assembled from off the shelf components, we just do not have the man-hours to create everything. So, we sourced a set of synthetic Reflection maps. By synthetic, I mean they are not derived from real-world photographs, but rather from CG renders of various simulated cloudscapes. The advantage of this was price vs variety i.e. we could get a LOT of maps for a reasonable outlay. These maps had clear horizons, with no trees or other elements that would not work in our game. The BIG downside to these maps was, they were all rendered in Low Dynamic Range (LDR). What this means is, that although the file format they are provided in EXR supports High Dynamic Range (HDR), each of the maps luma range was in fact from 0 - 1, with 1 being what we think of as white. Why is this bad? Well, if you expose your scene lighting down, you will find that your environment maps Sun, which is usually white, will appear in-game as light grey. This is really bad. Its made worse in that when you see the same sun reflected in marbles or other surfaces the same issue appears. Let's take another example. If you have a perfectly mirror snooker ball, let's say a black one. Its reflection of the Sun would appear as white. That's despite the fact it's only reflecting back maybe 20% of the environments values. But with LDR, the Sun, as white, would appear as grey in the reflection. So, we need HIGH DYNAMIC RANGE environments to rectify this. So here is how we did this. We take an existing LDR EXR cube map, that represents our scenes reflections. And we load it in Photoshop. Bear in mind, this is a EXR so it will load as a 32bit per channel bitmap, even though its only really making use of 8bit colour really. Here, we duplicate the base map 7 or so times, to create 8 layers in total. On each of those new layers, we colour correct it, getting successively closer to black, so we slowly isolate only the LDR maps highlights. The 7th layer then is totally black apart from the Sun and its halo. Being LDR, quite often the Sun and its Halo are both white, so this becomes one big blob. But we can guess where in there the Sun actually is. Next, we pick a colour in the colour picker, with a Hue that matches our scenes sun i.e. white-yellow at mid-day, and tend more to be strong yellows, oranges and reds in sunset environments. Being a 32bit bitmap, we can choose an exposure level for that colour, and as this will be our Sun, we go for an exposure of 5x - 7x. This means the Sun will be 5x to 7x brighter than White. We then apply this colour as a filled circle, where our sun would be on the 7th layer. What this layer gives us, is a Sun that when the env map is darkened to 20% in reflection or scene exposure, the Sun will still appear as white. Think about it numerically..... White = 100 luma 5x White = 500 luma 20% can be thought of as 0.2 as a multiplier... So if we multiply our 500 luma, by 0.2, it becomes 100 luma, which is till White! Now, with this done, all we need to do is make all 7 layers ADDITIVE in comp mode. This means each layer ADDS its values to the layer below. And this means that each successive layer adds less and less luma, BUT this is more and more focussed on where light actually was in the original LDR image. The result is a new image, where the sky itself is a little brighter than before, but where the clouds, the cloud highlights, and especially the sun and its halo are much brighter. The halo probably has brightnesses of 3x or so, the Sun is 7x, and the cloud highlights are more like 1.5x -> 3x. Now, we process this new image and take it back into our game engine. In-game, our buffed environment now comes alive! Its reflections now are much more photo real and punchy. Metals look much more realistic, and our entire lighting model changes. Hoorah! Baked lighting, using these maps will now have MUCH more contrast, as the sky now has much larger ranges of luminosity. This means bounce light and fill light from the sky will have much more effect. Also, BLOOM on our cameras lens, which makes objects look like they are glowing, is now MUCH more controllable and realistic. You can dial Bloom so it only happens on luma above 2-3, and this tends to be lights, and especially your Sun, with its 5x - 7x luma. This means you ONLY get lens Bloom where it makes sense, and when you get it, its much more effective. Was it worth the effort? Well, its one of those things you may not even be aware of. But certainly to our eye, it certainly was!
Marble Odyssey is a game that was inspired by (though certainly not copied from) an 1980s classic called Marble Madness . Lets be clear, simply copying a game from the 1980s, especially an arcade game, for the modern player, well, it just will not work. Why? Well, games for the video arcades of yore were designed to be crazy hard, to literally (OK, not really literally, that would have been very wrong...) milk coins out of the average player at a fierce old rate. And Marble Madness certainly acheived this. It was mind bogglingly hard. But it was certainly fun, and it did spin off a number of close relatives in the 80s. So our game would require many changes in the basic assumptions of a marble game, set out by MM, to make sense as a PC or console game. People need a different challenge in the modern era. And thats what we set about producing with Marble Odyssey. In the posts that follow, we'll discuss exactly what those changes were, and the decisions that has driven them.