[SOLVED] Pixel-perfect tilemap collision throws error. Not sure how to fix.

G

GeekmasterK

Guest
Disclaimer: this is my first post on the forums here, so I can't provide a link to the tutorial I was following.

So, I'm working on a top-down RPG, and found an excellent tutorial on pixel-perfect tilemap collisions. It works flawlessly when colliding with objects, but when the player object reaches the room edge, I get the following error:


___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object obj_player:

Push :: Execution Error - Variable Index [524,9184] out of range [1,-1] - -5.Heights(100003,16777184)
at gml_Script_scr_TestCollision (line 6) - var hh = global.Heights[column];
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_scr_TestCollision (line 6)
called from - gml_Object_obj_player_Step_0 (line 90) - yCol = scr_TestCollision(x, floor(y)+15);

I know that this kind of error is caused by an index exceeding the array limit, or going into the negative for the index. The problem is, I don't know how to prevent it in this case. After researching this problem extensively, I thought I would turn to the forums for help. Here's my code:

obj_init (initialization object)
Code:
// Create event

#macro TILE_MAX    192      // Maximum number of tiles in tilemap
#macro TILE_SIZE    32        // Size of each tile (applies to both width and height)
Code:
// Draw event

global.Heights = [TILE_SIZE * TILE_MAX];

// Surface and buffer to store the tiles
var surf = surface_create(TILE_SIZE * TILE_MAX, TILE_SIZE);
var buff = buffer_create(TILE_SIZE * TILE_MAX * TILE_SIZE * 4, buffer_fixed, 1);

surface_set_target(surf);
draw_clear_alpha(0, 0);

// Loop through tiles
for(var i = 0; i < TILE_MAX; i++){
    draw_tile(tl_test, i, 0, i*TILE_SIZE, 0);
}

surface_reset_target();
buffer_get_surface(buff, surf, 0, 0, 0);

// Nested loop to count the exact pixels of each tile
for(var xx = 0; xx < (TILE_SIZE * TILE_MAX); xx++){
    var count = 0;
    for(var yy = 0; yy < TILE_SIZE; yy++){
        var pixel = buffer_peek(buff, (xx+(yy * TILE_SIZE * TILE_MAX))*4, buffer_u32);
        if((pixel&0xff000000) != 0) break;
        count++;
    }
    global.Heights[xx] = count;
}

buffer_delete(buff);
surface_free(surf);

room_goto_next();
scr_TestCollision (Script to check for collisions)

Code:
// Define arguments
var _x = argument0;
var _y = argument1;

// Perform collision calculation
var t = tilemap_get_at_pixel(tileID, _x, _y) & tile_index_mask;
var column = (_x&(TILE_SIZE - 1)) + (t * TILE_SIZE);
var hh = global.Heights[column];
var yy = _y&(TILE_SIZE - 1);

// Check if a collision happens
if(hh == 0) return true;
if(hh == TILE_SIZE) return false;
if(yy >= hh) return true;
return false;
obj_player (player object)

Code:
// Create event

// Set default facing direction
directionFacing = "Down";

// Set default movement speed
baseSpeed = 7;

// Get the ID of the collision tile layer
var layerID = layer_get_id("Collision");
tileID = layer_tilemap_get_id(layerID);
Code:
// Step event

// Temporary variables
var xMove = 0;
var yMove = 0;

// Keep player in the room
x = clamp(x, 0, room_width);
y = clamp(y, 0, room_height);

// Get inputs
if(keyboard_check(ord("A")) || keyboard_check(vk_left)){
    xMove--;
    image_speed = 0.5;
    sprite_index = spr_player_walk_left;
    directionFacing = "Left";
}

if(keyboard_check(ord("D")) || keyboard_check(vk_right)){
    xMove++;
    image_speed = 0.5;
    sprite_index = spr_player_walk_right;
    directionFacing = "Right";
}

if(keyboard_check(ord("W")) || keyboard_check(vk_up)){
    yMove--;
    image_speed = 0.5;
    sprite_index = spr_player_walk_up;
    directionFacing = "Up";
}

if(keyboard_check(ord("S")) || keyboard_check(vk_down)){
    yMove++;
    image_speed = 0.5;
    sprite_index = spr_player_walk_down;
    directionFacing = "Down";
}

