A Foresty Game

Bombini

  • *
  • Posts: 1280
Random, unrelated question: Is it better for me to update this thread with small things frequently or only for large things every once in a while?

I think even  small updates can be very interesting! Please keep us up to date :)

« Last Edit: June 24, 2019, 11:40:29 pm by Bombini »

merrak

  • *
  • Posts: 2424
Random, unrelated question: Is it better for me to update this thread with small things frequently or only for large things every once in a while?

I'd say just pick the one that best fits your writing style. For me--I like to write long updates less frequently, but smaller, frequent posts may be more likely to be read completely through.

If you want more feedback, I don't think either matters so long as you prompt discussion. Asking questions is a good way to do that.

Fayabella

  • Posts: 191
Alrighty. I prefer writing smaller updates. Thanks!

I got sidetracked and added fences:

It's my first use of autotiling on actors, rather than tiles, and it works pretty well! I just have to fix a problem with lag- every time you place a fence, it triggers autotiling in every fence actor. I'll make it so that it only autotiles the connected ones.

Bombini

  • *
  • Posts: 1280
Autotiling can be such a relief. I wish it would be better embedded in the editor already.

Fayabella

  • Posts: 191
I agree, a built-in implementation of autotiling would be amazing. I think there's something, but it's unimplemented and has no UI. Instead I have to have hundreds of if-statements.
The fences weren't too bad, though: I didn't have to worry about corner tiles and so I only had to check 4 directions and set the animation accordingly.
I have a map attribute that stores item's positions in the game, so each key is the position and the value is the actor. I used this to check for fences around the placed fences. For tiles like the dirt, though, I have to check all 8 directions, and I have a very long chain of if-else blocks to set the right tile.

merrak

  • *
  • Posts: 2424
For tiles like the dirt, though, I have to check all 8 directions, and I have a very long chain of if-else blocks to set the right tile.

Make an attribute and call it something like "tile number". Set it equal to 0

If there is dirt to the north, add 1 to the tile number. If there is dirt to the northeast, add 2. If there is dirt to the east, add 4. If there is dirt to the southeast, add 8. Continue for the rest of the directions: 16, 32, 64, 128

For any tile in the map, the result will lie somewhere between 0 and 255.

This number will be the Tile ID you pick from a tile set. You just need to have the appropriate tiles in their respective slots.

Fayabella

  • Posts: 191
Actually, that is what I do, for the most part. For the dirt, I just check the original 4 directions, (1 up, 2 left, 4 right, 8 down), and then set the tile based on the value (1-16), then I reset the attribute and check again, but this time in the corners. I loop through the existing tiles, and set the tile based on what type it already is + the attribute value. But that makes for a VERY long code chain... I could have done it your way and it probably would have been better, but for some reason I didn't. What I have now works well, though, so there's no reason to change it.

Fayabella

  • Posts: 191
I've re-added grass-regrowing, in a much less laggy way. I had the same problem as I had with fences- I was autotiling the whole level every time a tile grew back. I've made a custom block to autotile a specific tile, and one to autotile all 8 tiles around a specific tile.

So, that's not laggy anymore- but you know what is? Trees. If you go through a patch of them, it's just terrible. The FPS without them on screen is usually between 50-60, but with a bunch on screen, it's down to 20-30. I know the cause of this: It's the z-ordering behaviour. However, I kind of need that, and I don't know how to optimize it. I've already changed it from an updating event to a 'every .075 seconds' event and it's slightly better but still very laggy. The more actors I have on screen, the laggier it gets. It's the same when I place an entire stack of fences.


merrak

  • *
  • Posts: 2424
Z-ordering was the worst bottleneck in my game renderer for a long time. In any given room there could be hundreds (or up to about a thousand) different elements that need sorting. It took quite a bit of research to come up with the algorithms to solve the problem.

