Category Archives: Gotchas

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.

Advertisements

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.

 

Gotcha: SLG, moving across edge issue

There is a gotcha with using SLG movement and edges which relates to cache cost (a parameter in SLG movement).

The image above describes how a movement from tile [1,13] to [1,12] should yield a path that comes around. However if the cache cost parameter is set to Yes then it will refuse to yield a path at all.

Setting it to No will solve the issue.

Not sure what this is about, but noting it down for future reference.

Workflow: Z-Sorting by Y

In the RND test project, I ran into performance issues with ZSorter due to the high number of instances I was using. I’ve easily re-implemented the C2 ‘native’ action of z sorting and the performance is much better.

However there is a gotcha.

The native version, despite what seems to be a vague reference of layering behaviour in the manual (search for Sort Z ordermoves instances which might have been in other layers to layer that had been sorted last. If the sorting is done per tick, there is no way to force a particular instance to another layer and expect that to render accordingly. (For example, I created a ghost clone over my main sprite. The ghost sits on a top layer, but is an instance of that sprite. Because the ghost belongs to the same sort family in which I base the sort, the ghost also gets sorted. And because the action moves the ghost into the same layer as the other instances, it loses its layer blending purpose.

The solution looks more like a hack: in cases like the above, where instances need to be separated, the sort reference variable (the variable that the action uses as a basis for a sort), should be overridden. In the case above, the ghost instance’s sort reference variable had an extremely large value so that the sort engine will always have to place it on top.