// Idle animation
if(!(keyboard_check(ord("A")) || keyboard_check(vk_left)) && !(keyboard_check(ord("D")) || keyboard_check(vk_right)) && !(keyboard_check(ord("W")) || keyboard_check(vk_up)) && !(keyboard_check(ord("S")) || keyboard_check(vk_down))){
    if(directionFacing == "Left"){
        sprite_index = spr_player_idle_left;
    }
    if(directionFacing == "Right"){
        sprite_index = spr_player_idle_right;
    }
    if(directionFacing == "Up"){
        sprite_index = spr_player_idle_up;
    }
    if(directionFacing == "Down"){
        sprite_index = spr_player_idle_down;
    }
}

// Reduce diagonal move speed
var moveSpeed = baseSpeed;

if(xMove != 0 && yMove != 0) moveSpeed = baseSpeed * 0.75;

// Horizontal collision detection
x += xMove * moveSpeed;

//Left
var xCol = true;
while(xCol){
    xCol = scr_TestCollision(floor(x)+15, y);
    if(xCol){
        xMove = 0;
        x = floor(x - 1);
    }
}

//Right
var xCol2 = true;
while(xCol2){
    xCol2 = scr_TestCollision(floor(x)-15, y);
    if(xCol2){
        xMove = 0;
        x = floor(x + 1);
    }
}

// Vertical collision detection
y += yMove * moveSpeed;

//Down
var yCol = true;
while(yCol){
    yCol = scr_TestCollision(x, floor(y)+15);
    if(yCol){
        yMove = 0;
        y = floor(y - 1);
    }
}

//Up
var yCol2 = true;
while(yCol2){
    yCol2 = scr_TestCollision(x, floor(y)-15);
    if(yCol2){
        yMove = 0;
        y = floor(y + 1);
    }
}
Sorry if this post is long, but I've been wracking my brain against this problem for a while now. Help is appreciated! :)

Edit: Forgot to mention. My version of Game Maker is v2.0.6.146
 
Last edited by a moderator:

TheouAegis

Member
// Perform collision calculation var t = tilemap_get_at_pixel(tileID, _x, _y) & tile_index_mask; var column = (_x&(TILE_SIZE - 1)) + (t * TILE_SIZE); var hh = global.Heights[column];
Oh yeah I did the math over again in my head. You're right. Was just going off what I recognize. But still, the issue is there in that line.

_x & (tile_size-1) = _x & 31 = {0,31}

That part of the formula is saying you want to find out how far off-center the unit is from the tile.

Then you add to it

t * tile_size = t * 32 = {0,32*tile_max} = {0,1644}

So then that formula ends up being the set of possibilities {0,1675}. HOWEVER, the array global.Heights only contains [0,1643].


I would think the formula would be something more like

_x + _y*tile_size & ~(tile_size & 1)
 
G

GeekmasterK

Guest
Oh yeah I did the math over again in my head. You're right. Was just going off what I recognize. But still, the issue is there in that line.

_x & (tile_size-1) = _x & 31 = {0,31}

That part of the formula is saying you want to find out how far off-center the unit is from the tile.

Then you add to it

t * tile_size = t * 32 = {0,32*tile_max} = {0,1644}

So then that formula ends up being the set of possibilities {0,1675}. HOWEVER, the array global.Heights only contains [0,1643].


I would think the formula would be something more like

_x + _y*tile_size & ~(tile_size & 1)
Tried it, but got the following error on running the game:

___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object obj_player:

Push :: Execution Error - Variable Index [0,17967] out of range [1,6144] - -5.Heights(100003,17967)
at gml_Script_scr_TestCollision (line 6) - var hh = global.Heights[column];
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_scr_TestCollision (line 6)
called from - gml_Object_obj_player_Step_0 (line 67) - xCol = scr_TestCollision(floor(x)+15, y);

So somehow, the formula is pushing the index out of bounds right off the bat, not just when I run into the edge of the room.
 
G

GeekmasterK

Guest
Does anyone have a potential way to fix this? I'm still banging my head against the wall over it.
 
G

GeekmasterK

Guest
Here it is. Again, it crashes with the previously mentioned error when I start the game.

Code:
// Define arguments
var _x = argument0;
var _y = argument1;

// Perform collision calculation
var t = tilemap_get_at_pixel(tileID, _x, _y) & tile_index_mask;
var column = _x + _y*tile_size & ~(tile_size & 1);
var hh = global.Heights[column];
var yy = _y&(TILE_SIZE - 1);

// Check if a collision happens
if(hh == 0) return true;
if(hh == TILE_SIZE) return false;
if(yy >= hh) return true;
return false;
 
G

GeekmasterK

Guest
Any ideas? I've still been trying to fix this error, but I've found nothing that works. If it helps, I can post a link to the tutorial I was following. Some minor details may be different, like the values for TILE_MAX and TILE_SIZE.

 

TheouAegis

Member
So let's just forget my previous replies for now. Let's go back to the original code. I misread what the code was supposed to do. Even though I don't approve of how this code (ideally) does what it's trying to do, I can at least appreciate its intent, which I didn't catch the first time I looked at it.

Code:
// Define arguments
var _x = argument0;
var _y = argument1;

// Perform collision calculation
var t = tilemap_get_at_pixel(tileID, _x, _y) & tile_index_mask;
var column = (_x&(TILE_SIZE - 1)) + (t * TILE_SIZE);
var hh = global.Heights[column];
var yy = _y&(TILE_SIZE - 1);

// Check if a collision happens
if(hh == 0) return true;
if(hh == TILE_SIZE) return false;
if(yy >= hh) return true;
return false;

_y & (TILE_SIZE - 1) is saying, "What is the y-coordinate's deviation from the tile grid?" I overlooked this part because I had neglected that you were using slopes. So this and its x-coordinate companion is fine.

So t is the id of the tile at (x,y), valued from 0 to 191, or at least it should be. I'd first make sure the mask is set right by using tilemap_get_mask() to make sure the mask is correct. If the mask is too inclusive and there are additional bits being saved to t, then that might throw your calculation for column off.

Also, make sure you actually have 192 tiles, which is what you set TILE_MAX to. If you have more than 192 tiles but TILE_MAX is only set to 192, then your array will be way, way too small. In your opening post, the error I think said the value of column was 9184, which would be mean t was set to 287. If you really do have 192 tiles (or fewer), then this suggests to me that the tile_index_mask is actually 1 bit too long and it is retrieving extra data. But then why does it work the rest of the time and not at the edge of the screen? I dunno....

So revert your code back to what you had originally. Prior to calling scr_TestCollision(), run a show_debug_message(x) so that way when the error appears and crashes the game, you can see what the last offending value of x was.
 
G

GeekmasterK

Guest
Okay, so first of all, 192 was one more tile than I actually had. I didn't consider the blank tile in the first square. The second thing is, I logged the value of t. I got values within the limit when colliding with an object, but when I touch any room edge, the value of t is 524287. I've tried a few things to set the index mask, but the value is always the same. I did some of my own research, but I haven't found a way to fix this yet.
 
G

GeekmasterK

Guest
Any ideas? Sorry if I sound like a broken record, but I've tried multiple methods to fix this, and so far, nothing has worked. The offending value is much higher than I thought it would be. I've looked into this with my own research, but I don't have a way to fix the index mask yet. This collision system is more advanced than anything I've done in Game Maker before. However, if I can get it working, I feel like it would make development of the rest of the game that much more straightforward. I also feel like it would expand my understanding of how tile masks work. Any help at all is greatly appreciated.
 

PyxelJock

Member
I would review the video at 43:08, he gets the same error as you. He even mentions he has not done a check going upward at 44:30. So that would be some good places to start, you can also add a check in the collision going upward.

Code:
// Define arguments
var _x = argument0;
var _y = argument1;

// Perform collision calculation
var t = tilemap_get_at_pixel(tileID, _x, _y) & tile_index_mask;
var column = (_x&(TILE_SIZE - 1)) + (t * TILE_SIZE);
var hh = global.Heights[column];
var yy = _y&(TILE_SIZE - 1);

// Check if a collision happens
if(hh == 0) return true;
if(hh == TILE_SIZE) return false;
if(yy >= hh) return true;
if(yy <= hh) return true;
return false;
You can see what I added if(yy <= hh) return true;. This will only return true for collision upward, not sure if it works. Like you said it is a little more advanced. You would still also have to add a check in the collisions going left and right as well since there aren't any added either. You would also have to add a column most likely for the left and right. let me know if you get any results, I am curious too. Thanks.
 
G

GeekmasterK

