• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Legacy GM Drawing Isometric Tiles to a Surface

RyanC

Member
Hi everyone, I'm trying to optimize the map on my game to get a higher fps by drawing all the floor object sprites to one large surface instead of them all being drawn separately.
Currently using depth = -y;

My question is how would I go about retaining the depth for when the player walks behind castles, trees and scenery?

 
Last edited:
M

maartenvt

Guest
The simplest solution would be to retain your scenery objects and use their build in depth values (depth = -y, as you probably already know since you've already got an isometric game) and draw the surface behind it using an object with the lowest depth, but seeing that you are looking for optimizations, this might not be fast enough.

So what you could do in addition to this, is making the scenery objects invisible (since they are also drawn on your surface they will still be visible on that) if there is no chance that a unit/player would be occluded by it. I say 'chance' because you would not want to use precise collision checking for this since it's slow, but rather something like a custom axis aligned bounding box intersection script.
 

RyanC

Member
I'm not sure it's possible to optimize an isometric game any more than using separate active objects to draw in GMS, because even drawing to a surface and then using activate_region() to activate floor objects around the player will just result in floor tiles jumping over the top of each other when the player moves down across the map. What about using actual tiles instead of objects? is there any way to convert objects into tiles?
 
M

maartenvt

Guest
I'm not sure it's possible to optimize an isometric game any more than using separate active objects to draw in GMS, because even drawing to a surface and then using activate_region() to activate floor objects around the player will just result in floor tiles jumping over the top of each other when the player moves down across the map. What about using actual tiles instead of objects? is there any way to convert objects into tiles?
Uhm I'm not sure what problems you are seeing in the way I suggested you could optimize your isometric game? Maybe I wasn't explaining it well:
1. Draw all your non moving objects (so any terrain for example, but not units and players) on a surface. Then draw this surface behind everything else.
2. Then you make all units/players, check if they could be occluded by a piece of terrain, and make those pieces visible. You can do this, for example, by checking if their bounding box intersects a bounding box of the scenery (you would have to make a custom bounding box system and bounding box intersection script for this.*).

I'm not sure if there are any benifits, speed wise, to using a background/tiles instead of a surface. Maybe someone could shed some light on this. Regardless of this, you can still optimize your game in the way I suggested above!

* To make the process of finding intersections between units/players and scenery faster, you could build an acceleration structure. This is a hierarchical data structure which enables you to check for intersections of large areas first, before diving down to the level of individual objects:
 

RyanC

Member
Thanks, I understand the method you are suggesting but I cannot see how this will work because for example: if the player were to intersect a boundry of the wooden block from above then the bottom of the wooden block wood then be drawn over the blue palm tree and any floor object below with a higher y value!
 
M

maartenvt

Guest
Ah right, I hadn't thought about that. However this can be taken care of by also checking if any other terrain pieces are occluding the terrain piece that needs to be made visible be cause it is occluding a unit/player and making them visible as well, and then recursively for those terrain pieces as wel and so on :)
 

RyanC

Member
I see what you mean, that might actually work, sounds like a really big job but hey! isn't everything.
Already started the surfaces but cannot seem to get them right.
The room is 3200 x 3200 and the target platform is android which means I cannot use a surface bigger than 2048, so this will require 4 surfaces at 1600.

Any ideas where to copy the application surface, I keep getting a surface that's double the size when I draw it back to the room at 0,0. Or perhaps it would be better to save 4 sprites because if the surfaces get lost I wouldn't be able to re-capture them easily because all the dynamic objects would be walking on the map by then.
 
M

maartenvt

Guest
Hmm, I don't have any experience with using surfaces on android devices, sadly. I do know that it is important to use surfacse with sizes which are a power of 2. I wrote a little script once that does this with any size as input:
Code:
/// surface_create_power2(int width, int height)

// Returns a newly created surface with size n x n,
// with n is as small as possible while n > width
// and n > height, and n is a power of 2. (this
// is important according to the GML manual)

var size = power(2, ceil(log2(max(argument0, argument1))));
return surface_create(size, size);
You can then draw the part of the surface you are actually using with draw_surface_part(). However, you might just want to use backgrounds instead, because of the volatile nature of surfaces. But this is quite a bit of work since you would need to make a screenshot of your world without dynamic objects, and then paste in a background. So until you are done with designing your map you might want to keep using surfaces.
Some other ways to optimize your game can be found in the following links. They might be worth to look into before you attempt to implement the implementation which we talked about above. Maybe they speed up your game enough already :)
http://www.yoyogames.com/blog/49
http://www.yoyogames.com/blog/23

Other than that, I thought of another optimization within the optimization that I suggested. For a static piece of terrain, it is always the same other pieces of static the terrain that are occluding it. This means you only need to determine these once at the beginning and save them locally in that piece of terrain. This means that the recursive 'searching' for pieces of static terrain that might occlude each other, is reduced from looking for intersections to traversing a singly linked list (waaaay faster lol).
For the other part (looking for intersections between dynamic objects and static terrain), I strongly suggest building an acceleration structure at the beginning of the game, to prevent having to loop over every single terrain piece for every unit+player, every step. EDIT: Now that I think about it, it would depend on the number of dynamic objects in comparison to the number of static objects which way you decide to handle dymanic-static intersections. For example, if you have lots of dynamic objects it might be faster to loop over all static objects and let them check for intersections with dynamic objects. In this case building an acceleration structure would not make sense since dynamic objects are, well, dynamic and thus you would need to make a new acceleration structure every step.
 
