• 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 Storing multiple instances and modifying them differently for different situations

DrScoop

Member
Hello again. This problem is related to a previous topic I opened (https://forum.yoyogames.com/index.php?threads/cropping-an-objects-sprite-with-code.92396/)

Most of the suggestions levied there did not solve the issue and I could not wrap my head around it, so I torched most of that code and began following a different method based on suggestions made there. However, for the sack of completeness, I will begin from the start explaining the code I have and its current issue. (I am not having cropping issues too similar to the previous topic's, that is why this is a new thread).

In my game, I am developing a tower stacking mini game. The goal of the game is to take a horizontal log shaped block thats moving back and forth at the top of the screen and to stack it on top of previous blocks placed as precisely as they can. If the player misses, the overhanging part of the block gets cut off. If the player ends up with blocks shaved too much before the game ends after, say, 20 blocks, they lose.
Here we have a collision event. The object obj_blockPiece is dropped, and as soon as it collides with an instance of obj_dummyBlock, it executes this code:
GML:
/// @description

// only allow this code to run if the piece is dropped
if self.y > 69 {
 
    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
    newDummy = instance_create_layer(self.x, collidedID.y - 24, "Puzzle_Elements", obj_dummyBlock);
    pieceIndex += 1;
 
    array_set(blockArray, pieceIndex, newDummy);
     
        // if the block is placed left, the xDifference is negative. Inverse that number to measure difference from the left
        if xDifference < 0 {
            xDifference = xDiffInverse;
         
            // record what the current Piece's x difference is in an array
            array_set(pieceArray, pieceIndex, xDifference);
         
            crop_left = true;
            if obj_dummyBlock.xDiffIndex {
                obj_dummyBlock.new_width = obj_dummyBlock.new_width-dummyBlock.xDiffIndex;
            }
         
        }
        if xDifference > 0 {
            crop_right = true;
        }

        crop_left = true;
 
    resetPiece();
}
Explaining how this works line by line:
First, I must explain all this code is for when we have already placed a block. When we place a block, it gets swapped out with a "dummy block" to stand in its place while the original block immediately teleports back to the top of the screen.
In this code, We check if our regular block is not at the top of the screen (this might not be needed but I thought if aint broke dont fix it), then we get the difference between the X Origin points between the regular block and the dummy block it just contacted. For the sake of the crop function used later, we also get the inverse of the difference (negative/opposite sign). We then set it so the tracking variable "hasPressedX" is false to make sure the game doesnt instantly drop the next block. We then set our regular block's Y position to 24 pixels above the dummy block to force it the proper spot in case we stopped the falling block at the wrong y position.
We then create a new dummy block at the exact spot the regular block stopped, and then immediately we increment the "pieceIndex" by 1 to help track which number of piece we are on. We then also add the newDummy instance to an array called blockArray. We then have a check to see if our xDifference was negative or positive, and depending on which one, we activate the specific crop direction function we want. After cropping, the resetPiece function executes. Here is its simple code inside the create event of this object:
Code:
function resetPiece() {
    spawnX = random_range(137, 348);
    self.y = 69;
    self.x = spawnX;
 
    turn += 1;
}
Moving on, I have to preface that first before I get crop_right working I'm trying to get crop_left working. Anyway, setting crop_left to true activates the following code in the DRAW EVENT of obj_dummyBlock:

GML:
show_debug_message("newDummy X = " + string(x));
show_debug_message("DSGRID X = " + string(ds_grid_width(widthGrid)));
show_debug_message("DSGRID Y = " + string(ds_grid_height(widthGrid)));
show_debug_message("argument 7 = " + string(widthGrid[# 1, obj_blockPiece.pieceIndex]));

//draw_rectangle_color(x-36, y-40, x+36, y, c_red, c_blue, c_yellow, c_green, true);

if instance_exists(obj_blockPiece.newDummy) {
    if id == obj_blockPiece.newDummy.id {
        if obj_blockPiece.crop_left { //-36
         
            xDiffIndex = array_get(obj_blockPiece.pieceArray, obj_blockPiece.pieceIndex);
         
            x = round(x);
            draw_sprite_part(sprite_index, image_index, xDiffIndex, 0, new_width, sprite_height, (x-36)+xDiffIndex, y-40);
         
            show_debug_message("newDummy crop_left X = " + string(x));
      
         
        } else if obj_blockPiece.crop_right {
         
        } else {
            draw_self();
        }
    } else {
            ds_grid_resize(widthGrid, 2, obj_blockPiece.pieceIndex + 1);
            ds_grid_set(widthGrid, 0, obj_blockPiece.pieceIndex, new_width);
            ds_grid_set(widthGrid, 1, obj_blockPiece.pieceIndex, xDiffIndex);
            draw_sprite_part(sprite_index, image_index, widthGrid[# 1, obj_blockPiece.pieceIndex], 0, widthGrid[# 0, obj_blockPiece.pieceIndex], sprite_height, (x - 36)+widthGrid[# 1, obj_blockPiece.pieceIndex], y-40);
            //draw_rectangle_color(x-(sprite_width/2), y-(sprite_width/2), x+(sprite_width/2), y+(sprite_height/2), c_red, c_red, c_red, c_red, true);
    }
} else {
    draw_self();
}
First, we check if an instance of the newDummy (the particular instance we just created) exists. If it does, we check if we are working with the particular instance we just created too. Then, if we turned on crop_left, it first gets the current xDifference of our particular instance of the dummy block and stores that in xDiffIndex. After that, we round the x value of the dummy block to make sure its a solid number, and then we crop the sprite using those parameters.

Skipping down a bit, we find that if we didnt activate crop_left or right, we just draw_self(). Further down, if the id of the current instance is not the same as the id of the newest Dummy, we take the ds_grid we defined earlier like this:
GML:
// create event of obj_dummyBlock

new_width = sprite_width;

xDiffIndex = noone;

widthGrid = ds_grid_create(2, 1);
and then we take that ds_grid and resize it by making its height equal to the current pieceIndex + 1. In the first column of that grid and in the y position of that column equal to the pieceIndex, we add in whatever the current instance's width is. The next part is we add in the current xDifference into the second column and the same y position as the pieceIndex.

We should then draw each individual piece we drop according to the recorded xDifference and width it had. However, in practice, this does not quite happen...


My main concern is why it crops the very first block slightly from the very right when the second block drops. My second concern is how each block placed after the second crops as if the whole block underneath it were still there, but thats besides the point. I forst want to focus on why the first block crops on its right erroneously. If you have a solution for the second problem however, I'm open to it.

Thank you for reading all this text, I'm aware how complicated this may be to understandšŸ˜…

(P.S. this time I'll be more open to re-reading and FULLY understanding any presented help and solutions. Last post was a bit of fiasco and I apologize for my behavior. Thank you for taking the time out of your busy day to respond if you do. Me and my other junior dev partner are banging our heads together trying to solve this mini game code challenge)

(EDIT: gif link was broken)
 
Last edited:

TheouAegis

Member
if xDifference < 0 {
xDifference = xDiffInverse; // record what the current Piece's x difference is in an array
array_set(pieceArray, pieceIndex, xDifference);
crop_left = true;
if obj_dummyBlock.xDiffIndex {
obj_dummyBlock.new_width = obj_dummyBlock.new_width-dummyBlock.xDiffIndex;
}
}
if xDifference > 0 {
crop_right = true;
}
crop_left = true;
First thing I notice is you set crop_left to true twice, the second time without any condition. Seems like a residual garbage line.

Second thing is you changed xDifference from negative to positive in the crop_left block, then checked xDifference to set crop_right. Don't change xDifference at all. Save xDiffInverse to the array instead.
 

DrScoop

Member
I see you using a lot of object dot variable syntax. That's bad news. Here's your required reading on the subject.



Here, in particular, you don't even seem to be using the same object ID for both checks. I think understanding this is going to be the most important piece of fixing your problem.
Before I reply to the TheouAegis here, I want to set the record that my actual code does not include mismatched object id's. This is because obj_blockPiece and obj_dummyBlock contain names of which i would rather not make public yet for my game. So in my haste to replace the names of each objectID in the copy and paste I mistakenly forgot the word Piece at the end of that CTRL-F.

First thing I notice is you set crop_left to true twice, the second time without any condition. Seems like a residual garbage line.

Second thing is you changed xDifference from negative to positive in the crop_left block, then checked xDifference to set crop_right. Don't change xDifference at all. Save xDiffInverse to the array instead.
I have remedied all issues brought up here and I see no change in the problems brought up in the Original Post. However I'm glad you caught those problems! They couldve been causing stuff I had no idea about. Do you have any other ideas, possibly? Thank you both again for responding. :D
 

DrScoop

Member
I see you using a lot of object dot variable syntax. That's bad news. Here's your required reading on the subject.
As for this, I'm a bit confused. The documentation says to just NEVER use object_id.variable when using multiple instances, right? So I've been trying to find a way to use my various array's ive set up (especially blockArray) so I can track each instance i've created of "newDummy" individually and make sure each one retains its set width and xDifference so they all stay cropped properly. The problem is I don't know how I would set up this kinda code. This is the first time I've ever had to deal with per-instance control of objects created in code.

Thanks again
 

Nidoking

Member
So I've been trying to find a way to use my various array's ive set up (especially blockArray) so I can track each instance i've created of "newDummy" individually and make sure each one retains its set width and xDifference so they all stay cropped properly.
Then you need to be using those instance IDs when you want to refer to those instances. Using the object ID can work, if and only if there is at all times exactly one instance of that object in your game - no more, no less. That will usually mean a control object that has no physical presence in your game. It's bad practice to use an instance of something controllable that way, and explicitly wrong if it's something that you have multiple instances of, like your obj_dummyBlock. This will cause problems.

Once you understand instances, you'll understand that instances contain their own variables. There's absolutely no reason that you'd need to store instances in an array to handle that. Just store the correct variable values in the instance after you've created it, and let it use its own variables to draw itself. Your problem is that you're placing your variables in the wrong scope. Once you know how much a specific obj_dummyBlock instance should be cropped, store those values in the obj_dummyBlock instance. Where you're using an array index to calculate a position, just calculate the position directly, one time, and store that in the instance. Store everything about the instance in the instance. It's all in the page I linked.
 

TheouAegis

Member
self.y = collidedID - 24;
This is wrong, but wouldn't cause the issues I saw in the video. wtf. Anyway, that should be other.y-24, should it not? And in which case, this line could just use self.y:
Code:
newDummy = instance_create_layer(self.x, self.y, "Puzzle_Elements", obj_dummyBlock);
I see no point for collidedID anyway.

if obj_dummyBlock.xDiffIndex {
obj_dummyBlock.new_width = obj_dummyBlock.new_width-dummyBlock.xDiffIndex;
This is setting new_width for ALL obj_dummyBlock. Is that really what you want to happen? Is that perhaps why the first dummyBlock got cropped on the right?

Also this
Code:
if obj_blockPiece.crop_left {
I don't think your draw code should be referencing g obj_blockPiece at all. You should have saved all the info you need to the arrays or in obj_dummyBlock directly. obj_blockPiece's variables change every step, obj_dummyBlock's variables never change. That volatility could be causing issues. Something to try out.
 
Last edited:

DrScoop

Member
I've heeded both of your guys' advice and reformatted my code to be more "multiple instance" friendly. My new code instead creates these dummy blocks after determining if the xDifference was greater than or less than 0, and then in the CREATE event of the dummy block I get all the variables I need to determine how a crop must be done on the given instance created. This solved my previous multiple instances issue and the first block cropping. Thanks! However, I've run into a new issue.

Here is the new code before I explain my problem:

obj_dummyBlock CREATE event:
GML:
/// @description

//TODO:
// Crop the OG blockPiece as much as we have cropped the last dummy

lastPieceCrop = noone;
lastCropWas = "none"; // either set as "none:, "left", "right", or "both"

inst_cropLeft = false;
inst_cropRight = false;
no_crop = false;


leftShave = 0; //measured amount to cut from left. Determined with xDiffInverse
rightShave = 0; // measured amount to cut from the right. Determined with xDifference
new_width = noone; //measured width from the point specified from the left. This variable is used to crop from the right

// first, grab how we must crop the instance with some variables

// What direction(s) are we cropping from?

// if we are cropping ONLY from the left
if obj_blockPiece.crop_left && !obj_blockPiece.crop_right {
    
    inst_cropRight = false;
    leftShave = obj_blockPiece.xDiffInverse;
    new_width = sprite_width-leftShave;
    
    pieceArrayLen = array_length(obj_blockPiece.pieceArray)
    // if we have placed any cropped blocks at all (the array size must greater than 1)
    if pieceArrayLen > 1 {
        // get the value(s) from the last index in our piece array
        lastPieceCrop = array_pop(obj_blockPiece.pieceArray);
        // check if the last time we cropped a piece it was cropped from the left
        if lastPieceCrop[0] == "left" {
            // increase the amount we shave from the left by the amount that was shaved on the last piece
            leftShave += lastPieceCrop[1];
            // modify the width accordingly
            new_width = sprite_width-leftShave;
            // the above values will then be added into the array and so on.
        }
        if lastPieceCrop[0] == "right" {
            // in case we have shaved this block to the left before, compound shave the block to the left
            
            // how would we search through the piece array for the last instance "lastCropWas" was "left"?
            leftShave += // the last leftShave amount stored in the array goes here
            // set our new width to be what the width of the previously right Shaved block is minus how much we're now shaving to the left
            new_width = lastPieceCrop[2]-leftShave;
        }
        
        
    }
    // begin infinitely drawing the instance cropped
    inst_cropLeft = true;
    
    // add info about this current instance's cropping to the piece array
    lastCropWas = "left";
    array_push(obj_blockPiece.pieceArray, [lastCropWas, leftShave, new_width])
}

if obj_blockPiece.crop_right && !obj_blockPiece.crop_left {
    inst_cropLeft = false;
    rightShave = obj_blockPiece.xDifference;
    new_width = sprite_width-rightShave;
    
    pieceArrayLen = array_length(obj_blockPiece.pieceArray)
    // if we have placed any cropped blocks at all (the array size must greater than 1)
    if pieceArrayLen > 1 {
        // get the value(s) from the last index in our piece array
        lastPieceCrop = array_pop(obj_blockPiece.pieceArray);
        // check if the last time we cropped a piece it was cropped from the right
        if lastPieceCrop[0] == "right" {
            // increase the amount we shave from the right by the amount that was shaved on the last piece
            rightShave += lastPieceCrop[1];
            // modify the width accordingly
            new_width = sprite_width-rightShave;
            // the above values will then be added into the array and so on.
        }
        // if we last cropped from the left
        if lastPieceCrop[0] == "left" {
            
        }
    }
    // begin infinitely drawing the instance cropped
    inst_cropRight = true;
    
    lastCropWas = "right";
    array_push(obj_blockPiece.pieceArray, [lastCropWas, rightShave, new_width])
}

// if we aren't cropping at all
if !obj_blockPiece.crop_left && !obj_blockPiece.crop_right {
    no_crop = true;
    // add the this crop to the array anyway
    array_push(obj_blockPiece.pieceArray, [lastCropWas, 0, sprite_width])
    
} else {
    no_crop = false;
}
The problem here is included in the comments.
But to explain, I have an array (pieceArray) which I use to track how we have cropped each instance over time. When an instance's variables for cropping are calculated, they get pushed into the end of the array. In each index of the array, another array is found which contains a string variable called "lastCropWas" which is used to determine what direction we cropped from in the last piece, the next variable is how much we have cropped from that direction, and then what the width of that previous instance was set to.
This works all fine and dandy, I just have a problem where I don;t know how I would reach further into the past indexes of the array and determine how much we have cropped from the left before if we cropped from the right in the last piece.
For example, we drop a piece and it crops 10 from the left. Then we drop another piece and it crops 10 from the right. How do we tell a new block cropping from the left how much we cropped from the left on the first block? Thanks!

(If you need further explanation on how the code ive shown here works, please ask. Thanks again for your help!)
 

Nidoking

Member
I don't think you need that array. The instances are there, and if I understand your premise correctly, the coordinates where the blocks are cropped carry upwards. Will the dummy block on top ever NOT have the same cropping as the one below? It seems like once you've put a new dummy block on top, there shouldn't be any need to look at what's under it at all. But if you do, you access elements of an array using indices. You've got some [0]s there already, showing that you know how to access elements of an array by number. You just use a number other than 0 to get an element other than 0.

Honestly, [lastCropWas, 0, sprite_width] is a pretty bad use of arrays. Structs would make more sense here, because each data element has a specific meaning - cropType, leftCrop, width. Using a struct lets you access those elements by meaningful names and not meaningless numbers, and makes everything you write easier to read. Compare:

lastPieceCrop[0]
lastPieceCrop.cropType

Isn't the second one much easier to interpret?
 

DrScoop

Member
I don't think you need that array. The instances are there, and if I understand your premise correctly, the coordinates where the blocks are cropped carry upwards. Will the dummy block on top ever NOT have the same cropping as the one below? It seems like once you've put a new dummy block on top, there shouldn't be any need to look at what's under it at all.
If what you are saying here means "Will the block placed on top of the previous one always be cropped exactly the same as the one its on top of?" then that is not true. Take this case as an example:

Screenshot 2022-01-17 194720.png

The third block was chopped more based on how the previous one's chopping was recorded in the array.

I want to have the ability to access the latest piece of the array that has a lastCropWas of "left", but idk how i would access an array index like that. Perhaps with your suggestion of reformatting my code to use a struct instead of an array would provide a good way to do this?

Hope I'm not misinterpreting you again. Thanks.
 

Nidoking

Member
I appreciate the picture, because it tells me that my assumption was correct. Show me what your stack would look like if you placed the falling block so that both ends hang over the one below, if the rest of this post doesn't match it.

I think, instead of calculating "left crop" and "right crop" based on the cropping amounts of the blocks below, what you want to do is find the actual left and right ends of the block below, and if the current block's left end is farther left than the top of the stack (for example), then you figure out what that difference is and that's your left crop value. Same thing on the right side - the difference between the falling block's right end and the dummy block's right end will be the amount you crop from the right side. So, in addition to storing crop amounts, you should store the left and right ends of the block as absolute pixel coordinates. Now, all you need to do when your new block lands on top is compare its left end to the left end of the top dummy block, and its right end to the right end of the top dummy block.

So, assuming that your last dummy block is currently in a variable called lastBlock, to find the leftCrop amount, you'd do something like max(0, lastBlock.leftEnd - leftEnd). Then your new block's leftEnd should be max(leftEnd, lastBlock.leftEnd). The right end would work in a similar way. You'll have to determine how to find leftEnd and rightEnd at first based on what you already have for those blocks, but this is the idea I would recommend using.
 

DrScoop

Member
I think, instead of calculating "left crop" and "right crop" based on the cropping amounts of the blocks below, what you want to do is find the actual left and right ends of the block below
Okay, I attempted to implement the ideas you came up with and I again have some logic issues.

Here is the new code:

this is the collision event for obj_dummyBlock IN obj_blockPiece
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", )
    
    // store the position of log barker on collision
    //xPlaced = x;
    //yPlaced = y;
    
    xDifference = round(self.x - other.x);
    xDiffInverse = xDifference * -1;
    
    hasPressedX = false;
    
    // get the instance id of the object collided with
    collidedID = other.id;
    
    pieceIndex += 1;
    
    // check if our difference is divisible by 2
    //if (xDifference % 2 == 0) {
        
        // if the block is placed left, the xDifference is negative. Inverse that number to measure difference from the left
        if xDifference < 0 {
            //lastBlock.leftCrop = xDiffInverse;
            crop_left = true;
        } else {
            crop_left = false;
        }
        
        // crop right
        if xDifference > 0 {
            //lastBlock.rightCrop = xDifference;
            crop_right = true;
        } else {
            crop_right = false;
        }
        
        if xDifference == 0 {
            crop_left = false;
            crop_right = false;
            
            lastBlock.leftCrop = 0;
            lastBlock.rightCrop = 0;
            no_crop = true;
        }
        
        // create the damn instance AFTER we know which direction crop
        newDummy = instance_create_layer(self.x, collidedID.y - 24, "Puzzle_Elements", obj_logBarkerDummy);
        
    //}

    
    resetPiece();
}
this is the create event of obj_dummyBlock

GML:
/// @description

//TODO:
// Crop the OG blockPiece as much as we have cropped the last dummy

lastPieceCrop = noone;
lastCropWas = "none"; // either set as "none:, "left", "right", or "both"

inst_cropLeft = false;
inst_cropRight = false;
no_crop = false;


leftShave = 0; //measured amount to cut from left. Determined with xDiffInverse
rightShave = 0; // measured amount to cut from the right. Determined with xDifference
new_width = noone; //measured width from the point specified from the left. This variable is used to crop from the right

// first, grab how we must crop the instance with some variables

// What direction(s) are we cropping from?



// if we are cropping ONLY from the left
if obj_blockPiece.crop_left && !obj_blockPiece.crop_right {

    inst_cropRight = false;
    leftShave = obj_blockPiece.xDiffInverse;
    
    // if we have cropped the block below from the left
    if obj_blockPiece.lastBlock.leftCrop > 0 {
        leftShave += obj_blockPiece.lastBlock.leftCrop;
    }
    new_width = sprite_width-leftShave;
    
    // if we have maybe cropped the block below from the right (not exclusive from if statment above)
    if obj_blockPiece.lastBlock.rightCrop > 0 {
        new_width -= obj_blockPiece.lastBlock.rightCrop;
    }
    

    // begin infinitely drawing the instance cropped
    inst_cropLeft = true;
    
    // store how much we cropped from the left this block
    obj_blockPiece.lastBlock.leftCrop = leftShave;
    // we dont have to store how much we cropped from the right because it will always be equal to the previous amount
    // cropped from the right
}

if obj_blockPiece.crop_right && !obj_blockPiece.crop_left {
    
    inst_cropLeft = false;
    rightShave = obj_blockPiece.xDifference;
    
    if obj_blockPiece.lastBlock.rightCrop > 0 {
        rightShave += obj_blockPiece.lastBlock.rightCrop;
    }
    
    new_width = sprite_width-rightShave;
    
    if obj_blockPiece.lastBlock.leftCrop > 0 {
        leftShave += obj_blockPiece.lastBlock.leftCrop;
    }

    // begin infinitely drawing the instance cropped
    inst_cropRight = true;
    
    // store how much we cropped from the right this block
    obj_blockPiece.lastBlock.rightCrop = rightShave;

}


// if we aren't cropping at all
if !obj_blockPiece.crop_left && !obj_blockPiece.crop_right {
    no_crop = true;
    // add the this crop to the array anyway
    
    // rewrite the blockCrop's structs
    obj_blockPiece.lastBlock.leftCrop = 0;
    obj_blockPiece.lastBlock.rightCrop = 0;
    
} else {
    no_crop = false;
}
and then this is how each draw function is working (and yeah I'm aware I could condense these down into the same draw_sprite_part(), but my strongest guess is that I'm drawing incorrectly and thats whats causing cropping to be messed up)
draw event of obj_dummyBlock
GML:
if inst_cropLeft {
    draw_sprite_part(sprite_index, image_index, leftShave, 0, new_width, sprite_height, (x-36)+leftShave, y-40);
}

if inst_cropRight {
    // we include left shave since a lot of the time we will need to crop from the left if we have EVER cropped from the left before
    draw_sprite_part(sprite_index, image_index, leftShave, 0, new_width, sprite_height, (x-36)+leftShave, y-40);
}

if no_crop {
    draw_self();
}
and then the set up for the struct is in the create event of obj_blockPiece:
GML:
lastBlock = {
    
    leftCrop : 0,
    rightCrop : 0
    
};
As you may be able to tell, I am not expert on Structs. I did my best with what info i could find on them.

So, the problem with the logic is, well, this:

Hopefully you can help me figure out what's causing this. My suspicion is that its a combination of storing values wrong and then using the draw_sprite_part() functions incorrectly
 

Nidoking

Member
obj_blockPiece.crop_left && !obj_blockPiece.crop_right
I don't see why crop_left and crop_right are properties of the obj_blockPiece. It's the dummyBlock that's cropped, so if those properties exist at all, they should be in the dummyBlock instance. You're tracking these as booleans, but that's really unnecessary. Think about this: Each dummyBlock only needs to know how many pixels to crop from its left side, and how many pixels to crop from its right side (or how wide it should be - you can use either piece of information to calculate the other). If it's not cropped on a particular side, set the cropping to zero for that side. Now your draw logic doesn't need cases. Always draw the sprite cropping from the left however many pixels, which may be zero, as wide as it needs to be. You also don't need to know how previous blocks were cropped to determine how to crop the new one. You should know where on the screen the left and right ends of the previous dummyBlock are, and the new block gets cropped at each end if it hangs over the edge. No structs needed. No arrays needed. Just grab the values from other in the collision event and calculate the cropping for the new block. If it doesn't hang over at either end, then it doesn't get cropped at that end, and the cropping for that end is zero. You can probably delete almost everything you have right now and write this more easily than trying to adapt what you've got.
 

DrScoop

Member
You should know where on the screen the left and right ends of the previous dummyBlock are, and the new block gets cropped at each end if it hangs over the edge. No structs needed. No arrays needed. Just grab the values from other in the collision event and calculate the cropping for the new block.
During implementation of this simplification Ive gotten confused again on where/how I should grab these variables set in the create event of the dummyBlock:
GML:
// leftCrop is only reset if we have an xDifference less than 0
shaveLeft = obj_blockPiece.leftCrop;
// same deal with shaveRight, only if xDifference is greater than 0
shaveRight = obj_blockPiece.rightCrop;

// example: if we drop the block 10 pixels left, xDiffInverse is 10
// the block is not overhanging on the right here so shaveRight is set to zero or the previous amount we cropped from the right
// the sprite width then becomes the normal width minus how much we have cropped most recently from the right and left
new_width = sprite_width-(shaveLeft+shaveRight)
and then manipulate them in the collision event in blockPiece set up like this:
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", )
    
    // store the position of the block on collision
    //xPlaced = x;
    //yPlaced = y;
    
    xDifference = round(self.x - other.x);
    xDiffInverse = xDifference * -1;
    
    hasPressedX = false;
    
    // get the instance id of the object collided with
    collidedID = other.id;
    
    pieceIndex += 1;
    
    // check if our difference is divisible by 2
    //if (xDifference % 2 == 0) {
        
        // if the block is placed left, the xDifference is negative. Inverse that number to measure difference from the left
        if xDifference < 0 {
            leftCrop = xDiffInverse;
        }
        if xDifference > 0 {
            rightCrop = xDifference
        }
        
        // create the damn instance AFTER we know which direction crop
        newDummy = instance_create_layer(self.x, collidedID.y - 24, "Puzzle_Elements", obj_dummyBlock);
        
    //}

    
    resetPiece();
}
The reason i decided to keep
Code:
if xDifference < 0 {
    leftCrop = xDiffInverse;
    }
    if xDifference > 0 {
    rightCrop = xDifference
}
Is because of the reason shown in the first comment of obj_dummyBlock's create event

Hope I'm following you okay enough. Sorry, it's just a bit hard to interpret some things you say into code šŸ˜…
 

TheouAegis

Member
Code:
var n = other.bbox_left + other.crop_left  - self.bbox_left;
if n var crop_left = n
else crop_Left = 0;
n = self.bbox_right - other.bbox_right - other.crop_right;
if n var crop_right = n
else crop_right = 0;
Then create a new dummy block and save crop_left and crop_right inside it. Then the dummy can use thise values to draw itself.

I think that's all it takes. You don't need anything in the Create event of dummyBlock necessarily, because blockPiece will set everything.
 

Nidoking

Member
xDifference = round(self.x - other.x);
Your problem is right here. Think about it - once a block gets cropped, how wide is it? Where is its right edge? When a block falls on top of it, how does this single number (comparing the positions of the left edges) tell you anything about how the right edges line up? You've fundamentally forgotten to consider an entire half of your game.

where/how I should grab these variables set in the create event of the dummyBlock:
Don't set them in the create event of the dummyBlock. Create the dummyBlock, then set the variables. You're storing the ID of the new dummyBlock instance as newDummy. That's exactly the reason why you do that.
 

DrScoop

Member
var n = other.bbox_left + other.crop_left - self.bbox_left; if n var crop_left = n else crop_Left = 0; n = self.bbox_right - other.bbox_right - other.crop_right; if n var crop_right = n else crop_right = 0;
Just to be certain, crop_left and crop_right were previously boolean variables in the code I used to have. Which variables are they in my latest code, so I can port this into my game properly?

I think I'll get back to Nidoking's solution after I get your response about this. I'm not sure if both your guys' solutions work together though.
 

DrScoop

Member
Code:
var n = other.bbox_left + other.crop_left  - self.bbox_left;
if n var crop_left = n
else crop_Left = 0;
n = self.bbox_right - other.bbox_right - other.crop_right;
if n var crop_right = n
else crop_right = 0;
Then create a new dummy block and save crop_left and crop_right inside it. Then the dummy can use thise values to draw itself.

I think that's all it takes. You don't need anything in the Create event of dummyBlock necessarily, because blockPiece will set everything.
This slice of code also doesnt make sense to me in how I'm supposed to save "crop_left" and "crop_right" in the new dummy block if they are created as temporary variables only useable in their original context of the obj_blockPiece collision event. If I need to import them to obj_dummyBlock, shouldnt they be just regular variables? Don't forget my other question of what crop_left and crop_right translate to in my latest code as well. Just dont wanna obscure that question in case you only read this. Just in case.
 

DrScoop

Member
Addressing variables in other instances

You save the value of a temporary variable into an instance variable, and it's stored in the instance variable.
Okay, I did that, but im still not clear on the way this piece of code is supposed to work:
var n = other.bbox_left + other.crop_left - self.bbox_left;

How was I supposed to define other.crop_left first, as in where does that number come from? This is why I asked TheouAegis what these variables are translated to as in my latest code. I tried replacing them with leftCrop and rightCrop, but of course I end up with the game telling me I didnt set other.leftCrop before reading it. That variable was never defined in other object. Perhaps these variables shouldnt be leftCrop and rightCrop but instead shaveLeft and shaveRight...? But then what are those variables supposed to be set to?

What am I trying to retrieve properly from obj_dummyBlock?
After I get all these variables right I'd also like to know how draw_sprite_part would be formatted properly with the variables because that has also been throwing me for a loop.

I'm so sorry about giving you guys so much trouble about all this. This is pretty confusing stuff to me and your perseverance to help me eventually understand the problem means a lot.
 

Nidoking

Member
What am I trying to retrieve properly from obj_dummyBlock?
That's the question for you. You've got screenshots/video. Label the parts. What is the set of information you need about a dummy block to determine how to crop a block that lands on it? You've confused yourself by thinking about what you already have, and the variables you defined but don't need. Throw them all away. Start from nothing. Draw your picture. Label everything. Determine what information you ACTUALLY need to make your game work. Don't think about variables. You are confusing yourself with variables. It's not variables. It's information. It's an engineering project. You're handing a blueprint to someone and explaining to them how the game works. Only the person you will ultimately hand the blueprint to is yourself. Look at your tower of blocks. No, LOOK at it. With your eyes. Look at that block on the top. What information about it determines what happens when a block lands on it? Don't use variable names. Variables are no longer part of your vocabulary. Just describe the thing you are seeing in front of you as your game happens. Name the information you need to describe the game. That is your set of variables. Now, with that knowledge firmly in your head, write your game. I believe you'll agree with me that most of what you already have is useless. In short, if you need to ask someone what values a variable should have, you literally do not understand the game you're trying to make. That's the problem you have to fix before you can hope to get anywhere.
 

DrScoop

Member
Respectfully I appreciate both of your guys' help so far. I really do. I'm going to take all of this information now and go at it alone. I don't think I can derive anymore help from here.
 

TheouAegis

Member
var n = other.bbox_left + other.crop_left - self.bbox_left;
The dummyBlock will not have existed prior to having crop_left and crop__right assigned to it. At the start, there are no dummyBlock instances in the room, thus there is no risk of blockPiece colliding with a dummyBlock that doesn't have crop_left defined. The first dummyBlock you will ever create, one way or another, will have crop_left and crop_right set to 0. You can do that in dummyBlock's create event if you want, or do it in whatever code detects when blockPiece collides with the bottom of the room. Other than that scenario, there would be no scenario when a dummyBlock - which is only created by blockPiece - will have ever been created without crop_left and crop_right defined.

And I'm not saying the code I wrote is flawless - I wrote it en route to work - but it's basically minimalist logic. If dummyBlock's bbox_left is greater than blockPiece's bbox_left, then you'd need to crop off the left of the newest dummyBlock that will be made. That bit was obvious enough, I'm sure. The questionable bit is factoring crop_left into the equation. I might have had a misconception about draw_sprite_part() when I edited the formula (I didn't write that originally with crop_left, I edited it just before posting); the intent was if dummyBlock got created with its origin at the same as blockPiece and the left part was blank, you'd need to account for that blank area. I don't think you could actually do that with draw_sprite_part(), or maybe you could, but the point is even if you use draw_sprite_part(), you never actually change the bounding box of dummyBlock, so your collision detection will be displaced by crop_left or crop_right. So... it's probably a logic fault of mine for crop_left, but it would undoubtedly be necessary to account for crop_right... in which case you would probably need to subtract crop_left as well if (and only if) crop_left isn't needed for collision on the left. But that's a case for debugging. I haven't been on my computer in a week.
 

DrScoop

Member
The dummyBlock will not have existed prior to having crop_left and crop__right assigned to it. At the start, there are no dummyBlock instances in the room, thus there is no risk of blockPiece colliding with a dummyBlock that doesn't have crop_left defined. The first dummyBlock you will ever create, one way or another, will have crop_left and crop_right set to 0. You can do that in dummyBlock's create event if you want, or do it in whatever code detects when blockPiece collides with the bottom of the room. Other than that scenario, there would be no scenario when a dummyBlock - which is only created by blockPiece - will have ever been created without crop_left and crop_right defined.

And I'm not saying the code I wrote is flawless - I wrote it en route to work - but it's basically minimalist logic. If dummyBlock's bbox_left is greater than blockPiece's bbox_left, then you'd need to crop off the left of the newest dummyBlock that will be made. That bit was obvious enough, I'm sure. The questionable bit is factoring crop_left into the equation. I might have had a misconception about draw_sprite_part() when I edited the formula (I didn't write that originally with crop_left, I edited it just before posting); the intent was if dummyBlock got created with its origin at the same as blockPiece and the left part was blank, you'd need to account for that blank area. I don't think you could actually do that with draw_sprite_part(), or maybe you could, but the point is even if you use draw_sprite_part(), you never actually change the bounding box of dummyBlock, so your collision detection will be displaced by crop_left or crop_right. So... it's probably a logic fault of mine for crop_left, but it would undoubtedly be necessary to account for crop_right... in which case you would probably need to subtract crop_left as well if (and only if) crop_left isn't needed for collision on the left. But that's a case for debugging. I haven't been on my computer in a week.
Thanks for reaching out again, but I actually solved all the cropping issues! It took a lot of thinking over your code and Nidoking's suggestions, but i completely rewrote the code one last time and it now crop's exactly as I want it to. For completeness' sake, here is all the code for the functioning cropping:

obj_blockPiece's collision event for when it collides with obj_dummyBlock:
GML:
/// @description

// only allow this code to run if the piece is dropped
if self.y > 69 {
   
    //xDifference = round(x - other.x);
    //xDiffInverse = xDifference * -1;
   
    hasPressedX = false;
   
    cropLeft = other.bbox_left + cropLeft - self.bbox_left;
   
       
    // new_width - cropRight = the width of a cropped on the right box
   
    // crop right from the collided box's bound subtracting the difference between it and the right bound of the og block
    // if we have cropped right before, subtract what we already have cropped from the right too
    cropRight = self.bbox_right - other.bbox_right + cropRight;
   
    if cropLeft < 0 {
        cropLeft = 0
    }
   
    if cropRight < 0 {
        cropRight = 0;
    }
   
    new_width = other.sprite_width - (cropLeft + cropRight)
   
    newDummy = instance_create_layer(self.x, other.id.y - 24, "Puzzle_Elements", obj_dummyBlock);
       
   
    resetPiece();
}
Here is the code for the create event of obj_dummyBlock:
Code:
shaveLeft = obj_blockPiece.cropLeft;

new_width = obj_blockPiece.new_width;
Here is the draw event for obj_dummyBlock:
Code:
draw_sprite_part(sprite_index, image_index, shaveLeft, 0, new_width, sprite_height, (x-36)+shaveLeft, y-40);
I realized just how many calculations and variables I could and should set and calculate in the collision event, just like you guys suggested, and then I followed through some of the math you came up and decided to replace using the xDifference with bbox's just like you did. I also included some checks that make sure if the crop amount for a certain side of the block is negative that it doesnt crop that side. Then after all cropLeft or right variables are fully calculated in all cases, I set the new_width and create the newDummy. This works just how I want it to, sans a few extra features I will add to the game when I get back on coding such as the OG blockPiece getting cropped and dropping as cropped as the latest dummy, and then checks that make it so the player has a minimum of 2 pixels left of the block before a game over happens, that obj_blockPiece also speeds up its horizontal speed before dropping over time to increase difficulty, and that the whole tower shifts downward off screen over time after a certain amount of blocks are placed to allow the game to last much longer without fully filling the screen.

Thank you both again for your persistence and pushing me to do better on my own. I would argue you both have made me a better programmer in the end. Thank you!
 
Last edited:

Nidoking

Member
Nice job! This is exactly the sort of thing I was talking about earlier, and I'm glad you got there in the end. A couple of small notes I'd like to put forth, to further illustrate a couple of style principles:

if cropLeft < 0 { cropLeft = 0 }
There's nothing wrong with this, but I usually write it as cropLeft = max(cropLeft, 0); for clarity. You can also wrap the max call around the original definition of the variable, depending on whether you prefer to have fewer lines or fewer operations per line. So, cropLeft = max(0, other.bbox_left + cropLeft - self.bbox_left);

Here is the code for the create event of obj_dummyBlock:
shaveLeft = obj_blockPiece.cropLeft;
new_width = obj_blockPiece.new_width;
What I was saying earlier is that you don't need to do this in the Create event - after creating the dummy block, which you store in a variable called newDummy, you can do
GML:
newDummy = instance_create_layer(self.x, other.id.y - 24, "Puzzle_Elements", obj_dummyBlock);

newDummy.shaveLeft = cropLeft;
newDummy.new_width = new_width;
The point of this is mainly that it eliminates your object dot variable syntax, which is something I try to avoid even when you have a singleton instance like in this case. It also makes things a bit more flexible for future use - let's say you wanted to create some kind of puzzle or challenge mode with some pre-placed dummy blocks. This format allows you to build that in code, if you want, without assuming that the obj_blockPiece will be in the state you want it when you create a obj_dummyBlock. It's good practice to put default values into the obj_dummyBlock create event anyway, but this lets you overwrite them without the obj_dummyBlock needing to know anything outside its scope.

Again, there's nothing wrong with what you've got here, but these are suggestions you might consider. If not for this game, they may help you in future games.
 

DrScoop

Member
Nice job! This is exactly the sort of thing I was talking about earlier, and I'm glad you got there in the end. A couple of small notes I'd like to put forth, to further illustrate a couple of style principles:



There's nothing wrong with this, but I usually write it as cropLeft = max(cropLeft, 0); for clarity. You can also wrap the max call around the original definition of the variable, depending on whether you prefer to have fewer lines or fewer operations per line. So, cropLeft = max(0, other.bbox_left + cropLeft - self.bbox_left);



What I was saying earlier is that you don't need to do this in the Create event - after creating the dummy block, which you store in a variable called newDummy, you can do
GML:
newDummy = instance_create_layer(self.x, other.id.y - 24, "Puzzle_Elements", obj_dummyBlock);

newDummy.shaveLeft = cropLeft;
newDummy.new_width = new_width;
The point of this is mainly that it eliminates your object dot variable syntax, which is something I try to avoid even when you have a singleton instance like in this case. It also makes things a bit more flexible for future use - let's say you wanted to create some kind of puzzle or challenge mode with some pre-placed dummy blocks. This format allows you to build that in code, if you want, without assuming that the obj_blockPiece will be in the state you want it when you create a obj_dummyBlock. It's good practice to put default values into the obj_dummyBlock create event anyway, but this lets you overwrite them without the obj_dummyBlock needing to know anything outside its scope.

Again, there's nothing wrong with what you've got here, but these are suggestions you might consider. If not for this game, they may help you in future games.

Great suggestions, I think I'll go ahead and put those in. Thanks again. I'm going to mark this thread as SOLVED now.
 
Top