Guest
SUCCESS! I took another look at the video and found a detail that I missed. When dealing with larger tilemaps, there is a limit to how many tiles can be stored in an array. It turns out, the fact that I was using an array was the problem. Smaller tilemaps don't have this problem when colliding with the edge of the room. To fix this, I stored the columns in a buffer rather than an array. Here's the fixed code, in case anyone is wondering:

obj_init (initialization object)
Code:
// Create event

#macro TILE_MAX    191      // Maximum number of tiles in tilemap
#macro TILE_SIZE    32        // Size of each tile (applies to both width and height)
Code:
// Draw event

// Global buffer to store the complete tilemap
globalvar heightBuff; 
heightBuff = buffer_create(TILE_SIZE * TILE_MAX, buffer_fixed, 1);

// Surface and buffer to store the tiles
var surf = surface_create(TILE_SIZE * TILE_MAX, TILE_SIZE);
var buff = buffer_create(TILE_SIZE * TILE_MAX * TILE_SIZE * 4, buffer_fixed, 1);

surface_set_target(surf);
draw_clear_alpha(0, 0);

// Loop through tiles
for(var i = 0; i < TILE_MAX; i++){
   draw_tile(tl_test, i, 0, i*TILE_SIZE, 0);
}

surface_reset_target();
buffer_get_surface(buff, surf, 0, 0, 0);

// Nested loop to count the exact pixels of each tile
for(var xx = 0; xx < (TILE_SIZE * TILE_MAX); xx++){
   var count = 0;
   for(var yy = 0; yy < TILE_SIZE; yy++){
       var pixel = buffer_peek(buff, (xx+(yy * TILE_SIZE * TILE_MAX))*4, buffer_u32);
       if((pixel&0xff000000) != 0) break;
       count++;
   }
   buffer_poke(heightBuff, xx, buffer_u8, count);
}

buffer_delete(buff);
surface_free(surf);

room_goto_next();
scr_TestCollision (Script to check for collisions)
Code:
// Define arguments
var _x = argument0;
var _y = argument1;

// Perform collision calculation
var t = tilemap_get_at_pixel(tileID, _x, _y) & tile_index_mask;
var column = (_x&(TILE_SIZE - 1)) + (t * TILE_SIZE);
var hh = buffer_peek(heightBuff, column, buffer_u8);
var yy = _y&(TILE_SIZE - 1);

// Check if a collision happens
if(hh == 0) return true;
if(hh == TILE_SIZE) return false;
if(yy >= hh) return true;
return false;

buffer_delete(heightBuff);
obj_player (player object)
Code:
// Create event

// Set default facing direction
directionFacing = "Down";

// Set default movement speed
baseSpeed = 7;

// Get the ID of the collision tile layer
var layerID = layer_get_id("Collision");
tileID = layer_tilemap_get_id(layerID);
Code:
// Step event

// Temporary variables
var xMove = 0;
var yMove = 0;

// Keep player in the room
x = clamp(x, 0, room_width);
y = clamp(y, 0, room_height);

// Get inputs
if(keyboard_check(ord("A")) || keyboard_check(vk_left)){
   xMove--;
   image_speed = 0.5;
   sprite_index = spr_player_walk_left;
   directionFacing = "Left";
}

if(keyboard_check(ord("D")) || keyboard_check(vk_right)){
   xMove++;
   image_speed = 0.5;
   sprite_index = spr_player_walk_right;
   directionFacing = "Right";
}

if(keyboard_check(ord("W")) || keyboard_check(vk_up)){
   yMove--;
   image_speed = 0.5;
   sprite_index = spr_player_walk_up;
   directionFacing = "Up";
}

if(keyboard_check(ord("S")) || keyboard_check(vk_down)){
   yMove++;
   image_speed = 0.5;
   sprite_index = spr_player_walk_down;
   directionFacing = "Down";
}

// Idle animation
if(!(keyboard_check(ord("A")) || keyboard_check(vk_left)) && !(keyboard_check(ord("D")) || keyboard_check(vk_right)) && !(keyboard_check(ord("W")) || keyboard_check(vk_up)) && !(keyboard_check(ord("S")) || keyboard_check(vk_down))){
   if(directionFacing == "Left"){
       sprite_index = spr_player_idle_left;
   }
   if(directionFacing == "Right"){
       sprite_index = spr_player_idle_right;
   }
   if(directionFacing == "Up"){
       sprite_index = spr_player_idle_up;
   }
   if(directionFacing == "Down"){
       sprite_index = spr_player_idle_down;
   }
}

