How to use Parts of a Sprite

Discussion in 'Tutorials' started by Shadow Gamer, Aug 18, 2016.

  1. Shadow Gamer

    Shadow Gamer Member

    Joined:
    Jun 22, 2016
    Posts:
    33
    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: Aug 19, 2016
  2. dream

    dream Guest

    Isn't it better to use draw_sprite_part() ?_( :зゝ∠)_
     
  3. chance

    chance predictably random Forum Staff Moderator

    Joined:
    Apr 22, 2016
    Posts:
    790
    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);
     
    Cantavanda likes this.
  4. Ariak

    Ariak Member

    Joined:
    Jun 20, 2016
    Posts:
    91
    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?
     
  5. chance

    chance predictably random Forum Staff Moderator

    Joined:
    Apr 22, 2016
    Posts:
    790
    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.
     
    Ariak likes this.
  6. Ariak

    Ariak Member

    Joined:
    Jun 20, 2016
    Posts:
    91
    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.
     
    chance likes this.
  7. MishMash

    MishMash Member

    Joined:
    Jun 20, 2016
    Posts:
    376
    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.
     
  8. chance

    chance predictably random Forum Staff Moderator

    Joined:
    Apr 22, 2016
    Posts:
    790
    @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.
     
    Ariak likes this.
  9. Shadow Gamer

    Shadow Gamer Member

    Joined:
    Jun 22, 2016
    Posts:
    33
    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: Aug 19, 2016

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice