xyz So, I messed around with what you suggested with your most recent code. I got it working very simply, where it would return a dictionary of all the top most cells, so then I sought to tweak it and make it return the top most cells as long as they didn't have a block directly on top of them.

Example a tower of 4 blocks, that was every other cell would return a dictionary of 4 coords at the same xz. However, it would be accessed by the xz coords and then they would be sorted by another dictionary holding their elevations.

Here is the code, it is quite rough, and I am sure not up to snuff.

extends GridMap

var map_cells: Dictionary

func _ready() -> void:
	map_cells = _get_top_cells()

func _get_top_cells() -> Dictionary:
	var top_cells: Dictionary
	for cells in get_used_cells():
		var cell_coords: = Vector2i(cells.x,cells.z)
		if not top_cells.has(cell_coords):
			top_cells[cell_coords] = []
		top_cells[cell_coords].push_back(cells)
	for x in top_cells.values():
		x.sort_custom(func(a,b): return a.y > b.y)
	return _get_moveable_cells(top_cells.values())

func _get_moveable_cells(stack: Array) -> Dictionary:
	var new_stack: Dictionary
	for grp in stack:
		var c: Vector3i = grp[0]
		if grp.size() == 1:
			new_stack[Vector2i(c.x,c.z)] = {c.y:c}
			continue
		if grp.size() == 2:
			if abs(c.y - grp[1].y) >= 2:
				new_stack[Vector2i(c.x,c.z)] = {c.y:c,grp[1].y:grp[1]}
				continue
			new_stack[Vector2i(c.x,c.z)] = {c.y:c}
			continue
		if grp.size() > 2:
			new_stack[Vector2i(c.x,c.z)] = _sort_elevation(grp)
	return new_stack

func _sort_elevation(grp: Array) -> Dictionary:
	var sorted: Dictionary
	var under: bool
	for c in grp:
		if grp.find(c) + 1 == grp.size():
			return sorted
		if abs(c.y - grp[grp.find(c) + 1].y) > 1:
			if under:
				under = false
				sorted[grp[grp.find(c) + 1].y] = grp[grp.find(c) + 1]
				continue
			sorted.merge({c.y:c,grp[grp.find(c) + 1].y:grp[grp.find(c) + 1]})
			print(sorted)
			continue
		if sorted.get(c.y + 1):
			continue
		sorted[c.y] = c
		under = true
	return sorted

I am also going to do some research on how to better search/compare elements in an array, maybe I should more of pop elements out of a stack or something of the sort to search elements.

Edit: I wonder if I could do something like this with slice as well? It isn't finished but I need to go to bed.