// Reduce diagonal move speed
var moveSpeed = baseSpeed;

if(xMove != 0 && yMove != 0) moveSpeed = baseSpeed * 0.75;

// Horizontal collision detection
x += xMove * moveSpeed;

//Left
var xCol = true;
while(xCol){
   xCol = scr_TestCollision(floor(x)+15, y);
   if(xCol){
       xMove = 0;
       x = floor(x - 1);
   }
}

//Right
var xCol2 = true;
while(xCol2){
   xCol2 = scr_TestCollision(floor(x)-15, y);
   if(xCol2){
       xMove = 0;
       x = floor(x + 1);
   }
}

// Vertical collision detection
y += yMove * moveSpeed;

//Down
var yCol = true;
while(yCol){
   yCol = scr_TestCollision(x, floor(y)+15);
   if(yCol){
       yMove = 0;
       y = floor(y - 1);
   }
}

//Up
var yCol2 = true;
while(yCol2){
   yCol2 = scr_TestCollision(x, floor(y)-15);
   if(yCol2){
       yMove = 0;
       y = floor(y + 1);
   }
}
And that's it! The solution was staring me in the face the whole time, at the end of the video, and I didn't even notice! Thanks for the help! I'll mark this as solved! :D
 
C

ChaosX2

Guest
So let's just forget my previous replies for now. Let's go back to the original code. I misread what the code was supposed to do. Even though I don't approve of how this code (ideally) does what it's trying to do, I can at least appreciate its intent, which I didn't catch the first time I looked at it.
Came across this thread and it's interesting. Just curious on what your approach would be as an alternative. I'm guessing it would be an alternative to using a buffer. If so, I was thinking store tiles in a ds_grid or something. On the right track?
 

TheouAegis

Member
As long as you have buffers available to you, I would use a buffer whenever possible. Some people attest otherwise but I found buffers to be considerably fast and they take up considerably less memory. If you do not have a buffer available to you because you are using a legacy version of game maker, then I would say use a grid.

Grids are going to be much faster I think, simply because all you need to do is calculate the position in the grid and then it's pretty much automatic from there. With a buffer, you have to factor in the horizontal position in the room and the vertical position in the room and then how those values combined translates into a position in the buffer. However grids take up considerably more memory because rather than every entry in the grid taking up one byte it's going to take up 8.
 
Last edited:
C

ChaosX2

Guest
Taking a crack at using this system too. I'm using a 2048 x 2048 tile sheet where the tiles are 64 x 64. The collisions aren't coming out right because I can't get the formula right in the nested loop to count the pixels. The difference with the way my tiles are drawn is that it's a grid of tiles 64 x 32 (in x and y-direction). Would greatly appreciate it if someone can help point me in the right direction!

TILE_SIZE = 64
TILE_MAX = 1024


Code:
global.heightBuff = buffer_create(TILE_SIZE * TILE_MAX * TILE_SIZE, buffer_fixed, 1);

//Surface & buffer to store tiles
var surf = surface_create(2048, 2048);
var buff = buffer_create(TILE_SIZE * TILE_MAX * TILE_SIZE * 4, buffer_fixed, 1);

surface_set_target(surf);
draw_clear_alpha(0,0);

//Loop through tiles to draw
for (var i = 0; i < 1024; i++)
{
    var j = i mod 32;
    var k = i div 32;
    draw_tile(ts_EGM, i, 0, j * TILE_SIZE, k * TILE_SIZE);
}

surface_reset_target();
buffer_get_surface(buff, surf, 0, 0, 0);

//Nested loop to count the exact pixels of each tile
for(var i = 0; i < TILE_SIZE * TILE_MAX; i++)
{
    var count = 0;
   
    var xx = i mod 2048;
   
    for(var j = 0; j < TILE_SIZE * 32; j++) //2048 - old num
    {
        var pixel = buffer_peek(buff, (xx + (j * TILE_SIZE * TILE_MAX)) * 4, buffer_u32);
       
        if((pixel&0xff000000) != 0) break;
        count++;
    }
    buffer_poke(global.heightBuff, i, buffer_u8, count);
}
 
Last edited by a moderator:

TheouAegis

