- Title: 1000000 Instances!!!!
- Description: Using titled surfaces for blood splatter or other inanimate images
- GM Version:
- Registered: yes
- File Type: .gmk
- File Size: 0.04MB
- File Link: http://www.host-a.ne...owCostItems.zip
- Required Extensions: none
- Required DLLs: none
1000000 Instances!!!! Well, it looks like it... They are fake though.
This tutorial shows how to have numerous inanimate instances on the ground without taking too much CPU.
You may skip to the good stuff below if you know this…
The problem many people have when trying to leave dead bodies on the ground or bullet casings or tire marks is threefold.
1) People make the mistake of disabling code in the step event when an item is no longer “living”,
2) People realize that eventually this method (1) cause GM to slow down and try to limit the damage by giving a lifespan to the dead item… Fading it until it is no longer visible. This resolved some issues but, eventually the CPU will no longer be able to keep up at around 200-400 instances + living instances.
3) People usually forget that instances still draw when outside the room.
Let’s solve problem 3 first.
+++++++++++++++
edit... this fact is currently being debated here
http://gmc.yoyogames...opic=353363&hl=
In the draw event
if(bbox_bottom<view_yview[view_current]) exit; if(bbox_right<view_xview[view_current]) exit; if(bbox_top>view_yview[view_current]+view_hview[view_current]) exit; if(bbox_left>view_xview[view_current]+view_wview[view_current]) exit; draw_sprite_ext(sprite_index,-1, x, y, image_xscale, image_yscale, image_angle, image_blend, image_alpha)
There… That will stop the instance from drawing itself. A faster solution is to disable the instance outside the room but this generic solution is the most viable for instance you wish to keep active… I realise this is a tutorial on how to alleviate CPU on dead instance… I’m just saying you should have this piece of code in all of your “on screen” object’s draw event, no matter what. You’ll see, in a large room, this is the difference between 20 FPS and 90.
edit... this fact is currently being debated here
http://gmc.yoyogames...opic=353363&hl=
I am now recomending not to do this unless you have code in the draw event (more than 5 lines).
It turns out I am about the only one so far that benefits from this check when only draw_sprite is called.
So, if you have a draw event because of other things like effects, please test if your code benefits from this check. It's simple... Just run the test bench in that thread. If you do not benefit from the check with the test bench, it means you are like 90% of the posters there, it means your PC is like most people...
It means you will truely see if the check improves perfomance in your case. Then add the check to your draw event and see if the performance increases.
If the test in that thread bench shows you get improvement from the simple check, your PC is not qualified to check your code... You will need to ask someone else to do it...
I do hope I worded all this correctly.
+++++++++++++++++
OK, now for the 1st item… How do I make an instance static… Disabling it without using code? Well, you clone it and destroy the original…
For that you need an object that will draw the image of the original and nothing else… It’s simple really, as long as all you need is to draw the image of the instance, all you need is pass along the state of the image to the clone.
Lets name the clone LowCostObj. It uses the draw event method mentioned above. That’s all it does… It’s a matter of setting it up.
Some "need to know" before "the good stuff"
Here is the script to clone an instance
When your instance dies, you call this script.
SCRIPT MakeLowCost
with(instance_create(x,y,LowCostObj))
{
sprite_index = other.sprite_index;
image_index = other.image_index;
image_xscale = other.image_xscale;
image_yscale = other.image_yscale;
image_angle = other.image_angle;
image_blend = other.image_blend;
image_alpha = other.image_alpha;
}
instance_destroy();So, instead of instance_destroy(), you call MakeLowCost() and presto, you have a clone of the instance… Killing the original…But still, having too many of those will kill your CPU, so you need to handle 2)… Fading and destroying the clone
It’s good to leave the clone intact for some time then fade it out. The best method is to set an alarm when the clone is created, so the clone does nothing for a while. When the alarm is done, you start to fade… Doing so right in the alarm, not the step event.
On Create
alarm[0] = room_speed * 4;//4 seconds good enough
On alarm[0]
image_alpha -= 1/(room_speed*4) //4 seconds to fade alarm[0] = 1; if(image_alpha <=0) instance_destroy();
That’s it, this give it life span of four seconds with a four second fading… You could use image_alpha -= .1 and set alarm[0] to room_speed, giving a longer but less smooth fading over a longer period, so the CPU only does a little every second. You may remove the alarm entirely if you plan not to fade and destroy the clone… It will save on CPU cycles (if you plan on not having that many instances)… Me, I like the soft fading… Which we will not use anyway. That is right… We won’t… Having 1000 dead instance fading off (or staying inactive) will still kill your CPU.
The good stuff
We only implemented good solutions to problem 1, 2 and 3 but still, it does not fix the overall problem that is the goal of the tutorial; having numerous “dead” instances.
So, how do we solve this?
TILED SURFACES and freaky (but simple) coding.
The idea of surfaces is not new but most implementations try to draw a single surface to the entire view (not screen) which means if your view is 5000x5000, forget it…
We will tile a bunch of small(er) 256x256 surfaces that will capture the dead instances and remember them.
The basic logic is simple… tile a bunch of surface handling instances in the room, each having a surface to draw and each having the task of catching a dead instance. All along trying not to kill the CPU or video memory. So, the step event is off limits… Besides, the collision event is much better at catching things…
So, on collision, we catch the instance, draw it onto the surface and on paint, we draw the surface. Simple. But what instance do we catch? Some of you might have guessed already… We already have our cloning system… so all we need is to catch those clone (sounds like a game).
So, the logic is Catch the clone, copy it to the surface and kill the clone. Then draw the surface on draw. The impact is minimal since the number of clones will instantly drop to 0 each step.
Let call our surface handler LowLowCostObj… And set it’s MASK to a 256x256 square non transparent red sprite, no precision collision (it’s square) with x,y origins set to 0,0. That will make the collision happen and simplify our calculations…
Pseudo code
On create
Surface = create surface
On collision with clone
Draw clone on surface
Destroy the clone
On draw
If inside the view,
Draw the surface.
The only problem with that is if the clone is not fully on the surface, only a portion is caught. So we tile a bunch of surface handlers. Now the problem it that overlapping clones will be destroyed and the collision event of the other surface handler will not happen. So, we need to delay the destruction of the clone so all surface handlers have their way with it. A simple alarm in the clone set to 1 will delay the destruction to the beginning of the next step, allowing all the collision to be handled.
On collision with clone
Draw clone on surface
set clone alarm[1] to 1 (alarm[0] is taken already)
And in clone alarm[1]
Destroy self.
So that takes care of the system. The last issue if failure handling.
You can’t create the surface in the create event. The surface may become invalid. And you may not even need it for the handler if nothing happens over its position. So, you delay the creation of the surface to the collision event and check if it exists every time we draw (which is the proper method when using surfaces).
Another problem is if the computer cannot or can no longer create surfaces… Video memory is blown… We need to revert to the original handling (the fading) and stop trying to create surfaces if that happens.
So, the new create event is
surface = -100; retrymax = 10; retry = 0;
The collision with LowCostObj is
if(!surface_exists(surface))
{
surface = surface_create(256,256);
if(!surface_exists(surface))
{
retry+=1;
if(retry>=retrymax)
{
instance_destroy();
}
exit;
}
retry = 0;
surface_set_target(surface);
draw_clear_alpha(c_black,0);
}
surface_set_target(surface);
with(other)
{
draw_sprite_ext( sprite_index, -1, x-other.x, y-other.y, image_xscale, image_yscale, image_angle, image_blend, image_alpha)
alarm[1] = 1;
}
surface_reset_target();And the draw is
if(bbox_bottom<view_yview[view_current]) exit; if(bbox_right<view_xview[view_current]) exit; if(bbox_top>view_yview[view_current]+view_hview[view_current]) exit; if(bbox_left>view_xview[view_current]+view_wview[view_current]) exit; if(!surface_exists(surface)) exit; draw_surface(surface,x,y);
Oh, and to clean up
On Room End
if(!surface_exists(surface)) exit; surface_free(surface);
So, now, when the object detects a clone, it tries to create the surface if it did not do it prior… It will try and fail a few times before committing suicide, leaving the clone to revert to its fading method.
All we need now is a tiling method. For that, we create a 3rd object that you will drop in the room. LowLowCostTileRoomObj
On Create
var i,j; for(i = 0;i<room_width;i+=256) for(j = 0;j<room_height;j+=256) instance_create(i,j,LowLowCostObj) instance_destroy();
Ok, lets recap…
You drop a “Tiler” object in the room. It tiles the room with the surface handlers…
The “surface handlers” do not draw until in view and until they actually have stuff to draw. They have a collision mask the size of the surface and capture any clones that collide with them (created over them)
The “clones” duplicate the look of the instance we destroyed using MakeLowCost(). And will fade away if they were not captured…
In your objects, you destroy the instance using MakeLowCost() and you are set…
Note, you can also create objects and set the parent to LowCostObj such as the tire marks in the example (Last Room)… If you need to have a damaged version of the instance that “died”, create a dead version, set the sprite to the dead looking sprite and set the parent to LowCostObj.
Now to create the dead looking inactive version of the instance, in the destroy event (which was called using instance_destroy() in this case, the code will look something like this…
with(instance_create(x,y,DeadLookingVersionObj))
{
image_angle = other.image_angle;
}…copying over the proper state of the image, in this case only the image angle is relevant.Note. All is not carved in stone, you may create a dead looking instance elsewhere than the destroy event… The tire thread in the example are created every step.
Also, even if this method handles large view, in reality, you may still have problems if the view requires 1000 surface handler instances each with its surface… that’s 1000 * 256 * 256 * 4… 256MB. Fortunately, the code self fixes itself to accommodate this issue but be aware that some video card may fail early.
So, I added a safeguard to the clones… They will destroy any other old clones when the limit is reached
On create
If(limit reached) destroy first clone in the clone list
Note the depth of the surface tiles is 1 and so are the clones. So make sure you don’t have anything under there or change the depth to match your game. You may implement the same system at different depths but be aware that each surface tile at each depth must be unique/different and handle it’s own clones besides the supplied example
By this I mean, if you want a LowLowCostDepth10000, you should have a LowCostObjDepth10000 so there is no conflict with overlapping surface handlers at different depths.
Also, if you set Interpolate Color Between Pixels in the game options you may have to call…
texture_set_interpolation(false)
//drawing to surface code in collision
//…
texture_set_interpolation(true)
…if you find odd differences between the original image and the fake one.
Finally, you can set the parent of any static “décor” objects to LowCostObj that you may want add in the room. Just add a (possibly empty) code box in the create event to disable the fading and setup the object. Be aware that if the system starts to fail during the game, and the surface is lost, you may loose valuable details.
Don’t let all these warnings scare you. They are all known issues using surfaces.
Have fun!
Icuurd12b42
Oh yeah, a little credit is always fun!
Edited by icuurd12b42, 22 July 2011 - 07:07 PM.











