Category Archives: Gotchas

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

Why not C2?

(This is a follow-up post to my other one which explained the reasons for choosing to remain working in C2).

When I review this whole venture, it stems from the desire to create a game on my own.  I want to create the graphics, the logic, the story, the words, the music — everything — like how those guy did it back in the day when I was a wee child playing their games.

I came to use C2 because of its simplicity, and the quickness in which I can throw something together and get results. There’s nothing like instant gratification that hooks you in.

But as project size increases, so do doubts about working in C2. Lots of niggles, lots of creaks and groans give me doubts as to appropriateness of the engine/framework for Citizen. A framework is a convenience. But everything out there is a convenience except C++. I could have approached Corona or Phaser, I could have used Godot or Unity, and some of them might have been more suited to the task.

When you turn to an established game engine like Unity, you don’t tend to have that many doubts that your main goal is achievable if you were clever enough to code/design your game well. That’s because you see the sort of games that have already been developed and you can’t really argue with the fact that Unity is established for a reason.

Then you take a gander to notice the sort of games that C2 is generally used for, which is not the sort of thing Citizen is. There are plenty of developmental previews and tutorials of isometric games, — none of them are serious enough to take umbrage — but no finished product as far as the Search Engine can see.

Then one day, you come upon some kind of undesirable behaviour that you have no control over (because Construct is a very blackbox environment). You start weighing in the facts: that C2 isn’t being developed any more; that critical bugs appear in the latest builds; that downgrading is the only recourse because the developers will likely not fix it because they are committed to C3, an app whose design philosophy doesn’t nearly tick enough of your own boxes for you to use with dignity.

These leave you imagining the sort of adventures you’ll have with this little boat, which will receive no more refits, as it takes you across the pond. What awaits you, who knows? But there are tales of show-stopping, insanity-inducing odds, and some are journalistic facts as the asset obfuscation that you won’t get.

 

 


At the end of the day, it’s a ‘use the right tool for the job’ situation. And as the days go by, C2 (and C3) are becoming less of the ‘right’ tool to actually publish a game. But as many C2 users know, C2 is great at prototyping. And so at prototyping it will be relegated to.

Even if from some miracle I complete the prototype and I am able to scale it to encompass the scope of the game I want to make, it will still be a hard-sell for me to publish the game in C2 because of the absence of even a rudimentary obfuscation method: another design philosophy that didn’t get ticked.

We shall see…

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.

GridMove Direction Limitation

As described in Rex’s own site about GridMove.Direction, this expression is only valid if the movement is to a neighbouring tile. In the Slidewalk system, however, although the construction of the Slidewalk path may be straight, the target tiles may be separated from each other for efficiency sake (ie less node paths to draw in Tiled).

It is inefficient to ‘connect-the-dots’ in C2 by tagging the tiles that the path goes over, so I’m instructing GridMove to move to the Slidewalk target tiles that are not neighbours, This makes GridMove.Direction invalid.

What I did, instead, was to do my own GridMove direction by comparing GridMove’s SourceLX/Y and DestinationLX/Y expression and generate a direction using conditions.

At this point, however, the conditions assume that the nodes are positioned in a way that it follows the lines for 8 directions, as it checks how the target tile’s LX and LY are different for the source’s corresponding LX and LY.

 

Workflow: Diagonal movement across edges

The basic problem is that when moving diagonally, the edges are ignored because edges only exist at the sides of the square, and not at the corners.

Moving across diagonally travels across the corner of the logical square, which do not have any edges.

The solution to this was to query both the start tile (slg.PreTileUID) and the target tile (slg.TileUID) and from there, determine the GridMove/Board direction index that the current path was taking.

Using the principal direction (let’s assume it was a diagonal value of 6 (as demonstrated in the above image), the two other directions flanking the main one was also queried.

In the above image, the movement from the start tile is at direction 6. Direction 2 and 3 were derived from this by a lookup that defined two flanking direction for any given direction.

Then the target tile was also queried, but this time, the principal direction was the reverse of the start tile (computing for the reverse tile was made simpler by using a lookup, although a simple modulus would have done it, too). In other words, 4 is the direction. Using the same lookup, 0 and 1 were looked up as the flanking reverse directions.

Then a check is made against existing edges: are there any edges on the 2 and 3 directions of the start tile? If so, movement cost is BLOCKING. If not, then check the target tile’s 0 and 1 direction edges if they exist. And if they do block the movement.

The method used in determining the principal direction was simply comparing the logical X and Y positions of the start and end tiles.