Member
Why are using 32 for both j and k? You said horizontally it should be 64, not 32.

And if your tiles are 64x64, why is your grid 64x32? If you are working with isometrics, you need to use isometric formulas.
 
C

ChaosX2

Guest
Why are using 32 for both j and k? You said horizontally it should be 64, not 32.

And if your tiles are 64x64, why is your grid 64x32? If you are working with isometrics, you need to use isometric formulas.
My mistake with the grid size xD I meant 32 x 32 for tiles that are 64 x 64 on a 2048 x 2048 surface. 32 tiles aligned horizontally and vertically to have a surface of 2048 x 2048.


I drew the tiles that way with j and k so it loops back over on the (2048 x 2048) surface and draws the tiles the way its laid out on the tile sheet (also 2048 x 2048). Because the tile sheet and surface are 2048 x 2048 and the tiles are 64 x 64, that means there are 32 tiles horizontally and vertically in a row. Also, I'm not working with isometrics, but since you're asking that, I'm wondering if I should draw the tiles so that they're all lined up horizontally in one row? And if that's the case, can there be a surface that big? I initially decided to draw them in grid form because I didn't think a surface can be made so long. That'd be the tile_size (64) multiplied by 1024 (max number of tiles), which results in a 65536 x 64 surface.
 
Last edited by a moderator:

TheouAegis

Member
Oh ok I see what you were saying.

Your code is different from the other guy's in that you used xx as i mod 2048 (rather than just using i by itself; and you're multiplying tile_size by 32 when setting the limit for j. The buffer has no shape, but you're trying to treat it as though it does.
 
C

ChaosX2

Guest
Oh ok I see what you were saying.

Your code is different from the other guy's in that you used xx as i mod 2048 (rather than just using i by itself; and you're multiplying tile_size by 32 when setting the limit for j. The buffer has no shape, but you're trying to treat it as though it does.
Yep! I'm using i to execute the loop i number of times I believe it'll take to check each pixel within a 2048 x 2048 map. xx loops from 0 - 2048 (number of pixels in the x axis) and I'm treating j as the loop to count pixels in the y-axis. I'll try changing up the loop to take shape out of the equation.
 
Last edited by a moderator:
C

ChaosX2

Guest
So let's just forget my previous replies for now. Let's go back to the original code. I misread what the code was supposed to do. Even though I don't approve of how this code (ideally) does what it's trying to do, I can at least appreciate its intent, which I didn't catch the first time I looked at it.

Code:
// Define arguments
var _x = argument0;
var _y = argument1;

// Perform collision calculation
var t = tilemap_get_at_pixel(tileID, _x, _y) & tile_index_mask;
var column = (_x&(TILE_SIZE - 1)) + (t * TILE_SIZE);
var hh = global.Heights[column];
var yy = _y&(TILE_SIZE - 1);

// Check if a collision happens
if(hh == 0) return true;
if(hh == TILE_SIZE) return false;
if(yy >= hh) return true;
return false;

_y & (TILE_SIZE - 1) is saying, "What is the y-coordinate's deviation from the tile grid?" I overlooked this part because I had neglected that you were using slopes. So this and its x-coordinate companion is fine.

So t is the id of the tile at (x,y), valued from 0 to 191, or at least it should be. I'd first make sure the mask is set right by using tilemap_get_mask() to make sure the mask is correct. If the mask is too inclusive and there are additional bits being saved to t, then that might throw your calculation for column off.

Also, make sure you actually have 192 tiles, which is what you set TILE_MAX to. If you have more than 192 tiles but TILE_MAX is only set to 192, then your array will be way, way too small. In your opening post, the error I think said the value of column was 9184, which would be mean t was set to 287. If you really do have 192 tiles (or fewer), then this suggests to me that the tile_index_mask is actually 1 bit too long and it is retrieving extra data. But then why does it work the rest of the time and not at the edge of the screen? I dunno....

So revert your code back to what you had originally. Prior to calling scr_TestCollision(), run a show_debug_message(x) so that way when the error appears and crashes the game, you can see what the last offending value of x was.

If I may, I'm having trouble doing collisions from the left and right sides of the tiles in a tile map. I tried so many things and am at a lost on how to go about it. My suspicion is that I need to calculate t differently since the for loops for left and right are scrolling through the tiles in the map differently than my for loops for top and bottom collisions.

