• 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!

SOLVED Cropping an objects sprite with code

DrScoop

Member
Hello there, Im attempting to add a mini game to my game like a tower stacking game. In this game, we have a block the player drops which the player has to try their best align properly with blocks already dropped.

So, when a player drops the block on another, if the block misses being aligned with the block it falls on by, say, 10 pixels- 10 pixels of the dropped block should be cropped off of the dropped block's sprite.
I attempted to program this using the following code in a collision event for when the main block touches another block:

GML:
/// @description

// only allow this code to run if the piece is dropped
if self.y > 69 {
    
    //var cropDummy = instance_create_layer(self.x, collidedID.y - 24, "Puzzle_Elements", )
    
    xDifference = round(self.x - other.x);
    xDiffInverse = xDifference * -1;
    
    hasPressedX = false;
    
    // get the instance id of the object collided with
    collidedID = other.id;

    self.y = collidedID - 24;

    // the math in the Y makes sure the y position of the piece is 16 pixels above the origin point
    var inst = instance_create_layer(self.x, collidedID.y - 24, "Puzzle_Elements", obj_DummyBlock);
    
    // check if our difference is divisible by 2
    if xDifference % 2 == 0 {
        if xDifference < 0 {
            // chop off part of the sprite FROM THE LEFT of the newly created instance
            draw_sprite_part(inst.sprite_index, inst.image_index, xDiffInverse, 0, xDifference+inst.sprite_width, inst.sprite_height, inst.x, inst.y);
            inst.sprite_index = spr_dummyBlock_inv;
        }
        if xDifference > 0 {
            // chop off part of the sprite FROM THE RIGHT
            draw_sprite_part(inst.sprite_index, inst.image_index, 0, 0, inst.sprite_width+xDiffInverse, inst.sprite_height, x, y);
            inst.sprite_index = spr_dummyBlock_inv;                                      // this should be correct because the measured difference in x would be (for example) 10 before inverse, then it would become -10 when inversed. The sprite width + -10 would subtract, cutting off 10 pixels precisely.
        }
        
    }

    
    resetPiece();
}
as you may be able to see, my method of doing this was to try and draw a cropped version of the sprite where the dummy block was created, and then turn the dummy block object invisible by changing its sprite to an entirely empty block sprite. Then the newly draw cropped sprite would stand in its place and the object could still be collided with because its still there just invisible.
However, in practice when the dummy block is made invisible, a cropped sprite doesnt appear in its place. Why?
Is this because im not using the draw_sprite_part function in a draw event? How would I get around this limitation?

Thank you. If you have any questions about how the code works please ask.
 

Nidoking

Member
Is this because im not using the draw_sprite_part function in a draw event? How would I get around this limitation?
Yes, draw functions only work when they're in Draw events (aside from stuff like drawing to surfaces, but that's beside the point). What you probably need to do is store the information about how to draw the sprite in variables and then do the drawing in the Draw event. Default the variables to drawing the whole sprite, and then in the Collision event, change them appropriately.
 

DrScoop

Member
Yes, draw functions only work when they're in Draw events (aside from stuff like drawing to surfaces, but that's beside the point). What you probably need to do is store the information about how to draw the sprite in variables and then do the drawing in the Draw event. Default the variables to drawing the whole sprite, and then in the Collision event, change them appropriately.
I moved both draw_sprite_part 's to some functions I defined in a draw event and called those functions where the draw_sprites used to be, however that didn't fix the issue. Was that not proper to do? Why didn't it fix it?
 

Nidoking

Member
Let me restate what you've said as I understand it. You defined functions in the Draw event, but you call those functions from the Collision event still. Where the functions are defined is irrelevant - if you're calling them from a Collision event, they're running in a Collision event. Any call to a draw_ function that you want to actually draw something must happen within the span between (the beginning of a Draw event for an instance) and (the end of the same Draw event for the same instance). If you choose to package your draw calls in a function, you can do that, but the function itself must then be called from a Draw event.
 

DrScoop

Member
Let me restate what you've said as I understand it. You defined functions in the Draw event, but you call those functions from the Collision event still. Where the functions are defined is irrelevant - if you're calling them from a Collision event, they're running in a Collision event. Any call to a draw_ function that you want to actually draw something must happen within the span between (the beginning of a Draw event for an instance) and (the end of the same Draw event for the same instance). If you choose to package your draw calls in a function, you can do that, but the function itself must then be called from a Draw event.
So then I assume I have to change my code so that these draw_sprite functions are just in the draw event and perhaps the collision event triggers the draw_sprite functions with variables I guess?
 

Nidoking

Member
What you probably need to do is store the information about how to draw the sprite in variables and then do the drawing in the Draw event.
Just like I said. It's not particularly different from setting variables like sprite_index or image_angle in the Step event. They don't really do anything until you get to the Draw event, at which point, they're used to tell Game Maker how to draw the sprite. You'll need to store the information about what portion of the sprite to draw, but it's exactly the same idea.
 

