A Foresty Game

Fayabella

  • Posts: 220

Here's everything under 'actor.getZIndex.' I also found that the day/night cycle takes some time, but not nearly as much. When I sort by 'Self Time,' the first thing is 'openfl.display.DisplayObjectContainer.getChildIndex,' with a self and total time of 65 ms. 'scripts.Design_107_107_zOrderingActors.init' takes 30ms (self) and 147 (total).

merrak

  • *
  • Posts: 2524
You can save/export the hxScout data. I'll take a look at it if you want me to... just attach it to a post.


merrak

  • *
  • Posts: 2524
I highlighted a 4.2 second frame and noticed openfl.display.DisplayObjectContainer.getChildIndex has a surprisingly high self time. 558ms out of 4,211 is a significant fraction. This function is called by the getZIndex block... so that appears to be the culprit.

For reference, here is that function:

Code: [Select]
public function getChildIndex (child:DisplayObject):Int {
for (i in 0...__children.length) {
if (__children[i] == child) return i;
}

return -1;
}

You can see it really doesn't do much--just loop through all of the elements in a list. The only conclusion I can draw is that that is one massive list. I assume that list consists of all of the actors in the layer.

You can right click on the 'for each actor on screen' block wrapper and view the code. Here it is.

Code: [Select]
engine.allActors.reuseIterator = false;
for(actorOnScreen in engine.allActors)
{
if(actorOnScreen != null && !actorOnScreen.dead && !actorOnScreen.recycled && actorOnScreen.isOnScreenCache)
{
// Here is where Stencyl will put the contents of the block wrapper
}
}
engine.allActors.reuseIterator = true;

So here's where this gets interesting. The boolean .isOnScreenCache is set in the engine itself (Engine.update function, which is the master update loop that triggers every 'always' event, physics updates, input events, among other things) It's on line 2632 in Engine.hx if you want to look at it. The thing to note is that if you have a bunch of trees on screen, there are about 30ish actors flagged .isOnScreenCache, which is not a large number.

Now here's the ugly part. The code in openfl.display.DisplayObjectContainer.getChildIndex will loop through every actor on the layer, regardless of whether or not it is flagged .isOnScreenCache. This means every engine tick you are looping through every tree in the game about 30 times. That's a lot.

That also explains why you got such a poor result with my code, while it worked fine for me. I wrote my own layering system--the 'subsectors' I was describing earlier.

What this comes down to is I think you're going to have to find a way to segment your maps into sectors. The number of actors on the scene (rather, one layer on the scene) is too large for even simple searching routines.

So now the good news/bad news. The good news is I think something as simple as just making a separate layer for each screen will be sufficient. My 'subsectors' code is complex, but a lot of that is because I'm solving other 3D problems at the same time as the sorting problem. All you really need to do is find a way to group trees in layers so you're not searching through every single one.

The bad news, though, is that as far as I know, Stencyl doesn't provide blocks for dynamic layer creation. If you know how many layers you'll need ahead of time then you can manually set them all up in the scene editor. If you want your map creation behaviors to make the layers, then you'll need to either use 'code mode' or the code blocks in flow > advanced palette to manage them.

Justin

  • *
  • Posts: 4356
Quote from: merrak
The bad news, though, is that as far as I know, Stencyl doesn't provide blocks for dynamic layer creation.

It does now!

http://www.stencyl.com/blocks/viewCategory/scene-view/create-tile-layer

They were added sometimes during the development of 4.0, IIRC.

For Live Support: Join our discord server and ping me @justin.
I'm most often available between 8am and midnight Japan time. (GMT+9)

merrak

  • *
  • Posts: 2524
Oh, neat! Learn something new every day :)

That should make this much easier to solve.

Fayabella

  • Posts: 220
So the 'get z-index of actor' loops through every actor in the scene on that layer, and that's what's causing it?

I'm sure I'd be able to do the sectors thing, but I'd have to rewrite a lot of things because every time an actor is added or I'm testing for an actor, it's going for that specific layer named 'Objects.' Maybe if  there's a way to name the current sector layer 'Objects' and set the rest to something like objects_inactive1,2,3, etc, but even then it'd be rather confusing.

Is there not a way to manually replace the z-index thing? Would I be able to put each actor on screen into a list that's somehow based on their z-index (runs that check once at the start of each loop, as opposed to looping through 30 or so times)? Though that might defeat the purpose, perhaps it'll be as laggy as the old behaviour. Not entirely sure how I'd do that with blocks but I might be able to find a way if I can use it to manually replace the z-index checker.

Or is there a better way to manually replace the z-index?

If it's not possible, then I'll try and figure out the layer sectors.


merrak

  • *
  • Posts: 2524
So the 'get z-index of actor' loops through every actor in the scene on that layer, and that's what's causing it?

That's what it's looking like. The self-time of the function getChildIndex is significant and the only thing it is doing is running that loop through the list. The only way to bring that self-time down is to reduce the size of the list.

The two options I see are to either break the map into sectors or replace the get z-index of actor function.