Code:
//Left
var l_x = 0;
for (var yy = 0; yy < TILE_SIZE * TILE_MAX; yy++)
{
   var count = 0;
   if(yy > 0 && yy mod 2048 == 0) l_x += (TILE_SIZE - 1);
 
   for(var xx = l_x; xx < l_x + TILE_SIZE; xx++)
   {
      var pixel = buffer_peek(buff, (xx + (yy  * TILE_SIZE * 32)) * 4, buffer_u32);
      if ((pixel&0xff000000) != 0 || count == TILE_SIZE) break;  
      count++;
   }
      buffer_poke(global.leftWidthBuff, yy, buffer_u8, count);
}

//Right
var r_x = TILE_SIZE - 1;
for (var yy = 0; yy < TILE_SIZE * TILE_MAX; yy++)
{
   var count = 64;
   if(yy > 0 && yy mod 2048 == 0) r_x += (TILE_SIZE - 1);
 
   for(xx = r_x; xx >= r_x - (TILE_SIZE - 1); xx--)
   {
      var pixel = buffer_peek(buff, (xx + (yy  * TILE_SIZE * 32)) * 4, buffer_u32);
      if ((pixel&0xff000000) != 0 || count == 0) break;  
      count--;
   }
      buffer_poke(global.rightWidthBuff, yy, buffer_u8, count);
}
I'm using a 2048 x 2048 tile sheet (32 tiles across horizontally and vertically) and I believe that it's not calculating properly because instead of calculating space within tiles from tile index 0 to 1, 2, etc, it's moving from tile index 0, 32, 64, etc. Hope that makes sense.

Code:
var t = tilemap_get_at_pixel(tile_id, _x, _y) & tile_index_mask;

var row = (_y&(TILE_SIZE - 1)) + (t * TILE_SIZE);

var w_left = buffer_peek(global.leftWidthBuff, row, buffer_u8);
var w_right = buffer_peek(global.rightWidthBuff, row, buffer_u8);

var xx = _x&(TILE_SIZE - 1);

//left collisions
if(w_left == 0) return true;
if(w_left == TILE_SIZE) return false;
if(xx >= w_left) return true;
return false;
buffer_delete(global.leftWidthBuff);

//right collisions

if(w_right == 0) return false;
if(w_right == TILE_SIZE) return true;
if(xx <= w_right) return true;
return false;
buffer_delete(global.rightWidthBuff);

Aside from trying to recalculate t, I had a theory to perhaps change the alignment of the buffer and make it a buffer_wrap, but that didn't work either. Could be I'm just not calculating that properly either though. Words alone cannot express my thanks if I receive help on fixing the calculations.
 

TheouAegis

Member
Did you create the buffer in a different order than with your vertical one? If so, they might need to be written in the exact same order.
 
C

ChaosX2

Guest
Did you create the buffer in a different order than with your vertical one? If so, they might need to be written in the exact same order.
No, the four buffers (up, down, left, right) that store the counts all receive their data from its respective for loops which all peek into the buffer buff that stores the tilemap. I made a post here but was unable to get help. Maybe it will help put more perspective on the problem :x
 

TheouAegis

Member
Code:
var t_y = 0;
for(var xx = 0; xx < (TILE_SIZE * TILE_MAX); xx++)
{
    var count = 0;
    if(xx > 0 && xx mod 2048 == 0) t_y +=(TILE_SIZE - 1);
 
    for(var yy = t_y; yy < (t_y + TILE_SIZE); yy++)
    {
        var pixel = buffer_peek(buff, (xx + (yy * TILE_SIZE * 32)) * 4, buffer_u32);
        if((pixel&0xff000000) != 0 || count == TILE_SIZE) break;
        count++;
    }
    buffer_poke(global.topHeightBuff, xx, buffer_u8, count);
}
Code:
//Left
var l_x = 0;
for (var yy = 0; yy < TILE_SIZE * TILE_MAX; yy++)
{
   var count = 0;
   if(yy > 0 && yy mod 2048 == 0) l_x += (TILE_SIZE - 1);
 
   for(var xx = l_x; xx < l_x + TILE_SIZE; xx++)
   {
      var pixel = buffer_peek(buff, (xx + (yy  * TILE_SIZE * 32)) * 4, buffer_u32);
      if ((pixel&0xff000000) != 0 || count == TILE_SIZE) break;
      count++;
   }
      buffer_poke(global.leftWidthBuff, yy, buffer_u8, count);
}
First off, take a look at both of those codes. They are nearly identical except for a few minor changes you applied uniformly... except in one spot. There's one line that's the same in both versions that shouldn't be.

