After six months (almost seven!) I can say that I’ve reached what is effectively the ‘end of the road’ insofar as the Citizen prototype is concerned.
I’d like to recap what I am trying to accomplish in this prototype.
I am very aware of my inexperience; I’ve never done a game before, and so I don’t know what works and what doesn’t. I only have my own taste and sensibility for what it’s worth. Though I believe (or hope) I can pull it off, I don’t know what it really is, so this prototype has been an effort to put all the ideas that I’ve come up with into one package. I thought to myself that creating systems for a game is relatively straightforward. But can I integrate any number of those systems with each other, and have them work? If I can manage that in a prototype, then I would have the blueprint for an actual game.
I created this prototype with that mind. And having experienced the troubles of illogical inconsistencies in system, bugs of a generic sort, it feels like I’m about to ‘ship’ a game without the danger of flagellation from Steam reviews.
This is my version of the Killing House, where I rehearse and train for the game I’m going to make. I’m using live rounds and making it as realistic as possible because I want to, as much as possible, be prepared for the task up ahead. I put as much effort into it because if I can get this to a level that I’m happy with, then it’s quite possible that I can pull it off when I make the final game in Unity.
Making the prototype itself was grueling enough. I don’t remember burning so much midnight oil since I was a fledgling 3D artist back more than a decade ago. Not every idea or implementation would make it to the end. But none of those aborted/abandoned things were wasted on the experience of trying to make it.
For example, the game narrative script that I wrote at the beginning is now completely different. However, it served as a springboard for identifying and designing the systems I need to have in place, so it was still a profitable and necessary first step.
There were other ideas, like the ‘Multi-Vision System’, ‘Stacker’, and ‘Map Layers’ that I dreamed up up that had to be thrown away due to either being too oblique, complicated, or just plainly stupid.
Also, I had created and rendered NPC assets which were not used in the final version of the prototype due to a change in narrative.
While there are clear advantages working in Unity — certain bugs and limitations that I’ve encountered in C2 will no longer be relevant — I’m aware that I’m facing a world of hurt in Unity-land, too. Naturally, I will take advantage of what Unity has to offer, as well, such as a full 3D environment (something which I’m comfortable with), dynamic lighting, and a robust pathfinding system, among others. All the while, I must remember to keep it simple. It’s possible that I may have to even dumb down the game more so in Unity, to keep it within my capabilities.
Towards the end of the development, I started working on a introductory cinematic, which was a fun thing to do. It wasn’t intended to accurate represent the all the details of the story but only to give a good idea what it’s all about. Here are some screen shots:
Here are some additional screen shots demonstrating how it looks like.
I’ve also cut up some audio (re-learning Cubase again!) to use with the prototype. I may post a complete playthrough in the future, perhaps after some of my friends play it first themselves and hopefully give some feedback.
So, finally, I’ve completed my last major developmental milestone and I think that deserves a post. 🙂
The above video simply shows the playthrough of some of the game mechanics. The quest has not been fully written yet, and though all of the game mechanics are working, they’re not readily apparent without some introductions. The video is mainly to see how adventure and combat are blending as one piece.
I’ve slaved away on numerous aspects to accomplish all the major milestones I had set out to do. There were a few additions to these, but they were minor changes, and all in part of the iterative process of figuring out the closest gameplay mechanics I wanted to implement in Unity.
Though my work isn’t done yet — there are still UI issues I need to sort out — and there are still some niggling bugs present in the prototype, it is largely playable. By ‘playable’ that means you can run around, talk to people, and shoot Robots, and get shot back. You can plant a bomb, blow it up, and you can blow yourself up in the process as well. You can ‘pox’ a powerlet to get energy, you can buy and use meds to heal yourself. Frankly, a few months ago I didn’t think I could end up saying all this in one paragraph.
Most of the joy, and fear, of this prototype has been the implementation of a bespoke AI graph framework. It’s a joy because it actually works; it’s a fear because it sometimes feels too deep for me to always grasp its innards when some things don’t go right.
I’ve gone through mounds of halved/quarter-A4 to-do sheets with heaps of orange highlighter marks signifying all the big and small tasks or goals I needed done. There are so many disparate systems working that that if I didn’t have a calendar tracking my progress, I wouldn’t be able to grasp what I myself had accomplished.
For example, here’s a quick run-down of the aspects.
I’ve heavily used Janus to break out animated sequences. Using FORFILEs, a Janus looping construct that iterates through the lines of a file, creating an animated character, such as the Player character, was simple as I needed only to set up one angle and let Janus break out all the other 15 directions. Variable frame ranges for a particular animation were also taken care of using the same principle.
Janus was an important cog in the making of the prototype because of the amount of iterations for the scenes. An element would sometimes become designated as an interactable element, which had to be split from the main scene and rendered separately.
NPC/Robot portraits had a separate animation and render, and specifically had to go through post-processing.
Tiled was used in making the maps, and Rex’s TMX Importer was used to carry that information in C2. I had to do some modifications to the TMX Importer to enable the retrieval of the Tiles and Objects image source. Tiled enabled me to experiment and implement concepts by introducing certain datatypes for the engine’s use, which informs me of how I may implement the maps in Unity.
This had to be balanced with Game Data Documents which are comprised of text-based files of varying structures. These Data Documents are the immutable attributes used by the systems. In the beginning, the data would come from different sources; one would be defined in the TMX, while others defined in a CSV table. As I progressed, I refined the categorisation of data.
The in-game Inventorysystem was one hell of an undertaking, The Inventory system is connected to the Trade system, which is further split into two variants: the Container system, and the Merchant system; the former simulates the ability to store items in ‘containers’, and the latter simulates buy/sell transactions with NPCs. Merchant data, like price, buy/sell limitations, and price adjusters are tied to tables and the NPC entity as defined in the TMX.
While the code related to the movement was entirely specific to C2, I had to nevertheless overcome these issues to get a working prototype. Pathfinding needed some optimisations, behaviours related to physicality of entities needed to be coded in relation to the established movement behaviours. This aspect will largely be replaced by Unity’s navmesh, in addition to a target grid overlay that I may custom-build myself.
The Action Strip (a.k.a. Astrip) system — the method for interacting with elements in the game — was developed to be authored using text files (like most systems in the game).It serves as the hub for all ‘adventure’ interactions. It was also designed to be generic so that the display of interaction results can be be tweaked directly from the text file. For example, a ‘look’ action, at an object may initiate a display of a description, or the narrative box, or initiate a dialogue, or anything else that has been allowed in the engine.
The Convo system was another early development. Some additional Python code was necessary to convert the authored .graphml files (using yEd) into a Markdown format (for readability in a text editor). However, the development of the AI graph framework proved that the Convo system was inferior, though both used node graphs. Although the Convo system has not yet been upgraded to use the same (or similar) framework of AI, this would eventually be done when the port to Unity is made.
The Convo system could be called by the Astrip system.
The Convo system also allows implicit trade of items. For example, if through speaking with an NPC, it gives you an item to be used. The Convo system communicates to the Inventory system and places the item in the Players inventory.
The AI system used the TGF format to represent a nodal graph. Then an in-game parser and callback/event handler framework handled the execution of the AI graph on a per-Robot basis.
The AI system is connected to other systems, such as the Inventory, the Trade, Convo (dialogue system) and of course, NPCs/Robots themselves.
Using the AI system, a Robot can accost you to do a contraband check, which was one of the first implementations of the AI (even before combat).
The AI can contextualise its own dialogue with the Player, changing it from a contraband check to an arrest, for example.
Lookups for gameplay values, such as hit-chance, effect of skills on gameplay, were done using a non-linear interpolation that was accomplished by using Open Office Calc’s cell formulas. This allowed me to tweak lookup values utilising functions as opposed to doing it individually, per cell! This application was conveniently placed to export to CSV directly, so no other intermediate process was needed to get it to C2.
The Combat system is closely tied with the AI and is comprised of many factors, a few of which include:
Alert level behaviour of Robots; certain actions at a certain alert level means differently for Robots. For example, running or crouching is OK when Alert Level is 0. But when the Alert Level is 1, running or crouching is interpreted as suspicious and Player will be fired upon.
Behaviour of Robots differ from one another. Some guard, some patrol, some check for contraband.
Player accuracy skill
Weapon attributes such as range, max_range, weapon dropoff (weapon damage and chance to hit is affected)
Rate of fire
Dual-wielding of weapons
Crouching increases accuracy
Bomb placement and detonation
Shock effect; certain weapon may stun a Robot for a period of time.
GMAC system, which is a modifier on top of a typical random number generator.
Use of cover for defence
Crouching reduces profile, increases Player defence against being hit
Running increases Player defence against hit but only if running perpendicular to Robot.
Crouching behind low obstacles for stealth
Noise level when running; Robot hears you!
Glitters is Electronic Counter-Measures and makes the Player invisible for a short period of time.
Hacking powerlets to get more energy, and the associated success rates, and the penalties for failure
And others that are too lengthy to include, but you get the idea…
Normally, a prototype is small, whose gameplay represents the root of what the game is about. Sometimes, a prototype is created to determine if a gameplay works or not, or if people like it enough.
But I built the prototype as a technical reconnoitre of what I’m going to come up against. You can say I was also trying to form a beachhead at the same time. I don’t know if people would like it, but I can’t be dissuaded either way; I’ve gone this far solely on the excitement of taking a childhood game to my present.
But a prototype is also made to present the gameplay as clear as possible, that if the prototype is fun to play, then the real thing would be as fun, if not more fun to play. The problem I have with Citizen is that it is an adventure as much as it is a shoot-em-up game. The fun in 2400 AD, Fallout, or Shadowrun, for example, is the fact that it is an adventure. But I find it difficult to express the full adventure by doing a half-adventure. I think that’s due to my lack of experience writing for games. At the same time, I think that I’ve been focused so much on the technical aspects that I’ve not really dug as deep as I should into the potential of the narrative. I’ve been working on the framework in which I hope to base an adventure story (of which I have a first draft already), and I think that this prototype, as it stands, should be just seen as the prototype for the framework.
In the past week or so, I had to jump back to the AI in order to implement key gameplay mechanics, namely the contraband check and the arrest warrant.
The contraband check (as it was in 2400AD) is a Robot approaching the Player, seemingly randomly, and conducting a spot-check whether the Player is carrying any items.
The arrest warrant — not in 2400AD — is similar in concept, but that it checks whether you have accrued too many demerits and therefore will be accosted and told to submit to imprisonment.
(In 2400AD, when you collect too many demerits, the Robots just shoot at you. The Robot Authority is slightly more civilised than the Tzorg.)
These two mechanics required AI to be improved.
Functions — dofunc
When I look back at Zak’s AI graph, I realise that the scope was very limited to moving, which was all I wanted from it at the time.
But I required a Robot to determine a Player’s total demerits, or decide on conducting a contraband check based on a controllable game mechanic formula, it was apparent that I needed to expand the AI graph’s ability to call in-game functions where those computations could be made.
Enter dofunc. dofunc is a directive inside the graph that simply calls in-game functions. The format is simple:
dofunc <function> [<arg>]
The image below shows an example. The blue circles are dofunc directives.
Let’s take dofunc IsCheckCooldown as the example. Contraband checks should not occur too often, so a cooldown threshold is implemented. That function’s purpose is to check whether a contraband check cooldown threshold has been met.
The in-game function looks like this.
It returns either “0” or “1” (strings), and that forms a GlobalEventHandler name of onfunc IsCheckCooldown 0 or onfunc IsCheckCooldown 1 depending on the result. In the top image, you can see that a function called RollForCheck does the same thing.
So that’s one application of the functions.
The other application is one that sets variables. In the previous AI version, variables were set using the var directive. But what was needed was for functions to set variables as dictated by the AI.
So here’s an example of that. When a Checker Robot is attacked, it doesn’t fight back because it doesn’t have weapons. So it runs away. It tries to find a location that is out of sight of your weapon.
I created a function called CheckerFindNearestHiddenTile. See the left blue circle below. Note that I use hiddentile as the <arg> parameter (the 3rd keyword).
The in-game function looks like:
One difference here is that I don’t use the GlobalEventHandler as a way to get the variable. Instead, the function uses the <arg>, which is "hiddentile" as the base name of the variables to set in the AI’s memory. In the function above, I set an X and Y variable using "hiddentile" as my base name, and back in the AI graph, I use it to determine the coordinates for the Robot’s movement.
Engine’s the limit
I also use functions to do what would normally be tedious to do if I didn’t have the AI as the framework. For example, I am able to call a function dofunc CheckerSayAttacked, which brings up a speech bubble above the Robot saying its being attacked.
The function capability now attached to the AI’s framework allows graphs to be re-used across different Robots, so that the same function can be used on another kind of Robot.
Functions are also used to raise the alert level on a global scope, they are used to bring up the Convo system so when the Robot comes close to the Player, and if the Robot intends to accost the Player, the Convo system comes up with the appropriate topic entry.
So far, the fanciest function I’ve done was a predictive search for the Player when the Robot has lost LOS of the Player. The power of engine is the limit, whatever the engine is. And because this AI is agnostic, I hope to be able to port this functionality in Unity when the time comes around to do it.
The video below shows a sample of the AI in action. The Player has accumulated enough demerits that the Checker will accost him. If the Player decides not to submit to detention, the Checker runs away, and the Minder (the other Robot) engages the Player in combat.
On 2017 08 09, I rendered this for the Slidewalk lookdev.
Last night (early morning) I finished the Sub-Rail lookdev:
Looking back at my basement lookdev:
It’s quite apparent that the style has changed a bit. The environments are a bit different in scale and focus and this is probably one reason why the style has moved places.
Analysing the three renders
The basement scene is the most ‘cartoony’:
Mainly shaded by AO. The AO is strictly controlled on a per element basis. Some don’t have AO at all.
There is an ambient that gives a base cool colour.
Then there are individual space lights.
Parametric shading (eg pipes) is tightly controlled
Textures are made contrasty to enhance the ‘cartoony’ look. Textures are also posterised, and colours are remapped for the ‘cartoony’ look.
The Slidewalk scene is mingling of more GI lighting with forced shading:
AO-like shading is achieved partly by GI solution.
There are also AO shaders where the darkness is emphasised.
Spacer lights are still there.
Apparent soft shadows both from volume spacer lights and AO.
There is still parametric shading but you can see the influence of spacer lights on other surfaces beyond the floor it was originally (ie in the basement scene) intended.
Textures were not posterised or made contrasty. Much of the ‘cartoony’ look is gone.
The Sub-Rail scene is dark and moody, which dictated the techniques I ended up using:
AO for dark areas
Lots of volume spacer lights achieving subtle shadows
Use of spacer lights to reveal texture (eg vertical tunnel lights against bricks)
Some textures were posterised to increase contrast (eg entrance wall)
Specifically chosen textures for floor to reduce textural complexity.
In my Look and feel post, I pointed at things I should avoid, mainly pointing out at the realism of the renderings. But it seems that it’s hard to avoid given the nature of the tools I’m using.
First, the mapping issue. Laying down a map section as I have done in 3d is much easier to visualise than if I went with the 2d route. I think, given my existing skillsets, it would have taken much longer as well. I think it would have produced a different look, probably closer to the pegs shown in the Look and feel post. But having laid it out in 3d, I had started using 3d lights, GI, AO, etc. And this consideratio is ultimately what causes the look to be more realistic than I originally intended.
In the basement scene, the area is small enough that it’s manageable for me to isolate shading against certain lights, and force parametric shading on certain objects. This workflow is closest to a ‘2d’ approach (with the use of Tiled in mind).
In the Slidewalk scene, the area was too big. There were so many elements that needed considering that it would have taken me loads of time to adjust every section of it.
In the Sub-Rail scene, the scale was reduced, and the scene’s mood was darker, hence it was easier to manage even some parametric shading. Nevertheless, I could see I was moving on to the realistic territory here.
In lookdev, I’m trying to sketch out a look. But if I’m starting to sketch (ie quick renderings), I’ll end up using the most efficient tools. And if those tools are geared to producing realistic renderings, then I have to take another step to ‘dumb it down’. For example, it takes more effort to tweak a posterised texture than simply to use the texture itself. In the area of shading and lighting, that problem balloons to the point where it no longer feels like sketching, especially when many elements are being placed in the scene, and/or if the scene itself is large.
The question goes back to the 4 bullet points that was supposed to guide me in the lookdev:
Bright neon lights
Dirty and clean contrasts
Feeling of unease in the environment
With these in mind, what re-considerations can I make?
Certainly, the Slidewalk scene was not as dark as the other scenes. But I didn’t really need everything to be dark. However, the expansiveness of the scene and the flat and dull blueness was overpowering. I think there is no contrast the makes anything pop because everything is lit evenly. There’s no concrete idea of what the sky looks like (whatever is there looking up) because it’s just grey-blue. Perhaps shadowing of tall buildings out of sight? Perhaps shadow movements of overhead cranes or floating cars (if floating cars exist). Also, it must be emphasised that the expansive space helped create the problem of how to light it interestingly. The space caused me to abuse ambient lighting. One lession that I think I’m seeing in the Slidewalk scene is that even if the physical space is big, it can be (should be?) partitioned off using shadows or its inverse, lights. Whatever the case may be, the use of contrast can be used to define major sections within a bigger area.
In the basement scene, the darkness was not literal darkness, but that everything was subdued, and there were only a few sources of light. The littleness of the scene made the dark aspect easy to control, because the partitioning of lightness and darkness was done in small parts.
In the Sub-Rail scene, I think this was the most judicious use of lights given its relative size. I think this scene hits the dark aspect of the game: overall subdued darkness punctuated by slithers of light coming at different angles. In addition, there are volume space lights that partition areas within areas into light and shadow, eg ‘Sub-Rail’ text on car is shown clearly while other parts of the car are in shadow.
I would say that the basement and Sub-Rail scene lent themselves well to the dark aspect, while the the Slidewalk scene was naturally more problematic. And what I’ve learned is that if I’m doing a large space, then I must compartamentalise that space more.
Bright neon lights
In the same manner as above, the more contrast the scene has, the more bright neon lights can be successful. The neon lights give off a sense of civilisation when everything else is hidden in unknown. In the Slidewalk scene, everything seems exposed, so that nothing is really that hidden. This makes it weak even in this respect.
Dirty & clean contrasts
We can think of dirty and clean contrasts in one scene or the comparison of moving from one scene to another. The Sub-Rail scene is a dirty scene, mainly. The dirtiness relates to the brick, the trash, the chairs, and cautionary markings. The cleanliness relates to the lights (perhaps there should be broken lights?) and screens, the ticket machine.
Even in this respect, I think the Slidewalk scene fails, even though there are trash on the floor and the bricks are used as textures. I think there is too much brick everywhere that the dirty/clean contrast can shine. The floor, possibly, contributes to these non-contrastiness. However, the Slidewalk flooring itself, is stark and pops out, which is probably the best thing in the scene in terms of dirty & clean contrast.
As mentioned, feeling unease might be more to do with gameplay, or even sound, but I think it’s worth a try to challenge myself into thinking about this more clearly in respect to lookdev.
I think the use of propaganda is a way, but that requires specific propaganda messages to come through, which means a specific design. Now, the question would be whether ‘feeling unease’ can come through in terms of lighting and shading and use of space.
Possible devices to use to cause unease:
a flickering bulb
pulsating lights (SCAMs?)
unseen depths or corridors (Slidewalk scene, big drop)