diff --git a/godot/project.godot b/godot/project.godot index f49c41e..f080d3e 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -45,16 +45,54 @@ move_right={ , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) ] } +look_up={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null) +] +} +look_down={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null) +] +} +look_left={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null) +] +} +look_right={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null) +] +} run={ "deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null) ] } interact={ "deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null) -, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null) +] +} +ready={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null) +] +} +fire={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null) +] +} +reload={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null) ] } diff --git a/godot/scenes/player.tscn b/godot/scenes/player.tscn index cecbfc6..80098a3 100644 --- a/godot/scenes/player.tscn +++ b/godot/scenes/player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=30 format=3 uid="uid://crbrniwi6kd3p"] +[gd_scene load_steps=36 format=3 uid="uid://crbrniwi6kd3p"] [ext_resource type="PackedScene" uid="uid://dpmbimh6m4ari" path="res://scenes/player_mesh.tscn" id="1_3vyb7"] [ext_resource type="Script" uid="uid://50vv0ta67tgl" path="res://src/player.gd" id="1_qhqgy"] @@ -9,6 +9,9 @@ [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_g2els"] +[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_smehm"] +animation = &"Aim" + [sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_tuyoq"] animation = &"PistolIdle" @@ -29,6 +32,9 @@ nodes/Blend2/node = SubResource("AnimationNodeBlend2_smehm") nodes/Blend2/position = Vector2(80, 120) node_connections = [&"Blend2", 0, &"Animation", &"Blend2", 1, &"Animation 2", &"output", 0, &"Blend2"] +[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_ur7pv"] +animation = &"Fire" + [sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_y4r1p"] animation = &"PistolIdle" @@ -43,12 +49,32 @@ advance_expression = "velocity" advance_mode = 2 advance_expression = "not velocity" +[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_oprun"] +advance_mode = 2 +advance_expression = "is_weapon_ready" + +[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_a8ls1"] +advance_mode = 2 +advance_expression = "is_firing" + +[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_qfm1y"] +switch_mode = 2 +advance_mode = 2 + +[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_fulsm"] +advance_mode = 2 +advance_expression = "not is_weapon_ready" + [sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_d2wvv"] +states/Aim/node = SubResource("AnimationNodeAnimation_smehm") +states/Aim/position = Vector2(327, 194) states/BlendTree/node = SubResource("AnimationNodeBlendTree_ur7pv") states/BlendTree/position = Vector2(488, 100) +states/Fire/node = SubResource("AnimationNodeAnimation_ur7pv") +states/Fire/position = Vector2(462, 194) states/PistolIdle/node = SubResource("AnimationNodeAnimation_y4r1p") states/PistolIdle/position = Vector2(327, 100) -transitions = ["Start", "PistolIdle", SubResource("AnimationNodeStateMachineTransition_ur7pv"), "PistolIdle", "BlendTree", SubResource("AnimationNodeStateMachineTransition_y4r1p"), "BlendTree", "PistolIdle", SubResource("AnimationNodeStateMachineTransition_d2wvv")] +transitions = ["Start", "PistolIdle", SubResource("AnimationNodeStateMachineTransition_ur7pv"), "PistolIdle", "BlendTree", SubResource("AnimationNodeStateMachineTransition_y4r1p"), "BlendTree", "PistolIdle", SubResource("AnimationNodeStateMachineTransition_d2wvv"), "PistolIdle", "Aim", SubResource("AnimationNodeStateMachineTransition_oprun"), "Aim", "Fire", SubResource("AnimationNodeStateMachineTransition_a8ls1"), "Fire", "Aim", SubResource("AnimationNodeStateMachineTransition_qfm1y"), "Aim", "PistolIdle", SubResource("AnimationNodeStateMachineTransition_fulsm")] [sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_qhqgy"] animation = &"Idle" @@ -72,7 +98,7 @@ advance_expression = "input.is_running" [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_qhqgy"] advance_mode = 2 -advance_expression = "not input.is_running" +advance_expression = "not input.is_running or not velocity" [sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_dqkch"] advance_mode = 2 diff --git a/godot/src/persistence.gd b/godot/src/persistence.gd index bf9693e..e2d463d 100644 --- a/godot/src/persistence.gd +++ b/godot/src/persistence.gd @@ -1,6 +1,6 @@ class_name Persistence extends Node -@export var path = "user://saves/data.sav" +@export var path = "user://saves/" @export var group_name = "persist" const SaveMethod = "on_save" @@ -14,9 +14,12 @@ static func get_instance_data(node: Node): data = node.call(SaveMethod) } -func save() -> Result: +func save(filename: String) -> Result: DirAccess.make_dir_recursive_absolute(path.get_base_dir()) - var file = FileAccess.open(path, FileAccess.WRITE) + var file_path = path.path_join(filename) + if not file_path.is_valid_filename(): + return Result.err('invalid file path "%s"' % file_path) + var file = FileAccess.open(file_path, FileAccess.WRITE) var nodes = get_tree().get_nodes_in_group(group_name) for node in nodes: diff --git a/godot/src/player.gd b/godot/src/player.gd index 0119678..fc174a7 100644 --- a/godot/src/player.gd +++ b/godot/src/player.gd @@ -6,23 +6,37 @@ class_name Player extends CharacterBody3D @onready var input = $Input @onready var interactor = $Interactor -var is_carrying_item = false +var is_carrying_item = true +var is_weapon_ready: bool: + get: return is_carrying_item and input.is_weapon_ready func _ready() -> void: - input.connect("interact", _on_interact) + input.connect('interact', _on_interact) + input.connect('fire', _on_fire) func _physics_process(delta: float) -> void: - move_and_rotate(delta) + if is_weapon_ready: + rotate_toward_look(delta) + else: + move_and_rotate(delta) func move_and_rotate(_delta: float): - var speed = run_speed if input.is_running else walk_speed - velocity = input.next_velocity(speed) - if !velocity.is_zero_approx(): - look_at(global_position + velocity, Vector3.UP, true) - move_and_slide() + var speed = run_speed if input.is_running else walk_speed + velocity = input.next_velocity(speed) + if !velocity.is_zero_approx(): + look_at(global_position + velocity, Vector3.UP, true) + move_and_slide() + +func rotate_toward_look(_delta: float): + var target = input.get_look_target(global_position) + if target.is_some(): + look_at(target.unwrap(), Vector3.UP, true) func _on_interact(): interactor.interact_nearest() +func _on_fire(): + if is_weapon_ready: + print('firing weapon') func on_save(): return { diff --git a/godot/src/player_input.gd b/godot/src/player_input.gd index 7e6dbe7..393739a 100644 --- a/godot/src/player_input.gd +++ b/godot/src/player_input.gd @@ -1,10 +1,59 @@ class_name PlayerInput extends Node +enum Device { + Unknown, + KeyboardMouse, + Gamepad +} + +class Action: + static var Run = 'run' + static var Interact = 'interact' + static var Ready = 'ready' + static var Fire = 'fire' + +signal walk signal run signal interact +signal ready_weapon +signal unready_weapon +signal fire -var is_running: bool = false -var is_interacting: bool = false +var last_known_device: Device = Device.Unknown + +var _is_running: bool = false +var is_running: bool: + get: return _is_running + set(value): + if _is_running != value: + _is_running = value + if value: run.emit() + else: walk.emit() + +var _is_interacting: bool = false +var is_interacting: bool: + get: return _is_interacting + set(value): + if _is_interacting != value: + _is_interacting = value + if value: interact.emit() + +var _is_weapon_ready: bool = false +var is_weapon_ready: bool: + get: return _is_weapon_ready + set(value): + if _is_weapon_ready != value: + _is_weapon_ready = value + if value: ready_weapon.emit() + else: unready_weapon.emit() + +var _is_firing: bool = false +var is_firing: bool: + get: return _is_firing + set(value): + if _is_firing != value: + _is_firing = value + if value: fire.emit() var movement_dir: Vector2: get: return Input.get_vector( @@ -12,17 +61,69 @@ var movement_dir: Vector2: 'move_up', 'move_down' ).normalized() +var _mouse_position: Vector2: + get: + var viewport = get_viewport() + var mouse_pos = viewport.get_mouse_position() + var cam = viewport.get_camera_3d() + var space_state = cam.get_world_3d().direct_space_state + var origin = cam.project_ray_origin(mouse_pos) + var end = origin + (cam.project_ray_normal(mouse_pos) * cam.far) + var query = PhysicsRayQueryParameters3D.create(origin, end) + + var result = space_state.intersect_ray(query) + var position = result.get('position') as Vector3 + return Vector2(position.x, position.z) + +var _analog_dir: Vector2: + get: return Input.get_vector( + 'look_left', 'look_right', + 'look_up', 'look_down' + ).normalized() + func _process(_delta: float) -> void: - var running = Input.is_action_pressed("run") - if running != is_running: - run.emit(running) - is_running = running + is_running = Input.is_action_pressed(Action.Run) - var was_interacting = is_interacting - is_interacting = Input.is_action_pressed("interact") +func _get_device(event: InputEvent) -> Device: + if event is InputEventMouse or event is InputEventKey: + return Device.KeyboardMouse + elif event is InputEventJoypadButton or event is InputEventJoypadMotion: + return Device.Gamepad + else: + return Device.Unknown - if is_interacting and not was_interacting: - interact.emit() +func _input(event: InputEvent) -> void: + last_known_device = _get_device(event) + + if event.is_action_pressed(Action.Interact): + is_interacting = true + + if event.is_action_released(Action.Interact): + is_interacting = false + + if event.is_action_pressed(Action.Ready): + is_weapon_ready = true + + if event.is_action_released(Action.Ready): + is_weapon_ready = false + + if event.is_action_pressed(Action.Fire): + is_firing = true + + if event.is_action_released(Action.Fire): + is_firing = false func next_velocity(speed: float, dir: Vector2 = movement_dir) -> Vector3: return Vector3(dir.x, 0, dir.y) * speed + +func get_look_target(position: Vector3) -> Option: + match last_known_device: + Device.KeyboardMouse: + var pos = _mouse_position + return Option.some(Vector3(pos.x, position.y, pos.y)) + Device.Gamepad: + var pos = _analog_dir + if pos.is_zero_approx(): + return Option.none + return Option.some(Vector3(pos.x, 0, pos.y) + position) + _: return Option.none