What my rendering engine does is separate actors into two different categories: Wall (static structures) and  VActor (mobile structures, including actors). The Walls are sorted when the scene loads, and VActors whenever they move. Although the VActor are sorted every frame, only the ones that change position are sorted. At any given time there may only be two or three moving things to sort. At worst there may be about 20 or so.

I can see this approach working for you (assuming you aren't already doing so... more on that later). The trees aren't going to move. Even if they're cut down and turned into a trunk, their position doesn't change. I would run the update every 0.01 seconds (i.e. in an 'always' event), not every 0.07--but you only need to sort actors when their positions change.

That would be the first thing I'd try, since it's the easiest to implement. If that doesn't turn out to be enough, the next step is to work out a way to break the scene down into sub-scenes. You don't have to sort every actor. First, make sure you're only sorting the actors on screen. It sounds like you already have this part down.

Your goal is to only sort the actors that need sorting. If your player is walking in front of a bunch of trees, only the player needs to be sorted. The trees don't. Efficiently identifying which actors need sorting becomes the challenge here.

My solution was to break each room into 'Subsector's, like so:


The four colors show how the room has been broken into four 'Subsector's. The two actors that are in the red Subsector will be sorted if they move, but the actor in the cyan Subsector will not be sorted.

Each Subsector is implemented as a Sprite (in Stencyl, layers are implemented as sprites, so you can think of a Subsector as a scene layer). The fact that the subsectors can overlap isn't an issue, because the subsectors themselves are also sorted when the scene loads. This means the solution can work for your forests, even though the trees can overlap at any point.

The basic premise behind implementation goes like this:

When an actor is placed into the scene, determine which Subsector it is assigned to. Subsectors, like Layer and Actors, are Sprites. In OpenFL, each Sprite has a list of children. In fact, an actor's position in the layer's list of children is what the z-order block gives you.

When an actor moves from one Subsector to another, remove it from the old Subsector's list of children and place it into the new list.

When an actor moves within a Subsector, or is placed into one, determine its position in the list of children. The list of children is an array, so you just need to sort the array by y-coordinate position.

If you prefer using blocks, this second step would be hard to implement. You need to access things like Sprites that Stencyl doesn't provide blocks for. You could use the code blocks and 'Anything' type attribute, so it is doable. If you haven't done so, it'd also be helpful to read how OpenFL handles sprites.

Fortunately, I don't really think you'd need to go this far. Taking the code you have and optimizing it so that it's only sorting the actors that move ought to be enough. If it's not, though, just note there are more things to throw at the problem.

Fayabella

  • Posts: 191

This is the code I have right now. 'Layers' is just each layer that I want to sort. I didn't make it, it's a behaviour I got from Stencylforge. I don't understand how z-ordering works in Stencyl very well, it's not like setting a specific value.

Your 'subsectors' thing seems super complex to me. Why isn't the cyan sector being sorted? Is it because there are no moving actors in it? I think I can get away with only sorting moving objects/newly added objects, but I don't know how to do it with blocks. I tried adding a 'if x-speed of actor on screen = 0 and y-speed of actor on screen = 0 then return to start of loop' but it didn't work.

Is it simple to add the movement check /new object check? Or do I have to write a new behaviour that sets the actor into a position in the list if it's moving/added or something?

merrak

  • *
  • Posts: 2424
That script is sorting all of the actors on screen every time it's called, which is what I was expecting. I think it can be improved without doing anything way out there like implementing subsectors.

The actor in the cyan space isn't sorted if the player, in the red space, is the only actor that moves. At least, that was the goal. If any actor in a subsector moves, then all of the actors in just that subsector are checked. That said, I just noticed a bug in my code that sorts all of the actors in every subsector. Oops :o But since actors and walls are treated as separate lists, and there are only three actors, that inefficiency is marginal at worst.

Quote
Is it simple to add the movement check /new object check? Or do I have to write a new behaviour that sets the actor into a position in the list if it's moving/added or something?

I should point out that I wrote my own physics engine from the ground up and designed it knowing I would have this problem to solve. The code that handles sorting piggybacks off of the code that moves the actors. I wouldn't recommend going that far just to solve the sorting problem, though. I needed a 3D physics engine which is why I did that.

The best solution in your case would be modify the Stencyl engine itself to flag an actor for sorting if it moves in any way. I don't think there are any blocks or events you can use to do that--you'd have to modify the .hx files themselves. The next best thing would be to check if their velocities are greater than zero. The only catch here is to be careful for cases like actors being pushed and tweens, where their positions change even though they have zero velocity.

But even all that might be overkill in solving this problem. Sorting for me is a bigger deal, since that triggers shadow and lighting drawing that you're not doing. Here's a relatively simple thing to try that wouldn't involve redesigning significant chunks of code you already have, or the Stencyl engine itself.

Below is the code I use to sort actors. I just copied and pasted it straight out of my .hx file. I'm confident you can implement this using blocks if you want to, I just don't know which blocks because a lot of that will depend on how you have your screen rendering behaviors set up. I added comments to explain the logic I'm using, though. The most significant difference between my code and what you're using is that I don't bother sorting actors whose images don't overlap. For example, if you have two trees on opposite ends of the screen then it doesn't matter which one is drawn first.

Code: [Select]
// Called (erroneously) every frame for every subsector
public function sortActorsByStack( )
{
    // if there is only one actor, or no actors, on the screen then don't do anything
    if ( population == null || population.length < 2 )
        return;

    // Loop through all of actors on screen. This is my version of the 'for each actor on screen' block wrapper
    for ( ia in 0...(population.length - 1) )
    {
        // a is an actor, the equivalent of the 'actor on screen' red block that you can click + drag out of the block wrapper
        // note--if I were using blocks to implement this, I would make an actor attribute (call it a) and set it
        // equal to that red 'actor on screen' block. Reason why--I'm going to be nesting another 'for each actor on screen'
        // inside this loop
        var a = population[ia];
        // va is irrelevant for your case. It houses 3D data for the actor
        var va = a.vallasData;

        // also irrelevant. I use "vanilla" Stencyl actors for decorations and effects, and they don't need to be sorted
        if ( va == null )
            continue;

        // Another 'for each actor on screen' block wrapper.
        for ( ib in (ia + 1)...population.length )
        {
            // Using blocks, make b an actor attribute and set it equal to the 'actor on screen' red block pulled out of the inner wrapper
            var b = population[ib];
            // irrelevant
            var vb = b.vallasData;

            // irrelevant
            if ( vb == null )
                continue;

            // here's where the sorting happens. I only need to sort two actors if their images overlap, which is what
            // AABBOverlap does. Using blocks, the most efficient way to do this is to make a global custom block
            // and implement the code in the link I put in later in this post. It can be implemented in blocks.
            // a.getHeight and b.getWidth are actually blocks anyway (height/width of actor blocks)
            // va.sx and va.sy are the equivalent of 'x (on screen) and y (on screen) of actor' blocks
            if ( IsometricUtils.AABBOverlap( va.sx, va.sy, va.sx + a.getWidth( ), va.sy + a.getHeight( ),
                                             vb.sx, vb.sy, vb.sx + b.getWidth( ), vb.sy + b.getHeight( ) ) )
            {
                // test which actor is in front of the other. The math behind this depends on the projection you're using.
                // I'm using isometric, and you're using oblique. For oblique, just set this equal to the difference of the
                // y position (on screen) of actor b and the y position (on screen) of actor a. A positive value means actor a
                // is in front of b and hence a's y position is greater.
                var atest:Int = DrawStackWrapper.actorInFrontOfActor( a, b );

                // You'll need to use the code blocks provided in the Flow > Advanced palette to utilize 'swapChildren'
                // a.parent.getChildIndex(a) is the code that the 'z index of actor' block calls
                if ( ( a.parent.getChildIndex( a ) > b.parent.getChildIndex( b ) && atest < 0 ) ||
                     ( a.parent.getChildIndex( a ) < b.parent.getChildIndex( b ) && atest > 0 ) )
                    a.parent.swapChildren( a, b );
            }
        }
    }
}

AABB overlap test just a few arithmetic operations and comparisons. Use x/y on screen of actor and width/height of actor in the code this link provides: https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection

You can make it a global custom block if you think it'd be useful elsewhere... otherwise, just put the big if statement in place of where I have my AABB overlap test.

There is a possibility this won't solve the problem. It's faster, because we replace some of the relatively expensive sprite swaps with relatively fast arithmetic... but I'm not sure if it'll be fast enough. This still doesn't isolate only the actors that move.

I thought about that some more and realized that separating the actors into two lists could be a very difficult problem in your game. My maps consist of indoor structures and yours consist of forests. There are more irregularities in the architecture of your maps. You asked how to optimize Z-ordering so consider my approach an idea... just keep in mind that every game is different so there's not always a one-size-fits-all solution.

Fayabella

  • Posts: 191
Ooh. This looks good, and it makes sense. Perhaps I'm doing something wrong:

This is my translation of your code into blocks. Unfortunately, I get <1 fps using it. I've disabled the other z-ordering behaviour. 
The 'layers' thing is me being picky about which layers to organize, and the 'offset' stuff is also my own addition; they might be the cause of the lag, but I doubt it, so you can probably ignore them. It appears to work correctly, though, just slowly. Am I doing anything wrong or will this way just not work for me?

merrak

  • *
  • Posts: 2424
...Unfortunately, I get <1 fps using it. I've disabled the other z-ordering behaviour. 
The 'layers' thing is me being picky about which layers to organize, and the 'offset' stuff is also my own addition; they might be the cause of the lag, but I doubt it, so you can probably ignore them. It appears to work correctly, though, just slowly. Am I doing anything wrong or will this way just not work for me?

I'm kinda surprised and also kinda not. The fact that this method is so much worse for you is interesting.

Before going much further I think it'd be worth to spend more time working out where the bottleneck is. I'm starting to think it isn't your (or my) code, but some other routine that both scripts are calling... my guess would be one of the drawing routines. Have you ran the game with a profiler like hxScout?

There's only so much efficiency you can squeeze out of the Painter's Algorithm for rendering a scene. Earlier I mentioned Z-ordering was the worst bottleneck in my game, but I should clarify that it was Z-ordering related to overdrawing sprites.

Any proper solution is probably going to take an investment of time to work out. A lot of things happen when the screen draws, so it's worth figuring out exactly where the problem is occurring so you don't waste a lot of time solving the wrong problem.

Fayabella

  • Posts: 191
Here's a few things that hxscout said took a while:

com.stencyl.behavior.TimedTask.update takes 147 ms (I assume this is the .01 secs wrapper)
com.stencyl.models.Actor.getZIndex takes 67 ms (inside the every .01 secs wrapper)

I don't know if this means getting the z-index of each actor is laggy or something in that line of code is, but if that's the cause, what do I do instead? Is this enough info or do I need to search deeper?

« Last Edit: June 28, 2019, 09:55:07 pm by Fayabella »

merrak

  • *
  • Posts: 2424
Here's a few things that hxscout said took a while:

com.stencyl.behavior.TimedTask.update takes 147 ms (I assume this is the .01 secs wrapper)
com.stencyl.models.Actor.getZIndex takes 67 ms (inside the every .01 secs wrapper)

I don't know if this means getting the z-index of each actor is laggy or something in that line of code is, but if that's the cause, what do I do instead? Is this enough info or do I need to search deeper?

147ms and 67ms isn't very telling by itself. How long of a time period are you inspecting? 147ms out of 1,000 would be bad, but out of 10,000 would be ok. Also, is that self time or total time for these parts? In either case, you want to dig deeper into the hierarchy. Both of those functions call other functions and you want to get their execution time as well. Ideally, expand it completely.