func _sort_elevation(grp: Array) -> Dictionary:
	var sorted_dict: Dictionary
	var sorted: Array
	var first:bool = true
	for x in grp.size() - 1:
		var slice = grp.slice(0,2)
		if first:
			if abs(slice.front().y - slice.back().y) > 1:
				sorted.append(slice.back())
			sorted.append(grp.pop_front())
			first = false
			continue
		if abs(slice.front().y - slice.back().y) > 1:
			sorted.append(slice.back())
		grp.pop_front()
	return {}
  • xyz replied to this.
    • Edited

    xRegnarokx Again, way too much code. You could have just used my function that gets stacks. Since stacks are sorted by y, simply check if two last y coords in a stack are not adjacent or there's only one cell in a stack. If either is the case, you have a top cell with nothing underneath it:

    func _get_floating_top_cells() -> Array:
    	var stacks = _get_stacks().values()
    	stacks = stacks.filter( func(s): return s.size() == 1 or abs(s[-1].y - s[-2].y) > 1 )
    	return stacks.map( func(s): return s[-1] )
    • Edited

    xRegnarokx Oh, just realized you wanted all occupied cells that have space above them. Well that's even simpler. Iterate through all used cells and just check if the cell above is occupied:

    func _get_platform_cells() -> Array:
    	var platform_cells = []
    	for c in get_used_cells():
    		if get_cell_item(Vector3i(c.x, c.y + 1, c.z)) == -1:
    			platform_cells.push_back(c)
    	return platform_cells

    Those could also easily be grouped in a dictionary with xz as a key. It's 2 additional lines of code.

    EDIT: Why do all of this though? In a 2.5D game like this there should be no holes in stacks. So simply throw an exception if a hole is found. And display a message that the map is not designed according to rules 🙂

      xyz Ahh that is right, you could just check if the cell above returns -1 and this is empty... duh...

      Well as far as why to do this, in my game eventually I want to have bridges and such or platforms that are above that you can walk under (like there are in Tibia). In 2D there are no actual gaps it's just rendering order.

      However, for 3D in 2.5D I assumed this was the best way to do that.

      • xyz replied to this.
        • Edited

        xRegnarokx You're again trying to make a complex system before you made a simple system.

        • Edited

        xRegnarokx Here's a version of demo that can have holes. All tiles are now same sized cubes. It can handle any number of "bridges" one on top of another.
        This is 15 additional lines of code compared to previous version. 60 lines in total in the whole project.

        grid-movement2.zip
        8kB

          xyz Cool, thanks, I'll try and implement just that movement. Then I'll slowly add things to it such as other units moving, and detecting/setting occupation of tiles, ect... Thank you so much!

          Edit: So, using your code I created a simple movement, and am working with it to understand it better. I did tweak it, and am planning on implementing obstacles to test it out, here is my occupied changing that I added.

          func update_occupied(from: Vector2i,to: Vector2i,from_elevation: int, to_elevation: int) -> void:
          	for cf in map_data[from]:
          		if not cf.elevation == from_elevation:
          			continue
          		cf.occupied = false
          	for ct in map_data[to]:
          		if not ct.elevation == to_elevation:
          			continue
          		ct.occupied = true
          • xyz replied to this.
            • Edited

            xRegnarokx Remember that the most important thing is figuring out the best fitting data structure(s) for the problem you want to solve. Doing so can simplify/shorten your code by the order of magnitude. Conversely, choosing inappropriate structure(s) can turn your code into a mess.

            Time to yet again inject that timeless Fred Brooks piece of wisdom:

            xRegnarokx Cool, thanks, I'll try and implement just that movement. Then I'll slowly add things to it such as other units moving, and detecting/setting occupation of tiles, ect... Thank you so much!

            Note that adding other moving units to a system like this is trivial because movement only happens in respect to map data, and is instant. Only the visual representation shows (fake) gradual transition. So when you need to move the unit to some cell, just check if no other unit has its map position at that cell. If that's true, you can safely move it there.

            xRegnarokx For completeness, here's the version with the player and arbitrary number of npcs, all moving and respecting cell occupancy of each other. As I already mentioned, this is trivial to add if you store your map/cell data in a proper structure.

            We're now at 90 lines of code in total.
            gridmap - 20 LOC
            character base class - 50 LOC
            player - 10 LOC
            npc - 10 LOC

            grid-movement3.zip
            9kB

              xyz You definitely make it look easy haha, I have a long way to go to learn how to think through implementation and such.

              • xyz replied to this.
                7 days later

                xyz Thanks for giving me a launching pad, I have been expanding what you suggested to implement things like stairs/slopes that you can mount and normal blocks that you can't. So, rather than any block at a certain elevation, I've made it that there are certain mountable blocks, that you can mount, or descend from (following same 1 elevation difference principle) however, if you aren't on a mountable block, or moving to a mountable block you can't move to a higher or lower elevation.

                I think I'll close this question for the moment since I think you sufficiently answered my original question.

                I'll work on pushing objects and other movement types now, such as dashing to fleshout my movement system.

                • xyz replied to this.
                  • Edited

                  xRegnarokx Pushable objects should be treated in the similar way NPCs are. So again, they'd be relatively easy to implement.