diff --git a/Globals.gd b/Globals.gd index 5f27864..353c07e 100644 --- a/Globals.gd +++ b/Globals.gd @@ -9,17 +9,22 @@ const SHOVEL_DURATION=1 var DebugLabel = null var HexGrid = null var HexCell = null -var OceanGrid = null +var OceanNavGrid = null +var IslandNavGrid = null var hex_size = 128 +var debug_nav = false # Called when the node enters the scene tree for the first time. func _ready(): HexGrid = preload("../thirdparty/gdhexgrid/HexGrid.gd").new() HexGrid.hex_scale = Vector2(hex_size, hex_size) - OceanGrid = preload("../thirdparty/gdhexgrid/HexGrid.gd").new() - OceanGrid.hex_scale = Vector2(hex_size, hex_size) + OceanNavGrid = preload("../thirdparty/gdhexgrid/HexGrid.gd").new() + OceanNavGrid.hex_scale = Vector2(hex_size, hex_size) + + IslandNavGrid = preload("../thirdparty/gdhexgrid/HexGrid.gd").new() + IslandNavGrid.hex_scale = Vector2(hex_size, hex_size) HexCell = preload("../thirdparty/gdhexgrid/HexCell.gd").new() diff --git a/scenes/HexTileDrawer.gd b/scenes/HexTileDrawer.gd index a967d34..598f42d 100644 --- a/scenes/HexTileDrawer.gd +++ b/scenes/HexTileDrawer.gd @@ -23,24 +23,28 @@ func set_hex_scale(scale): HexPoints = PoolVector2Array() - var NoneColors = PoolColorArray() - var SandColors = PoolColorArray() - var GrassColors = PoolColorArray() - for i in range (7): var angle = (60 * i) * PI / 180 HexPoints.append(Vector2(cos(angle), sin(angle)) * hex_scale.x / 2) - NoneColors.append("#885555") - SandColors.append("#ffa106") - GrassColors.append("#4b9635") - + var NoneColors = create_color_array ("#885555") + var SandColors = create_color_array ("#ffa106") + var GrassColors = create_color_array ("#4b9635") + HexColors = { TileType.None: NoneColors, TileType.Sand: SandColors, TileType.Grass: GrassColors } +func create_color_array(color: Color): + var result = PoolColorArray() + for _i in range (7): + result.append(color) + + return result + + func get_tile_color (type_name: String): match type_name: "None": return HexColors[TileType.None] diff --git a/scenes/Island.gd b/scenes/Island.gd index 17fb4a5..cd4ffdd 100644 --- a/scenes/Island.gd +++ b/scenes/Island.gd @@ -8,6 +8,11 @@ var tile_local_coords = [] var rect_local = Rect2() var radius_world = 0.0 var is_active = false +var landing_site_local_coord = null +var landing_site_world = null +var ship_anchor_world = null +var obstacles_local_coords = {} + # Called when the node enters the scene tree for the first time. func _ready(): @@ -57,6 +62,19 @@ func calc_bbox(): center_coord = Globals.WorldToHex(center_world_coord) print ("center coord: " + str(center_coord)) radius_world = max(rect_local.size.x, rect_local.size.y) * 0.5 + + # brute force the bbox tiles + obstacles_local_coords = {} + var dx = Globals.hex_size * 0.5 + var dy = Globals.hex_size * 0.5 + var n_tiles_x = (rect_local.size.x + Globals.hex_size * 2) / dx + var n_tiles_y = (rect_local.size.y + Globals.hex_size * 2) / dy + + for r in range (n_tiles_y): + for c in range (n_tiles_x): + var point_hex_local = Globals.WorldToHexCenter(Vector2(rect_local.position.x+ c * dx, rect_local.position.y + r * dy) - Vector2.ONE * Globals.hex_size * 0.5) + if not obstacles_local_coords.has(point_hex_local) and not tiles.has(point_hex_local): + obstacles_local_coords[point_hex_local] = "#bb44aa" func save_island(path: String): @@ -110,12 +128,21 @@ func check_overlap(other): func get_tile_by_world_coord(world_coord: Vector2): - var center_world = Globals.WorldToHexCenter(world_coord - offset_world) - if center_world in tiles.keys(): - return tiles[center_world] + var local_coord = get_local_coord_by_world_coord(world_coord) + + if local_coord in tiles.keys(): + return tiles[local_coord] return null +func is_point_on_landing_site(world_coord: Vector2): + return (Globals.WorldToHex(world_coord) - Globals.WorldToHex(landing_site_world)).length_squared() < 1 + + +func get_local_coord_by_world_coord(world_coord: Vector2): + return Globals.WorldToHexCenter(world_coord - offset_world) + + func calc_rect_world(): var rect_world = Rect2(rect_local) rect_world.position = transform.origin + rect_world.position + offset_world @@ -130,15 +157,19 @@ func draw_bsphere(): func _draw(): + var transform = get_transform() + for coord in tiles.keys(): draw_set_transform (coord + offset_world, 0, Vector2.ONE) draw_polygon(HexTileDrawer.HexPoints, HexTileDrawer.get_tile_color(tiles[coord])) - var transform = get_transform() draw_set_transform(transform.origin + offset_world, transform.get_rotation(), transform.get_scale()) - if is_active: + if Globals.debug_nav and is_active: draw_rect(rect_local, Color.red, false) - - var default_font = Control.new().get_font("font") - draw_string(default_font, Vector2(0, 0), name + str(" ") + str(offset_world)) + + draw_set_transform_matrix(transform) + for coord in obstacles_local_coords.keys(): + draw_set_transform(coord + offset_world, 0, Vector2.ONE) + draw_polygon(HexTileDrawer.HexPoints, HexTileDrawer.create_color_array("#922")) + diff --git a/scenes/World.gd b/scenes/World.gd index e7042af..2ce44bb 100644 --- a/scenes/World.gd +++ b/scenes/World.gd @@ -26,7 +26,11 @@ var drag_start = null var target = Vector2() var tile_data = {} var current_island = null +var anchor_tile = null +var landing_tile = null var player_navigation_path = [] +var player_path_plan_start = null +var player_path_plan_end = null var hex_line_path = [] @@ -45,7 +49,6 @@ func _ready(): func _process(_delta): WorldCamera.offset = PlayerChar.position - PlayerBoat.transform.origin = PlayerChar.transform.origin if len(player_navigation_path) > 1: var player_coord = Globals.WorldToHexCenter(PlayerChar.transform.origin) @@ -56,10 +59,11 @@ func _process(_delta): PlayerChar.target = player_navigation_path[0] update_current_island() + if current_island: - PlayerBoat.visible = false + PlayerBoat.transform.origin = Globals.HexToWorld(anchor_tile) else: - PlayerBoat.visible = true + PlayerBoat.transform.origin = PlayerChar.transform.origin func draw_hex_path (path: Array, color: Color): @@ -75,9 +79,13 @@ func draw_hex_path (path: Array, color: Color): func _draw(): - draw_hex_path (hex_line_path, "#00f2f2") - draw_hex_path (player_navigation_path, "#f200f2") + if Globals.debug_nav: + draw_hex_path (hex_line_path, "#00f2f2") + draw_hex_path (player_navigation_path, "#f200f2") + if player_path_plan_start != null and player_path_plan_end != null: + draw_circle (player_path_plan_start, 12, "#2288f2") + draw_circle (player_path_plan_end, 12, "#2288f2") # # World Modification/Query @@ -155,28 +163,36 @@ func generate(): else: print ("Placed after " + str(overlap_retry_num) + " retries.") Islands.add_child(island) - - populate_ocean_grid() - -# var island = Islands.get_children()[0] -# var player_pos = island.center_world_coord + island.offset_world -# PlayerChar.position = player_pos -# PlayerChar.target = player_pos -# PlayerChar.update() -# print (player_pos) + + populate_ocean_nav_grid() + # # Navigation # -func populate_ocean_grid(): - var obstacles = Globals.OceanGrid.get_obstacles() - Globals.OceanGrid.remove_obstacles(obstacles.keys()) - Globals.OceanGrid.set_bounds(Vector2.ONE * -500, Vector2.ONE * 500) +func populate_ocean_nav_grid(): + var obstacles = Globals.OceanNavGrid.get_obstacles() + Globals.OceanNavGrid.remove_obstacles(obstacles.keys()) + Globals.OceanNavGrid.set_bounds(Vector2.ONE * -500, Vector2.ONE * 500) for island in Islands.get_children(): for tile in island.tiles.keys(): var grid_coords = Globals.WorldToHex(tile + island.offset_world) - Globals.OceanGrid.add_obstacles(grid_coords, 15) + Globals.OceanNavGrid.add_obstacles(grid_coords, 0) + + +func populate_island_nav_grid(): + var obstacles = Globals.IslandNavGrid.get_obstacles() + Globals.IslandNavGrid.remove_obstacles(obstacles.keys()) + Globals.IslandNavGrid.set_bounds(Vector2.ONE * -500, Vector2.ONE * 500) + + if current_island == null: + print ("Error: cannot populate island nav grid: no island") + return + + for tile in current_island.obstacles_local_coords.keys(): + var grid_coords = Globals.WorldToHex(tile + current_island.offset_world) + Globals.IslandNavGrid.add_obstacles(grid_coords, 0) func update_current_island(): @@ -191,36 +207,82 @@ func update_current_island(): if last_current_island != current_island: if last_current_island != null: - last_current_island.is_active = false - last_current_island.update() + on_leave_island(last_current_island) + if current_island != null: - current_island.is_active = true - current_island.update() + on_enter_island(current_island) -func update_player_navigation_target(target_world: Vector2): - player_navigation_path = [] +func on_enter_island(island): + print ("Entering island") + current_island = island + anchor_tile = PlayerChar.prev_tile + landing_tile = PlayerChar.cur_tile + island.landing_site_world = Globals.WorldToHexCenter(PlayerChar.position) + island.landing_site_local_coord = island.get_local_coord_by_world_coord(PlayerChar.position) + island.is_active = true + populate_island_nav_grid() + island.update() + + +func on_leave_island(island): + print ("Leaving island") + island.is_active = false + island.landing_site_local_coord = null + island.update() + + +func check_player_near_anchor(): + if current_island == null: + return false - var start_world = PlayerChar.transform.origin + var anchor_world = Globals.HexToWorld(anchor_tile) + return (PlayerChar.position - anchor_world).length() < Globals.hex_size + +func update_navigation_target_ocean(start_world: Vector2, target_world: Vector2): var start_coord = Globals.WorldToHex(start_world) var goal_coord = Globals.WorldToHex(target_world) + var island_landing_site_world = null + player_path_plan_start = null + player_path_plan_end = null + + var direct_path = Globals.WorldLineToHexTiles(start_world, target_world) + + if get_tile_type(start_world) != null and current_island != null and len(direct_path) > 1: + print ("type 0: ", get_tile_type(direct_path[0])) + print ("type 1: ", get_tile_type(direct_path[1])) + print ("on landing site: ", current_island.is_point_on_landing_site (start_world)) + if current_island.is_point_on_landing_site (start_world): + if get_tile_type(direct_path[1]) == null: + print ("starting from landing site") + direct_path.pop_front() + start_coord = Globals.WorldToHex(direct_path.front()) + else: + print ("Invalid start") + return + + # In case target is on an island we find both a landing site and + # the first point on the ocean that we can reach. if get_tile_type(target_world) != null: - var direct_path = Globals.WorldLineToHexTiles(target_world, start_world) var last_removed = null - while len(direct_path) > 0 and get_tile_type(direct_path[0]) != null: - last_removed = direct_path[0] - direct_path.remove(0) + while len(direct_path) > 0 and get_tile_type(direct_path.back()) != null: + last_removed = direct_path.back() + direct_path.pop_back() if len(direct_path) == 0: print ("Could not find path!") return - print ("Using ", Globals.WorldToHex(direct_path[0]), " instead of ", goal_coord, " as goal.") - goal_coord = Globals.WorldToHex(last_removed) + print ("Using ", Globals.WorldToHex(direct_path.back()), " instead of ", goal_coord, " as goal.") + goal_coord = Globals.WorldToHex(direct_path.back()) + island_landing_site_world = last_removed - var path = Globals.OceanGrid.find_path(start_coord, goal_coord) - for target in path.slice(1,-1): + player_path_plan_start = Globals.HexToWorld(start_coord) + player_path_plan_end = Globals.HexToWorld(goal_coord) + + var path = Globals.OceanNavGrid.find_path(start_coord, goal_coord) + for target in path.slice(0,-1): var target_world_coord = Globals.HexToWorld(target.axial_coords) var target_type = get_tile_type(target_world_coord) @@ -229,8 +291,48 @@ func update_player_navigation_target(target_world: Vector2): if target_type == "Sand": break + if island_landing_site_world != null: + player_navigation_path.append(island_landing_site_world) + if len(player_navigation_path) > 0: PlayerChar.target = player_navigation_path[0] + + +func update_navigation_target_island(start_world: Vector2, target_world: Vector2): + print ("Navigating Island") + var start_coord = Globals.WorldToHex(start_world) + var goal_coord = Globals.WorldToHex(target_world) + + var path = Globals.IslandNavGrid.find_path(start_coord, goal_coord) + print ("Path length: ", len(path)) + for target in path.slice(0,-1): + var target_world_coord = Globals.HexToWorld(target.axial_coords) + var target_type = get_tile_type(target_world_coord) + + player_navigation_path.append(target_world_coord) + + +func update_player_navigation_target(target_world: Vector2): + var start_world = PlayerChar.transform.origin + + player_navigation_path = [] + + var start_timestamp = OS.get_system_time_msecs() + + var player_near_anchor = check_player_near_anchor() + print ("Player near anchor: ", player_near_anchor) + + if current_island != null: + update_navigation_target_island(start_world, target_world) + + if len(player_navigation_path) == 0 and player_near_anchor: + start_world = Globals.HexToWorld(anchor_tile) + + if current_island == null or (len(player_navigation_path) == 0 and player_near_anchor): + update_navigation_target_ocean(start_world, target_world) + + var planning_duration_msec = OS.get_system_time_msecs() - start_timestamp + print ("Planning took ", round(planning_duration_msec), "ms.") update() diff --git a/scenes/pirate.gd b/scenes/pirate.gd index 9256b53..2a49921 100644 --- a/scenes/pirate.gd +++ b/scenes/pirate.gd @@ -4,6 +4,8 @@ export (int) var speed = 200 var velocity = Vector2() var target = Vector2() +var prev_tile = null # Hex coords of previous tile +var cur_tile = null # Hex coords of current tile func get_input(): velocity = Vector2() @@ -16,12 +18,19 @@ func get_input(): if Input.is_action_pressed("walk_up"): velocity.y -= 1 velocity = velocity.normalized() * speed - + + func _physics_process(_delta): velocity = position.direction_to(target) * speed if position.distance_to(target) > 5: velocity = move_and_slide(velocity) + var tile = Globals.WorldToHex(position) + if tile != cur_tile: + prev_tile = cur_tile + cur_tile = tile + + # Called when the node enters the scene tree for the first time. func _ready(): position = Vector2(0,0)