Hexagon tiles keyboard/mouse control system

Discussion in 'Programming' started by streng, Jun 10, 2019.

  1. streng

    streng Member

    Joined:
    Jun 10, 2019
    Posts:
    3
    Hi,

    I want to implement controls system on hexagonal tileset. The tileset should be the same as image in attachment. I want to be able to mark each tile by arrow keys or mouse click.

    In mouse click should be easy, if I click for example on tile 5, then tile 5 hexagon is marked (meaning that change color to blue), when I click on tile 17, then tile 5 change color back to white and tile 17 change color to blue etc.

    Same should work if I want to mark tile hexagon by keyboard, but with difference when I’m on tile 1 and press rightkey, should move on tile 2, next rightkey on tile 3 next on the tile 4, but leftkey pressed when I’m on tile 4 move me back to tile 3.

    The hardest part be, when I’m on 1 and press downkey should move to tile 5, another downkey move me to tile 10 upkey to tile 5 etc.

    The question is, how to achieve this in some efficient way?

    If there will be implemented only movement from left to right and right to left, then can be easily done by changing some control variable to + -1, but I don’t know how to do it for Up and down control mechanism.

    Thank you for answers
     

    Attached Files:

  2. jo-thijs

    jo-thijs Member

    Joined:
    Jun 20, 2016
    Posts:
    2,844
    Hi and welcome to the GMC!

    I understand how you assigned indices to the tiles, but it feels somewhat strange to me.
    If I want to move from tile 11 to tile 8 (which are neighbors), then I have to press 3 keys, which will first bring me on the other side of the field before bringing me to the neighboring location.
    Usually a key press always moves you to a neighbouring tile.

    Anyway, to your question: you will first need to decide how you will represent your tilles.
    Are they object instances? Are the tiles (before GM:S 2)? Are they sprites from the asset layer (in GM:S 2)? Are they controlled by a single controller object?
    It is also important to consider the fields you'll be using.
    Are your fields always hexagons? Are they always the same size?

    If you go with the instance per tile approach, you can do the vertical movement through collision checks and horizontal movement through with-structures.
    These are not super efficient, but will probably suffice.
    You could also create a global array of all the tiles and then you just need to calculate the number of the next tile.
    To do the latter for vertical movement, you will need to be able to make some assumptions about the field shape.

    If you go with the controller approach, you can organize the tiles in a grid.
    It still helps here if you can make some assumptions about the field shape.
     
  3. streng

    streng Member

    Joined:
    Jun 10, 2019
    Posts:
    3
    I understand your strange feeling, but imagine if you are on the tile 10 and press left arrow, what will be destination in this case? 7 or 12? What is closest neighbor?

    I’m using GMS2. Currently objects in game (like main menu) are a simple sprites. I only changing variable index to + -1 when player hit upkey or downkey and officially change sprite graphic on this event and are controlled by one control object. This method seems to be ineffective to achieve same with hexagonal. I have open mind to any other approach, so it can be instances instead of sprites. In other worlds, I like to chose approach which will be easier to write/understand
     
  4. jo-thijs

    jo-thijs Member

    Joined:
    Jun 20, 2016
    Posts:
    2,844
    I don't think you'll run into performance issues here, so I would suggest this (assuming all tiles are placed in the room editor and none are removed or added in run-time):
    Create event of tile objects:
    Code:
    var hFirstTile = noone;
    var hLastTile = noone;
    var vFirstTile = noone;
    var vLastTile = noone;
    hNextTile = noone;
    hPrevTile = noone;
    vNextTile = noone;
    vPrevTile = noone;
    
    with object_index {
        if hFirstTile == noone || y < hFirstTile.y || (y == hFirstTile.y && x < hFirstTile.x) {
            hFirstTile = id;
        }
        if hLastTile == noone || y > hLastTile.y || (y == hLastTile.y && x > hlastTile.x) {
            hLastTile = id;
        }
        if vFirstTile == noone || x < vFirstTile.x || (x == vFirstTile.x && y < vFirstTile.y) {
            vFirstTile = id;
        }
        if vLastTile == noone || x > vLastTile.x || (x == vLastTile.x && y > vlastTile.y) {
            hLastTile = id;
        }
        if (y > other.y || (y == other.y && x > other.x))
        && (hNextTile == noone || y < hNextTile.y || (y == hNextTile.y && x < hNextTile.x)) {
            hNextTile = id;
        }
        if (y < other.y || (y == other.y && x < other.x))
        && (hPrevTile == noone || y > hPrevTile.y || (y == hPrevTile.y && x > hPrevTile.x)) {
            hPrevTile = id;
        }
        if (x > other.x || (x == other.x && y > other.y))
        && (vNextTile == noone || x < vNextTile.x || (x == vNextTile.x && y < vNextTile.y)) {
            vNextTile = id;
        }
        if (x < other.x || (x == other.x && y < other.y))
        && (vPrevTile == noone || x > vPrevTile.x || (x == vPrevTile.x && y > vPrevTile.y)) {
            vPrevTile = id;
        }
    }
    
    if hNextTile == noone {
        hNextTile = hFirstTile;
    }
    if hPrevTile == noone {
        hPrevTile = hLastTile;
    }
    if vNextTile == noone {
        vNextTile = vFirstTile;
    }
    if vPrevTile == noone {
        vPrevTile = vLastTile;
    }
    step event of tile object:
    Code:
    if prevSprite == sprSelected {
        if keyboard_check(vk_right) {
            sprite_index = sprUnselected;
            hNextTile.sprite_index = sprSelected;
        } else if keyboard_check(vk_left) {
            sprite_index = sprUnselected;
            hPrevTile.sprite_index = sprSelected;
        } else if keyboard_check(vk_down) {
            sprite_index = sprUnselected;
            vNextTile.sprite_index = sprSelected;
        } else if keyboard_check(vk_up) {
            sprite_index = sprUnselected;
            vPrevTile.sprite_index = sprSelected;
        }
    }
    Begin step event of tile object:
    Code:
    prevSprite = sprite_index;
    If you do need to remove or add tiles at run-time, you can use this as create event instead:
    Code:
    hNextTile = id;
    hPrevTile = id;
    vNextTile = id;
    vPrevTile = id;
    
    if global.hFirstTile == noone {
        global.hFirstTile = id;
        global.vFirstTile = id;
    } else {
        var cTile;
        
        // Horizontally
        cTile = global.hFirstTile;
        if y < cTile.y || (y == cTile.y && x < cTile.x) {
            global.hFirstTile = id;
        } else {
            do {
                cTile = cTile.hNextTile;
            until cTile == global.hFirstTile || (y < cTile.y || (y == cTile.y && x < cTile.x));
        }
        
        hNextTile = cTile;
        hPrevTile = cTile.hPrevTile;
        hPrevTile.hNextTile = id;
        cTile.hPrevTile = id;
        
        // Vertically
        cTile = global.vFirstTile;
        if x < cTile.x || (x == cTile.x && y < cTile.y) {
            global.vFirstTile = id;
        } else {
            do {
                cTile = cTile.vNextTile;
            until cTile == global.vFirstTile || (x < cTile.x || (x == cTile.x && y < cTile.y));
        }
        
        vNextTile = cTile;
        vPrevTile = cTile.vPrevTile;
        vPrevTile.vNextTile = id;
        vTile.vPrevTile = id;
    }
    
    sprSelected = ...;
    sprUnselected = ...;
    
    sprite_index = id == hFirstTile ? sprSelected : sprUnselected;
    prevSprite = sprite_index;
    
    and add this to the destroy event:
    Code:
    hNextTile.hPrevTile = hPrevTile;
    hPrevTile.hNextTile = hNextTile;
    vNextTile.vPrevTile = vPrevTile;
    vPrevTile.vNextTile = vNextTile;
    
    if global.hFirstTile == id {
        global.hFirstTile = hNextTile == id ? noone : hNextTile;
    }
    
    if global.vFirstTile == id {
        global.vFirstTile = vNextTile == id ? noone : vNextTile;
    }
    You'll have to initialize global.hFirstTile and global.vFirstTile to noone before creating the tiles (e.g. before entering the room) with this approach.
     
  5. streng

    streng Member

    Joined:
    Jun 10, 2019
    Posts:
    3
    Thank you, i will try it. Also have additional question, what represent "id" in your code?
     
  6. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    1,757
    ID is a built-in variable that always returns the id of the instance running the code in current context.
     
  7. Rob

    Rob Member

    Joined:
    Jul 12, 2016
    Posts:
    650
    You could always use 6 letters/keys for movement rarer than 4.

    I wouldn't like to move around a hex map with keys if I were limited to 4 - at least at first anyway.
     
  8. woods

    woods Member

    Joined:
    Jun 21, 2016
    Posts:
    182
    i prefer using a rotation and forward/back when selecting on a hex grid.. like A/D rotate by 60^ and W/S moves forward and back one cell... but .. there are many many ways to handle it ;o)
     

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