TheouAegis

Member
How complex are the sprites? Consider using multiple sprites instead, with each sprite being the size of the minimum chunk of sprite that could break off. This wouldn't be too good if you want 1px increments, but for larger increments, should run a lot faster than draw_sprite_part() in theory and - at least in my mind - would be easier to manage.
 

DrScoop

Member
Just like I said. It's not particularly different from setting variables like sprite_index or image_angle in the Step event. They don't really do anything until you get to the Draw event, at which point, they're used to tell Game Maker how to draw the sprite. You'll need to store the information about what portion of the sprite to draw, but it's exactly the same idea.
The new code I tried to implement with this works as follows:

On collision with a dummy block, the code checks if the xDifference between origin points is divisible by 2. If it is, the variable "crop_left" or "crop_right" is set to true which should then trigger this code in the draw event:

GML:
/// @description draw functions

draw_self();

// note to self: these functions still dont draw sprites for some reason

if crop_left {
    draw_sprite_part(newDummy.sprite_index, newDummy.image_index, xDiffInverse, 0, newDummy.sprite_width, newDummy.sprite_height, newDummy.x, newDummy.y);
    crop_left = false;
}

if crop_left {
    draw_sprite_part(newDummy.sprite_index, newDummy.image_index, xDiffInverse, 0, xDifference+newDummy.sprite_width, newDummy.sprite_height, newDummy.x, newDummy.y);
    crop_right = false;
}
the newDummy variable is the stored instance of the dummy block created in the collision code from earlier but reformatted to be a permanent variable so
this: var inst = instance_create_layer(self.x, collidedID.y - 24, "Puzzle_Elements", obj_DummyBlock);
became this: newDummy = instance_create_layer(self.x, collidedID.y - 24, "Puzzle_Elements", obj_DummyBlock);

In practice this code yields a problem that the dummy block does not show up cropped. Why not?? Thank you for your contined help btw I appreciate it. I hope im following your directions okay enough.
 

Nidoking

Member
if crop_left { ... crop_left = false; }
In other words, it will draw with the crop for exactly one frame and then set itself to not be cropped anymore. Do you not understand that the Draw event happens every step, and redraws the entire screen? That's a pretty fundamental part of Game Maker.

if crop_left { ... } if crop_left
And you're checking crop_left twice, so it will just never do the second part at all.

Plus, you're doing draw_self first, so it's drawing the full sprite every step anyway and then maybe drawing the cropped sprite on top of it.

The question is, why are you bothering with this dummy block business when you can just tell the block to draw itself either cropped or uncropped based on the variables?
 

DrScoop

Member
In other words, it will draw with the crop for exactly one frame and then set itself to not be cropped anymore. Do you not understand that the Draw event happens every step, and redraws the entire screen? That's a pretty fundamental part of Game Maker.



And you're checking crop_left twice, so it will just never do the second part at all.

Plus, you're doing draw_self first, so it's drawing the full sprite every step anyway and then maybe drawing the cropped sprite on top of it.

The question is, why are you bothering with this dummy block business when you can just tell the block to draw itself either cropped or uncropped based on the variables?
I do understand that the draw event runs like a step, I just turned off the variable because i assumed it would just need to draw the sprite once and it shouldnt have to be instructed to draw it again every frame.

The second thing is that I made a mistake and typed "crop_left" a second time instead of crop right in the second if statement.

I bother with a dummy block because this is how the game works:

a block spawns, the player drops the block to the bottom of the screen. When it reaches the bottom, a "dummy block" is spawned and the original block is instantly moved to the top of the screen to be dropped again. The player than drops that block on top of the dummy block, where the game then must check the difference in x positions of the origin points between each object, and then use the draw_sprite_part function to chop the sprite of the dummy block down. Once the player chops down too much of the tower, they lose. The dummy block's object must remain because it is used for collision.

So i assume the solution for this problem your saying is I should not turn off the crop_left variable after the draw_sprite_part functions. The problem with that is how am i supposed to be able to reuse the crop for when i need to then crop the next dummy block that spawns?
 

TheouAegis

Member
I feel like you're missing the points of everything.

First off, just addressing the last issue Nido pointed out, you're calling draw_self() every step. If you want your crop_left and subsequently crop_right variable method, then you need draw_self() to only get called when both are false.
Code:
if crop_left {
    //draw partial sprite
}
else if crop_right {
    //draw other partial sprite
}
else
    draw_self();
But I think rather what Nido originally suggested was to have a single "crop" variable and then have other variables you'd also set that you can pass to draw_sprite_part(). So then your code would just be
Code:
if crop
    //draw partial sprite based on ALL the relevant argument variables
else
    draw_self();
