-
Notifications
You must be signed in to change notification settings - Fork 19
Updates Behaviors 3D #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ivan-267
wants to merge
2
commits into
behaviors_3d
Choose a base branch
from
behaviors_3d_update
base: behaviors_3d
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,3 +20,4 @@ builds/ | |
|
|
||
| # Blender | ||
| *.blend1 | ||
| /examples/behaviors3d/addons | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,69 +1,107 @@ | ||
| extends Level | ||
|
|
||
| const AABB_MARGIN := Vector3(1.0, 1.0, 1.0) | ||
| const AABB_MARGIN := 0.5 | ||
| var aabbs : Array[AABB] = [] | ||
|
|
||
| @export var object_pool : Array[PackedScene] | ||
|
|
||
| @onready var ground = $Ground | ||
|
|
||
| @onready var character_location: Vector3 | ||
| @onready var target_location: Vector3 | ||
|
|
||
| var _free_character_locations: Array[Vector3] | ||
| var _free_character_location_idx: int | ||
| var _free_target_locations: Array[Vector3] | ||
| var _free_target_location_idx: int | ||
|
|
||
| func generate_level() -> void: | ||
| _adjust_ground() | ||
| _spawn_objects() | ||
| _adjust_ground() | ||
| _spawn_objects() | ||
|
|
||
|
|
||
| ## Returns a free position for the character | ||
| func get_spawn_location() -> Vector3: | ||
| return Vector3( | ||
| randf_range(-ground.size.x/2, ground.size.x/2), | ||
| 5, | ||
| randf_range(-ground.size.z/2, ground.size.z/2) | ||
| ) | ||
| var location := _free_character_locations[_free_character_location_idx] | ||
| _free_character_location_idx = (_free_character_location_idx + 1) % _free_character_locations.size() | ||
| return location | ||
|
|
||
|
|
||
| ## Returns a free position for the target | ||
| func get_target_location() -> Vector3: | ||
| return Vector3( | ||
| randf_range(-ground.size.x/2, ground.size.x/2), | ||
| 1, | ||
| randf_range(-ground.size.z/2, ground.size.z/2) | ||
| ) | ||
| var location := _free_target_locations[_free_target_location_idx] | ||
| _free_target_location_idx = (_free_target_location_idx + 1) % _free_target_locations.size() | ||
| return location | ||
|
|
||
| func get_random_location(y_offset := 1.0, xz_padding := 0.0) -> Vector3: | ||
| return Vector3( | ||
| randf_range(-ground.size.x/2 + xz_padding, ground.size.x/2 - xz_padding), | ||
| y_offset, | ||
| randf_range(-ground.size.z/2 + xz_padding, ground.size.z/2 - xz_padding) | ||
| ) | ||
|
|
||
| func _intersects_aabb(aabb:AABB) -> bool: | ||
| for other_aabb in aabbs: | ||
| if aabb.intersects(other_aabb): | ||
| return true | ||
| return false | ||
| for other_aabb in aabbs: | ||
| if aabb.intersects(other_aabb): | ||
| return true | ||
| return false | ||
|
|
||
|
|
||
| func _adjust_ground() -> void: | ||
| ground.size = Vector3(randf_range(10, 40), 1, randf_range(10, 40)) | ||
| ground.size = Vector3(randf_range(10, 40), 1, randf_range(10, 40)) | ||
|
|
||
| get_node("Walls/Left").position = Vector3(-ground.size.x/2, 0, 0) | ||
| get_node("Walls/Left").size.x = ground.size.z | ||
| get_node("Walls/Right").position = Vector3(ground.size.x/2, 0, 0) | ||
| get_node("Walls/Right").size.x = ground.size.z | ||
| get_node("Walls/Forward").position = Vector3(0, 0, -ground.size.z/2) | ||
| get_node("Walls/Forward").size.x = ground.size.x | ||
| get_node("Walls/Backward").position = Vector3(0, 0, ground.size.z/2) | ||
| get_node("Walls/Backward").size.x = ground.size.x | ||
| get_node("Walls/Left").position = Vector3(-ground.size.x/2, 0, 0) | ||
| get_node("Walls/Left").size.x = ground.size.z | ||
| get_node("Walls/Right").position = Vector3(ground.size.x/2, 0, 0) | ||
| get_node("Walls/Right").size.x = ground.size.z | ||
| get_node("Walls/Forward").position = Vector3(0, 0, -ground.size.z/2) | ||
| get_node("Walls/Forward").size.x = ground.size.x | ||
| get_node("Walls/Backward").position = Vector3(0, 0, ground.size.z/2) | ||
| get_node("Walls/Backward").size.x = ground.size.x | ||
|
|
||
| func _spawn_objects() -> void: | ||
| var total_area = ground.size.x * ground.size.z | ||
| var max_objects = int(total_area) | ||
| for i in range(max_objects): | ||
| var object = object_pool.pick_random().instantiate() | ||
| add_child(object) | ||
| # object.rotate_y(deg_to_rad(randf_range(0, 360))) | ||
| object.position = Vector3( | ||
| randf_range(-ground.size.x/2 + object.size.x/2, ground.size.x/2 - object.size.x/2), | ||
| 5, | ||
| randf_range(-ground.size.z/2 + object.size.z/2, ground.size.z/2 - object.size.z/2) | ||
| ) | ||
| # TODO: this is not perfect, but it's good enough for now | ||
| var object_aabb = AABB(Vector3.ZERO, object.size + AABB_MARGIN) | ||
| var rotated_aabb = object.transform * object_aabb | ||
| rotated_aabb.position = object.position | ||
| if _intersects_aabb(rotated_aabb): | ||
| object.queue_free() | ||
| continue | ||
|
|
||
|
|
||
| aabbs.append(rotated_aabb) | ||
| var total_area = ground.size.x * ground.size.z | ||
| var max_objects = int(total_area) | ||
|
|
||
| #region Sets a few free positions for character and target to spawn | ||
| var free_locations_count: int = 10 | ||
| for i in free_locations_count: | ||
| character_location = get_random_location(ground.size.y/2.0 + _character_aabb.size.y / 2.0, 1) | ||
| target_location = get_random_location(ground.size.y/2.0 + _target_aabb.size.y / 2.0, 1) | ||
|
|
||
| var character_aabb_global: AABB = _character_aabb | ||
| character_aabb_global.position += to_global(character_location) | ||
| character_aabb_global = character_aabb_global.grow(AABB_MARGIN) | ||
|
|
||
| var target_aabb_global: AABB = _character_aabb | ||
| target_aabb_global.position += to_global(target_location) | ||
| target_aabb_global = target_aabb_global.grow(AABB_MARGIN) | ||
|
|
||
| aabbs.append(character_aabb_global) | ||
| aabbs.append(target_aabb_global) | ||
|
|
||
| _free_character_locations.append(character_location) | ||
| _free_target_locations.append(target_location) | ||
| #endregion | ||
|
|
||
| for i in range(max_objects): | ||
| var object = object_pool.pick_random().instantiate() | ||
| add_child(object) | ||
| # object.rotate_y(deg_to_rad(randf_range(0, 360))) | ||
| object.position = Vector3( | ||
| randf_range(-ground.size.x/2 + object.mesh.size.x/2, ground.size.x/2 - object.mesh.size.x/2), | ||
| ground.size.y/2 + object.mesh.size.y / 2.0, | ||
| #5, | ||
| randf_range(-ground.size.z/2 + object.mesh.size.z/2, ground.size.z/2 - object.mesh.size.z/2) | ||
| ) | ||
| # TODO: this is not perfect, but it's good enough for now | ||
| var object_global_aabb: AABB = object.aabb | ||
| object_global_aabb.position += object.global_position | ||
| object_global_aabb = object_global_aabb.grow(AABB_MARGIN) | ||
|
|
||
| if _intersects_aabb(object_global_aabb): | ||
| object.queue_free() | ||
| continue | ||
|
|
||
|
|
||
| aabbs.append(object_global_aabb) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,94 +1,115 @@ | ||
| extends CharacterBody3D | ||
| class_name AICharacter | ||
|
|
||
| ## A reference to the main mesh, used for AABB overlap checks | ||
| @export var aabb_mesh: Mesh | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| @export var target: Node3D | ||
| const SPEED = 5.0 | ||
| const JUMP_VELOCITY = 4.5 | ||
|
|
||
| @onready var ai_controller = $AIController3D | ||
| @onready var aabb := aabb_mesh.get_aabb() | ||
| @onready var level_runner: LevelRunner = get_parent() | ||
|
|
||
| var forward_backward_action: float = 0.0 | ||
| var straf_left_right_action: float = 0.0 | ||
| var turn_left_right_action: float = 0.0 | ||
| var jump_action: bool = false | ||
|
|
||
|
|
||
| func _ready() -> void: | ||
| ai_controller.init(self) | ||
| CameraManager.register_player(self) | ||
| ai_controller.init(self) | ||
| CameraManager.register_player(self) | ||
|
|
||
|
|
||
| func _physics_process(delta: float) -> void: | ||
| # Add the gravity. | ||
| var drag = 1.0 | ||
| if not is_on_floor(): | ||
| velocity += get_gravity() * delta | ||
| drag = 0.8 | ||
|
|
||
| # Handle jump. | ||
|
|
||
|
|
||
| if get_jump() and is_on_floor(): | ||
| velocity.y = JUMP_VELOCITY | ||
|
|
||
| # Get the input direction and handle the movement/deceleration. | ||
| # As good practice, you should replace UI actions with custom gameplay actions. | ||
| var direction := get_input_dir() | ||
| if direction: | ||
| velocity.x = direction.x * SPEED * drag | ||
| velocity.z = direction.z * SPEED * drag | ||
| else: | ||
| velocity.x = move_toward(velocity.x, 0, SPEED) | ||
| velocity.z = move_toward(velocity.z, 0, SPEED) | ||
|
|
||
| # Player turning logic | ||
| var turn = get_turn_dir() | ||
| if turn: | ||
| var target_rotation = rotation.y + turn | ||
| rotation.y = move_toward(rotation.y, target_rotation, 5.0 * delta) | ||
|
|
||
| move_and_slide() | ||
|
|
||
| # Add the gravity. | ||
| var drag = 1.0 | ||
| if not is_on_floor(): | ||
| velocity += get_gravity() * delta | ||
| drag = 0.8 | ||
|
|
||
| # Handle jump. | ||
| if get_jump() and is_on_floor(): | ||
| velocity.y = JUMP_VELOCITY | ||
|
|
||
| # Get the input direction and handle the movement/deceleration. | ||
| # As good practice, you should replace UI actions with custom gameplay actions. | ||
| var direction := get_input_dir() | ||
| if direction: | ||
| velocity.x = direction.x * SPEED * drag | ||
| velocity.z = direction.z * SPEED * drag | ||
| else: | ||
| velocity.x = move_toward(velocity.x, 0, SPEED) | ||
| velocity.z = move_toward(velocity.z, 0, SPEED) | ||
|
|
||
| # Player turning logic | ||
| var turn = get_turn_dir() | ||
| if turn: | ||
| var target_rotation = rotation.y + turn | ||
| rotation.y = move_toward(rotation.y, target_rotation, 5.0 * delta) | ||
|
|
||
| move_and_slide() | ||
| process_debug_reset() | ||
| process_debug_character_fell() | ||
|
|
||
|
|
||
| ## [Debug method] Used to test different spawn positions | ||
| func process_debug_reset(): | ||
| if Input.is_action_just_pressed("reset"): | ||
| reset() | ||
|
|
||
|
|
||
| ## [Debug method] Prints out if the character falls | ||
| func process_debug_character_fell(): | ||
| if position.y < -10: | ||
| print("[Debug] character fell, scene: ", level_runner.level_scene.name) | ||
|
|
||
|
|
||
| func get_jump(): | ||
| if ai_controller.heuristic == "model": | ||
| return jump_action | ||
| else: | ||
| return Input.is_action_just_pressed("ui_accept") | ||
| if ai_controller.heuristic == "model": | ||
| return jump_action | ||
| else: | ||
| return Input.is_action_just_pressed("ui_accept") | ||
|
|
||
|
|
||
| func get_input_dir() -> Vector3: | ||
| var input_dir = Vector2() | ||
| if ai_controller.heuristic == "model": | ||
| input_dir = Vector2(straf_left_right_action, forward_backward_action) | ||
| else: | ||
| input_dir = Input.get_vector("right", "left", "backward", "forward") | ||
| var input_dir = Vector2() | ||
| if ai_controller.heuristic == "model": | ||
| input_dir = Vector2(straf_left_right_action, forward_backward_action) | ||
| else: | ||
| input_dir = Input.get_vector("right", "left", "backward", "forward") | ||
|
|
||
|
|
||
|
|
||
| var dir = Vector3(input_dir.x, 0, input_dir.y).normalized() | ||
| dir.z = clamp(dir.z, -0.5, 1.0) | ||
| return transform.basis * dir | ||
| var dir = Vector3(input_dir.x, 0, input_dir.y).normalized() | ||
| dir.z = clamp(dir.z, -0.5, 1.0) | ||
| return transform.basis * dir | ||
|
|
||
| func get_turn_dir() -> float: | ||
| if ai_controller.heuristic == "model": | ||
| return turn_left_right_action | ||
| if ai_controller.heuristic == "model": | ||
| return turn_left_right_action | ||
|
|
||
| return Input.get_action_strength("turn_left") - Input.get_action_strength("turn_right") | ||
| return Input.get_action_strength("turn_left") - Input.get_action_strength("turn_right") | ||
|
|
||
|
|
||
|
|
||
| func _on_collector_area_entered(_area: Area3D) -> void: | ||
| ai_controller.reward += 10.0 | ||
| ai_controller.reset_best_goal_distance() | ||
| ai_controller.reward += 10.0 | ||
| ai_controller.reset_best_goal_distance() | ||
|
|
||
| func reset(): | ||
| position = Vector3(0, 1, 0) | ||
| velocity = Vector3() | ||
| rotation = Vector3() | ||
| get_parent().reset_target() | ||
| ai_controller.reset_best_goal_distance() | ||
| position = level_runner.level_scene.get_spawn_location() | ||
| velocity = Vector3() | ||
| rotation = Vector3() | ||
| level_runner.reset_target() | ||
| ai_controller.reset_best_goal_distance() | ||
|
|
||
|
|
||
| func toggle_camera(on: bool): | ||
| if on: | ||
| $Camera3D.make_current() | ||
| else: | ||
| $Camera3D.clear_current() | ||
| if on: | ||
| $Camera3D.make_current() | ||
| else: | ||
| $Camera3D.clear_current() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,35 @@ | ||
| extends Node3D | ||
| class_name Level | ||
|
|
||
|
|
||
| #region Currently set by LevelRunner | ||
| var character: AICharacter | ||
| var target: AITarget | ||
| #endregion | ||
|
|
||
|
|
||
| var _character_aabb: AABB | ||
| var _target_aabb: AABB | ||
|
|
||
|
|
||
| func _ready(): | ||
| _set_aabbs() | ||
|
|
||
|
|
||
| ## Sets the character and target aabb's | ||
| func _set_aabbs(): | ||
| _character_aabb = character.aabb | ||
| _target_aabb = target.aabb | ||
|
|
||
|
|
||
| ## Default implementation for spawn location | ||
| func get_spawn_location() -> Vector3: | ||
| var spawn_location: Vector3 | ||
| spawn_location.y = 2 | ||
| return spawn_location | ||
|
|
||
| func generate_level(): | ||
| assert(false) | ||
| assert(false) | ||
|
|
||
| func get_target_location(): | ||
| assert(false) | ||
| assert(false) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to ensure there's always some free space between character/target and the boxes. While it limits the amount of random positions, this is one way to fix the player or target sometimes getting spawned inside of a box, while not requiring the entire level to regenerate on reset. Sometimes the character got stuck at the edges (at least with my other modifications, not sure about the original), so a padding parameter was added to the random position method.