use std::borrow::Borrow; use gdnative::api::*; use gdnative::prelude::*; /// the input state for the player enum InputState { Default, Shooting, Moving } type SpatialRef = Option>; /// the basic die used by the player #[derive(NativeClass)] #[inherit(Spatial)] #[register_with(Self::register_builder)] pub struct BasicDie { #[property(path="camera/camera_clamp")] camera_clamp: Vector2, #[property(path="shooting/max_force")] max_force: f32, #[property(path="shooting/up_angle")] up_angle: f32, #[property(path="shooting/stopping_velocity")] stopping_velocity: f32, #[property(path="shooting/stopping_min_milliseconds")] stopping_min_ms: i64, #[property(path="input/camera_mouse_sensitivity")] mouse_sensitivity: Vector2, #[property(path="input/shoot_sensitivity")] shoot_sensitivity: f32, input_state: InputState, current_force: f32, last_shot_time: i64, action_shooting: String, node_die: SpatialRef, node_camera_root: SpatialRef, node_camera_arm_horizontal: SpatialRef, node_camera_arm_vertical: SpatialRef, node_camera: SpatialRef, } #[methods] impl BasicDie { // Register the builder for methods, properties and/or signals. fn register_builder(_builder: &ClassBuilder) { godot_print!("BasicDie builder is registered!"); } fn new(_owner: &Spatial) -> Self { BasicDie { camera_clamp: Vector2 { x: 0.0, y: 0.0 }, max_force: 0.0, up_angle: 5.0, stopping_velocity: 0.0, stopping_min_ms: 1000, mouse_sensitivity: Vector2 { x: 1.0, y: 1.0 }, shoot_sensitivity: 1.0, input_state: InputState::Default, current_force: 0.0, last_shot_time: 0, action_shooting: String::from("mouse_btn_left"), node_die: None, node_camera_root: None, node_camera_arm_horizontal: None, node_camera_arm_vertical: None, node_camera: None, } } #[export] unsafe fn _ready(&mut self, owner: &Spatial) { Input::godot_singleton().set_mouse_mode(Input::MOUSE_MODE_CAPTURED); owner.set_physics_process(true); self.last_shot_time = OS::godot_singleton().get_ticks_msec(); let search_node = | parent: &SpatialRef, name: String | { let parent_node = match parent { Some(node) => node.assume_safe().as_ref(), None => owner }; match parent_node.get_node(NodePath::from_str(&name)) { None => { godot_warn!("Could not find {}.", name); None }, Some(node) => { let save_node = node.assume_safe(); match save_node.cast::() { None => { godot_warn!("{} was not of type 'Spatial'.", name); None }, Some(casted) => Some(casted.claim()) } } } }; // look for the camera and arm nodes self.node_camera_root = search_node(&None, String::from("Camera")); self.node_camera_arm_horizontal = search_node(&self.node_camera_root, String::from("CameraArmHorizontal")); self.node_camera_arm_vertical = search_node(&self.node_camera_arm_horizontal, String::from("CameraArmVertical")); self.node_camera = search_node(&self.node_camera_arm_vertical, String::from("Camera")); godot_print!("{:?}", self.node_camera_root); godot_print!("{:?}", self.node_camera_arm_horizontal); godot_print!("{:?}", self.node_camera_arm_vertical); godot_print!("{:?}", self.node_camera); self.node_die = search_node(&None, String::from("W8")); godot_print!("Player is ready"); } #[export] unsafe fn _physics_process(&mut self, _owner: &Spatial, _delta: f64) { let die = match self.node_die { None => { godot_warn!("No W8 assigned."); return; }, Some(node) => match node.assume_safe().cast::() { None => { godot_warn!("W8 is not a RigidBody."); return; }, Some(rb) => rb } }; // let the cam follow the die match self.node_camera_root { None => { godot_warn!("No W8 assigned."); }, Some(cam) => { cam.assume_safe().set_translation(die.translation()); } } // detect if the die stops moving let delta_ms = OS::godot_singleton().get_ticks_msec() - self.last_shot_time; if matches!(self.input_state, InputState::Moving) && (delta_ms > self.stopping_min_ms) { // check if the velocity is less than the threshold and change input state in that case let current_vel = die.linear_velocity().length(); if current_vel <= self.stopping_velocity { self.input_state = InputState::Default; godot_print!("Die stopped moving at velocity {} after {} ms", current_vel, delta_ms); } }; } #[export] unsafe fn _input(&mut self, _owner: &Spatial, event: Ref) { self.general_input(event.borrow()); match self.input_state { InputState::Default => self.default_input(event), InputState::Shooting => self.shooting_input(event), InputState::Moving => self.moving_input(event), } } /// this input method will always be called, regardless of the input state unsafe fn general_input(&mut self, event: &Ref) { let save_event = event.assume_safe(); // rotate camera horizontally let mouse_event = save_event.cast::(); // get the input as mouse input match mouse_event { Some(motion_event) => { let x_mov = motion_event.relative().x * self.mouse_sensitivity.x; self.rotate_cam_horizontal(x_mov); }, _ => {} } let key_event = save_event.cast::(); match key_event { Some(key_event) => { if key_event.scancode() == GlobalConstants::KEY_ESCAPE { Input::godot_singleton().set_mouse_mode(Input::MOUSE_MODE_VISIBLE); } }, _ => {} } } /// this input method will be called when looking around, before taking a shot unsafe fn default_input(&mut self, event: Ref) { let save_event = event.assume_safe(); // left mouse button was pressed => switch to shooting mode if save_event.is_action_pressed(GodotString::from_str(&self.action_shooting), false, false) { godot_print!("mouse_button, switching to shooting mode"); self.input_state = InputState::Shooting; return; } // rotate camera vertically let mouse_event = save_event.cast::(); // get the input as mouse input match mouse_event { Some(motion_event) => { let y_mov = -motion_event.relative().y * self.mouse_sensitivity.y; self.rotate_cam_vertical(y_mov); }, _ => {} }; } /// this input method will be called when player is currently taking a shot unsafe fn shooting_input(&mut self, event: Ref) { let save_event = event.assume_safe(); // mouse released, shoot if save_event.is_action_released(GodotString::from_str(&self.action_shooting), false) { self.input_state = InputState::Moving; self.shoot(); self.current_force = 0.0; return; } // charge shot with vertical mouse movement let mouse_event = save_event.cast::(); // get the input as mouse input match mouse_event { Some(motion_event) => { let y_mov = motion_event.relative().y * self.shoot_sensitivity; self.current_force = match self.current_force + y_mov { x if x < 0.0 => 0.0, x if x > self.max_force => self.max_force, x => x }; godot_print!("current force: {}", self.current_force); }, _ => {} }; } /// this input method will be called when player is moving unsafe fn moving_input(&mut self, event: Ref) { let save_event = event.assume_safe(); // rotate camera vertically let mouse_event = save_event.cast::(); // get the input as mouse input match mouse_event { Some(motion_event) => { let y_mov = motion_event.relative().y * self.mouse_sensitivity.y; self.rotate_cam_vertical(y_mov); }, _ => {} }; } unsafe fn rotate_cam_horizontal(&mut self, input: f32) { // make sure the arm exists match self.node_camera_arm_horizontal { Some(arm) => { // rotate the horizontal camera arm let save_arm = arm.assume_safe(); save_arm.rotate_object_local(Vector3 {x: 0.0, y: 1.0, z: 0.0}, input as f64) }, _ => godot_warn!("No horizontal camera arm assigned.") } } unsafe fn rotate_cam_vertical(&mut self, input: f32) { // make sure the camera arm actually exists match self.node_camera_arm_vertical { Some(arm) => { // check for the current rotation let save_arm = arm.assume_safe(); let current_rot = save_arm.rotation(); // clamp the rotation if current_rot.x + input > self.camera_clamp.x || current_rot.x + input <= self.camera_clamp.y { return; } // actually rotate if possible let save_arm = arm.assume_safe(); save_arm.rotate_object_local(Vector3 {x: 1.0, y: 0.0, z: 0.0}, input as f64) }, _ => godot_warn!("No vertical camera arm assigned") } } unsafe fn shoot(&mut self) { // make sure the camera actually exists let impulse_dir = match self.node_camera { None => { godot_warn!("No camera assigned!"); return; }, Some(cam) => { // get the forward vector of the camera setting the up angle to the defined value in the editor let mut forward_vector = -cam.assume_safe().global_transform().basis.c().normalized(); forward_vector.y = self.up_angle; // calculate the impulse force forward_vector.normalized() * self.current_force } }; // get the die let die = match self.node_die { None => { godot_warn!("No W8 assigned."); return; }, Some(node) => match node.assume_safe().cast::() { None => { godot_warn!("W8 is not a RigidBody."); return; }, Some(rb) => rb } }; // actually add the force die.apply_impulse(die.transform().origin, impulse_dir); self.last_shot_time = OS::godot_singleton().get_ticks_msec(); } }