Help with Collision Masks

M

Mattw11486

Guest
Hello all,

I need some help with Collision Masks. I created a sprite and converted it into a tileset. I then placed the mask around the borders of my map to create a collision mask.

My problem is when I move my Character (object) to test, it interacts with the mask but seems to act funny and gets stuck or it throws the Character to the opposite side of the bounding box. It is not interacting as it should.




I am not sure what I am missing here to get this to work flawlessly. Any help would be much appreciated.

Code:
/// @description Insert description here
image_speed = 0; //How quickly the frames are played in the game, at 0 it wont' animate
walkSpeed = 3; //The variable controlling how quickly the player walks

//Tile map info
tilemap = layer_tilemap_get_id("collisionmask");
Step Code:
Code:
/// @description Insert description here
if(keyboard_check(ord("D"))) { //Move to the right
    x += walkSpeed; //Physically moves the object the amount of walkSpeed to the right
    image_speed = walkSpeed / 2; //Moves through the frames of the playing sprite
    sprite_index = sprDMWalkRight; //Assigns the walking right sprite when D is pressed
}
if(keyboard_check(ord("A"))) { //Move to the left
    x -= walkSpeed;
    image_speed = walkSpeed / 2;
    sprite_index = sprDMWalkLeft;
}
if(keyboard_check(ord("W"))) { //Move Up
    y -= walkSpeed;
    image_speed = walkSpeed / 2;
    sprite_index = sprDMWalkUp;
}
if(keyboard_check(ord("S"))) { //Move Down
    y += walkSpeed;
    image_speed = walkSpeed / 2;
    sprite_index = sprDMWalkDown;
}
if(keyboard_check(vk_shift)) { //Run faster
    walkSpeed = 3; //Doubles the walk speed into a running speed
}
if(keyboard_check(vk_nokey)) { //Stop animating
    image_speed = 0; //Set speed of playing frames to 0
    image_index = 0; //Set current frame to 0
    walkSpeed = 2; //Return speed to normal
}

//Collision Checks
//Horizontal Collision
var bbox_side;

if (walkSpeed > 0) bbox_side = bbox_right; else bbox_side = bbox_left;
if (tilemap_get_at_pixel(tilemap,bbox_side+walkSpeed,bbox_top) != 0) or (tilemap_get_at_pixel(tilemap,bbox_side+walkSpeed,bbox_bottom) != 0)
{
    if (walkSpeed > 0) x = x - (x mod 32) + 31 - (bbox_right - x);
    else x = x - (x mod 32) - (bbox_left - x);
    walkSpeed = 0;
}

//Vertical Collision
if (walkSpeed > 0) bbox_side = bbox_bottom; else bbox_side = bbox_top;
if (tilemap_get_at_pixel(tilemap,bbox_left,bbox_side+walkSpeed) != 0) or (tilemap_get_at_pixel(tilemap,bbox_right,bbox_side+walkSpeed) != 0)
{
    if (walkSpeed > 0) y = y - (y mod 32) + 31 - (bbox_bottom - y);
    else y = y - (y mod 32) - (bbox_top - y);
    walkSpeed = 0;
}
 
So normally you'll have to set up bounding box collision points when you use tile set collision.
In your Create Event for your player, it would be set up something like this:

Code:
//Tile map info
var l = layer_get_id("collision_map");
tilemap = layer_tilemap_get_id(l);

//Collision points on
sprite_bbox_left = sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
sprite_bbox_right = sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);
sprite_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);
sprite_bbox_bottom = sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);
Check out this video, it really helped me out with this kind of collision!

EDIT:
Also, great sprite work, is that your art? It looks pretty nice! ;)
 

NightFrost

Member
Your problems start with immediately modifying the x and/or y positions without first checking for collisions. You should save the positional change into variables, check for walls in the potential position and change x/y values only if everything is clear. If there is a wall in the way, step 1 pixel at a time into the direction until wall is reached. In short, the same methodology you'd use with object collisions - check out movement tutorials if you're not familiar with this. Another point, when you modify coordinate position as far as I'm aware it immediately changes collision mask position too, so your bbox_side+walksSpeed isn't pointing anymore to position you are wanting to check. Lastly, your code is makes no difference how much was moved horizontally and vertically during the step as both fire off the same walkSpeed variable. The collision checks fire even when you're not moving, so simply standing near a wall will trigger a collision (walkSpeed is set to zero only after a collision has been detected, when it is already too late). There's also several tests against negative speed in the code but I don't see anything that would set it below zero.
 
