extends Node2D #TODO add player at a tile coordinate from tile array. #TODO player movement #TODO player actions (movement and attack) #TODO highlight possible tiles for actions #TODO collisions and pathfinding #TODO add "physiks" and "destroy" feature of tiles #TODO add enemy with some kind of AI #TODO refractor code to be more modular #TODO get nice tileset and create first normal level # camera stuff @onready var camera = $Camera2D var drag_start = Vector2() var drag_active = false var zoom_min = 0.1 var zoom_max = 4.0 var zoom_speed = 0.1 var initial_camera_position = Vector2(0, 0) var initial_camera_zoom = Vector2(1, 1) var INITIAL_GRID_SIZE_WIDTH = 50 # play area size x var INITIAL_GRID_SIZE_LENGTH = 30 # play area size y var INITIAL_GRID_SIZE_HEIGHT = 30 # play area size z var GRID_SIZE_WIDTH = INITIAL_GRID_SIZE_WIDTH # play area size x var GRID_SIZE_LENGTH = INITIAL_GRID_SIZE_LENGTH # play area size y var GRID_SIZE_HEIGHT = INITIAL_GRID_SIZE_HEIGHT # play area size z var TILE_SIZE = 32 # in px var TILE_SIZE_ISOMETRIC_X = 32 # in px var TILE_SIZE_ISOMETRIC_Y = 16 # in px var ROTATION = 0 const MAIN_SOURCE_ID = 0 const BLUE_ISOMETRICTILE_ATLAS_POSITION = Vector2i(0,0) const RED_ISOMETRICTILE_ATLAS_POSITION = Vector2i(1,0) const GREEN_ISOMETRICTILE_ATLAS_POSITION = Vector2i(2,0) const WHITE_ISOMETRICTILE_ATLAS_POSITION = Vector2i(3,0) const BLACK_ISOMETRICTILE_ATLAS_POSITION = Vector2i(4,0) const PURPLE_ISOMETRICTILE_ATLAS_POSITION = Vector2i(5,0) const ORANGE_ISOMETRICTILE_ATLAS_POSITION = Vector2i(6,0) const OFFSET = 0 var MAX_ARRAY_SIZE = 0 func _set_max_array_size(): MAX_ARRAY_SIZE = 0 if MAX_ARRAY_SIZE < GRID_SIZE_WIDTH: MAX_ARRAY_SIZE = GRID_SIZE_WIDTH if MAX_ARRAY_SIZE < GRID_SIZE_LENGTH: MAX_ARRAY_SIZE = GRID_SIZE_LENGTH if MAX_ARRAY_SIZE < GRID_SIZE_HEIGHT: MAX_ARRAY_SIZE = GRID_SIZE_HEIGHT func create_debug_map_array(): var _debug_map = [] _set_max_array_size() for x in MAX_ARRAY_SIZE: var y_array = [] for y in MAX_ARRAY_SIZE: var z_array = [] for z in MAX_ARRAY_SIZE: z_array.append(null) y_array.append(z_array) _debug_map.append(y_array) for z in INITIAL_GRID_SIZE_HEIGHT: for y in INITIAL_GRID_SIZE_LENGTH: for x in INITIAL_GRID_SIZE_WIDTH: var coord_x = x + (-1 * z) + OFFSET var coord_y = y + (-1 * z) - OFFSET var tile_data = { "x": coord_x, "y": coord_y, "z": z, "atlas_base_position": null, "atlas_highlight_position": null, "highlighted": false, "selected": false, "hp": 100, "armour": 0, "destroyable": false, "visibility": false, "unit": null, "unit_type": null, # PC (more options e.g. can move it) or NPC (can only view it) "gravity": null, } _debug_map[x][y][z] = tile_data var tile = _debug_map[0][0][0] for y in INITIAL_GRID_SIZE_LENGTH: for x in INITIAL_GRID_SIZE_WIDTH: tile = _debug_map[x][y][0] tile["atlas_base_position"] = BLUE_ISOMETRICTILE_ATLAS_POSITION tile["atlas_highlight_position"] = WHITE_ISOMETRICTILE_ATLAS_POSITION tile["visibility"] = true for i in range(1,10): tile = _debug_map[5][5][i] tile["atlas_base_position"] = RED_ISOMETRICTILE_ATLAS_POSITION tile["atlas_highlight_position"] = WHITE_ISOMETRICTILE_ATLAS_POSITION tile["visibility"] = true # for y in INITIAL_GRID_SIZE_LENGTH: # for x in INITIAL_GRID_SIZE_WIDTH: # tile = _debug_map[x][y][INITIAL_GRID_SIZE_HEIGHT-1] # tile["atlas_position"] = BLUE_ISOMETRICTILE_ATLAS_POSITION # tile["visibility"] = true # for z in range(1,INITIAL_GRID_SIZE_HEIGHT-1): # for x in INITIAL_GRID_SIZE_WIDTH: # tile = _debug_map[x][0][z] # tile["atlas_position"] = GREEN_ISOMETRICTILE_ATLAS_POSITION # tile["visibility"] = true # for z in range(1,INITIAL_GRID_SIZE_HEIGHT-1): # for x in INITIAL_GRID_SIZE_WIDTH: # tile = _debug_map[x][INITIAL_GRID_SIZE_LENGTH-1][z] # tile["atlas_position"] = GREEN_ISOMETRICTILE_ATLAS_POSITION # tile["visibility"] = true # for z in range(1,INITIAL_GRID_SIZE_HEIGHT-1): # for y in range(1,INITIAL_GRID_SIZE_LENGTH-1): # tile = _debug_map[0][y][z] # tile["atlas_position"] = BLACK_ISOMETRICTILE_ATLAS_POSITION # tile["visibility"] = true # for z in range(1,INITIAL_GRID_SIZE_HEIGHT-1): # for y in range(1,INITIAL_GRID_SIZE_LENGTH-1): # tile = _debug_map[INITIAL_GRID_SIZE_WIDTH-1][y][z] # tile["atlas_position"] = RED_ISOMETRICTILE_ATLAS_POSITION # tile["visibility"] = true GRID_SIZE_WIDTH = INITIAL_GRID_SIZE_WIDTH # play area size x GRID_SIZE_LENGTH = INITIAL_GRID_SIZE_LENGTH # play area size y GRID_SIZE_HEIGHT = INITIAL_GRID_SIZE_HEIGHT # play area size z return _debug_map func rotate_map_around_x_axis(rotation_steps = 1): rotation_steps = rotation_steps % 4 if rotation_steps == 0: return var temp_map = [] # Initialize temp_map with same structure _set_max_array_size() for x in MAX_ARRAY_SIZE: var y_array = [] for y in MAX_ARRAY_SIZE: var z_array = [] for z in MAX_ARRAY_SIZE: z_array.append(null) y_array.append(z_array) temp_map.append(y_array) # Create a new map with adjusted dimensions var new_height = int(GRID_SIZE_LENGTH) if rotation_steps % 2 == 1 else int(GRID_SIZE_HEIGHT) var new_length = int(GRID_SIZE_HEIGHT) if rotation_steps % 2 == 1 else int(GRID_SIZE_LENGTH) # Store the original dimensions var original_height = GRID_SIZE_HEIGHT var original_length = GRID_SIZE_LENGTH # Temporarily adjust grid dimensions for coordinate calculation GRID_SIZE_HEIGHT = new_height GRID_SIZE_LENGTH = new_length for x in GRID_SIZE_WIDTH: for y in original_length: for z in original_height: if debug_map[x][y][z] == null: continue var new_y = 0 var new_z = 0 match rotation_steps: 1: # 90 degrees new_y = z new_z = original_length - 1 - y 2: # 180 degrees new_y = original_length - 1 - y new_z = original_height - 1 - z 3: # 270 degrees new_y = original_height - 1 - z new_z = y if new_y >= 0 and new_y < new_length and new_z >= 0 and new_z < new_height: temp_map[x][new_y][new_z] = debug_map[x][y][z].duplicate() var new_coord_x = x + (-1 * new_z) + OFFSET var new_coord_y = new_y + (-1 * new_z) - OFFSET temp_map[x][new_y][new_z]["x"] = new_coord_x temp_map[x][new_y][new_z]["y"] = new_coord_y temp_map[x][new_y][new_z]["z"] = new_z # Update the grid dimensions GRID_SIZE_HEIGHT = new_height GRID_SIZE_LENGTH = new_length debug_map = temp_map func rotate_map_around_y_axis(rotation_steps = 1): rotation_steps = rotation_steps % 4 if rotation_steps == 0: return var temp_map = [] # Initialize temp_map with same structure _set_max_array_size() for x in MAX_ARRAY_SIZE: var y_array = [] for y in MAX_ARRAY_SIZE: var z_array = [] for z in MAX_ARRAY_SIZE: z_array.append(null) y_array.append(z_array) temp_map.append(y_array) # Create a new map with adjusted dimensions var new_width = int(GRID_SIZE_HEIGHT) if rotation_steps % 2 == 1 else int(GRID_SIZE_WIDTH) var new_height = int(GRID_SIZE_WIDTH) if rotation_steps % 2 == 1 else int(GRID_SIZE_HEIGHT) # Store the original dimensions var original_width = GRID_SIZE_WIDTH var original_height = GRID_SIZE_HEIGHT # Temporarily adjust grid dimensions for coordinate calculation GRID_SIZE_WIDTH = new_width GRID_SIZE_HEIGHT = new_height for x in original_width: for y in GRID_SIZE_LENGTH: for z in original_height: if debug_map[x][y][z] == null: continue var new_x = 0 var new_z = 0 match rotation_steps: 1: # 90 degrees clockwise around Y axis new_x = original_height - 1 - z new_z = x 2: # 180 degrees new_x = original_width - 1 - x new_z = original_height - 1 - z 3: # 270 degrees new_x = z new_z = original_width - 1 - x if new_x >= 0 and new_x < new_width and new_z >= 0 and new_z < new_height: temp_map[new_x][y][new_z] = debug_map[x][y][z].duplicate() var new_coord_x = new_x + (-1 * new_z) + OFFSET var new_coord_y = y + (-1 * new_z) - OFFSET temp_map[new_x][y][new_z]["x"] = new_coord_x temp_map[new_x][y][new_z]["y"] = new_coord_y temp_map[new_x][y][new_z]["z"] = new_z # Update the grid dimensions GRID_SIZE_WIDTH = new_width GRID_SIZE_HEIGHT = new_height debug_map = temp_map func rotate_map_around_z_axis(rotation_steps = 1): rotation_steps = rotation_steps % 4 if rotation_steps == 0: return var temp_map = [] # Initialize temp_map with same structure _set_max_array_size() for x in MAX_ARRAY_SIZE: var y_array = [] for y in MAX_ARRAY_SIZE: var z_array = [] for z in MAX_ARRAY_SIZE: z_array.append(null) y_array.append(z_array) temp_map.append(y_array) # Create a new map with adjusted dimensions based on rotation var new_width = int(GRID_SIZE_LENGTH) if rotation_steps % 2 == 1 else int(GRID_SIZE_WIDTH) var new_length = int(GRID_SIZE_WIDTH) if rotation_steps % 2 == 1 else int(GRID_SIZE_LENGTH) # Store the original dimensions var original_width = GRID_SIZE_WIDTH var original_length = GRID_SIZE_LENGTH # Temporarily adjust grid dimensions for coordinate calculation GRID_SIZE_WIDTH = new_width GRID_SIZE_LENGTH = new_length for x in original_width: for y in original_length: for z in GRID_SIZE_HEIGHT: if debug_map[x][y][z] == null: continue var new_x = 0 var new_y = 0 match rotation_steps: 1: # 90 degrees new_x = y new_y = original_width - 1 - x 2: # 180 degrees new_x = original_width - 1 - x new_y = original_length - 1 - y 3: # 270 degrees new_x = original_length - 1 - y new_y = x if new_x >= 0 and new_x < new_width and new_y >= 0 and new_y < new_length: temp_map[new_x][new_y][z] = debug_map[x][y][z].duplicate() var new_coord_x = new_x + (-1 * z) + OFFSET var new_coord_y = new_y + (-1 * z) - OFFSET temp_map[new_x][new_y][z]["x"] = new_coord_x temp_map[new_x][new_y][z]["y"] = new_coord_y temp_map[new_x][new_y][z]["z"] = z # Update the grid dimensions GRID_SIZE_WIDTH = new_width GRID_SIZE_LENGTH = new_length debug_map = temp_map var isometric_map_layers = [] func initialize_map_layers(): # Clear any existing layers for layer in isometric_map_layers: if is_instance_valid(layer): layer.queue_free() isometric_map_layers.clear() # Create new layers for each height level for z in GRID_SIZE_HEIGHT: var new_layer = TileMapLayer.new() new_layer.set_name("IsometricMapLayer_" + str(z)) new_layer.tile_set = $TileMapLayer.tile_set # Set your tileset new_layer.z_index = z # Set the z-index to match the layer's height add_child(new_layer) isometric_map_layers.append(new_layer) var debug_map = create_debug_map_array() # Draw the map func draw_visible_tiles(): # TODO change it so that tiles behind something is not drawn z+1 at same tile["x"] and tile["y"] # clear all previously drawn tiles on each layer for layer in isometric_map_layers: layer.clear() # draw the map for z in GRID_SIZE_HEIGHT: for y in GRID_SIZE_LENGTH: for x in GRID_SIZE_WIDTH: var tile = debug_map[x][y][z] if tile != null and tile["visibility"]: # Use the layer that corresponds to the tile's z-value if z < isometric_map_layers.size(): isometric_map_layers[z].set_cell( Vector2i(tile["x"], tile["y"]), MAIN_SOURCE_ID, tile["atlas_base_position"] ) var highlighted_tiles = {} # Dictionary to store all currently highlighted tiles func isometric_grid_hover(): var hover_tile_position = isometric_map_layers[0].local_to_map(get_global_mouse_position()) var new_highlighted_tiles = {} # Will store tiles to highlight this frame # First find all tiles that should be highlighted for z in GRID_SIZE_HEIGHT: for x in GRID_SIZE_WIDTH: for y in GRID_SIZE_LENGTH: var tile = debug_map[x][y][z] if tile != null and tile["visibility"]: if tile["x"] == hover_tile_position.x and tile["y"] == hover_tile_position.y: # TODO: change highlighing so when the mouse is over an drawn tile and not the grid var tile_above = isometric_map_layers[z+1].get_cell_source_id( Vector2i(tile["x"]-1, tile["y"]-1) ) # Store this tile for highlighting var tile_key = str(x) + "_" + str(y) + "_" + str(z) # print(tile_above) # print(tile["x"], " ", tile["y"], " ", z) # print(hover_tile_position) if not tile_above: new_highlighted_tiles[tile_key] = { "x": x, "y": y, "z": z+1 } else: new_highlighted_tiles[tile_key] = { "x": x, "y": y, "z": z } new_highlighted_tiles[tile_key] = { "x": x, "y": y, "z": z } # Highlight new tiles for tile_key in new_highlighted_tiles: var tile_data = new_highlighted_tiles[tile_key] var tile = debug_map[tile_data.x][tile_data.y][tile_data.z] if not tile["selected"]: isometric_map_layers[tile_data.z].set_cell( Vector2i(tile["x"], tile["y"]), MAIN_SOURCE_ID, tile["atlas_highlight_position"] ) else: isometric_map_layers[tile_data.z].set_cell( Vector2i(tile["x"], tile["y"]), 1, Vector2i(3,1) # ) tile["highlighted"] = true # De-highlight tiles that are no longer under the cursor for tile_key in highlighted_tiles: if not (tile_key in new_highlighted_tiles): var tile_data = highlighted_tiles[tile_key] var tile = debug_map[tile_data.x][tile_data.y][tile_data.z] if tile != null and tile["visibility"]: if not tile["selected"]: isometric_map_layers[tile_data.z].set_cell( Vector2i(tile["x"], tile["y"]), MAIN_SOURCE_ID, tile["atlas_base_position"] # Original appearance ) else: isometric_map_layers[tile_data.z].set_cell( Vector2i(tile["x"], tile["y"]), 1, Vector2i(0,1) # ) tile["highlighted"] = false # Update the highlighted tiles list highlighted_tiles = new_highlighted_tiles func _ready() -> void: # Store initial camera position initial_camera_position = camera.position initial_camera_zoom = camera.zoom initialize_map_layers() draw_visible_tiles() func _process(_delta: float) -> void: pass # isometric_grid_hover() func _input(event): if event is InputEventMouseMotion: isometric_grid_hover() # Camera drag (pan) control if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_RIGHT: if event.pressed: drag_start = event.position drag_active = true else: drag_active = false # Zoom control with mouse wheel if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: zoom_camera(-zoom_speed) elif event.button_index == MOUSE_BUTTON_WHEEL_UP: zoom_camera(zoom_speed) # Handle camera movement while dragging if event is InputEventMouseMotion and drag_active: camera.position -= event.relative / camera.zoom func _on_reset_button_pressed() -> void: # Reset camera position and zoom debug_map = create_debug_map_array() initialize_map_layers() draw_visible_tiles() camera.position = initial_camera_position camera.zoom = initial_camera_zoom func zoom_camera(zoom_amount): var new_zoom = camera.zoom.x + zoom_amount new_zoom = clamp(new_zoom, zoom_min, zoom_max) camera.zoom = Vector2(new_zoom, new_zoom) func _on_cw_pressed() -> void: _reset_class_values() rotate_map_around_z_axis(3) initialize_map_layers() draw_visible_tiles() func _on_ccw_pressed() -> void: _reset_class_values() rotate_map_around_z_axis(1) initialize_map_layers() draw_visible_tiles() func _on_x_cw_pressed() -> void: _reset_class_values() rotate_map_around_x_axis(1) initialize_map_layers() draw_visible_tiles() func _on_x_ccw_pressed() -> void: _reset_class_values() rotate_map_around_x_axis(3) initialize_map_layers() draw_visible_tiles() func _on_y_cw_pressed() -> void: _reset_class_values() rotate_map_around_y_axis(3) initialize_map_layers() draw_visible_tiles() func _on_y_ccw_pressed() -> void: _reset_class_values() rotate_map_around_y_axis(1) initialize_map_layers() draw_visible_tiles() func _reset_class_values(): highlighted_tiles = {} last_click_time = 0.0 clicked_position = Vector2i(1, 1) is_tile_selected = false var click_delay: float = 0.2 var last_click_time: float = 0.0 var clicked_position: Vector2i = Vector2i(1, 1) var is_tile_selected: bool = false func tile_selction_logic(): for z in GRID_SIZE_HEIGHT: for x in GRID_SIZE_WIDTH: for y in GRID_SIZE_LENGTH: var tile = debug_map[x][y][z] if tile["selected"] and tile["highlighted"]: isometric_map_layers[z].set_cell( Vector2i(tile["x"], tile["y"]), 1, Vector2i(0,1) # ) tile["selected"] = false if tile["selected"] and not tile["highlighted"]: isometric_map_layers[z].set_cell( Vector2i(tile["x"], tile["y"]), 0, Vector2i(0,0) # ) tile["selected"] = false if not tile["selected"] and tile["highlighted"]: isometric_map_layers[z].set_cell( Vector2i(tile["x"], tile["y"]), 1, Vector2i(3,1) # ) tile["selected"] = true func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: var current_time = Time.get_ticks_msec() / 1000.0 if current_time - last_click_time >= click_delay: last_click_time = current_time tile_selction_logic() elif event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE: pass #deselect_tile() var player_node func set_player(p): player_node = p func init_player(): var tile = debug_map[10][10][1] tile["visibility"] = true tile["unit"] = player_node tile["unit_type"] ="PC" var init_player_pos = iso_to_world(tile["x"], tile["y"], tile["z"]) tile["unit"].set_unit_position(init_player_pos.x, init_player_pos.y, tile["z"]) func _move_up_or_down(up_or_down = "up"): var z_offset = 0 if up_or_down == "up": z_offset = 1 elif up_or_down == "down": z_offset = -1 var new_z = 0 var new_y = 0 var new_x = 0 var tile = debug_map[0][0][0] for z in GRID_SIZE_HEIGHT: for x in GRID_SIZE_WIDTH: for y in GRID_SIZE_LENGTH: tile = debug_map[x][y][z] if tile["unit_type"] == "PC": new_z = z + z_offset new_y = y new_x = x break if tile["unit_type"] == "PC": break if tile["unit_type"] == "PC": break print("x ", new_x, " y ", new_y, " z ", new_z) tile["unit"] = null tile["unit_type"] = null tile["visibility"] = null var new_player_tile = debug_map[new_x][new_y][new_z] new_player_tile["unit"] = player_node new_player_tile["visibility"] = true new_player_tile["unit_type"] ="PC" var new_player_pos = iso_to_world(tile["x"], tile["y"], tile["z"]) new_player_tile["unit"].set_unit_position(new_player_pos.x, new_player_pos.y, tile["z"]) print(new_player_tile["x"], new_player_tile["y"], new_player_tile["z"]) func iso_to_world(tile_x, tile_y, player_z_layer = 0): # Get the tile size from your tilemap var tile_width = isometric_map_layers[player_z_layer].tile_set.tile_size.x var tile_height = isometric_map_layers[player_z_layer].tile_set.tile_size.y # Calculate the world position var world_x = (tile_x - tile_y) * (tile_width / 2) var world_y = (tile_x + tile_y) * (tile_height / 2) - (player_z_layer * tile_height / 2) return Vector2(world_x, world_y) # Converts world position to isometric tile coordinates func world_to_iso(world_pos, player_z_layer = 0): # Get the tile size from your tilemap var tile_width = isometric_map_layers[player_z_layer].tile_set.tile_size.x var tile_height = isometric_map_layers[player_z_layer].tile_set.tile_size.y # Adjust y position based on z-layer var adjusted_y = world_pos.y + (player_z_layer * tile_height / 2) # Calculate the tile coordinates var tile_x = (world_pos.x / (tile_width / 2) + adjusted_y / (tile_height / 2)) / 2 var tile_y = (adjusted_y / (tile_height / 2) - world_pos.x / (tile_width / 2)) / 2 return Vector2i(round(tile_x), round(tile_y)) func _on_player_z_layer_change_pressed() -> void: _move_up_or_down("up") func _on_player_z_layer_change_down_pressed() -> void: _move_up_or_down("down")