GM:S 1.4 On Slopes and Grids: subpixel-perfect topdown movement and collision line without objects

Discussion in 'Tutorials' started by Ariak, Aug 7, 2016.

  1. Ariak

    Ariak Member

    Joined:
    Jun 20, 2016
    Posts:
    91
    GM Version: 1.4.1575
    Target Platform: ALL
    Download:
    Links: Pastebin http://pastebin.com/dxJjsptD


    Summary:
    This tutorial will feature a couple of scripts that will enable you to work with 45° slopes in GameMaker without using any wall objects – entirely based on grids. Additionally there will be terribly drawn visual explanations.
    • Checking a single point (x,y) for a collision vs a 45° triangle in a grid_cell
    • collision line checking between two points
    • 2D top-down 45° slope movement code, including subpixels, and angular adjustment

    Tutorial:
    [​IMG]

    Hey,
    thanks for your interest in this tutorial. Sadly the massive text I wrote for this got scrapped by the forums character limit. Use the GMZ with commented code, and maybe the pictures will help too.


    A few things to note first:
    • Try the demo project first to see what’s being worked on. Use the visualization button (b) and Zoom (x) to get a closer look at what is going on and how the collision checks are done. Press space to delete all the invisible objects to see the performance boost (instance counter bot left corner).
    • If you cant run the .gmz file with the YYC or its just to slow going for your machine, adjust the roomsize to 3200x3200 px and test again. You will still see a good perfomance boost without objects (press Spacebar)
    • Go check out Heartbeast Tutorial called "Random Level Generation [P2] Grid Collision" -it helps understanding my tutorial alot.
    • Cell Dimensions are a fixed 32x32. For simplicity and readibility there is no cell dimension macro variable.
    • The player is a 32x32 object, thus exactly the same as the cell. See comments below on how to adapt this to a 16x16 player object.
    • Yes, its unusual to have the grid stored in player object - hes not designed to die.
    • This tutorial is for 45° slopes only, especially the 2D top-down movement code. Some of the other elements, like the collision_line or collision_point code may work for any form of shape/slope – depending on your adaptations.
    • sx,sy,tx,ty stand for start x, start y, target x, target y
    • There is no one size fits all solution, I've limited it to 8 directional movement for now. More is definetly possible!
    Essential function : &~31 returns the nearest value evenly dividable by 32: 99&~31=96.
    Some people might not know this and get stuck in the gmz file.

    So why use a grid and not wall-objects, since they can do precise slopes!?


    Because using wall objects causes major perfomance headaches. Especially since you will most likely need Game Makers precise sprite collision checking activated if you want slopes.
    Using grids is tons more efficient, as there are no wall objects that constantly update their built-in variables.
    With the following pictures n thoughts i'd like to demonstrate that there is a viable alternative.

    /// ==========================================================
    PART 1: Checking if a single point (x,y) collides with a 45° triangle in a grid cell.
    /// ==========================================================


    [​IMG]

    The linear equation for the hypotenuse of a triangle of 45° is given by y(x)=x. Thus the above picture shows how we can check collisions using a simple boolean check, which returns either 1 for true, so a collision did occur, or 0 for false - no collision. We will only need remainders (mod) of a given x,y value pair to determine collision, and the div function to reference the grid.

    [​IMG]

    NOTE:

    In Game maker there is a among other similar functions one called point_in_triangle which does the same thing, but it needs a lot more coordinates. See GML: Collision Checking Without Masks. https://docs.yoyogames.com/source/dadiospice/002_reference/movement and collisions/collisions/index.html

    /// ==========================================================
    PART 2: Collision Line
    /// ==========================================================

    We will need to check 2 points per cell, which represent the borders - see visuals provided!
    • The start point (sx,sy) and its nearest respective boundary with the grid - x can be 0, or 31 and y can be 0 or 31 to get the cell borders. See the picture on the points above for this. So 2 checks for the start cell
    • The end point (tx,ty) and alike to the start function, its nearest boundary intersect along the line. Same as above, 2 checks for the target cell.
    • and 2 checks for all the grid cells that the line crosses between (sx,sy) and (tx,ty).
    Why? There can at most be 2 intersections of the line and a given cell. And we need 2 points to actually determine the collision with a triangle - for squares one collision point per cell would suffice. Special cases:
    • Only one intersection is present if the line either just touches the corners, or either the start or end point fall within the cell.
    • There’s also the special case 0 where both start and end points are within the same cell.
    • Furthermore another problem occurs if we use rounded values. If the angle is steep enough there is a possibility that multiple pixels actually intersect the border. See the picture below ,right hand side. Think of it as the pixel art vs anti aliasing which at least tries to be 'as correct' as possible.

    [​IMG]

    To circumvent the rounded pixel problem (right hand side depiction), I determined the intersections mathematically, yielding subpixel perfect intersections, using the slope of the given equation line between two points.

    The equation for 2 points along a line is given by y=mx+b. Assuming we know both (sx,sy) and (tx,ty) the slope m is determined by m=(delta y / delta x). We can use this to get a corresponding y value, for a given x. And vice-versa.

    Right of the bat we can check whether both start or end points are in collision with the grid using our collision_point code. If they are, we're good and can break//exit already.

    Otherwise we will split the task up into two components:
    • incrementing x values to nearest cell boundary intersect and calculated corresponding Y - using +1 to enter the next cells adjacent border, and +32 from the same point to get to the opposite cell border.
    • the same vice_versa for Y, and adjusted X values;
    Here’s a visual representation of my collision line – red for start and end points. Green for integer X, white for integer Y. Play around with the gmz and use the zoom function. Every drawn point represents a potential collision check. It will draw the whole line for demonstration, the actual collision check terminates upon first collision. For 90° lines the check along one axis will suffice, and you should see only green X or only white Y points.

    [​IMG]

    [​IMG]

    /// ==========================================================
    PART 3: 2D Top-Down Movement Code, 45° Slopes and Angular Adjustment
    /// ==========================================================

    Pixels vs subpixels: The movement will always be in full pixels to avoid sprite tearing. Decimals along an axis are cumulated until their sum is equal to or greater than 1. In which case 1 is added to the next speed input along the given axis, and the counter starts anew. To avoid janky movement along slopes there is one extra OPTIONAL line in the code for the X axis check. It will limit the speed along y axis to that of the x axis to create perfect 45°, regardless of whether that little decimal +1 just kicked in for y - which would cause the janky movement. It’s not necessary to do the same for y, as it runs after the x checks.

    Disclaimer: Due to the nature of the angular adjustments to the nearest integer (=> full pixel value) the movement will feel off for speeds that are below 3px per step @ 60 fps. Its either that or sprite tearing.

    EDIT:
    Please Scroll Down to McWolke's comment. There is a minor mistake in the movement code (movement in combined direction) that leads to shaky movement (most noticeable at lower speeds).


    The base logic is to first check if there’s a collision ahead along both x,y axis simultaneously. If not, we can skip …. everything. With one exception, we need to manually slow down our movement speeds if we’re moving at an angle. Otherwise moving along any angle will always be faster then moving along on a 90°, 180° etc angle. The distance covered by x+1,y+1 is not 1, but 1.4… . Thus to preserve a natural speed we adjust the x,y vectors.

    Now assume that the combined collision check returns there is a collision. We will now need to seperatly check both axis as the the direct way forwards is blocked. The principle for the slope movement is depicted below. The picture in the middle shows how the direction choice is being made: up and down are being checked, the return value is then the new direction. This is similar to how a sign function would essentially work. It can be -1 for up, 0 for no decision (both spots collide), and 1 for down. See commented code for details.

    [​IMG]


    avx,avy are set in the players create event to properly carry over values between steps. The comment should read same for y.
    [​IMG]

    If you want this top down movement code to work with objects you will need precise collision checking enabled. Next, simply replace ‘gridcol_place_meeting’ with GM's standard ‘place_meeting’ and your object to check against. Done! :)

    So how does the gridcol_place_meeting function look like, and why can't we simple use gridcol_point from Part 1?

    The gridcol_place_meeting works in a very similar fashion to what Heartbeast demonstrated in his grid collision tutorial. We determine the 4 corners of the sprite with the grid. Only checking the sprite origin point is not enough. You would be able to move halfway through walls.

    Gridcol_quadrant_rectangle performs the actual collision check, for each of the 4 points specified by gridcol_place_meeting. As we are using slopes we need to check even more points then just the 4 corners - see left side picture below, where the player can enter the triangle if only its 4 corner points are checked with gridcol_point. The problem is that the character is a rectangle, not just 4 indivual corners, it has edges. To solve this issue the gridcol_quadrant_rectangle function will additionally require the sprite origin. It uses this to determine max 3 collision points per cell (=> quadrant) - see middle pixture below. So per cell the target value and the respective nearest intersections with the grid. It can now detect whether there is a triangle in the way - picture on the right. The actual collision checks now follow the same principle as gridcol_point, but we those extra points. If the character is boxed in on all 4 sides by a slope 12 point checks are performed. 3 for each slope.

    Visual representation helps! Notice how you can enter the triangle, if you only check the corner points? That’s why the extra points "in the middle" are added by the quadrant_collision. They check the points aligning with the grid. See visual representation in the demo and use the zoom to see how they behave. Although all 12 points are always shown in the demo (they can overlap to "hide") , if the cell is free or a square wall only one point, the corner point, is checked for efficiency.

    [​IMG]

    [​IMG]

    /// ==========================================================
    Final Thoughts
    /// ==========================================================

    I hope this tutorial helped you - i certainly wracked my brains coming up with the collision line code.
    In my tests it was alot more efficient to use my collision line versus the standard while loop, sx+=lengthdir and a step counter method. With a step size of 5 my code was 3-4x faster. Additionally the brute force method is also inaccurate due to the nature of the step...! (see 'brute force' script in gmz file)

    The walls are visually represented as tiles in my demo. If you want your room to appear even larger than 6400x6400 you will need to think about drawing the tiles to a surface instead to 'fake it' - its the method that Rujik is using in his 'Pale Meridian [Action RPG]', his room is actually tiny. But the world appears friggin huge - see his post in the 'Work in Progress Section'. I tested running the demo in 32000x32000 (with objects previously disabled). It took forever to load, but it runs at around 250fps , quite similar to the performance of the demo rooms 6400x6400 with objects. [FPS value based on my end.]

    There is a wonderful script of nested if statements by Nocturne for Autotiling - srsly... hats off. Which is demoed by Shaun in one of his tutorial videos. The script can easily be adapted to work with a grid, instead of wall objects. I even managed to modify it to factor in slope tiling. Im mentioning this, because it would be the next logical step when you think about game world creation.

    Happy coding.
     
    Last edited: Feb 6, 2017
    atmobeat, NeZvers, Shirsh and 22 others like this.
  2. Cristiander

    Cristiander Guest

    Very eye-opening tutorial.
    Thank's man :)
     
    Ariak likes this.
  3. Ariak

    Ariak Member

    Joined:
    Jun 20, 2016
    Posts:
    91
    Here's how to quickly adapt my math to feature a 16x16 player object, as per private message request.

    The problem is the quadrant checks in the script gridcol_quadrant_rectangle, which affects the gridcol_place_meeting functions used for collisions in the movement code.

    First Lets look at the problem visually:

    [​IMG]

    • If we leave everything as is, and simply adjust our sprite to 16x16 (dont forget to adjust origin to center). The nearest boundary adjustments in the script gridcol_quadrant rectangle, which is called by gridcol_place_meeting, will result in PIC 1. ax,ay can only take on the values of [0,31] without adjustment.
    • Obviously we need to adjust this to the nearest 16px! Thus we go into the script and look for where these nearest boundary checks are implemented. They are ax and ay.
      we change ax and ay to the code below, and get PIC 2! Ax,ay can now take on the values [0,15].
      Code:
      var ax=15*(ox>xx); // from 0, ne
      var ay=15*(oy>yy);
    • Looking better, half of the points are now in correct alignment with players bounding box. Whilst the other half is not, this is because we need to restore ax,ay ability to again take on values of 31, to net us ax,ay[0,15,16,31].
      Replace with this:
      Code:
      var ax=16*(xx mod 32 >= 16)+15*(ox>xx);   // are we more than 16x to the right? then add 16.
      var ay=16*(yy mod 32 >= 16)+15*(oy>yy);
    • It should work for a 16x16 character object now - the results are shown in PICS 3-5.
     
    Last edited: Feb 7, 2017
    Lickatoad, net8floz and Cristiander like this.
  4. Alexx

    Alexx Member

    Joined:
    Jun 21, 2016
    Posts:
    430
    Very smooth and elegantly programmed. Could be implemented and used in a variety of ways.
    If combined with some path-finding, I could see lots of practical uses for this.
     
    Last edited: Aug 9, 2016
    Ariak likes this.
  5. Zerb Games

    Zerb Games Member

    Joined:
    Jun 27, 2016
    Posts:
    259
    Best tutorial I've seen in a long time, am I allowed to bump this? CAUSE THIS IS GREAT!
     
    Ariak likes this.
  6. LarryP

    LarryP Member

    Joined:
    Aug 13, 2016
    Posts:
    33
    This is what has been on my mind for quite a while, how to check for slopes. Thanks so very very much for your time! :D :)

    I have this bookmarked now.
     
    mockgames and Ariak like this.
  7. Ariak

    Ariak Member

    Joined:
    Jun 20, 2016
    Posts:
    91
    I guess I really should update this tutorial at some point.
    • Any sized rectangles are actually incredibly simple to implement... at virtually zero performance impact.
    • I should throw in a grid bounce function aswell (with slopes).
    • 360° movement ( a lil complicated for the subpixel part)
    • overall performance improvements (~25-50% faster on movement code, and slightly faster collision checks.
    • grid + object combined collisions
    • GMS2 tilemap collisions instead of grids.
     
    Last edited: Dec 21, 2016
  8. LarryP

    LarryP Member

    Joined:
    Aug 13, 2016
    Posts:
    33
    If you made basic templates for collisions, and put them in the Marketplace, I would buy every one of them. Great stuff here!
     
  9. net8floz

    net8floz Guest

    Fantastic!
     
  10. Lickatoad

    Lickatoad Guest

    This is amazing! thank you for sharing your code and the excellent tutorial.

    I have a question, would it be difficult to implement cornering on a square tile, so if say 1/4 of the player object side is colliding with it, but remainder isn't, you are automatically pushed into a free space to move around it?

    For example, in the image attached, the red square would be automatically pushed around the blue square with a right input key press only.
     

    Attached Files:

  11. Ariak

    Ariak Member

    Joined:
    Jun 20, 2016
    Posts:
    91
    Hey Lick,

    no it wouldn't be very difficult to implement. Essentially my grid collision scripts are faster (and static) versions of GM built-in collision functions such as `place_meeting`.
    As such the questions you asked is somewhat unrelated to my grid collision system, and more generally about movement collision code.

    To achieve what you're describing would require an additional collision check in the movement script that is similar to how the slope direction is determined. Instead of checking eg [x+1,y+1] you would simply need to check [x+1/4width,y+1], and if that is not a collision move there directly. This check should only happen if there truly is a collision ahead on the x-axis, or y axis respectively.
     
    Last edited: Feb 7, 2017
    Lickatoad likes this.
  12. McWolke

    McWolke Guest

    Hey Ariak,
    your tutorial is awesome, thank you for that!
    I followed your tutorial and rewrote everything you did, so i could understand what you did and also make it work with GMS2 Tilemaps.
    however, i found this line in the scr_gridcol_move_speeds script suspicious:
    Code:
    if !scr_gridcol_place_meeting(x+pvx,y+pvy) {x=round(x+pvx);y=round(y+pvy);}   
    you're checking the grid with unrounded values, but move to rounded ones.
    i tried it that way, but it made my player stutter and moving a pixel inside the wall when sliding down+right along walls.
    so i changed it to rounded values
    Code:
    if !scr_gridcol_place_meeting(round(x+pvx),round(y+pvy)) {x=round(x+pvx);y=round(y+pvy);}   
    and now it works perfectly fine.

    just wanted to let you and others know, if they encounter the same problem. i think the stuttering only happens with lower speeds?
     
  13. Ariak

    Ariak Member

    Joined:
    Jun 20, 2016
    Posts:
    91
    Good catch :)
    Made an edit in my original post!

    The tutorial is a little outdated. Along with many other improvements i've since modified it to make the angular correction first, storing the fractions in avx,avy, and then moving in perfect integers.
     
    Last edited: Feb 6, 2017
    atmobeat likes this.
  14. Lickatoad

    Lickatoad Guest

    Hi Ariak,

    Thanks for the reply and for your help, I really appreciate it! I'll have a go at implementing it tonight. I've got an idea how it needs to be done.
     
  15. Lickatoad

    Lickatoad Guest

    Hey Ariak, thanks for the information on implementing cornering, it seems to be working now. The code I used is below in case it is helpful to anyone wanting to do the same..

    I added the following to the gridcol_move_speeds script.

    At the end of the x axis if statement:
    Code:
        if(gridcol_place_meeting(x+pvx, y-8) && (!gridcol_place_meeting(x+pvx, y+8))) {y+=1;} //Cornering
        else if(gridcol_place_meeting(x+pvx, y+8) && (!gridcol_place_meeting(x+pvx, y-8))) {y-=1;} //Cornering

    and this at the end of the y axis if statement
    Code:
        if(gridcol_place_meeting(x-8, y+pvy) && (!gridcol_place_meeting(x+8, y+pvy))) {x+=1;}
        else if(gridcol_place_meeting(x+8, y+pvy) && (!gridcol_place_meeting(x-8, y+pvy))) {x-=1;}
     
    Last edited by a moderator: Feb 10, 2017
  16. Gregory Tapper

    Gregory Tapper Member

    Joined:
    Aug 1, 2016
    Posts:
    19
    Wow, this is awesome, @Ariak ! THANK YOU!

    Using GMS2 now, so wondering if you've figured out a way to use this as a tilemap? Were you able to get it working @McWolke or @Ariak ?
     
  17. Ariak

    Ariak Member

    Joined:
    Jun 20, 2016
    Posts:
    91
    The two are incredibly similar. All you need to do is swap out the calls to access the grid with the corresponding tilemap_get functions (and a tileset/sprite).
    grid[# x,y] becomes tilemap_get(tilemap,x,y).
     
  18. Gregory Tapper

    Gregory Tapper Member

    Joined:
    Aug 1, 2016
    Posts:
    19
    I attempted to adapt my 48x48 character sprite on 64x64 enviro tiles using your 16x16 explanation. Not quite there yet. Getting some unexpected hitching.

    Would this :
    Code:
    switch (o_player.grid[# xx div 64, yy div 64]){
    
    Become something like this when using a tileset?
    Code:
    with(o_player) {
        tmg1 = tilemap_get(tilemap, other.xx div 64, other.yy div 64);
    }
    switch (o_player.tmg1){
    
     
    Last edited: May 14, 2018
  19. Shirsh

    Shirsh Member

    Joined:
    Dec 28, 2016
    Posts:
    14
    Great system, changed it to use 32x16 tiles and 2x:1y slopes, it totally rocks!
    Too bad images disappears so it took a bit longer to understand, without visual examples.
    Especially those commented as "MAGIC =D see pictures in tutorial for explanation"
    in case anyone else want to try it for dimetric/iso, that's changes to slope part in gridcol_quadrant_rectangle (beside obvious changing 32 to 16 and 31 to 15 in all y related parts of project)
    Code:
    switch (obj_player.grid[# xx div 32, yy div 16]){
        case 2: col =  ( (tx/2 >= ty)   ||  (tx/2 >= ay)    ||  (ax/2 >= ty)  );  break;  // 4 Slopes - MAGIC =D see pictures in tutorial for explanation
        case 3: col =  ( (tx/2 <= ty)   ||  (tx/2 <= ay)    ||  (ax/2 <= ty)  );  break;
        case 1: col =  ( ((31-tx)/2 >= ty)||  ((31-tx)/2 >= ay) ||  ((31-ax)/2 >= ty)); break;
        case 4: col =  ( ((31-tx)/2 <= ty)||  ((31-tx)/2 <= ay) ||  ((31-ax)/2 <= ty)); break;
    
     
  20. Shirsh

    Shirsh Member

    Joined:
    Dec 28, 2016
    Posts:
    14
    Hey guys, I figured that it's more versatile to combine two 16x16 tiles than using 32x16, and I put there more cases of collision:

    [​IMG]

    In case if you'd like to use that as a base for collision detection in your game:

    [​IMG] <-- resize it to tilesize you need, or use as a reference, 16x16 source --> [​IMG]

    Not all of them working great: 18,19, 46,47, 56, 57 and other round stuff require extra collision check if player's collision mask is bigger than half of the tile
    also there no cases 49 and 59 in code, sorry for misleading pic, should be not hard to implement like 48/58

    I use smaller collision mask so it works for me*
    Didn't tried to implemented collision line for them yet.

    also it's not perfect as I'm not smart enough yet to understand where to use 31 (tilesize -1) instead of 32 (tilesize), but I guess you can figure it out and fix, right?
    also it contains tilesize_Y, from thoughts of using rectangle tiles, I'm using #macro tilesize_X 16 and #macro tilesize_Y 16
    Code:
    /// @description gridcol_quadrant_rectangle(ox,oy,xx,yy);
    /// @param ox
    /// @param oy
    /// @param xx
    /// @param yy
    
    var ox=argument0;   // origin of sprite,object
    var oy=argument1;
    var xx=argument2;   // target coordinates to check as determined by gridcol_place_meeting
    var yy=argument3;
    var x_dif = tilesize_X - sprite_get_width(mask_index);
    var y_dif = tilesize_Y - sprite_get_height(mask_index);
    
    // Quadrant intersections along border - see the visualization for this - on slopes we check more than 1 point per quadrant
    var ax=x_dif*(xx mod tilesize_X >= x_dif)+(tilesize_X - x_dif-1)*(ox>xx);
    var ay=y_dif*(yy mod tilesize_Y >= y_dif)+(tilesize_Y - y_dif-1)*(oy>yy);
    
    var tx = xx mod tilesize_X;
    var ty = yy mod tilesize_Y;
    var col=false;
    
    //that's for GMS2, for GMS1: var tileID = (obj_player.grid[# xx div tilesize_X, yy div tilesize_Y]) or something
    
    var layID = layer_get_id("Collision_map");
    var mapID = layer_tilemap_get_id(layID);
    var tileID = tilemap_get(mapID, xx div tilesize_X, yy div tilesize_Y);
    
    switch (tileID){
         // 1 Square Wall
        case 1:        col=1;                                                                break;  
         // 45 degree slopes, 4 tiles
        case 2: col =  ( (tx >= ty)   ||  (tx >= ay)    ||  (ax >= ty)  );                break;
        case 3: col =  ( (tx <= ty)   ||  (tx <= ay)    ||  (ax <= ty)  );                break;
        case 4: col =  ( ((tilesize_X-1)-tx <= ty)||  ((tilesize_X-1)-tx <= ay) ||  ((tilesize_X-1)-ax <= ty));                break;
        case 5: col =  ( ((tilesize_X-1)-tx >= ty)||  ((tilesize_X-1)-tx >= ay) ||  ((tilesize_X-1)-ax >= ty));                break;   
     
        // ~22.5 degree horizontal slopes 8 tiles
        case 6: col =  ( (tx/2 >= ty)   ||  (tx/2 >= ay)    ||  (ax/2 >= ty)  );                                             break;
        case 7: col =  ( ((tilesize_X-1+tx)/2 >= ty)   ||  ((tilesize_X-1+tx)/2 >= ay)    ||  ((tilesize_X-1+ax)/2 >= ty)); break;
        case 8: col =  ( (tx/2 <= ty)   ||  (tx/2 <= ay)    ||  (ax/2 <= ty)  );                                            break;
        case 9: col =  ( ((tilesize_X-1+tx)/2 <= ty)   ||  ((tilesize_X-1+tx)/2 <= ay)    ||  ((tilesize_X-1+ax)/2 <= ty)); break;
        case 10: col = ( (tilesize_X-1-tx/2 <= ty) ||  (tilesize_X-1-tx/2 <= ay) ||  (tilesize_X-1-ax/2 <= ty));            break;
        case 11: col = (((tilesize_X-1-tx)/2 <= ty) ||  ((tilesize_X-1-tx)/2 <= ay) ||  ((tilesize_X-1-ax)/2 <= ty));        break;
        case 12: col = ( (tilesize_X-1-tx/2 >= ty) ||  (tilesize_X-1-tx/2 >= ay) ||  (tilesize_X-1-ax/2 >= ty));            break;
        case 13: col = ( ((tilesize_X-1-tx)/2 >= ty) ||  ((tilesize_X-1-tx)/2 >= ay) ||  ((tilesize_X-1-ax)/2 >= ty));        break; 
    
        // 2 vertical halves
        case 14: col = ( (tx >= tilesize_X/2) || (ax >= tilesize_X/2) );            break;      
        case 15: col = ( (tx <= tilesize_X/2) || (ax <= tilesize_X/2) );            break; 
    
        // 2 horizontal halves
        case 16: col = ( (ty <= tilesize_Y/2) || (ay <= tilesize_Y/2) );            break;    
        case 17: col = ( (ty >= tilesize_Y/2) || (ay >= tilesize_Y/2) );            break;     
    
        // vertical middle half
        case 18: col = ( (tx >= tilesize_X/4 && tx <=tilesize_X*3/4) || (ax >= tilesize_X/4 && ax <=tilesize_X*3/4) );        break;  
     
        //horizontal middle half
        case 19: col = ( (ty >= tilesize_Y/4 && ty <=tilesize_Y*3/4) || (ay >= tilesize_Y/4 && ay <=tilesize_Y*3/4) );        break;    
    
        // ~67,5 degree vertical slopes 8 tiles
        case 20: col = ( (tilesize_X-(tx-tilesize_X/2)*2 <=ty) || (tilesize_X-(tx-tilesize_X/2)*2 <=ay) ||(tilesize_X-(ax-tilesize_X/2)*2 <=ty) ); break;
        case 30: col = ( (tilesize_X-2*tx <= ty) || (tilesize_X-2*tx <= ay) || (tilesize_X- 2*ax <= ty) );                    break;
        case 21: col = ( (tilesize_X-(tx-tilesize_X/2)*2 >=ty) || (tilesize_X-(tx-tilesize_X/2)*2 >=ay) ||(tilesize_X-(ax-tilesize_X/2)*2 >=ty) ); break;
        case 31: col = ( (tilesize_X-2*tx >= ty) || (tilesize_X-2*tx >= ay) || (tilesize_X-2*ax >= ty) );                    break;
        case 22: col = ( (2*tx >= ty)|| (2*tx >= ay) || (2*ax >= ty) );                                                        break;
        case 32: col = ( ((tx-tilesize_X/2)*2 >= ty) || ((tx-tilesize_X/2)*2 >= ay) || ((ax-tilesize_X/2)*2 >= ty));        break;
        case 23: col = ( (2*tx <= ty)|| (2*tx <= ay) || (2*ax <= ty) );                                                        break;
        case 33: col = ( ((tx-tilesize_X/2)*2 <= ty) || ((tx-tilesize_X/2)*2 <= ay) || ((ax-tilesize_X/2)*2 <= ty));        break;
    
        // outer round corners, 4 tiles, radius = tilesize
        case 24: col = ( (ty >= -1*sqrt(power(tilesize_X, 2) - power(tx - tilesize_X, 2)) + tilesize_Y) ||
                         (ay >= -1*sqrt(power(tilesize_X, 2) - power(tx - tilesize_X, 2)) + tilesize_Y) ||
                         (ty >= -1*sqrt(power(tilesize_X, 2) - power(ax - tilesize_X, 2)) + tilesize_Y) );    break;
        case 34: col = ( (ty <= sqrt(power(tilesize_X, 2) - power(tx - tilesize_X, 2))) ||
                         (ay <= sqrt(power(tilesize_X, 2) - power(tx - tilesize_X, 2))) ||
                         (ty <= sqrt(power(tilesize_X, 2) - power(ax - tilesize_X, 2))) );                    break;                    
        case 25: col = ( (ty >= -1*sqrt(power(tilesize_X, 2) - power(tx, 2)) + tilesize_Y) ||
                         (ay >= -1*sqrt(power(tilesize_X, 2) - power(tx, 2)) + tilesize_Y) ||
                         (ty >= -1*sqrt(power(tilesize_X, 2) - power(ax, 2)) + tilesize_Y) );                break;                
        case 35: col = ( (ty <= sqrt(power(tilesize_X, 2) - power(tx, 2))) ||
                         (ay <= sqrt(power(tilesize_X, 2) - power(tx, 2))) ||
                         (ty <= sqrt(power(tilesize_X, 2) - power(ax, 2))) );                                break;
    
        // inner round corners, 4 tiles, radius = tilesize            
        case 26: col = ( (ty <= -1*sqrt(power(tilesize_X, 2) - power(tx - tilesize_X, 2)) + tilesize_Y) ||
                         (ay <= -1*sqrt(power(tilesize_X, 2) - power(tx - tilesize_X, 2)) + tilesize_Y) ||
                         (ty <= -1*sqrt(power(tilesize_X, 2) - power(ax - tilesize_X, 2)) + tilesize_Y) );    break;
                     
        case 36: col = ( (ty >= sqrt(power(tilesize_X, 2) - power(tx - tilesize_X, 2))) ||
                         (ay >= sqrt(power(tilesize_X, 2) - power(tx - tilesize_X, 2))) ||
                         (ty >= sqrt(power(tilesize_X, 2) - power(ax - tilesize_X, 2))) );  break;
     
     
        case 27: col = ( (ty <= -1*sqrt(power(tilesize_X, 2) - power(tx, 2)) + tilesize_Y) ||
                         (ay <= -1*sqrt(power(tilesize_X, 2) - power(tx, 2)) + tilesize_Y) ||
                         (ty <= -1*sqrt(power(tilesize_X, 2) - power(ax, 2)) + tilesize_Y) );    break;
                     
        case 37: col = ( (tx = tilesize_X || ax = tilesize_X || ty = tilesize_Y || ay = tilesize_Y) ||
                         (ty >= sqrt(power(tilesize_X, 2) - power(tx, 2))) ||
                         (ay >= sqrt(power(tilesize_X, 2) - power(tx, 2))) ||
                         (ty >= sqrt(power(tilesize_X, 2) - power(ax, 2))) );                    break;     
              
        // outer round smaller corners, 4 tiles, radius = tilesize/2
        case 28: col = ( (ty >=tilesize_Y/2 && tx >= tilesize_X/2) &&
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2)) + tilesize_Y) ||
                         (ay >=tilesize_X/2 && tx >= tilesize_X/2) &&
                         (ay >= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2)) + tilesize_Y) ||
                         (ty >=tilesize_X/2 && ax >= tilesize_X/2) &&
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X, 2)) + tilesize_Y) );    break;
        case 38: col = ( (ty <=tilesize_Y/2 && tx >= tilesize_X/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2))) ||
                         (ay <=tilesize_Y/2 && tx >= tilesize_X/2) &&
                         (ay <= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2))) ||
                         (ty <=tilesize_Y/2 && ax >= tilesize_X/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X, 2))) );                    break;                    
        case 29: col = ( (ty >=tilesize_Y/2 && tx <= tilesize_X/2) &&
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(tx, 2)) + tilesize_Y) ||
                         (ay >=tilesize_Y/2 && tx <= tilesize_X/2) &&
                         (ay >= -1*sqrt(power(tilesize_X/2, 2) - power(tx, 2)) + tilesize_Y) ||
                         (ty >=tilesize_Y/2 && ax <= tilesize_X/2) &&
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(ax, 2)) + tilesize_Y) );                break;                
        case 39: col = ( (ty <=tilesize_Y/2 && tx <= tilesize_X/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(tx, 2))) ||
                         (ay <=tilesize_Y/2 && tx <= tilesize_X/2) &&
                         (ay <= sqrt(power(tilesize_X/2, 2) - power(tx, 2))) ||
                         (ty <=tilesize_Y/2 && ax <= tilesize_X/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(ax, 2))) );                                    break; 
    
        // inner round smaller corners, 4 tiles, radius = tilesize/2
        case 40: col = ( (ty <=tilesize_Y/2 || tx <= tilesize_X/2 || ay<=tilesize_Y/2 || ax <= tilesize_X/2 ) ||                
                         (ty >=tilesize_Y/2 && tx >= tilesize_X/2) &&
                         (ty <= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2)) + tilesize_Y) ||
                         (ay >=tilesize_X/2 && tx >= tilesize_X/2) &&
                         (ay <= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2)) + tilesize_Y) ||
                         (ty >=tilesize_X/2 && ax >= tilesize_X/2) &&
                         (ty <= -1*sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X, 2)) + tilesize_Y) );    break;
        case 50: col = ( (ty >=tilesize_Y/2 || tx <= tilesize_X/2 || ay >=tilesize_Y/2 || ax <= tilesize_X/2) ||                
                         (ty <=tilesize_Y/2 && tx >= tilesize_X/2) &&
                         (ty >= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2))) ||
                         (ay <=tilesize_Y/2 && tx >= tilesize_X/2) &&
                         (ay >= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2))) ||
                         (ty <=tilesize_Y/2 && ax >= tilesize_X/2) &&
                         (ty >= sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X, 2))) );        break;                    
        case 41: col = ( (ty <=tilesize_Y/2 || tx >= tilesize_X/2 || ay <=tilesize_Y/2 || ax >= tilesize_X/2) ||
                         (ty >=tilesize_Y/2 && tx <= tilesize_X/2) &&
                         (ty <= -1*sqrt(power(tilesize_X/2, 2) - power(tx, 2)) + tilesize_Y) ||
                         (ay >=tilesize_Y/2 && tx <= tilesize_X/2) &&
                         (ay <= -1*sqrt(power(tilesize_X/2, 2) - power(tx, 2)) + tilesize_Y) ||
                         (ty >=tilesize_Y/2 && ax <= tilesize_X/2) &&
                         (ty <= -1*sqrt(power(tilesize_X/2, 2) - power(ax, 2)) + tilesize_Y) );        break;                
        case 51: col = ( (ty >=tilesize_Y/2 || tx >= tilesize_X/2 || ay >=tilesize_Y/2 || ax >= tilesize_X/2) ||
                         (ty <=tilesize_Y/2 && tx <= tilesize_X/2) &&
                         (ty >= sqrt(power(tilesize_X/2, 2) - power(tx, 2))) ||
                         (ay <=tilesize_Y/2 && tx <= tilesize_X/2) &&
                         (ay >= sqrt(power(tilesize_X/2, 2) - power(tx, 2))) ||
                         (ty <=tilesize_Y/2 && ax <= tilesize_X/2) &&
                         (ty >= sqrt(power(tilesize_X/2, 2) - power(ax, 2))) );                        break; 
    
        // outer 45 degrees halftile corners, 4 tiles
        case 42: col = ( (((tilesize_X-1)-tx <= ty-tilesize_Y/2))||
                         (((tilesize_X-1)-tx <= ay-tilesize_Y/2))||
                         (((tilesize_X-1)-ax <= ty-tilesize_Y/2)));    break;                  
        case 52: col = ( (((tx >= ty+tilesize_Y/2)))||
                         ( (tx >= ay+tilesize_Y/2))||
                         ( (ax >= ty+tilesize_Y/2)));                break;      
        case 43: col = ( ( ((tx <= ty-tilesize_Y/2)))||
                         ( (tx <= ay-tilesize_Y/2))||
                         ( (ax <= ty-tilesize_Y/2)));                break;
        case 53: col = ( ( ((tilesize_X-1)-tx >=  tilesize_Y/2+ty))    ||
                         ( ((tilesize_X-1)-tx >=  tilesize_Y/2+ay))    ||
                         ( ((tilesize_X-1)-ax >=  tilesize_Y/2+ty)));    break;     
     
        // inner 45 degrees halftile corners, 4 tiles
        case 44: col = ( (((tilesize_X-1)-tx >= ty-tilesize_Y/2))||
                         (((tilesize_X-1)-tx >= ay-tilesize_Y/2))||
                         (((tilesize_X-1)-ax >= ty-tilesize_Y/2)));    break;              
        case 54: col = ( (((tx <= ty+tilesize_Y/2)))||
                         ( (tx <= ay+tilesize_Y/2))||
                         ( (ax <= ty+tilesize_Y/2)));                break;          
        case 45: col = ( ( ((tx >= ty-tilesize_Y/2)))||
                         ( (tx >= ay-tilesize_Y/2))||
                         ( (ax >= ty-tilesize_Y/2)));                break;
        case 55: col = ( ( ((tilesize_X-1)-tx <=  tilesize_Y/2+ty))    ||
                         ( ((tilesize_X-1)-tx <=  tilesize_Y/2+ay))    ||
                         ( ((tilesize_X-1)-ax <=  tilesize_Y/2+ty)));    break;             
          
        // small circle tile, tilesize_X/2 for radius
        case 46: col = ( (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y/2) ||                
                         (ay >= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y/2) &&
                         (ay <= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y/2) ||                
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X/2, 2)) + tilesize_Y/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X/2, 2)) + tilesize_Y/2) ); break;
    
        // smaller circle tile, tilesize_X/4 for radius            
        case 47: col = ( (ty >= tilesize_Y/4 && ty <= tilesize_Y*3/4 && tx >= tilesize_X/4 && tx <= tilesize_X*3/4) &&
                         (ty >= -1*sqrt(power(tilesize_X/4, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y/2) &&
                         (ty <= sqrt(power(tilesize_X/4, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y/2) ||
                         (ay >= tilesize_Y/4 && ay <= tilesize_Y*3/4 && tx >= tilesize_X/4 && tx <= tilesize_X*3/4) &&
                         (ay >= -1*sqrt(power(tilesize_X/4, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y/2) &&
                         (ay <= sqrt(power(tilesize_X/4, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y/2) ||
                         (ty >= tilesize_Y/4 && ty <= tilesize_Y*3/4 && ax >= tilesize_X/4 && ax <= tilesize_X*3/4) &&
                         (ty >= -1*sqrt(power(tilesize_X/4, 2) - power(ax - tilesize_X/2, 2)) + tilesize_Y/2) &&
                         (ty <= sqrt(power(tilesize_X/4, 2) - power(ax - tilesize_X/2, 2)) + tilesize_Y/2)  ); break;
    
        // half circle horizontal tiles, tilesize_X/2 for radius      
        case 48: col = ( (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y) ||                
                         (ay >= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X/2, 2)) + tilesize_Y) ||                
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X/2, 2)) + tilesize_Y)  ); break;
        case 58: col = ( (ty <= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X/2, 2)))  ||                
                         (ay <= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X/2, 2)))  ||                
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X/2, 2)))  ); break;
    
        // half circle vertical tiles, tilesize_X/2 for radius
        case 56: col = ( (tx>=tilesize_X/2)&&
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2)) + tilesize_Y/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2)) + tilesize_Y/2) ||
                         (tx>=tilesize_X/2)&&
                         (ay >= -1*sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2)) + tilesize_Y/2) &&
                         (ay <= sqrt(power(tilesize_X/2, 2) - power(tx - tilesize_X, 2)) + tilesize_Y/2) ||    
                         (ax>=tilesize_X/2)&&
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X, 2)) + tilesize_Y/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(ax - tilesize_X, 2)) + tilesize_Y/2) ); break;
        case 57: col = ( (tx<=tilesize_X/2)&&
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(tx, 2)) + tilesize_Y/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(tx, 2)) + tilesize_Y/2) ||
                         (tx<=tilesize_X/2)&&
                         (ay >= -1*sqrt(power(tilesize_X/2, 2) - power(tx, 2)) + tilesize_Y/2) &&
                         (ay <= sqrt(power(tilesize_X/2, 2) - power(tx, 2)) + tilesize_Y/2) ||
                         (ax<=tilesize_X/2)&&
                         (ty >= -1*sqrt(power(tilesize_X/2, 2) - power(ax, 2)) + tilesize_Y/2) &&
                         (ty <= sqrt(power(tilesize_X/2, 2) - power(ax, 2)) + tilesize_Y/2) ); break;
     
    }
    return col;
    
     
    Last edited: Sep 21, 2018
    atmobeat and Rukola like this.
  21. atmobeat

    atmobeat Member

    Joined:
    Feb 24, 2017
    Posts:
    45
    The grid + object combined collisions is what I'd be interested in seeing. Everyone does tutorials on tile/grid collision but they ignore the fact that you still have to check for collision with objects too, and there are tuts on different kinds of object collision systems but they ignore tiles. Ideally, you want both for the performance of tile collision and the fact that object collision is inevitable for some things. Combining the two is not trivial, however, as I'm finding right now.
     

Share This Page

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