diff options
author | Lucas Fryzek <lucas.fryzek@fryzekconcepts.com> | 2024-09-17 22:02:33 +0100 |
---|---|---|
committer | Lucas Fryzek <lucas.fryzek@fryzekconcepts.com> | 2024-09-17 22:02:33 +0100 |
commit | 0abec8156bd944aa883d544850ee1187219ba943 (patch) | |
tree | 55e222da361d176b0ba79611704b56e2d942a831 /scripts |
Initial commit
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/bsp_level_generator.gd | 78 | ||||
-rw-r--r-- | scripts/bullet.gd | 17 | ||||
-rw-r--r-- | scripts/enemy.gd | 87 | ||||
-rw-r--r-- | scripts/level_generator.gd | 118 | ||||
-rw-r--r-- | scripts/player.gd | 76 | ||||
-rw-r--r-- | scripts/spin_ground.gd | 15 |
6 files changed, 391 insertions, 0 deletions
diff --git a/scripts/bsp_level_generator.gd b/scripts/bsp_level_generator.gd new file mode 100644 index 0000000..af34e0b --- /dev/null +++ b/scripts/bsp_level_generator.gd @@ -0,0 +1,78 @@ +extends Node2D + +@export var width: int = 40 +@export var height: int = 40 +@export var min_dim: int = 5 + +var min_room_size: int = min_dim * min_dim + +var rects: Array[Vector4i] = [] + +class BSPNode: + var min_dims: Vector2i + var max_dims: Vector2i + var left: BSPNode + var right: BSPNode + + func _init(p_min_dims: Vector2i, p_max_dims: Vector2i, p_left: BSPNode, p_right: BSPNode): + min_dims = p_min_dims + max_dims = p_max_dims + left = p_left + right = p_right + +func generate_level(axis: int, min_space: Vector2i, max_space: Vector2i, depth: int = 0) -> BSPNode: + var dims = max_space - min_space + if dims[axis] / 2 < min_dim: + #rects.append(Vector4i(min_space.x, min_space.y, max_space.x, max_space.y)) + return null + + var new_axis = (axis + 1) % 2 + # 10% we stop here and just create a big room + if (depth > 2 and randi_range(0, 9) == 0) \ + or dims.x * dims.y <= min_dim * min_dim \ + or dims[new_axis] / 2 < min_dim: + rects.append(Vector4i(min_space.x, min_space.y, max_space.x, max_space.y)) + return BSPNode.new(min_space, max_space, null, null) + + # Calculate min and max ranges so that a split + # doesn't create a room that violates min dimensions + var min_value = min_space[axis] + min_dim + var max_value = max_space[axis] - min_dim + var split = randi_range(min_value, max_value) + print("Spliting axis ", axis, " at ", split) + + var left_min_space = min_space + var left_max_space = max_space + left_max_space[axis] = split + var left = generate_level(new_axis, left_min_space, left_max_space, depth + 1) + + var right_min_space = min_space + right_min_space[axis] = split + var right_max_space = max_space + var right = generate_level(new_axis, right_min_space, right_max_space, depth + 1) + + assert((left == null and right == null) or (left != null and right != null)) + + if left == null and right == null: + rects.append(Vector4i(min_space.x, min_space.y, max_space.x, max_space.y)) + + return BSPNode.new(min_space, max_space, left, right) + +func _ready() -> void: + var starting_axis = randi_range(0, 1) + var min_space = Vector2i(0, 0) + var max_space = Vector2i(width, height) + var map = generate_level(starting_axis, min_space, max_space) + +func _draw(): + var mult = 1 + for rect in rects: + var pos1 = 5*Vector2i(rect.x, rect.y) + var pos2 = 5*Vector2i(rect.z, rect.w) + var dims = pos2 - pos1 + draw_rect(Rect2(pos1, dims), mult*Color(0.01, 0.01, 0.01)) + draw_line(pos1, Vector2i(pos1.x, pos2.y), Color(0, 1, 0)) + draw_line(Vector2i(pos1.x, pos2.y), pos2, Color(0, 1, 0)) + draw_line(pos2, Vector2i(pos2.x, pos1.y), Color(0, 1, 0)) + draw_line(Vector2i(pos2.x, pos1.y), pos1, Color(0, 1, 0)) + mult += 2 diff --git a/scripts/bullet.gd b/scripts/bullet.gd new file mode 100644 index 0000000..93c119a --- /dev/null +++ b/scripts/bullet.gd @@ -0,0 +1,17 @@ +extends Node3D + +var target: Vector3 + +var bullet_speed = 100.0 + +# Called when the node enters the scene tree for the first time. +#func _ready() -> void: +# pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + position = position.move_toward(target, bullet_speed * delta) + + if (target - position).length_squared() <= 2: + queue_free() diff --git a/scripts/enemy.gd b/scripts/enemy.gd new file mode 100644 index 0000000..569905f --- /dev/null +++ b/scripts/enemy.gd @@ -0,0 +1,87 @@ +extends CharacterBody3D + +## Speed of enemy +@export var speed: float = 2.0 +## Target for enemy to follow +@export var target: Node3D + +## Whether to use gravity on enemy +@export var gravity_enabled: bool = false + +var has_astar: bool = false +var astar: AStar3D +var tar_vec: Vector3 +var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") + +var original_color: Color + +func _ready() -> void: + if gravity_enabled: + motion_mode = MotionMode.MOTION_MODE_GROUNDED + +func chase_target(delta: float) -> void: + var target_pos = target.position + var direction = (target_pos - position).normalized() + + if direction: + velocity.x = direction.x * speed + velocity.z = direction.z * speed + else: + velocity.x = move_toward(velocity.x, 0, speed) + velocity.z = move_toward(velocity.z, 0, speed) + + if not is_on_floor() and gravity_enabled: + velocity.y -= gravity * delta + + move_and_slide() + +func _physics_process(delta: float) -> void: + if target == null: + return + + if not has_astar: + # Just try to chase player directly + chase_target(delta) + return + + if Input.is_action_pressed("ui_right"): + return + + var target_pos = target.position + var closest_me = astar.get_closest_point(position) + var closest_target = astar.get_closest_point(target_pos) + + var direction: Vector3 + if closest_me == closest_target: + direction = (target_pos - position).normalized() + else: + var target_path = astar.get_point_path(closest_me, closest_target) + if len(target_path) >= 2: + direction = (target_path[1] - position).normalized() + + if direction: + #velocity.x = move_toward(velocity.x, direction.x * speed, 10.0 * delta) + #velocity.z = move_toward(velocity.z, direction.z * speed, 10.0 * delta) + velocity.x = direction.x * speed + velocity.z = direction.z * speed + else: + velocity.x = move_toward(velocity.x, 0, speed) + velocity.z = move_toward(velocity.z, 0, speed) + + move_and_slide() + +func on_hit(_hit_position: Vector3) -> void: + if $HitTimer.is_stopped(): + var material: StandardMaterial3D = $MeshInstance3D.get_active_material(0) + original_color = material.albedo_color + material.albedo_color = Color(1.0, 0.0, 0.0, 1.0) + $HitTimer.start() + +func _on_level_generator_astar_created(in_astar: AStar3D) -> void: + astar = in_astar + has_astar = true + +func _on_hit_timer_timeout() -> void: + var material: StandardMaterial3D = $MeshInstance3D.get_active_material(0) + material.albedo_color = original_color + $HitTimer.stop() diff --git a/scripts/level_generator.gd b/scripts/level_generator.gd new file mode 100644 index 0000000..94a87dd --- /dev/null +++ b/scripts/level_generator.gd @@ -0,0 +1,118 @@ +extends Node3D + +@export var width: int = 10 +@export var height: int = 10 + +var room = preload("res://prefabs/round_room.tscn") +var door = preload("res://prefabs/door.tscn") + +var bounds: Vector3 = Vector3.ZERO +var astar: AStar3D + +signal astar_created + +func get_grid_index(pos: Vector2i) -> int: + return pos.y*height + pos.x + +func set_grid_index(grid: Array[int], pos: Vector2i): + # If the point lies outside the grid we return + if pos.x < 0 or pos.x >= width or pos.y < 0 or pos.y >= height: + return + + var index = get_grid_index(pos) + + # If we already set this point we exit + if grid[index] != 0: + return + + grid[index] = 1 + var new_room: Node3D = room.instantiate() + var mesh = new_room.find_child("MeshInstance3D") + # TODO can we just load and cache the bounds? + bounds = mesh.get_aabb().size + var offset_pos = Vector3(pos.x - width / 2, 0, pos.y - height / 2) + new_room.position = (offset_pos * bounds) + #print("Placing at ", pos) + add_child(new_room) + + var next = [pos + Vector2i.UP, + pos + Vector2i.LEFT, + pos + Vector2i.DOWN, + pos + Vector2i.RIGHT] + + for n in next: + var chance = randi_range(0, 1) + if chance == 0: + set_grid_index(grid, n) + +func place_door(grid: Array[int], pos: Vector2i): + var index = get_grid_index(pos) + if grid[index] == 0: + return + + var next = [pos + Vector2i.UP, + pos + Vector2i.LEFT, + pos + Vector2i.DOWN, + pos + Vector2i.RIGHT] + + var angle = -90 + for n in next: + angle += 90 + var next_index = get_grid_index(n) + if n.x < 0 or n.x >= width or n.y < 0 or n.y >= height or grid[next_index] == 0: + var new_door: Node3D = door.instantiate() + var offset_pos = Vector3(pos.x - width / 2, 0, pos.y - height / 2) + new_door.position = (offset_pos * bounds) + new_door.rotate_y(deg_to_rad(angle)) + add_child(new_door) + +func place_doors(grid: Array[int]): + for y in range(0, height): + for x in range(0, width): + place_door(grid, Vector2i(x, y)) + +func compute_astar(grid: Array[int]): + for y in range(0, height): + for x in range(0, width): + var pos = Vector2i(x, y) + var index = get_grid_index(pos) + if grid[index] == 0: + continue + var offset_pos = Vector3(pos.x - width / 2, 0, pos.y - height / 2) + astar.add_point(index, offset_pos * bounds) + + for y in range(0, height): + for x in range(0, width): + var pos = Vector2i(x, y) + var index = get_grid_index(pos) + if grid[index] == 0: + continue + + var next = [pos + Vector2i.UP, + pos + Vector2i.LEFT, + pos + Vector2i.DOWN, + pos + Vector2i.RIGHT] + for n in next: + var next_index = get_grid_index(n) + if n.x < 0 or n.x >= width or n.y < 0 or n.y >= height or grid[next_index] == 0: + continue + + astar.connect_points(index, next_index) + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + astar = AStar3D.new() + var grid: Array[int] = [0] + grid.resize(width*height) + grid.fill(0) + + var middle_x: int = width / 2 + var middle_y: int = height / 2 + + var pos = Vector2i(middle_x, middle_y) + set_grid_index(grid, pos) + + place_doors(grid) + compute_astar(grid) + + astar_created.emit(astar) diff --git a/scripts/player.gd b/scripts/player.gd new file mode 100644 index 0000000..e76b4d1 --- /dev/null +++ b/scripts/player.gd @@ -0,0 +1,76 @@ +extends Node3D + +@export var grapple_distance: float = 10 +@export var grapple_speed: float = 10 +@export var fire_distance: float = 100 + +enum State { + NO_GRAPPLE, + FREE, + GRAPPLING +} + +var current_state: State = State.FREE +var grapple_target: Vector3 = Vector3.ZERO + +var bullet_inst = preload("res://prefabs/bullet.tscn") + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + +func do_grapple() -> void: + if Input.is_action_just_pressed("attack") and current_state == State.FREE: + var space_state = get_world_3d().direct_space_state + + var starting_pos = global_position + var ending_pos = starting_pos + grapple_distance * -global_transform.basis.z + var query = PhysicsRayQueryParameters3D.create(starting_pos, ending_pos) + query.collision_mask = 0x2 + var result = space_state.intersect_ray(query) + + if result: + grapple_target = result.position + global_transform.basis.z + current_state = State.GRAPPLING + + if Input.is_action_just_released("attack") and current_state == State.GRAPPLING: + current_state = State.FREE + grapple_target = Vector3.ZERO + + + #print("State ", current_state, " ", grapple_target) + if current_state == State.GRAPPLING: + var dir_to_target = (grapple_target - global_position).normalized() + + get_parent().local_velocity = dir_to_target * grapple_speed + +func fire_weapon() -> void: + if Input.is_action_just_pressed("attack"): + var space_state = get_world_3d().direct_space_state + var starting_pos = global_position + var ending_pos = starting_pos + fire_distance * -global_transform.basis.z + var query = PhysicsRayQueryParameters3D.create(starting_pos, ending_pos) + var result = space_state.intersect_ray(query) + + var end = ending_pos + if result: + print("Hit! ") + if result.collider.has_method("on_hit"): + result.collider.on_hit(result.position) + end = result.position + else: + print("Miss") + + var bullet: Node3D = bullet_inst.instantiate() + bullet.position = starting_pos + bullet.target = end + get_tree().get_root().add_child(bullet) + bullet.look_at(end) + +func _physics_process(_delta: float) -> void: + do_grapple() + fire_weapon() + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(_delta: float) -> void: + pass diff --git a/scripts/spin_ground.gd b/scripts/spin_ground.gd new file mode 100644 index 0000000..078a757 --- /dev/null +++ b/scripts/spin_ground.gd @@ -0,0 +1,15 @@ +extends Node3D + +@export var rotation_dir: float = 1 +@export var rotation_axis: Vector3 = Vector3.BACK + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + #rotate_z(deg_to_rad(180)) + pass + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + rotate(rotation_axis, rotation_dir * delta); + pass |