How to use Parts of a Sprite

S

Shadow Gamer

Guest
GM Version: Studio (recent versions)
Target Platform: Windows
Download: see code below

This is a simple little tutorial on how to get parts of a sprite as another sprite that you can use elsewhere inside your game. All credit goes to Kepons, who basically just gave me the code.

In this example, we'll be dividing a enemy up into 4 sections for use in, say, a special effect, like shattering. Please note this code splits sprites into an even amount of sections horizontally and vertically, however, it can easily be modified to split unevenly.

Code:
//PLAYER DEFINED VARIABLES (Just change these)
startingSprite=spr_enemy; //Starting sprite is an enemy
startingIndex=0; //Starting image_index is 0
sectionCount=4; //Number of even sections to divide into
newOriginX=-1000 //Use -1000 for center, otherwise just use the new chunk origin you want
newOriginY=-1000 //Use -1000 for center, otherwise just use the new chunk origin you want

//Setting/Changing variables for use below
sectionCount*=0.5; //We need to do this because we are splitting both horizontally and vertically
startingOriginX=sprite_get_xoffset(startingSprite); //X Origin of starting sprite
startingOriginY=sprite_get_yoffset(startingSprite); //Y Origin of starting sprite
ChunkW=sprite_get_width(startingSprite)/sectionCount; //Width of new sprite (width of sprite/2)
ChunkH=sprite_get_height(startingSprite)/sectionCount; //Height of new sprite (height of sprite/2)
if (newOriginX==-1000) {
newOriginX=ChunkW*0.5; //Center
newOriginY=ChunkH*0.5; //Center
}

var surf=surface_create(ChunkW,ChunkH);
surface_set_target(surf);
for (var xp=0;xp<sectionCount;xp++) {
for (var yp=0;yp<sectionCount;yp++) {
  //Drawing to surface
  draw_clear(c_white);
  draw_sprite(startingSprite,startingIndex,(xp*-ChunkW)+startingOriginX,(yp*-ChunkH)+startingOriginY);
  //Creating the actual chunk
  spr[xp,yp] = sprite_create_from_surface(surf,0,0,ChunkW,ChunkH,false,false,newOriginX,newOriginY);
}
}

//Setting the Target back to the application surface and destroying the surface
surface_reset_target();
surface_free(surf);
The script outputs a 2 dimensional array called spr, which contains a sprite that corresponds a place on a "grid" of the original sprite, known as xp and yp. For example, in the code above, spr[1,1] would be the bottom-right chunk. I highly recommend doing whatever you want to be doing inside the innermost FOR loop, since you can easily use xp and yp when placing and using chunks/objects in your game.

I know it seems like a lot of code, but you really only have to modify the variables in the Player Defined variables section if you don't want to go too deep into it.

Note: Clean up your sprite when you are done with them using sprite_delete()! Failure to do so will result in a memory leak.

Anyway, enjoy! If you have any comments or questions, just say them.

EDIT: As many users have pointed out, this is a rather inefficient method if you don't absolutely have to use it. In many cases, you can simply create a template sprite for collisions and then use draw_sprite_part to draw the chunk you desire.
 
Last edited by a moderator:

chance

predictably random
Forum Staff
Moderator
You could use the draw_sprite_part() functions too. The difference is that approach does the "dicing" every DRAW event. Whereas the surface approach creates a new permanent sprite that can be drawn simply using the draw_sprite() function.

So users can decide which approach is best for their application, in terms of speed and overhead.

One problem with the example, however, is that the surface is "reused" for each new sprite section without being cleared (or filled with color). So if the sprite has transparent regions, the previous sprite section will show through.

If that's an issue, you could clear the previous image before each new section is drawn.

Code:
draw_clear(c_white); // clear the surface of any previous image
draw_sprite(startingSprite,startingIndex,(xp*-ChunkW)+startingOriginX,(yp*-ChunkH)+startingOriginY);
 
A

Ariak

Guest
Correct me if I'm wrong: I thought the sprite_create_from_surface function creates a new seperate texture page every single time its called, and containing only that particular sprite. Wouldn't this method massivly increase the texture page swaps per frame and thus massily drop fps?
 

chance

predictably random
Forum Staff
Moderator
I don't know about texture page usage. But your comment reminds me of the potential pitfalls of sprite creation -- namely, that they must be destroyed when no longer needed, to avoid memory leaks and game crashes. So in the example here, that means sprite_delete() must be applied to every spr[i,j] index pair.

I did a test where I used this approach to create sprites, and then restarted the room without deleting the spr[i,j] sprites. With the debugger, I could see memory use increase each time I restarted, even though I was re-using the same variable names for the sprites.

But if I deleted the spr[i,j] sprites before a restart, the memory usage remained constant. So your comment was a good reminder.
 
A

Ariak

Guest
"Loading sprites and backgrounds from an external source can be done in GameMaker:Studio, as can creating new assets using functions like sprite_duplicate(). However each new asset that you create in this way will also create a new texture page, meaning that (for example) adding 10 new sprites will create 10 new texture pages! And each time you draw these sprites it is a new texture swap and a break in the batch to the graphics card.

As you can imagine, this is not very efficient, and so (unlike previous versions of GameMaker) it should be avoided, with all the graphic assets being added to the game bundle from the IDE. Note that you can use these functions for adding/creating small numbers of things and they won't adversely affect performance, but adding many, many images in this way should always be avoided as it will have an impact."
Link: http://www.yoyogames.com/blog/23

From the yoyo techblog - im fairly certain that this applies to all graphical assets added at runtime, including sprite_create_from_surface.
 
M

MishMash

Guest
Eh, sorry but this is a really inefficient way to go about it. draw_sprite_part is far more efficient as it simply remaps UVs coordinates, it doesn't do any work to "split up" or "generate" new sprites. There are a number of problems with a method like this:

1) potential for memory leaks if not all parts are cleaned up and managed correctly
2) A horrendous number of texture swaps introduced as each sprite section will have its own texture page. No gain in reducing draw calls, infact, internally there will be more calls as GM batches things drawn on the same texture page internally.
 

chance

predictably random
Forum Staff
Moderator
@Ariak: thanks for that reference. That info should be in the manual (unless it already is, and I missed it). The manual does warn about the importance of deleting on-the-fly created sprites, so that's good. But a word about texture pages would be useful too. I'll suggest that for Nocturne's next manual iteration.

The surface-based technique isn't the optimal approach for sprites used for short duration and then destroyed -- like debris from an explosion. But Shadow Gamer's example is interesting nonetheless. And it led to a good discussion about alternative techniques.
 
S

Shadow Gamer

Guest
Hello everyone,

Thank you for pointing this stuff out! I didn't know draw_sprite_part existed, that is so useful. I'm using this in my game, so I will be sure to rework my approach, and simply have a template sprite for collisions and a draw_sprite_part for the sprite.

As for this tutorial, I'm going to add in the surface clearing @chance suggested and will leave two notes courtesy of @MishMash and @Ariak saying that this is a rather inefficient method and that you should always clear up your sprites after you are done.

Edit:
draw_sprite_general is WAY better for drawing Debris since you can rotate the sprite how you wish.
 
Last edited by a moderator:
Top