But that won't fix anything......

Secondly, the first code says the tile buffer is laid out like so:
Code:
0 1 2 3 4 5 6 7 8 9
But the second code says the tile buffer is laid out like so:
Code:
0
1
2
3
4
5
6
7
8
9
Yes, you want to read the tiles in that order...but your buffer's not laid out that way. So your second code is wrong in a couple ways.

There. Done with that part.

So let's look again at what the first code does. It starts with the top-leftmost pixel-column and counts how many empty pixels there are above the first solid pixel, up to the height of a tile. It does this for every pixel-column and then it drops down 1 tile height and resumes for the next set of tiles. This works just fine because your tile buffer is laid out the same way as your tile set, so your collision buffer lists the tile heights in order.

Now consider the second code. You're changing the order in which you write the tile heights in the collision buffer. The way I'd do it, you'll need an extra variable. We'll keep xx and yy as the coordinates in the tile buffer, we'll keep l_x for its original use, and also use l_y.
Code:
var l_x = 0;
var l_y = 0;
repeat TILE_SIZE * TILE_MAX //we don't need a for loop this time
{
    var count = 0;
    for(var yy = l_y; yy - l_y < TILE_SIZE; yy++;)
    for(var xx = l_x; xx - l_x < TILE_SIZE; xx++;)
    {
        var pixel = buffer_peek(buff, (xx + yy * TILE_SIZE * 32) * 4,buffer_u32);
        if pixel&0xff000000 != 0 || count == tile_size break;
        count++;
    }

    l_y += TILE_SIZE;
    if l_y == TILE_SIZE * 32
    {
        l_x += TILE_SIZE;
        l_y = 0;
    }
}

Something along those lines. I took out the -1s because they seemed odd to me; but maybe you need them, so put them back in if you do.
 
C

ChaosX2

Guest
Thanks so much for the help! It's an interesting setup and while its definitely calculating the collisions much better, it's still off by some pixels. I slightly modified the loop to look like...

Code:
//Left
var l_x = 0;
var l_y = 0;
repeat TILE_SIZE * TILE_MAX
{
    var count = 0;
    for(var yy = l_y; yy - l_y < TILE_SIZE; yy++)
    {
        for(var xx = l_x; xx - l_x < TILE_SIZE; xx++)
        {
            var pixel = buffer_peek(buff, (xx + yy * TILE_SIZE * 32) * 4,buffer_u32);
            if ((pixel&0xff000000 != 0) || count == TILE_SIZE) break;
            count++;
        }
        buffer_poke(global.leftWidthBuff, yy, buffer_u8, count);
      
        l_y += TILE_SIZE;

        if (l_y == TILE_SIZE * 32)
        {
            l_x += TILE_SIZE;
            l_y = 0;
        }
    }
}
Because the calculations are off by a few pixels, I tried adding the -1's again. I came about using them for top and bottom collisions because it helped correct the slight offset of pixel calculations. So I modified the code to look like this.

Code:
//Left
var l_x = 0;
var l_y = 0;
var i_y = 0;
repeat TILE_SIZE * TILE_MAX
{
    var count = 0;
    for(var yy = i_y; yy - i_y < TILE_SIZE; yy++)
    {
        for(var xx = l_x; xx - l_x < TILE_SIZE; xx++)
        {
            var pixel = buffer_peek(buff, (xx + yy * TILE_SIZE * 32) * 4,buffer_u32);
            if ((pixel&0xff000000 != 0) || count == TILE_SIZE) break;
            count++;
        }
        buffer_poke(global.leftWidthBuff, yy, buffer_u8, count);
      
        l_y += TILE_SIZE;
      
        if(l_y >= 0)
            i_y = l_y - 1; 

        if (l_y == TILE_SIZE * 32)
        {
            l_x += TILE_SIZE - 1;
            l_y = 0;
        }
    }
}
But still no luck with correcting the calculations. I'll keep trying to figure it out in the meantime. I know I've bugged you a lot but know that your help is immensely appreciated! And my understanding of buffers keeps getting better as I continue working on this and receive your help.
 
Last edited by a moderator:
Top