Category Archives: Workflow

Prototyping – What have I learned?

This post is for technical reference.

Consolidation

If there’s one thing I need to consider seriously is how to consolidate data that the systems use.

The main data holders are Accomps, INV, TINV, and Entity State.

The problem of Entity State, as I mentioned in one dev video is whether Entity State can be persistent If it can, then this is valid data holder. If not, we nix it.

Accomps spelling mistakes

Creating and referencing Accomps was an issue because an Accomp could be created and referenced anywhere and spelling mistakes could occur in the referencing between any of the systems. I don’t think there’s a real workaround for this except to organise the Accomps properly.

Accomps master list

If the Accomps are created in a nodal graph, they may be easier to spot within the yEd interface. However, Accomps may be ‘declared’ in any document. Thus it is useful to have a master Accomps document to reference from. But this needs to be constantly updated. If naming convention is strictly use in the creation of Accomps, then a script could be made to crawl all the documents to see where Accomps exist and update the master list.

INV and TINV

INV was queried in the Condition (# in SNTX notation). This is fine for now, but TINV should also be supported. In this case, the TINV is self and TINV is always persistent.

Systems that reference data

Each of these data holders are queried and set in all of the systems:

  • Convo
  • AI
  • Astrip
  • Trade (not implemented in the prototype)

How to consolidate?

  • Convo should be re-designed to use the TGF format. This means connections should be taken into consideration, and perhaps edge labels should identify relationship between nodes.
  • Conditions using TGF could be represented in nodes rather than in the SNTX notation.
  • Astrip is currently only expressed in SNTX notation. Astrip should be converted to TGF, too. But obviously, this requires a complete redesign of the Astrip nomenclature. But since a graph is the goal, we can use branching for Conditions and connecting to the same nodal directives of  dofunc, doaccomps, etc.
  • AI uses TGFs fully, but lacks dofuncs and doaccomps and dostate. AI uses in-game ‘DoFunc’ functions in order to accomplish specific things, which may or may not involve executing dofunc, doaccomps, or even dostate (though that’s not really being used due to the aforementioned persistence issue). But I can see where it may be useful to issue a directive through the AI.
    • When the Fixers were haxed, it was the AI that executed a special (but generic) DoFunc that added an Accomps saying it was hacked. It did this under the onhaxed event handler.
    • The improvement I see in this is to mirror the handling of the event to the Astrip. See below.

Re-purpose SNTX

SNTX’s heavy lifting is most in the areas of:

  • Entity reference
  • Conditions

Entity reference is something like ==poxingalley.bin. In a TGF, there are no node types, so referencing an entity would still require a keyword to represent the type of node being processed, so I think prefixes like the == symbol would still need to exist.

Conditions are like ?$!entry_desc_shown. Like entity referencing, this needs to remain to identify the node as a condition. However, unlike the original SNTX notation, which features the entity reference and condition (and directive) in one line, the nodal graph will split entity reference, conditions, and directives as separate nodes, which makes for a more readable graph, and makes it possible to branch and re-use other subgraphs.

The image above shows a possible way to go about it. The entity is identified first. Then contextualised immediately with the relevant action that operating on it (look). Then the Condition is checked. Edges branch from that Condition check with a label 0 and 1 and -1 denoting False and True and Nominal (Default). Nominal means that any directive will fire regardless of the Condition. This -1 edge may not be necessary, however, because it should be possible to connect the ++look node to the ~doaccomps node directly, it should grok it.

So the SNTX notation of ==, ++, ?, and ~ is still the same, but is re-purposed in TGF to directly indicate the type of node being processed. Also, we have the added 0 and 1  and -1 edges.

Astrip and event handlers

This is a narrative tool, not a generic one, so we’re operating upon set entities, not spawned ones.

Why not deal with event handlers in Astrip just as we do in AI? When an event is called in the AI, it is handled in the context of the AI. And as mentioned above regarding Fixers modifying the Accomps, it seems untidy to do that. What if, then, we query for an event handler in Astrip document and then process the directive from there?

Here, the ++ symbol abstractly represents an ‘actionable’ context, so it makes sense that can also be used as an event handler name.

So, again, the narrative construction for Accomps (in particular) is done in the Astrip, rather than the AI, which helps consolidate the actions to one place.

Namespace

The above reminds me about namespaces, names and types, and how this must be designed carefully. The namespace of e.g. poxingalley.fixer refers, firstly, to the name of the entity. However, it is possible that the name doesn’t exist in the scene. When that happens, then the type of the entity is queried. In this case there would be a match.

This is used to create entity references to type rather than name, and so some care must be taken to name (Astrip name, that is) the entities uniquely from their type.

Accomps scripting, Narrative scripting, Triggers

Introduced at later state was Accomps scripting, which monitors the Accomps and then executes a directive. However, this wasn’t terribly useful.

It was more useful to shift the work to Astrip since Astrip handles a lot of interaction. I’ve already talked about Player interaction using Astrip, but Astrip handles Triggers, too, which are the primary handlers of the narrative. So if TGF is implemented, Triggers could be easier to monitor and handle, especially since the Accomps keywords would be in their respective documents.

Convo improvements

In addition to using TGF for Convo, there are certain workflows that need to be addressed.

  • Convo should have a way to sort which Choices comes first
  • Feedback from tester: default SPACE to advance the conversation instead of clicking on the Choice. In some cases a [...] is presented to the Player, and the SPACE bar could be used to click on this implicitly.
  • The above could be improved in a way to make the Convo navigable by keyboard, so that a selection halo appear on the active Choice, and the SPACE bar (or ENTER) may be used to select the haloed Choice. This is also in line with the first item of having the ability to sort Choices, so that the most ‘obvious’ Choice is haloed first.
  • The Convo should feature the ability to not cycle back to the Choice that has already been chosen in the same Convo session. This requires keeping track of the chosen Topics for any given session. This could have been implemented in the prototype, but due to other things needed done, it wasn’t.

Unique and non-unique items

In the prototype, unique and non-unique items were delineated for the purpose of figuring out how to make them arrange themselves into icon in the inventory. However, the implementation was not totally complete. Unique items should not have any ‘quantity’ but this was not enforced in the prototype; e.g. assigning TINV bin:#1x1,shokgun=1 yields a numeral above the Shokgun icon. In the real game, unique and non-unique items should be enforced especially in regards to how items are counted.

AI vs FSM

So far, the event handler system I’ve created works well with what I’ve required it to do. In fact, I don’t want to overcomplicate the AI, but I am still investigating whether FSM might be tidier.

CX and ENX integration to INV

CX and ENX  are not integrated as INV items. I’m wondering whether this is needed. First, in the protoype, CX cannot be had by any way except through Merchant Trade. The reasoning is that CX is an electronic currency, so you can’t really ‘loot’ CX. But if it so happens that there’s a narrative justification for it, then CX should be lootable. On the other hand, I could introduce a ‘credit booster‘ item which loads the CX attribute just like powercaps load ENX.

Robot LOS reaction time

The introduction of an AI based ‘downtimer’ introduced an apparent random delay in the reaction time of Robots when they wanted to shoot or provide some reaction. This seemed to be a desirable effect. And it also made performance better by not hitting the AI each tick.

Downtimers and Uptimers

Downtimers and Uptimers were a specific AI feature that the game engine was connecting to. When an AI variable which was prefixed with downtimer or uptimer was created, it would update the value every 0.25s. If it were a downtimer, it will subtract 0.25s; if it were an uptimer it would add. Uptimers didn’t feature in any AI at all because downtimers didn’t need to check against a custom value. Downtimers called an event called ondowntimer when a downtimer reached 0 or below.

In Unity, I think it may be possible for the AI to instruct a creation of a Timer class. This Timer class would then raise events when it expires. The AI can configure the Timer class for other special purposes if need be.

Options for stealth: dive and roll

Dive and roll, like Crusader, gives a good option to dash between openings.

  • Could be a roll for success against detection
  • Could be always success if not within attack FOV, even if within nominal FOV
  • May have a noise penalty (Agility roll) so that the Robot may be attracted to face the area.
  • Has a cooldown, so you can’t keep on using it.

Options for stealth: shadows

Shadows, if anything else, should be implemented. Shadows enable the Player to hide better.

Dynamic lighting may, or may not play a part in this, though I think it may be too complicated to do so.

Options for combat

Some combat options for a more aggressive game style could be added.

  • Grenades were planned but not implemented.
  • Grenades are of 2 types: lobbed and discus. The discus type can be positioned around corners. The lobbed grenades can only be thrown overhead.
  • Although area effect was implemented in the prototype, I locked out the weapon that used it.

Reconsideration of ActionStrip user-friendliness

This refers to how obvious interactibility should be for scene elements.

  • Should we mouseover the element before the Astrip is valid (like the current implementation)?
  • Should we display all interactables on the SPACE keypress and then have the Player move the mouse and the Astrip icons pop up dynamically based on the mouseover?

I received feedback about this:

  • Mouseover should bring up a default interaction icon.
    • NPC – talk, or if not applicable, look.
    • Scene elements – search, if searchable, or look.
    • Robots – none, as they are attackable.
  • When LMB after mouseover, then default action is done.
  • If long LMB after mouseover, then potentially more options are displayed.
  • RMB over mouseover does nothing, as this is the fire button.

Area look-ahead, limited or unlimited range

This is the MMB look-ahead feature. Perhaps the MVS or at least the Longsense module could make a comeback so that it’s possible to modify this feature. Right now the look-ahead is unlimited, but this may not suit well. Not sure.

Shock effectivity

The shock effectivity is very effective, actually. The Shokgun feels that it’s not meant to ‘kill’ Robots, but just to shock them enough to get away, which has a nice feel to it.

Help tooltips

What are the tooltips that can help introduce the gameplay mechanics?

Pickups

Pickups are necessary especially in regards to the bomb placement. The prototype did not implement TMX-originated pickups for simplicity, though this should be implemented in the game.

Attack FOV vs Nominal FOV

This refers to the FOV needed by a Robot to attack. Let’s say this is the fire cone of the weapon. The Nominal FOV refers to the actual sighting FOV. A Robot might see you because of a high Nominal FOV, but until it faces you within its Attack FOV, it is unable to shoot.

Cooldown/heat-up period for certain actions/items

  • The C-Bomb required some time to set
  • The Haxbox required some time to set as well as a cooldown period before it could be used again.
  • Glitters had a duration
  • New movements, such as diving/rolling and dash may also have a cooldown period
  • Meds or dope could be restricted

C-Band GUI redesign, more icons, less bulk

More action icons were but along the C-Band causing it to expand horizontally. This made the interface bulkier than I originally intended, and thus the frame looks a bit bulky. More icons would be added to include the use of dope and potentially other actions, so this redesign is necessary.

Move on Intended Action location before action

The prototype featured moving to an exit tile when an exit element was clicked. But this did not reflect the other actions, such as talking or searching.

Removed or unimplemented features

  • Map Layers, MVS, Stacker (removed)
  • Poxbox (unimplemented)
  • Dope use (unimplemented)
  • Armour mechanic (unimplemented)
  • Nixing (unimplemented)
  • SCAMs (unimplemented)
  • Merchant price adjusters (unimplemented)
  • Confuse effect (unimplemented)

Player  concept design

The new narrative might see the Player’s backstory as an engineer. The current Player design doesn’t look like an engineer or anything particularly ‘technical’.

Re-evaluate Powercaps charge and Meds healing

The powercaps seemed to be recharge weakly, while meds seemed to heal very much. Need some thought about this.

Robot positional persistence vs movement in the background

This one is a tough one. Should Robots be virtually moving around? I think this is an overhead that’s rather hard. Perhaps it could be faked: before a given time threshold, Robots persist in their locations. After a certain game time, their position can be moved to a different location within a given radius, giving the impression that they have moved there when the Player has moved out of the area.

Spawning, random TINV, random attributes?

This was only partially worked out in the prototype. The spawned entity drew from a fixed TINV db. In the real game, the spawned entities should be able to randomise their inventory.

Save games

Save games are easy in C2, but I don’t think I’ll have the same ease in Unity. I think the first thing that must be taken care of is the ability to save games in Unity.

Debugging requirements

Most of the debugging requirements involve the checking of Accomps (conditions) as the Player progresses.

  • Ability to configure inventory, Accomps, during run-time using presets.
  • Ability to configure location of Player as well using presets.
  • The abovementioned configuration should reside in one preset system.

 

Advertisements

Prototyping – A Killing House

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.


Sights…


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.

Sample environment. On-screen interfaces (bottom, upper-left, upper-right)
Sample inventory screen. Item descriptions, icons.
Sample dialogue panel (Convo). Portrait.

I posted a gameplay sampler which demonstrates some of the systems in action.


… and sounds!


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.

 

Isometric reveal mechanism wip

A short demo of the mechanism of revealing the Player behind certain elements in the scene. Still rather rough; some adjustments are needed, but the principle seems to work.


The image below illustrates the implementation.

The mechanism consists of 3 colliders, and elements that have a reveal attribute that can either be always, back, front, top.

The 3 colliders are:

  • frontface_reveal_collider
  • backface_reveal_collider
  • top_reveal_collider

The procedure is:

  • When the frontface_reveal_collider overlaps a reveal=front element, then that element becomes semi-transparent.
  • When the backface_reveal_collider overlaps a reveal=back element, then that element becomes semi-transparent.
  • When the top_reveal_collider overlaps a reveal=top element, then that element becomes invisible.

Inventory

One of Citizen’s gameplay themes is the limitation of what weapons you can use. As a citizen of a dystopian, Robot-controlled city, you couldn’t freely carry dangerous goods and not expect to be accosted by Robot patrollers.

It was in the INV system that I first wanted to express that game concept. The INV system was originally conceived so that the Player can carry only a few weapons. So I delineated certain types of items can only be placed in certain ‘slots’. I also limited the number of slots for a particular category.

However, in time, I came to think that the limitation was a bit too extreme. It was complicated from the point of view of mechanism, but it also had logical game problems.


Nearly 3 years ago I was working on an RnD game project (dubbed Henry) which was supposed to feature multiple characters. The system allowed the viewing of different inventories within the same interface, and allowed trading between characters through a drag-and-drop mechanism. It featured multiple pages and a categorisation of items; weapons and armour were automatically put into the upper slots, and other adventure items were put underneath.

The Inventory system from the RnD project called ‘Henry’. I think there’s a bit of Jagged Alliance in the graphic design decisions…

It was Henry‘s Inventory system that gave me my first experience in the in the difficulty in doing inventories, from the organisation of items, to the behaviours of drag-and-drop and how the logic of how things are arranged and displayed.

Also from Henry I took the idea of categorisation, which is the exclusive placement of items of a certain type into a section of slots in the interface.

But categorisation, I later decided, was not necessary if I was just simply gunning for weapon limitations. There were other ways of discouraging the Player from carrying too many, from the increased likelihood of getting checked by Robots, or simply the inability to use them effectively once the shooting started.

Because there were hundreds of ways to skin the limitation cat, I eased my rigid rules in the INV system. However, unlike Henry I had two other Inventory-related concepts that I had to address to introduced their own complexity: Readyslot, and Trade.

The Citizen INV (right), Trade( left) and Readyslot (bottom-right) interfaces.

Readyslot

Without going into too much details about the the Readyslot’s mechanics, it is simply the place where weapons that will be used immediately for combat are put.

Switching weapons that are already in the Readyslot are done immediately. However, there’s a time-delay when you try equipping weapons from the INV, which may be akin to taking something from your backpack. This is how the game discourages the Player from swapping weapons from the INV which may potentially contain a lot of different weapons in the game.

There are other characteristics: there are only 3 slots in the Readyslot area, and that is significant. Pistols are 1-slot weapons, subguns (i.e. SMG) are 2-slot weapons, and rifles are 3-slot weapons. You can mix and match any weapon configuration that the number of slots numerically allows.

But there are special considerations for pistols, too: you can dual-wield pistols.

If you choose to equip a subgun, you can carry another pistol as ‘backup’.

If you choose a rifle, the most powerful weapons in the game, you are limited to that weapon only, and if you try to change weapons from the backpack, there’s a time-delay to get it.

The technical challenged associated with the Readyslot is how that in itself is an extension of the INV system even though it may not look like it visually. The Readyslot is a categorisation, so only weapons can be placed in there.

Trade

The Trade system is essentially the INV system, but using a different source for the contents of the INV. For example, NPCs have their own INV database, and even scene elements, like a rubbish bin that can potentially hold items, have their own INV system.

The Trade system is a little different from INV in that there is a variable slot designation that is dictated by some database (in this case it’s specified in Tiled). For example, a rubbish bin will have 2×2 Trade INV (a.k.a. TINV). A dead robot will have 1×1. Some may have 3×1, or 4×2, etc. And thus there were many considerations about how the system will respond if there was an attempt to populate the TINV with more slots that it could hold, or items that wouldn’t fit the dimension of the slots. For example, a 3-slot rifle cannot fit in a 2×2 TINV.

Slot size, width and height

In games like Diablo, items occupy ‘slots’ in the inventory. But Diablo’s system is very elaborate, as items have both width and height. For Citizen, I decided only to consider how many slots a certain item will occupy. For the most part, only rifles and subguns occupy more than one slot. This greatly simplified the system.

The reason why this is a big deal is because one of the challenges of making an INV is the correct display of items in their proper slots. When dragging a rifle (3-slots) at the right-most slot of the INV, you expect the system to compensate for the size; it must not place it the right-most slot, but 3-slots to the left in order to the rifle to fit the intended placement location.

Also, you have to consider if there are items currently in place in those slots. Will you allow items to be displaced? If so, how do you logically re-position them that makes intuitive sense?

If dragging a rifle from the INV to the Readyslot that is already full of weapons, will you make a swap? Or disallow it?

It’s questions like that, and every conceivable permutation of how one item is dragged from one place or another, dropped onto itself, or another dropped onto it, or something else entirely, all those things filled the 2 weeks I spent designing and iterating through the INV and Readyslot systems

The multi-page function isn’t yet implemented, but I have to make sure to what extent I implement it. How many items will I end up implementing in the game’s narrative and combat? How many pages will it fill? Should I have unlimited pages? Or is one page a good simply limit?

Lots of questions. But it’s all part of the fun, right?

 

AI and dialogue using node graphs and custom language

One of the significant progress milestones I’ve done since the beginning of March was the implementation of a system I call Convo and a rudimentary NPC AI that allows NPCs to move/wander and do things randomly, if their base purpose allows it.

To achieve the implementation, two fundamental concepts had to be developed. The first is SNTX, and the second is the usage of the TGF to express nodal networks that are are interpreted at runtime.

SNTX

SNTX is a procedural markup language designed to be injected into dicts. The resulting dict keys are procedurally looked-up to get to a resulting value.

SNTX is a significant upgrade to the TalkDialogue system that I developed in 2015.  The difference lies in the robustness of handling Conditions, as well as clarity. The idea behind the TalkDialogue and SNTX markups was the ability to author a dialogue tree using a text file. By and large this has been possible, but the branching nature of dialogue trees makes writing everything down in one linear text file still confusing. It was this reason that I looked to yEd in order to visualise the dialogue tree .

Conditions

One of the important aspects of SNTX is the concept of Conditions. Conditions are simply asking: is this node valid? If a node has a Condition, the Condition must be True in order for the node to be processed by the system.

Conditions check 3 things:

  • Accomps – a global dict that represent arbitrary ‘accomplishments’.
  • State – the NPC’s ‘state’ variable which is essentially a CSV string, and can comprise any number string tokens describing its state
  • INV – the Player’s Inventory can be searched for a particular item, for a particular quantity.

Doers

The other side of Conditions are Doers. Doers set Accomps or an NPC’s State. Within the Convo (and Astrip) systems, a node is capable of executing a command telling to either add/remove/modify a key in the Accomps, or amend an NPC’s State. It also allows transferring of items from NPC to Player, and vice-versa.

With Doers and Conditions combined, it allows me to script an interactive storyline.

In fact, I have completed a sample quest using all these systems as an early-stage trial for the prototype. I’m happy to say that it also involves having to kill a Robot.

yEd and TGF

yEd is a very capable diagramming application. It has become my weapon of choice because it is one of the very few programs that allow TGF export. TGF (Trivial Graph Format) is a super-lightweight nodal graph format that, when coupled with a well-thought-out markup, can solve a large number of data relationship issues.

My usage of yEd and TGFs began with simply creating nodes that were labeled as SNTX keys. I drew edges that served as annotations to their relationships, though they didn’t actually define the relationship. That is, except for Choices: for every Topic a line can be drawn to a Choice, which will then be recognised by the TGF2Convo converter tool (explained later).

A Convo graph. ‘choice’-labelled edges are the only edges that are processed in the TGF.

The above image shows Topics (yellow), Choices (green), Doers (cyan), ChoiceGroups (pink), Entry (white).

The labels in the nodes reflect directly as it is written in SNTX. When it is written in TGF it looks something like:

4 ==intro -1 ~text:: [The man seems to be so worried that he hardly notices you when you come up to talk.]\nWha? Oh, hi. You must be the new recruit. My name's Zak.
5 ->::intro
6 ++worried ~text:: Worried
7 ~dostate::+met_player
8 -> ?@met_player,$!zak_quest_rejected::intro_hi_again
9 ==intro_hi_again -1 ~text::Hi, again. 
10 ==worried -1 ~text:: I've lost the Sub- Rail pass that I was issued with. My team leader is going to kill me.
11 ++subrailpass ~text:: Sub-Rail Pass
12 ==subrailpass -1 ~text:: I don't know where I might have dropped it. I swear it was in my pocket.
13 ++quest ?@!questaccepted ~text:: [...]

This is translated using a Python script called TGF2Convo, and the output looks like this:

# EntryTopic: Entry for cond -----
->::intro

# EntryTopic: Entry for cond ?@met_player,$!zak_quest_rejected -----
-> ?@met_player,$!zak_quest_rejected::intro_hi_again
# Topic: intro -----------------------

# ~text
==intro -1 ~text::[The man seems to be so worried that he hardly notices you when you come up to talk.]\nWha? Oh, hi. You must be the new recruit. My name's Zak.

# ~choices
==intro -1 ~choices::worried

# ~dostate
==intro -1 ~dostate::+met_player
> Choice
```yml
# Choice: worried -----------------------
# ~text
++worried ~text:: Worried

# Choice: subrailpass -----------------------
# ~text
++subrailpass ~text:: Sub-Rail Pass

The TGF2Convo tool uses Markdown syntax so that when a Markdown viewer is used, it is easier to understand.

Ultimately, the dict is populated with these values, and the runtime uses the dict to determine the path of the dialogue.

However, this is not my ideal way. I had developed SNTX ahead of using TGF. Since using TGF with AI, I realised that utilising TGF fully would be a better way to go for dialogues, but this requires a re-working of the Convo system. This might be done at the end of the prototype phase.

AI and TGF

After the Convo system and SNTX were developed, I had to jump into AI. At that point I had two choices: I could go and write the AI in C2 as events, or I could attempt something much harder, ultimately more flexible, and platform-agnostic. I chose the latter.

When looking at what I wanted to do with the AI, I decided that I just needed a very simple system of controlling the actions and movements of NPCs (not the enemy NPCs). I outlined the requirements of what it would take for an NPC (Zak) who has lost something and is wandering around a given area.

First, there is the point of movement. The NPC should be able to use waypoints. Second, the NPC should have some random ability to use waypoints. Third, the NPC should have some random wait times. Fourth, the NPC should have random animation.

With all that in mind I went into the specifics of what components need to exist to make that happen. I needed:

  • ability to set NPC variables
  • ability to query NPC variables from within the AI graph
  • ability to initiate a ‘move’ (pathfinding) command from the AI graph to the runtime
  • likewise, the ability to ‘wait’
  • the ability to stop
  • the ability to have AI graph randomly choose between named choices
  • the ability to receive event handlers from the runtime
A portion of Zak’s AI graph
The AI at work. Zak is the guy with red pants. 🙂

Event handlers

When I started developing the AI, I would click on Zak to talk to him. A ‘Talk’ icon would appear, but Zak kept on moving. Though I could have effectively paused the game so that I could properly select the icon before Zak walked away, I thought it would be better to tell the AI to stop Zak.

That’s when event handlers came into the picture, which also brought forth a host of different possibilities. For example, I created an event handler called onastrip, which is called when you try to initiate an interaction. In the AI graph, this event handler is connected to make Zak stop. If the icons are ‘aborted’, the event onastripend is fired, and the AI graph is wired to make Zak resume where he left off. When a Convo is initiatedonconvo is fired, and this stops him, too. When Convo is ended, onconvoend is fired.

If a waypoint is missing, there is a wpmissing event that fires, which allows the AI to adjust itself in order to get a proper waypoint index.

The idea of ‘events handlers’ also gradually slid into the other aspects of the AI graph, where ‘events’ are triggered as a result of an operation (a Doer). For example, a choose node chooses between one of any number of events to fire. As long as that event handler is present, then it is a valid event.

Example of a `choose` node. `0`, `1`, and `2` are not really numbers; they are event names and when `choose` chooses an event the handler should be named `onchosen <event_name>`.

There is an immense satisfaction in working this way. There’s definitely a lot more work involved, but it brings the systems I’m working on at a higher level of flexibility, while at the same time, it’s still within the realm of my understanding since I’m the one developing it.

Although I don’t know how much of the AI, in particular, will make it through the Unity alpha, it is undoubtedly a very useful  piece of development because it informs me of the kinds of behaviours I may need to do; which aspects to simplify, and which aspects need more complex behaviours.

 

Another method for z-sorting

Z-sorting has been, in my experiments, been based on the Y value of a given Sprite object. It’s the sorting mechanisms have been specifically explored rather than the logic that they were arranged.

In this post I want to describe an alternate method to sort using collisions.

This method was developed to solve the issue of complex graphical elements. By ‘complex’, this refers to images that are possibly concave, or elongated. In previous z-sorting implementations, the Sprites being sorted needed to remain within a given Tile area in order for the Y position to be specifically determined for that graphic element to be properly sorted. So what I aimed to do is to overcome/get rid of the limitation that forced discrete graphical elements to remain within one Tile.

Consider the image below.

Note the elongated ‘wall’ and the L-shaped wall, both of which implicitly extend beyond one Tile area. The goal here is this: given these irregularly-sized/shaped elements, find a way to determine whether the character Sprite should be sorted behind/above the ‘walls’.

First I considered finding intersections of 2d vectors in which to construct a depth stack, but then thought it was too computationally cumbersome as an initial approach. Furthermore, this required multiple definitions of 2d vectors if, for example, the shape of the ‘wall’ was L-shaped. Note that the bottom edges were the 2d vectors that were being checked against the character’s position and vector which was, in turn, based on the isometric tile ratio, which determined the vector direction to check with. Again, very cumbersome.

So I came upon an idea of using collisions for a check. The logic is simple. If the character’s base position touches (collides into) a wall, it will be set to be behind the wall. This is the starting point and main principle of the method.

The image below describes it in C2’s event sheet.

The main Sprite being sorted is pc, and mover is the collision line. pc is pinned to mover.

The offsets shown in the image above are somewhat arbitrary. What’s important is the principle of the collisions, which are explained below.

The first issue is that the base of the character (a.k.a mover’) must actually be a volume or area, not a single point because the Sprite needs to check for a hit on the full base width of the Sprite.

The red line is the ‘mover’, and is tailor fitted to the width extent of the Sprite that is being sorted. In fact it should encompass the widest width of the Sprite animation. The ‘mover’ is collision-aware, of course.

Second, we have to consider the Y position of the mover in relation to the Sprite being sorted. Ideally, the position should be the bottom vertex of the virtual Tile diamond.  When the ‘mover’ is too high, the collision will occur ahead of time and will seem too premature. This offset doesn’t need to be precise as it is based on the specifics of the graphics, and the parameters of the game’s aesthetics.

Third, the ‘wall’ Sprites must have accurate collision polygons; as accurate as you need them to have.

Fourth, since we are dealing with 2d layers, we need to design our ‘sortable’ graphical elements so that they can split up so that can be sorted at all.

So if we were designing a four-sided room, you can encompass it with 2 elements. The first element is the bottom L-shaped wall, and the second element is the upper L-shaped wall. In this way, it possible for the character to appear either in front, or behind, either of these two elements.

 

Importing into the C2 /Files folder

Auto-importing

The C2 manual references the project file folder which contains other files other than its default.

Even though files and subfolders can be created in this folder, it doesn’t automatically become part of the caproj unless it is actually specified/registered inside the caproj. Inside C2, it is possible to ‘auto-import’ files, but this only works at the root level of /Files; directories aren’t traversed, making this mechanism suitable for single files, like configuration files.

However, when using the Files folder in other ways, such as replacing animation using Rex’s Animation Loader, it would be a monumental task to get all these files in. So I’ve written a Python function that traverses any given folder and writes out a block that can be copied and pasted into the caproj, which is near the end of the caproj. Perhaps in future, I will make the procedure more seamless; right now, the manual copy-paste is for security reasons.

The code below is very unpolished, but gets the idea across.

def make_xml_c2_file_folder():
    ''' Create a folder structure in caproj/xml format with a given directory
        The intention is to create a sprite animation folder in the /Files
        project folder, and have that referenced as imported files in the caproj.
        The output of this function is to be copied and pasted into the caproj.
    '''
    gb = glob_buffer()
    ext = '.png'
    filesdir = 'X:/GAME_PROJECTS/c2/Files/'
    unitdir = 'hero_w'
    rootdir_name = '%s%s' % (filesdir, unitdir)


    rootfolder = CaprojFileFolder(rootdir_name, ext, filesdir)
    gb.buffer += '\n%s' %rootfolder.xml
    process_folder(rootfolder,gb)


    fn = 'c:/outputcaproj.txt'
    f = open(fn,'w')
    for b in gb.buffer:
        f.write(b)


def process_folder(folder,gb):
    for c in folder.content:        
        # print(c)
        if isinstance(c,CaprojFileFolder) == True:
            # print('hi')
            gb.buffer += '\n%s' %c.xml
            process_folder(c,gb)
        else:
            gb.buffer += '\n\t%s' %c
    gb.buffer += '\n</file-folder>'
class CaprojFileFolder:
    ''' The folder class contains info about the folder, eg content, name '''
    def __init__(self, path, ext, rootdir):
        # code below considers trailing / separator like c:/test/folder/
        # where the last -1 index will contain ''. code below doesn't allow that ''
        self.rootdir = rootdir # root for relative path
        self.ext = ext # allowed file extension
        self.name = [x for x in path.split('/') if x != ''][-1]
        self.xml = '<file-folder name="%s">' % self.name
        self.path = path
        self.content = []
        self.get_folder_content()
    def get_folder_content(self):
        mf = matchfiles_full(self.path,'*')
        for m in mf:
            if os.path.isfile(m) == False: # folder
                newfolder = self.__class__(m,self.ext, self.rootdir)
                self.content.append(newfolder)
            else:

                fl = [x for x in m.split('/') if x != ''][-1]
                # check if extension is allowed
                relpath = self.path[len(self.rootdir):]

                if fl.endswith(self.ext) == True:
                    fls = '<file name="%s/%s" />' % (relpath,fl)
                    self.content.append(fls)

The class CaprojFolder represents a folder and the contents of the folder (stored in the content list). An element in content may either be another CaprojFolder object, or may be a path to a file. The caproj code snippet is written in a text file in the C: drive (!).

Once this snippet is pasted over to the caproj new folders and subfolders would be created in the /Files folder the matches the one found in the file system. The only major difference is the name of the actual files, which I explain below.

Referencing the files

Though the script mimicks the file system folder structure, C2 does not use these folders as a path to the file. In other words, C2’s folders structure is purely for visual organisation within the C2 editor. The files themselves are treated as though they were in the root directory. Therefore, I opted to name the files to represent their full relative path.

For example, say the script references a file: hero_w/run/000.png

This file is put under /Files/hero_w/run. But it is also named, literally: hero_w/run/000.png, and not just 000.png as you would normally expect. If I had named the file 000.png, there would be no way to distinguish this 000.png with other files in other C2 File subfolders. So a unique name was necessary.