As for the other issue Nido brought up, you have a blatant typo that you didn't even catch. And don't try to pass it off as a copypasta error -- that doesn't fly in coding. If you're that sloppy on the forum seeking help, God only knows how sloppy you are when coding without fear of anyone else judging it. In what you pasted, you would need a crop_left AND a crop_right variable, otherwise it's just illogical.

Now let's touch upon the crop variables in general. The object will draw a cropped sprite until it resets back to the starting coordinates. You need the crop variable(s) to persist every step until detecting a solid collision, at which point it can't be cropped off any more. So naturally, setting crop_left to false on the very first frame of being cropped is wrong.

Now let's address the next issue with your cropped sprite method. What happens with the dummy block it creates? How does the dummy block know which amount of sprite to draw? You're going to have to pass the inverse of the main block's variables to the dummy block. So now you'll have hundreds of dummy blocks all calling draw_sprite_part() every step. No biggy if your overall draw pipeline is simple enough. But what about collision detection? How are you going to detect collisions with dummy blocks? Or how are you going to handle collisions with dummy blocks so narrow that they trisect the main block?
Code:
MMMMMMMM
MMMMMMMM
MMMMMMMM
MMMMMMMM
MMMMMMMM
MMMMMMMM
   DD
   DD
   DD
   DD
   DD
   DD
??

Personally, and this is coming from a background of NES gaming, I would just make multiple sprites -- each sprite being a "part" of the whole sprite -- which you can then assign to dummy blocks as needed and easily detect collisions either via precise collision checks or simple math (which is what would be required for the cropped method, albeit more convoluted).

..........Unless I'm just misunderstanding your goal completely.
 

DrScoop

Member
I feel like you're missing the points of everything.

First off, just addressing the last issue Nido pointed out, you're calling draw_self() every step. If you want your crop_left and subsequently crop_right variable method, then you need draw_self() to only get called when both are false.
Code:
if crop_left {
    //draw partial sprite
}
else if crop_right {
    //draw other partial sprite
}
else
    draw_self();
But I think rather what Nido originally suggested was to have a single "crop" variable and then have other variables you'd also set that you can pass to draw_sprite_part(). So then your code would just be
Code:
if crop
    //draw partial sprite based on ALL the relevant argument variables
else
    draw_self();
As for the other issue Nido brought up, you have a blatant typo that you didn't even catch. And don't try to pass it off as a copypasta error -- that doesn't fly in coding. If you're that sloppy on the forum seeking help, God only knows how sloppy you are when coding without fear of anyone else judging it. In what you pasted, you would need a crop_left AND a crop_right variable, otherwise it's just illogical.

Now let's touch upon the crop variables in general. The object will draw a cropped sprite until it resets back to the starting coordinates. You need the crop variable(s) to persist every step until detecting a solid collision, at which point it can't be cropped off any more. So naturally, setting crop_left to false on the very first frame of being cropped is wrong.

Now let's address the next issue with your cropped sprite method. What happens with the dummy block it creates? How does the dummy block know which amount of sprite to draw? You're going to have to pass the inverse of the main block's variables to the dummy block. So now you'll have hundreds of dummy blocks all calling draw_sprite_part() every step. No biggy if your overall draw pipeline is simple enough. But what about collision detection? How are you going to detect collisions with dummy blocks? Or how are you going to handle collisions with dummy blocks so narrow that they trisect the main block?
Code:
MMMMMMMM
MMMMMMMM
MMMMMMMM
MMMMMMMM
MMMMMMMM
MMMMMMMM
   DD
   DD
   DD
   DD
   DD
   DD
??

Personally, and this is coming from a background of NES gaming, I would just make multiple sprites -- each sprite being a "part" of the whole sprite -- which you can then assign to dummy blocks as needed and easily detect collisions either via precise collision checks or simple math (which is what would be required for the cropped method, albeit more convoluted).

..........Unless I'm just misunderstanding your goal completely.
I see. I'll go ahead and implement these solutions. I don't appreciate the small attacks at my programming skills levied here though, I consider that rude and uncalled for. But again, thank you for the help. I'm going to close this issue now.
 

Nidoking

Member
I do understand that the draw event runs like a step, I just turned off the variable because i assumed it would just need to draw the sprite once and it shouldnt have to be instructed to draw it again every frame.
This is a contradiction. The thing I said you didn't understand was, precisely, that the sprite needs to be drawn again every frame. You have said that you understood, but you have demonstrated that you didn't. As long as you've learned that much, good. But you really need to understand the tools you're using to get the most out of them.
 

DrScoop

Member
This is a contradiction. The thing I said you didn't understand was, precisely, that the sprite needs to be drawn again every frame. You have said that you understood, but you have demonstrated that you didn't. As long as you've learned that much, good. But you really need to understand the tools you're using to get the most out of them.
I hope you understand that I'm not here to be lectured, I'm here to be helped. Now please, this thread is closed, so stop replying.
 
Top