2 次代碼提交 e62ab6b1ff ... c62f585e17

作者 SHA1 備註 提交日期
  Trey Del Bonis c62f585e17 game: applied some changes from Godot 4.3 1 月之前
  Trey Del Bonis 5b416c5b86 misc: heavy refactoring to support entity physics 1 月之前

+ 60 - 5
clientlib/src/client.rs

@@ -7,13 +7,13 @@ use craftdig_datamodel::block::{action, action::Action};
 use craftdig_datamodel::block::{BlkId, BlockStateSnapshot};
 use craftdig_datamodel::message::MsgTyId;
 use craftdig_datamodel::player::id::PlayerId;
-use craftdig_datamodel::world::action::PlayerJoin;
 use craftdig_datamodel::world::editable::Diff;
 use craftdig_datamodel::world::environment::WorldEnv;
 use craftdig_datamodel::world::map::WorldState;
 use craftdig_datamodel::world::{ChunkState, WorldView};
 use craftdig_math::*;
 use craftdig_sim::input::InputRing;
+use craftdig_sim::phys::compute_entity_physics_update;
 use craftdig_sim::tick::control::ServerMsg;
 use craftdig_sim::tick::{
     self, config::BlockMsgIds, config::SimConfig, control::LoopIo, control::TickMsg,
@@ -21,6 +21,7 @@ use craftdig_sim::tick::{
 
 use super::ctrl;
 use super::error::Error;
+use crate::player::{ControlInput, PlayerPhysics, PlayerState};
 
 /// Shared thread-local state.
 pub struct ClientState {
@@ -52,6 +53,15 @@ pub struct ClientHandle {
     registrar: Arc<registrar::GameRegistrar>,
     player_id: PlayerId,
 
+    /// Player state we store in advance of the tick loop.
+    player_ent_state: PlayerState,
+
+    /// Last player control inputs, if any.
+    last_player_input: Option<ControlInput>,
+
+    /// World environment we're currently in.
+    world_env: WorldEnv,
+
     /// Actions that have been initiated but not acted on.
     input_ring: Arc<InputRing>,
 
@@ -103,6 +113,11 @@ impl ClientHandle {
         self.shared_state.get_status()
     }
 
+    /// Updates the stored player control input used for physics calculations.
+    pub fn update_control_input(&mut self, inp: ControlInput) {
+        self.last_player_input = Some(inp);
+    }
+
     /// Submits an action to the client, to be wrapped up with context from the client.
     pub fn submit_player_action(&mut self, action_data: action::ActionData) {
         // Submit the action.
@@ -153,8 +168,30 @@ impl ClientHandle {
         self.changed_chunks.insert(cc);
     }
 
-    /// Used to resync the tick thread state with the game state, providing new
-    /// input and acting on world/player updates.
+    /// Should be called at a fixed interval to update rendered physics.
+    pub fn resync_phys(&mut self, delta_time: f32) {
+        if let Some(inp) = self.last_player_input {
+            // TODO respect the player inputs lol
+
+            // Prepare the stubs to apply physics logic.
+            let aabb = make_player_aabb();
+            let pphys = PlayerPhysics::new(aabb, self.player_ent_state, true);
+
+            // We have to make a fake world env because we're not operating with the normal physics.
+            let dummy_env = self.world_env.with_new_dt(delta_time);
+
+            // Actually compute the stuff.
+            let world = self.dirty_world_state.as_ref();
+            let reg = self.registrar.as_ref();
+            let update = compute_entity_physics_update(&pphys, world, reg, &dummy_env);
+
+            // Apply the changes.
+            self.player_ent_state.apply_physics_update(&update);
+        }
+    }
+
+    /// Used to resync the tick thread state with the game state, acting on
+    /// world/player updates.
     pub fn resync(&mut self, scene: &mut dyn ctrl::SceneController) -> Result<(), Error> {
         // Immediately mark a few chunks as dirty.  This doesn't do neighbors
         // because we'll probably regenerate those in a tick or two anyways.
@@ -201,6 +238,9 @@ impl ClientHandle {
             self.dirty_world_state = Arc::new(dirty_ws);
         }
 
+        // Update our currently tracked player position, as we probably just ran physics.
+        scene.set_player_position(self.player_ent_state.pos());
+
         Ok(())
     }
 }
@@ -224,23 +264,29 @@ pub fn launch(reg: Arc<registrar::GameRegistrar>) -> ClientHandle {
         4,
         Arc::new(wprov),
         5,
-        world_env,
+        world_env.clone(),
     );
 
+    // Construct the input communication channels.
     let inp_ring = Arc::new(InputRing::new(4));
     let (serv_inp_tx, serv_inp_rx) = mpsc::channel();
     let (output_tx, output_rx) = mpsc::channel();
     let loop_io = LoopIo::new(player_id, serv_inp_rx, inp_ring.clone(), output_tx);
 
     // Generate the player join event for ourselves so that we process it immediately.
+    // TODO split this out into a separate controller handle thing
+    let init_player_loc = Vec3f::new_y(3.0);
     let player_join_msg = ServerMsg::NewPlayer {
         id: player_id,
-        loc: Vec3f::new_y(3.0),
+        loc: init_player_loc,
     };
     serv_inp_tx
         .send(player_join_msg)
         .expect("client: send player join");
 
+    // Construct initial local player entity state.
+    let player_state = PlayerState::new_at(init_player_loc);
+
     thread::spawn(move || {
         tick::tick_loop_main(init_world_state, loop_io, cfg);
     });
@@ -252,6 +298,9 @@ pub fn launch(reg: Arc<registrar::GameRegistrar>) -> ClientHandle {
         }),
         registrar: reg,
         player_id,
+        player_ent_state: player_state,
+        last_player_input: None,
+        world_env,
         input_ring: inp_ring,
         output_queue: output_rx,
         last_world_state: init_ws.clone(),
@@ -289,3 +338,9 @@ impl ClientStatus {
 fn now_ts() -> u64 {
     time::UNIX_EPOCH.elapsed().unwrap().as_millis() as u64
 }
+
+fn make_player_aabb() -> AABB {
+    let height = 1.7;
+    let width = 0.65;
+    AABB::from_center(Vec3f::new_y(height / 2.0), Vec3f::new(width, height, width))
+}

+ 4 - 1
clientlib/src/ctrl.rs

@@ -1,4 +1,4 @@
-use craftdig_math::ChunkCoord;
+use craftdig_math::{ChunkCoord, Vec3f};
 
 /// Callback trait passed from a game implementation to the core client every
 /// frame for the client to signal world changes.
@@ -7,6 +7,9 @@ pub trait SceneController {
     /// same frame, should only be re-rendered once.
     fn flag_dirty_chunk(&mut self, cc: ChunkCoord);
 
+    /// Set the player's position, immediately.
+    fn set_player_position(&mut self, pos: Vec3f);
+
     /// Shows a chat message on screen.
     fn show_chat_message(&mut self, msg: String);
 }

+ 1 - 0
clientlib/src/lib.rs

@@ -1,6 +1,7 @@
 pub mod client;
 pub mod ctrl;
 pub mod error;
+pub mod player;
 
 pub use client::ClientHandle;
 pub use ctrl::SceneController;

+ 134 - 0
clientlib/src/player.rs

@@ -0,0 +1,134 @@
+//! Player tracking state.
+#![allow(dead_code)]
+
+use craftdig_math::{Vec3f, AABB};
+use craftdig_sim::phys::{PhysicsEntity, PhysicsUpdate};
+
+/// State that tracks the player's world state in advance of the sim tick.
+///
+/// We always have our own view of this and push updates to the sim tick since
+/// we want our physics to feel snappier than the game can handle.
+#[derive(Copy, Clone, Debug)]
+pub struct PlayerState {
+    /// Local copy of position.
+    pos: Vec3f,
+
+    /// Velocity.
+    vel: Vec3f,
+}
+
+impl PlayerState {
+    pub fn new(pos: Vec3f, vel: Vec3f) -> Self {
+        Self { pos, vel }
+    }
+
+    pub fn new_at(pos: Vec3f) -> Self {
+        Self::new(pos, Vec3f::zero())
+    }
+
+    pub fn pos(&self) -> Vec3f {
+        self.pos
+    }
+
+    pub fn set_pos(&mut self, pos: Vec3f) {
+        self.pos = pos;
+    }
+
+    pub fn vel(&self) -> Vec3f {
+        self.vel
+    }
+
+    pub fn set_vel(&mut self, vel: Vec3f) {
+        self.vel = vel;
+    }
+
+    pub fn apply_physics_update(&mut self, update: &PhysicsUpdate) {
+        if let Some(npos) = update.new_pos() {
+            self.pos = npos;
+        }
+
+        if let Some(nvel) = update.new_vel() {
+            self.vel = nvel;
+        }
+
+        // TODO grounded state?
+    }
+}
+
+pub struct PlayerPhysics {
+    aabb: AABB,
+    pstate: PlayerState,
+    obeys_gravify: bool,
+}
+
+impl PlayerPhysics {
+    pub fn new(aabb: AABB, pstate: PlayerState, obeys_gravify: bool) -> Self {
+        Self {
+            aabb,
+            pstate,
+            obeys_gravify,
+        }
+    }
+}
+
+impl PhysicsEntity for PlayerPhysics {
+    fn collider(&self) -> &AABB {
+        &self.aabb
+    }
+
+    fn position(&self) -> Vec3f {
+        self.pstate.pos()
+    }
+
+    fn velocity(&self) -> Vec3f {
+        self.pstate.vel()
+    }
+
+    fn obeys_gravity(&self) -> bool {
+        self.obeys_gravify
+    }
+}
+
+/// Player control inputs extracted from the engine.  This should be a directish
+/// representation of the user's request without any processing to account for
+/// if actions should be possible at some given moment.
+#[derive(Copy, Clone, Debug, Default)]
+pub struct ControlInput {
+    /// The player's requested movement direction.
+    ///
+    /// This should be a unit vector, already accounting for the diagonal thing.
+    movement: Vec3f,
+
+    /// If the player initiated a jump.
+    jumped: bool,
+
+    /// If the player is trying to crouch right now.
+    crouched: bool,
+
+    /// If the player is trying to sprint right now.
+    sprint: bool,
+}
+
+impl ControlInput {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn movement(&self) -> Vec3f {
+        self.movement
+    }
+
+    pub fn set_movement(&mut self, movement: Vec3f) {
+        self.movement = movement;
+    }
+
+    pub fn jumped(&self) -> bool {
+        self.jumped
+    }
+
+    pub fn set_jumped(&mut self, jumped: bool) {
+        self.jumped = jumped;
+    }
+
+    // TODO other accessors when we implemented them
+}

+ 56 - 1
datamodel/src/entity/state.rs

@@ -1,6 +1,6 @@
 //! In-memory entity state structures.
 
-use craftdig_math::Vec3f;
+use craftdig_math::{Vec3f, AABB};
 
 #[derive(Clone, Debug)]
 pub struct EntityState {
@@ -10,11 +10,40 @@ pub struct EntityState {
     /// Entity position.
     position: Vec3f,
 
+    /// Collision box in local space.
+    collider: AABB,
+
     /// Physics component.
     // TODO make this more general
     phys: Option<PhysicsComponent>,
 }
 
+impl EntityState {
+    pub fn name(&self) -> &str {
+        &self.name
+    }
+
+    pub fn position(&self) -> Vec3f {
+        self.position
+    }
+
+    pub fn set_position(&mut self, pos: Vec3f) {
+        self.position = pos;
+    }
+
+    pub fn collider(&self) -> &AABB {
+        &self.collider
+    }
+
+    pub fn phys(&self) -> Option<&PhysicsComponent> {
+        self.phys.as_ref()
+    }
+
+    pub fn phys_mut(&mut self) -> &mut Option<PhysicsComponent> {
+        &mut self.phys
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct PhysicsComponent {
     /// Velocity vector, added to position every tick, adjusted for tick duration.
@@ -26,3 +55,29 @@ pub struct PhysicsComponent {
     /// If we should apply gravity and try to move the entity down.
     is_grounded: bool,
 }
+
+impl PhysicsComponent {
+    pub fn velocity(&self) -> Vec3f {
+        self.velocity
+    }
+
+    pub fn set_velocity(&mut self, vel: Vec3f) {
+        self.velocity = vel;
+    }
+
+    pub fn obey_gravity(&self) -> bool {
+        self.obey_gravity
+    }
+
+    pub fn set_obey_gravity(&mut self, obey_gravity: bool) {
+        self.obey_gravity = obey_gravity;
+    }
+
+    pub fn is_grounded(&self) -> bool {
+        self.is_grounded
+    }
+
+    pub fn set_grounded(&mut self, g: bool) {
+        self.is_grounded = g;
+    }
+}

+ 9 - 1
datamodel/src/player/id.rs

@@ -1,7 +1,9 @@
-use std::str::FromStr;
+use std::{mem, str::FromStr};
 
 use uuid::Uuid;
 
+use crate::entity::EntityId;
+
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
 pub struct PlayerId(Uuid);
 
@@ -22,6 +24,12 @@ impl PlayerId {
     pub fn into_inner(self) -> Uuid {
         self.0
     }
+
+    /// Returns the player ID as an entity ID.
+    pub fn as_entity_id(&self) -> &EntityId {
+        // SAFETY same ABI
+        unsafe { mem::transmute(self) }
+    }
 }
 
 // TODO convert into const

+ 18 - 0
datamodel/src/world/environment.rs

@@ -19,4 +19,22 @@ impl WorldEnv {
             phys_delta_time,
         }
     }
+
+    pub fn gravity(&self) -> Vec3f {
+        self.gravity
+    }
+
+    pub fn phys_delta_time(&self) -> f32 {
+        self.phys_delta_time
+    }
+
+    /// Copies this instance but changes only the deltatime.
+    ///
+    /// This is used in the render thread when updating the player-local physics.
+    pub fn with_new_dt(&self, delta_time: f32) -> Self {
+        Self {
+            phys_delta_time: delta_time,
+            ..*self
+        }
+    }
 }

+ 11 - 11
game/project.godot

@@ -12,7 +12,7 @@ config_version=5
 
 config/name="CraftdigGameTest"
 run/main_scene="res://root_scene.tscn"
-config/features=PackedStringArray("4.2", "Forward Plus")
+config/features=PackedStringArray("4.3", "Forward Plus")
 config/icon="res://icon.svg"
 
 [compression]
@@ -28,47 +28,47 @@ window/size/viewport_height=720
 
 move_right={
 "deadzone": 0.5,
-"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":68,"key_label":0,"unicode":100,"echo":false,"script":null)
+"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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
 ]
 }
 move_forward={
 "deadzone": 0.5,
-"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":87,"key_label":0,"unicode":119,"echo":false,"script":null)
+"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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
 ]
 }
 move_left={
 "deadzone": 0.5,
-"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":65,"key_label":0,"unicode":97,"echo":false,"script":null)
+"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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
 ]
 }
 move_backward={
 "deadzone": 0.5,
-"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":83,"key_label":0,"unicode":115,"echo":false,"script":null)
+"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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
 ]
 }
 move_jump={
 "deadzone": 0.5,
-"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":32,"key_label":0,"unicode":32,"echo":false,"script":null)
+"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":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
 ]
 }
 move_sprint={
 "deadzone": 0.5,
-"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":4194326,"key_label":0,"unicode":0,"echo":false,"script":null)
+"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":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
 ]
 }
 move_crouch={
 "deadzone": 0.5,
-"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,"echo":false,"script":null)
+"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)
 ]
 }
 move_fly_mode={
 "deadzone": 0.5,
-"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":70,"key_label":0,"unicode":102,"echo":false,"script":null)
+"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":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
 ]
 }
 change_mouse_input={
 "deadzone": 0.5,
-"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":4194306,"key_label":0,"unicode":0,"echo":false,"script":null)
+"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":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
 ]
 }
 player_attack={
@@ -83,7 +83,7 @@ player_interact={
 }
 player_cycle_block={
 "deadzone": 0.5,
-"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":71,"key_label":0,"unicode":103,"echo":false,"script":null)
+"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":71,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null)
 ]
 }
 

+ 1 - 1
game/root_scene.tscn

@@ -27,7 +27,7 @@ size = Vector3(1.05, 1.05, 1.05)
 [node name="Root" type="Node"]
 
 [node name="CraftdigBridge" type="CraftdigBridge" parent="." node_paths=PackedStringArray("player", "camera", "selection", "lbl_coords", "lbl_blkty")]
-chunk_update_rate = 8
+chunk_update_rate = 128
 init_world_script = "
 p 0 0 0
 

+ 0 - 0
gdio/src/bridge.rs


Some files were not shown because too many files changed in this diff