diff options
| author | tpart <tpart120@proton.me> | 2025-12-20 01:52:46 +0100 |
|---|---|---|
| committer | tpart <tpart120@proton.me> | 2025-12-20 01:52:46 +0100 |
| commit | 202a63be327bc62b514a73e325b73c055026fa48 (patch) | |
| tree | 90f444c138a043b3ab32787fe972c9007f262819 /client | |
| parent | 163cb0e76a5c4a70b0bf0fc591fe02e585731e29 (diff) | |
| download | hurrycurry-202a63be327bc62b514a73e325b73c055026fa48.tar hurrycurry-202a63be327bc62b514a73e325b73c055026fa48.tar.bz2 hurrycurry-202a63be327bc62b514a73e325b73c055026fa48.tar.zst | |
Player prediction part 1
Diffstat (limited to 'client')
| -rw-r--r-- | client/game.gd | 2 | ||||
| -rw-r--r-- | client/multiplayer.gd | 3 | ||||
| -rw-r--r-- | client/player/controllable_player.gd | 91 | ||||
| -rw-r--r-- | client/player/player.gd | 78 |
4 files changed, 87 insertions, 87 deletions
diff --git a/client/game.gd b/client/game.gd index d57099ed..c1a99a6c 100644 --- a/client/game.gd +++ b/client/game.gd @@ -154,7 +154,7 @@ func handle_packet(p): "movement": if not players.has(p.player): return var player_instance: Player = players[p.player] - player_instance.update_position(p.pos, p.rot, p.boost) + player_instance.update_position(p.pos, p.dir, p.rot, p.boost) if p.player == my_player_id: last_position = p.pos "movement_sync": if not players.has(my_player_id): return diff --git a/client/multiplayer.gd b/client/multiplayer.gd index d5d31322..a0260df5 100644 --- a/client/multiplayer.gd +++ b/client/multiplayer.gd @@ -107,7 +107,7 @@ func fix_packet_types(val): for k in val.keys(): if typeof(val[k]) == TYPE_ARRAY and val[k].size() == 2 and typeof(val[k][0]) == TYPE_FLOAT and typeof(val[k][1]) == TYPE_FLOAT: if k in ["tile"]: newval[k] = Vector2i(val[k][0], val[k][1]) - elif k in ["pos", "position"]: newval[k] = Vector2(val[k][0], val[k][1]) + elif k in ["pos", "position", "dir"]: newval[k] = Vector2(val[k][0], val[k][1]) else: newval[k] = val[k] # TODO reenable when fixed # elif k in ["player", "id"] and typeof(val[k]) == TYPE_FLOAT: @@ -148,6 +148,7 @@ func send_movement(player, pos: Vector2, direction: Vector2, boost: bool): "dir": [direction.x, direction.y], "boost": boost }) + func send_tile_interact(player, pos: Vector2i, edge: bool, hand: int): @warning_ignore("incompatible_ternary") diff --git a/client/player/controllable_player.gd b/client/player/controllable_player.gd index a9cdba2b..15567d65 100644 --- a/client/player/controllable_player.gd +++ b/client/player/controllable_player.gd @@ -41,42 +41,26 @@ class TileTarget extends InteractTarget: target_visual_pos = target_visual_pos_ target_visual_rot = target_visual_rot_ -const PLAYER_SPEED = 55 -const PLAYER_FRICTION = 15 -const BOOST_FACTOR = 2.5 -const BOOST_DURATION = 0.3 -const BOOST_RESTORE = 0.5 const MAX_PLAYER_INTERACT_DIST := 1.9 var onscreen_controls = preload("res://player/onscreen_controls/controls.tscn").instantiate() var marker: Marker = preload("res://player/marker/marker.tscn").instantiate() var current_interact_target: InteractTarget = EmptyTarget.new() -var facing := Vector2(1, 0) -var velocity_ := Vector2(0, 0) -var direction := Vector2(0, 0) -var stamina := 0. var chat_open := false var input_rotation = 0 -var movement_update_timer := Timer.new() - var vibration_timer := Timer.new() var current_vibration_strength := 0. var current_vibration_change := 0. +var was_boosting = boosting + func _ready(): vibration_timer = Timer.new() vibration_timer.wait_time = 0.1 vibration_timer.timeout.connect(_on_vibration_timeout) add_child(vibration_timer) - movement_update_timer.one_shot = false - add_child(movement_update_timer) - movement_update_timer.start() - movement_update_timer.connect("timeout", func(): - if game.mp != null and game.join_state == Game.JoinState.JOINED: - game.mp.send_movement(game.my_player_id, position_, direction, boosting) - ) add_child(onscreen_controls) add_child(marker) super() @@ -85,22 +69,10 @@ func _ready(): character.username_tag.visible = not fps ) -const MAX_DT = 1. / 50. func _process(delta): super(delta) marker.position = G.interpolate(marker.position, current_interact_target.target_visual_pos, delta * 30.) if is_despawning: return - while delta > 0.001: - var dt = min(delta, MAX_DT) - _process_movement(dt) - delta -= dt - if velocity_.length() > 0.01: - movement_update_timer.timeout.emit() - movement_update_timer.wait_time = 1. / 50. - else: - movement_update_timer.timeout.emit() - movement_update_timer.wait_time = 1. / 5. - update_touch_scrolls() func _input(event: InputEvent): @@ -139,7 +111,13 @@ func _process_movement(delta): interact(current_interact_target) var boost = Input.is_action_pressed("boost") or (Settings.read("gameplay.latch_boost") and boosting) - var was_boosting = boosting + + if direction != input or boosting != was_boosting: + if game.mp != null and game.join_state == Game.JoinState.JOINED: + # print("sending %s %s" % [boost, was_boosting]) + game.mp.send_movement(game.my_player_id, position_, input, boost) + + was_boosting = boosting direction = input update_movement(delta, boost) if boosting and not was_boosting and Settings.read("gameplay.vibration"): @@ -149,51 +127,6 @@ func _process_movement(delta): position_anim = position_ rotation_anim = rotation_ -func update_movement(dt: float, boost: bool): - # This function implements the server's movement system. - # It may not be modified, so that it matches the server's logic. - direction = direction.limit_length(1.) - if direction.length() > 0.05: - facing = direction + (facing - direction) * exp(-dt * 10.) - if direction.length() < 0.5: - direction *= 0 - rotation_ = atan2(facing.x, facing.y); - boost = boost and direction.length() > 0.1 - boosting = boost and (boosting or stamina >= 1.0) and stamina > 0 - if boosting: stamina -= dt / BOOST_DURATION - else: stamina += dt / BOOST_RESTORE - stamina = max(min(stamina, 1.0), 0.0) - var speed = PLAYER_SPEED * (BOOST_FACTOR if boosting else 1.) - velocity_ += direction * dt * speed - position_ += velocity_ * dt - velocity_ = velocity_ * exp(-dt * PLAYER_FRICTION) - collide(dt) - -func collide(dt: float): - for xo in range(-1, 2): - for yo in range(-1, 2): - var tile = Vector2i(xo, yo) + Vector2i(position_) - if !game.get_tile_collision(tile): continue - tile = Vector2(tile) - var d = aabb_point_distance(tile, tile + Vector2.ONE, position_) - if d > PLAYER_SIZE: continue - var h = 0.01; - var d_sample_x = aabb_point_distance(tile, tile + Vector2.ONE, position_ + Vector2(h, 0)) - var d_sample_y = aabb_point_distance(tile, tile + Vector2.ONE, position_ + Vector2(0, h)) - var grad = (Vector2(d_sample_x - d, d_sample_y - d)) / h - - position_ += (PLAYER_SIZE - d) * grad; - velocity_ -= grad * grad.dot(velocity_) - for player: Player in game.players.values(): - var diff = position_ - player.position_ - var d = diff.length() - if d < 0.01: continue - if d >= PLAYER_SIZE * 2: continue - var norm = diff.normalized(); - var f = 100 / (1 + d) - velocity_.x += norm.x * f * dt - velocity_.y += norm.y * f * dt - func is_input_enabled() -> bool: return not game.menu.covered and not Global.game_paused @@ -203,11 +136,7 @@ func update_touch_scrolls(): if onscreen_controls.touch_enabled: onscreen_controls.visible = is_input_enabled() -func aabb_point_distance(mi: Vector2, ma: Vector2, p: Vector2) -> float: - # Calculates the signed distance of a point p to an axis aligned bounding box - return (p - p.clamp(mi, ma)).length() - -func update_position(_new_position: Vector2, _new_rotation: float, _new_boosting: bool): +func update_position(_new_position: Vector2, _new_direction: Vector2, _new_rotation: float, _new_boosting: bool): # Ignore movement packets from server. (This is not a movement sync) pass diff --git a/client/player/player.gd b/client/player/player.gd index 18ebcc7f..645fa72b 100644 --- a/client/player/player.gd +++ b/client/player/player.gd @@ -21,7 +21,11 @@ const ITEM_BUBBLE: PackedScene = preload("res://player/item_bubble.tscn") const EFFECT: PackedScene = preload("res://player/particles/effect.tscn") const PLAYER_SIZE: float = 0.4 -const SPEED: float = 25. +const PLAYER_SPEED = 55 +const PLAYER_FRICTION = 15 +const BOOST_FACTOR = 2.5 +const BOOST_DURATION = 0.3 +const BOOST_RESTORE = 0.5 var game: Game var id := -1. # float because json @@ -54,6 +58,11 @@ var is_customer: bool var is_chef: bool var current_item_message = null +var velocity_ := Vector2(0, 0) +var direction := Vector2(0, 0) +var facing := Vector2(1, 0) +var stamina := 0. + var _anim_angle: float = 0.0 const DEFAULT_HAND_BASE_POSITION_CENTER: Vector3 = Vector3(0, .425, .4) @@ -115,10 +124,61 @@ func _ready(): Settings.hook_changed_init("gameplay.usernames", self, update_username_tag) -func update_position(new_position: Vector2, new_rotation: float, new_boosting: bool): +func update_position(new_position: Vector2, new_direction: Vector2, new_rotation: float, new_boosting: bool): position_ = new_position rotation_ = new_rotation boosting = new_boosting + direction = new_direction + print("BOOSTING: ", boosting) + +func update_movement(dt: float, boost: bool): + # This function implements the server's movement system. + # It may not be modified, so that it matches the server's logic. + direction = direction.limit_length(1.) + if direction.length() > 0.05: + facing = direction + (facing - direction) * exp(-dt * 10.) + if direction.length() < 0.5: + direction *= 0 + rotation_ = atan2(facing.x, facing.y); + boost = boost and direction.length() > 0.1 + boosting = boost and (boosting or stamina >= 1.0) and stamina > 0 + if boosting: stamina -= dt / BOOST_DURATION + else: stamina += dt / BOOST_RESTORE + stamina = max(min(stamina, 1.0), 0.0) + var speed = PLAYER_SPEED * (BOOST_FACTOR if boosting else 1.) + velocity_ += direction * dt * speed + position_ += velocity_ * dt + velocity_ = velocity_ * exp(-dt * PLAYER_FRICTION) + collide(dt) + +func collide(dt: float): + for xo in range(-1, 2): + for yo in range(-1, 2): + var tile = Vector2i(xo, yo) + Vector2i(position_) + if !game.get_tile_collision(tile): continue + tile = Vector2(tile) + var d = aabb_point_distance(tile, tile + Vector2.ONE, position_) + if d > PLAYER_SIZE: continue + var h = 0.01; + var d_sample_x = aabb_point_distance(tile, tile + Vector2.ONE, position_ + Vector2(h, 0)) + var d_sample_y = aabb_point_distance(tile, tile + Vector2.ONE, position_ + Vector2(0, h)) + var grad = (Vector2(d_sample_x - d, d_sample_y - d)) / h + + position_ += (PLAYER_SIZE - d) * grad; + velocity_ -= grad * grad.dot(velocity_) + for player: Player in game.players.values(): + var diff = position_ - player.position_ + var d = diff.length() + if d < 0.01: continue + if d >= PLAYER_SIZE * 2: continue + var norm = diff.normalized(); + var f = 100 / (1 + d) + velocity_.x += norm.x * f * dt + velocity_.y += norm.y * f * dt + +func aabb_point_distance(mi: Vector2, ma: Vector2, p: Vector2) -> float: + # Calculates the signed distance of a point p to an axis aligned bounding box + return (p - p.clamp(mi, ma)).length() func update_username_tag(state): var tag: Label3D = character.username_tag @@ -167,6 +227,7 @@ func pass_to(player: Player, hfrom: int, hto: int): push_error("target is already holding an item") player.set_item(i, hto) +const MAX_DT = 1. / 50. func _process(delta): for h in hand.size(): if hand[h] != null: @@ -178,15 +239,24 @@ func _process(delta): movement_base.position.x = position_anim.x movement_base.position.z = position_anim.y movement_base.rotation.y = rotation_anim - walking = walking or position_.distance_squared_to(position_anim) > 0.001 + walking = direction.length_squared() > 0.1 character.walking = walking character.boosting = boosting - walking = false if is_despawning: movement_base.scale /= exp(delta * 8) if movement_base.scale.length() < 0.01: self.queue_free() else: movement_base.scale = Vector3.ONE * G.interpolate(movement_base.scale.x, 1, delta * 8) + + while delta > 0.001: + var dt = min(delta, MAX_DT) + _process_movement(delta) + position_anim = position_ + rotation_anim = rotation_ + delta -= dt + +func _process_movement(dt): + update_movement(dt, boosting) func item_message(item_name: String, timeout_initial: float, timeout_remaining: float): if item_bubble != null: |