M

Mattw11486

Guest
So normally you'll have to set up bounding box collision points when you use tile set collision.
In your Create Event for your player, it would be set up something like this:

Code:
//Tile map info
var l = layer_get_id("collision_map");
tilemap = layer_tilemap_get_id(l);

//Collision points on
sprite_bbox_left = sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
sprite_bbox_right = sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);
sprite_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);
sprite_bbox_bottom = sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);
Check out this video, it really helped me out with this kind of collision!

EDIT:
Also, great sprite work, is that your art? It looks pretty nice! ;)
Thanks for the reply. This is the video I was working with. I will try adding that snippet of code to see if that resolves. Also the sprite is a modified sprite of a character sheet I found online somewhere. I am no artist by any means :(

Your problems start with immediately modifying the x and/or y positions without first checking for collisions. You should save the positional change into variables, check for walls in the potential position and change x/y values only if everything is clear. If there is a wall in the way, step 1 pixel at a time into the direction until wall is reached. In short, the same methodology you'd use with object collisions - check out movement tutorials if you're not familiar with this. Another point, when you modify coordinate position as far as I'm aware it immediately changes collision mask position too, so your bbox_side+walksSpeed isn't pointing anymore to position you are wanting to check. Lastly, your code is makes no difference how much was moved horizontally and vertically during the step as both fire off the same walkSpeed variable. The collision checks fire even when you're not moving, so simply standing near a wall will trigger a collision (walkSpeed is set to zero only after a collision has been detected, when it is already too late). There's also several tests against negative speed in the code but I don't see anything that would set it below zero.
So change the walkspeed variable to something that checks horizontal and vertical collisions? How do I go about doing this?
 

NightFrost

Member
So change the walkspeed variable to something that checks horizontal and vertical collisions? How do I go about doing this?
For the controls, you could do something like this:
Code:
// Get keypresses
var left = keyboard_check(ord("A"));
var right = keyboard_check(ord("D"));
var up = keyboard_check(ord("W"));
var down = keyboard_check(ord("S"));

// Calculate deltas
hspd = (right - left) * walkSpeed;
vspd = (up - down) * walkSpeed;
With this, hspd will be negative if you are moving left, positive if you are moving right, and zero if neither. Same goes for vspd and vertical movement. Having separated the components, you can use them for collision checks.
Code:
// Horizontal collision
if(hspd != 0){ // No need to check horizontal collision if there is no horizontal movement
    var bbox_side;
    if(hspd > 0) bbox_side = bbox_right;
    else bbox_side = bbox_left;
    // Actual collision checking code here
}
At this point I noticed you use pixel checking, it doesn't work the way you've put it. Imagine if you are checking the middle point of left side, but only the lower left corner of player touches the wall. The collision won't register. I don't use GMS2 so I'm not familiar with the tilemap commands, so I don't know if there's some general collision check available. You could check the bbox corner pixels, assuming it is no larger than a wall tile, to detect all collisions. The x / y positions to check for horizontal movement would be bbox_side + hspd, bbox_top and bbox_side + hspd, bbox_bottom. Similar pattern for vertical checking.

Also, since we're talking about wholly grid-aligned collisions, you can use mod to align with the grid edges. However you can't straight mod your x and y since they do not correspond to your bbox edge locations, and will throw off your positioning. You must factor in the difference between the origin point and mask edges from sprite_get_xoffset and sprite_get_yoffset if your mask covers the entire sprite, otherwise set the instance to integet position during create and calculate the difference from there. (EDIT: because coordinate values are floats but bbox values are rounded integers, at least in GM1.4, so calculating from non-integer coordinate positions will throw off your system.)
 
M

Mattw11486

Guest
For the controls, you could do something like this:
Code:
// Get keypresses
var left = keyboard_check(ord("A"));
var right = keyboard_check(ord("D"));
var up = keyboard_check(ord("W"));
var down = keyboard_check(ord("S"));

// Calculate deltas
hspd = (right - left) * walkSpeed;
vspd = (up - down) * walkSpeed;
With this, hspd will be negative if you are moving left, positive if you are moving right, and zero if neither. Same goes for vspd and vertical movement. Having separated the components, you can use them for collision checks.
Code:
// Horizontal collision
if(hspd != 0){ // No need to check horizontal collision if there is no horizontal movement
    var bbox_side;
    if(hspd > 0) bbox_side = bbox_right;
    else bbox_side = bbox_left;
    // Actual collision checking code here
}
At this point I noticed you use pixel checking, it doesn't work the way you've put it. Imagine if you are checking the middle point of left side, but only the lower left corner of player touches the wall. The collision won't register. I don't use GMS2 so I'm not familiar with the tilemap commands, so I don't know if there's some general collision check available. You could check the bbox corner pixels, assuming it is no larger than a wall tile, to detect all collisions. The x / y positions to check for horizontal movement would be bbox_side + hspd, bbox_top and bbox_side + hspd, bbox_bottom. Similar pattern for vertical checking.

Also, since we're talking about wholly grid-aligned collisions, you can use mod to align with the grid edges. However you can't straight mod your x and y since they do not correspond to your bbox edge locations, and will throw off your positioning. You must factor in the difference between the origin point and mask edges from sprite_get_xoffset and sprite_get_yoffset if your mask covers the entire sprite, otherwise set the instance to integet position during create and calculate the difference from there. (EDIT: because coordinate values are floats but bbox values are rounded integers, at least in GM1.4, so calculating from non-integer coordinate positions will throw off your system.)
Maybe I am totally lost then. Is doing collision masks with tilesheets the most efficient way to do collisions for RPG style games such as the video example? Is there a better way?
 
M

Mattw11486

Guest
Alright I was able to resolve this actually. Here is what I did. Instead of using the tilesets, I created sprites and converted it to an object. Named it ObjCollision. Made the object invisible but a solid. It works but still some minor annoyances. One thing I think I am going to have to do is reduce the character sprite to 16x16 as the cave tilesets are 16x16 on a 16x16 grid. I find myself getting stuck again the walls, or not able to walk through certain smaller corridors. I used a 16x16 placeholder spite that was just a blue block and it seemed to walk around nicely.

Video Example:

Also I don't like how if I walk directly into a wall the sprite continues to keep moving. How would I fix that? I want the sprite to stop moving once it actually collides face first into a wall, if that makes any sense. Here is my current code:

Create Event:
Code:
/// @description Insert description here
image_speed = 0; //How quickly the frames are played in the game, at 0 it wont' animate
walkSpeed = 3.5; //The variable controlling how quickly the player walks
collisionSpeed = walkSpeed + 2;
Step Event:
Code:
/// @description Insert description here
if(keyboard_check(ord("D")) && place_free(x + collisionSpeed, y)) { //Move to the right
    x += walkSpeed; //Physically moves the object the amount of walkSpeed to the right
    image_speed = walkSpeed / 3; //Moves through the frames of the playing sprite
    sprite_index = sprDMWalkRight; //Assigns the walking right sprite when D is pressed
}
if(keyboard_check(ord("A")) && place_free(x - collisionSpeed, y)) { //Move to the left
    x -= walkSpeed;
    image_speed = walkSpeed / 3;
    sprite_index = sprDMWalkLeft;
}
if(keyboard_check(ord("W")) && place_free(x, y - collisionSpeed)) { //Move Up
    y -= walkSpeed;
    image_speed = walkSpeed / 3;
    sprite_index = sprDMWalkUp;
}
if(keyboard_check(ord("S")) and place_free(x, y + collisionSpeed)) { //Move Down
    y += walkSpeed;
    image_speed = walkSpeed / 3;
    sprite_index = sprDMWalkDown;
}
if(keyboard_check(vk_shift)) { //Run faster
    walkSpeed = 7; //Doubles the walk speed into a running speed
}
if(keyboard_check(vk_nokey)) { //Stop animating
    image_speed = 0; //Set speed of playing frames to 0
    image_index = 0; //Set current frame to 0
    walkSpeed = 3.5; //Return speed to normal
}
 
M

Mattw11486

Guest
I managed to solve a lot of the issues I was having minus two small problems. I would like to stop the animation once I hit face first into a collision. And for some reason, the character sprite seems to bleed when I am moving towards a bounding box from its right. What I did was made the character sprite's collision mask the sprWall. sprWall was created as a solid object and then the code I used is provided in previous post.

Video Example:
 
Top