Last edited by a moderator:

RyanC

Member
Thanks for links to optimisation, found some useful things there. The method we've been talking about doesn't seem fit for isometric games that have these kind of floor sprites because if just a single tile is occluded and checks for others and so on, every single tile with a higher y value will check true and the whole lot all light up. So the only time this works is if the player is standing at the bottom of the room!
 
M

maartenvt

Guest
Hhm I don't know what you mean by "floor sprites". The floor is always drawn at the bottom right? There is no depth issue regarding floor tiles?
 

RyanC

Member
Sorry that's my fault, I didn't explain very well, each floor object has a sprite, for example the wooden block is one floor object, the flashing blue levels are also floor objects, and every piece of floor that you see is also a floor object, these all raise up and down upon map creation. They are actually 128 by 256 so if one of them were to be drawn over the surface then the bottom of its sprite would overlap the floor object below it.
 
I

icuurd12b42

Guest
Basically surface is the wrong solution to this problem

First things first. what is the cause of your slowdown? Likely the texture swaps. If you dont have proper setup for your texture groups then you would get a large amount of swaps.

call
show_debug_overlay(true) when the game starts and note the (swaps)(batch) value at the top of the screen

Since you are changing the depth you cant really control the swaps much asides being very careful to define sprites dedicated for each levels of your game and a texture group for said levels. for example, the castle levels with all the castle sprites would go on the castle texture groups

and your character would go on it's own dedicated group so no matter where your character position is depth wise, you would get 1 swap to the castle group to draw stuff behind the player, then a swap for the player to draw him, then a swap back to the castle group to finish off the remaining elements of the castle level in front of the player...

As you have multiple characters I can see, if you have AI only for that castle, they too should be on the castle group...

It may not be possible to put the castle level AI in the castle level group if your animated sprite is big ant your texture size is too small.... so they may need to be in the another texture group. if all the castel characters can be on the same texture page then that would be cool. Each character in the level would cause an extra swap.

Target max swaps for android: 10
Target max swaps for Windows: 40

Second cause of slowdown: Depth change
the depth = -y sure looks innocent enough does it?

As implied above this causes a great deal of swaps which can cause slowdowns but the other issue rarely mentioned effect this has on the internal ordered draw list.

Basically every time you change a depth it causes studio to re-order the internal draw list... which has a huge impact if you have a ton of instances

This video for example, initially was limited top 500 lemmings

the way I approached the solution from Mike's input was to override the depth handling with my own.

How it works is I have an array based on the height of the room... or the height of the view if you want more efficiency...

so if the room is 1000 pixels tall I have an array of 1000 items.
each item holds an second array of sprites to draw. so it's an array inside an array....
each end step if the thing is visible/in view, I add the sprite to draw and required values to draw the sprite. I actually use my script batch system, get it while it's free on the store

on room start create array the size of the room height. I set a script batch container to the array item
pcode
for i = 0, i<room_height, i++
global.horizArr = create script batch();

end step of object
if(in view)
batch = global.horizArr[y]
add script to batch, draw_this_scr, x,y

a controller loops through all the items in global.horizArr, processes the script batch, causing the items to draw themselves back to front, without having touched the depth...

you could have a global.horizArr for the level static element and a second global.horizArr for the moving characters... populate the static array once and the end step only need to loop through fewer elements that are moving.

your controller can do the static element then the moving element in the draw loop.
 
Last edited by a moderator:

RyanC

Member
Thanks very much, all the help is really appreciated.

I'm currently getting around 20 texture swaps, it goes up to nearly 30 when there are more enemies around. All the texture pages are optimized already with the player on its own and the guards on their own texture page with little room left on them.
The depth =-y; is only declared once for everything upon the creation of the map but the player and enemies have this in their step events.
I'll certainly take a look at your script batch system to optimize the draw pipeline, hope I can get the swaps down to 10 or so using this method...
 
Last edited:
I

icuurd12b42

Guest
Check if the character is in view in the end step. if it is, only then change the depth. also turn visible off if not in view in the same conditional. it may just be enough.
 

RyanC

Member
I think they get deactivated out of view, will check this when I get back.
Hey I've just seen TMC Delta T engine, could this be an option too?
 
I

icuurd12b42

Guest
deactivation is very slow, in fact it may be the main reason you are looking for means to improve performance. if you are deactivating/reactivating every step...

Deltat T... it depends what exactly is the cause of the slow down... if you are doing collision checks with a ton of existing instance, SGS is more a solution
 

RyanC

Member
deactivation is very slow, in fact it may be the main reason you are looking for means to improve performance. if you are deactivating/reactivating every step...

Deltat T... it depends what exactly is the cause of the slow down... if you are doing collision checks with a ton of existing instance, SGS is more a solution
Oh I didn't know that, I have a deactivate_parent object that I deactivate and then use activate region, I guess I could move the deactivation to an alarm every 60 steps instead. The overlay draw debug yellow line is the same size as the red step event line so the slow down is pretty equal
What's SGS?
 

RyanC

Member
I've now taken a good look at the game with the debug overlay, and the main issue seems to be the draw pipeline, I made a mistake, the yellow draw line is actually twice as long as the red step debug line.
 
Top