Replacing the get z-index function is doable, but I think your idea is going to get tricky when you integrate it with a scrolling camera. As the camera scrolls, you'll need to be constantly adding and removing actors from the on-screen list. This may also involve searching through all of the actors in order to figure out which ones to work with.

I think the biggest issue, though, is that you will still end up with a lot of actors in one list that is used frequently. This issue you're having could be the first of many symptoms you'll run into. Then again, maybe not. It could be the best approach. It's hard for me to make a recommendation since I don't know what all you have planned.

I can tell you what I would do, though--break the map into sectors. That's not necessarily an easy solution either, but it's probably something you'll need to do anyway if you want to implement real-time path finding in the future.

I think the simplest way to go about this would be to create a custom block that takes either coordinates or an actor as an input and returns the name of a layer as an output. You can then replace the literal string "objects" with this new block instead.

An easy way to partition the map into sectors would be to just divide it into rectangles that are screen-sized. This is actually what my rendering engine did back when it was using blocks. I attached my code--although it's in 3D. You would only need row and column, not level. Part of it is grayed out because I don't use this function anymore, but if I did then I would need to re-enable that part.

My screen size was 12 x 12. So I divided my maps into 12 x 12 sectors. My maps were 48 x 36, and so 4 sector-columns across and 3 sector-rows tall.

To compute the sector number of an actor, divide its column by the screen width (in tiles) to get sector-column, and row by screen height (in tiles) to get sector-row--in my case this was 12 and 12 respectively. Then take the sector-row, multiply by the total number of sector columns, and add actor's sector-column. This will give a unique number. Name each layer this number so the function returns the name of a layer.

If you do implement this, you'll need to be sure to place actors into the correct layer whenever they move. You don't have a lot of moving actors so that shouldn't be an issue, even if the code that handles this problem runs in an 'always' event. If it does then that's a problem that can be solved, but I'd recommend starting simple with this since there may be several things to debug.

This seems to be a problem that needs to be fixed. Hopefully it's the only problem and you get smoother gameplay out of this. If you don't, then the next lag bottleneck will stand out and can be tackled. I played the demo you have linked on the first post and I don't get any lag at all... so I think you're close to getting this resolved.

Fayabella

  • Posts: 220
I played the demo you have linked on the first post and I don't get any lag at all... so I think you're close to getting this resolved.

That's interesting. I tested it myself and also got no lag on the online version. I don't know why it'd be running better in the browser. Perhaps it runs off of something else when it's in the browser?
I just tested my current game (using the old behaviour) in the browser as well, and got 50-60 fps there, no matter if I was in the forest or not.

How odd. I guess I don't need to fix it? I might need to fix it later if I decide to make the level bigger but for now it's alright!

Well, thank you for all the help! I really appreciate it :D
I'll keep the sectors thing in mind for later, like when I make the map update.

Edit: I just did a test and filled the whole screen with fences, and the FPS was still good. Perhaps the SWF player is just laggier than the browser.

« Last Edit: June 30, 2019, 08:27:29 pm by Fayabella »

merrak

  • *
  • Posts: 2524
My SWF player is significantly laggier than the browser. I think Adobe stopped updating it for Linux some time ago, so I don't know if people on other OSs have the same issue.

Fayabella

  • Posts: 220
I use Windows, so it's most likely a problem on multiple platforms.

Fayabella

  • Posts: 220
Well, back to normal updates.

I'm planning on adding farming, but also being able to replant trees.

Do you all think I should have these grow based on a random ticking thing or should they grow for a specified time?

For example, 4 times a second I do a random tick count. I use this for grass already. It would choose a random crop/sapling and with a certain chance, grow it.

Or, when the crop is placed, it will wait, say, 1 in-game day (six minutes, or 360 seconds) to grow.

Which one is the better choice? I'm leaning toward strictly time-based, but I think both are relatively equally good.

Luyren

  • *
  • Posts: 1855
I prefer specified time.

On that note, does the Day and Night Cycle behavior you're using has the "elapsed time" custom block?
I have my Stencyl behaviors and resource packs available here: https://luyren.itch.io/

Fayabella

  • Posts: 220
I prefer specified time.
Alrighty. I also realized that with 'specified time' I could do stuff like fertilizer that makes it grow 2x quicker. I think I'll go with that. Thanks!

On that note, does the Day and Night Cycle behavior you're using has the "elapsed time" custom block?
No, it only has the 'set in-game time to _ hours and _minutes' block. Why do you ask?



Luyren

  • *
  • Posts: 1855
On that note, does the Day and Night Cycle behavior you're using has the "elapsed time" custom block?
No, it only has the 'set in-game time to _ hours and _minutes' block. Why do you ask?
A while back I updated my behavior to check elapsed time since a given event took place, tied to the in-game time. If your game has anything that advances time faster (like sleeping and skipping 8 in-game hours), these changes would take that into account. See the attached image.

If that's not the case, then you can do your own timers without issues, I reckon.
I have my Stencyl behaviors and resource packs available here: https://luyren.itch.io/