From eababca6d1f18fa572853834b30ba76f0f78be68 Mon Sep 17 00:00:00 2001 From: fheinrich <fheinrich@techfak.uni-bielefeld.de> Date: Fri, 19 Jan 2024 16:48:35 +0100 Subject: [PATCH] Movement based on timedelta, independent of gui fps --- overcooked_simulator/fastapi_game_server.py | 14 ++++++--- .../game_content/environment_config.yaml | 2 +- .../gui_2d_vis/overcooked_gui.py | 23 +++++++------- .../overcooked_environment.py | 30 ++++++++++++------- overcooked_simulator/player.py | 10 ++++++- tests/test_start.py | 2 +- 6 files changed, 53 insertions(+), 28 deletions(-) diff --git a/overcooked_simulator/fastapi_game_server.py b/overcooked_simulator/fastapi_game_server.py index 2a9fa234..a725684f 100644 --- a/overcooked_simulator/fastapi_game_server.py +++ b/overcooked_simulator/fastapi_game_server.py @@ -102,13 +102,17 @@ def parse_action(message: str) -> Action: value = np.array([x, y], dtype=float) else: value = None - action = Action(message_dict["player_name"], message_dict["act_type"], value) + action = Action( + message_dict["player_name"], + message_dict["act_type"], + value, + duration=message_dict["duration"], + ) return action def manage_message(message: str): answer = None - print("MESSAGE:", message) if "get_state" in message: return oc_api.get_state() @@ -119,7 +123,8 @@ def manage_message(message: str): action = parse_action(message) oc_api.simulator.enter_action(action) - return oc_api.get_state() + answer = oc_api.get_state() + return answer @app.get("/") @@ -134,8 +139,9 @@ async def websocket_endpoint(websocket: WebSocket, client_id: int): try: while True: message = await websocket.receive_text() + # print("MESSAGE:", message) answer = manage_message(message) - print("ANSWER:", answer) + # print("ANSWER:", answer) await manager.send_personal_message(answer, websocket) except WebSocketDisconnect: diff --git a/overcooked_simulator/game_content/environment_config.yaml b/overcooked_simulator/game_content/environment_config.yaml index a88b5d10..f23b3902 100644 --- a/overcooked_simulator/game_content/environment_config.yaml +++ b/overcooked_simulator/game_content/environment_config.yaml @@ -59,5 +59,5 @@ orders: player_config: radius: 0.4 - move_dist: 0.15 + player_speed_units_per_seconds: 5 interaction_range: 1.6 \ No newline at end of file diff --git a/overcooked_simulator/gui_2d_vis/overcooked_gui.py b/overcooked_simulator/gui_2d_vis/overcooked_gui.py index b6e6cfdf..0500a3a6 100644 --- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -201,7 +201,7 @@ class PyGameGUI: return player_colors - def handle_keys(self, websocket): + def handle_keys(self): """Handles keyboard inputs. Sends action for the respective players. When a key is held down, every frame an action is sent in this function. """ @@ -216,10 +216,12 @@ class PyGameGUI: if np.linalg.norm(move_vec) != 0: move_vec = move_vec / np.linalg.norm(move_vec) - action = Action(key_set.name, "movement", move_vec) - self.send_action(action, websocket) + action = Action( + key_set.name, "movement", move_vec, duration=2 / (self.FPS) + ) + self.send_action(action) - def handle_key_event(self, event, websocket): + def handle_key_event(self, event): """Handles key events for the pickup and interaction keys. Pickup is a single action, for interaction keydown and keyup is necessary, because the player has to be able to hold the key down. @@ -230,15 +232,15 @@ class PyGameGUI: for key_set in self.player_key_sets: if event.key == key_set.pickup_key and event.type == pygame.KEYDOWN: action = Action(key_set.name, "pickup", "pickup") - self.send_action(action, websocket) + self.send_action(action) if event.key == key_set.interact_key: if event.type == pygame.KEYDOWN: action = Action(key_set.name, "interact", "keydown") - self.send_action(action, websocket) + self.send_action(action) elif event.type == pygame.KEYUP: action = Action(key_set.name, "interact", "keyup") - self.send_action(action, websocket) + self.send_action(action) def draw_background(self): """Visualizes a game background.""" @@ -858,7 +860,7 @@ class PyGameGUI: self.reset_window_size() log.debug("Pressed finished button") - def send_action(self, action: Action, websocket): + def send_action(self, action: Action): """Sends an action to the game environment. Args: @@ -872,6 +874,7 @@ class PyGameGUI: "player_name": action.player, "act_type": action.act_type, "value": value, + "duration": action.duration, } _ = self.websocket_communicate(message_dict) @@ -938,7 +941,7 @@ class PyGameGUI: and self.menu_state == MenuStates.Game ): pass - self.handle_key_event(event, websocket) + self.handle_key_event(event) self.manager.process_events(event) @@ -962,7 +965,7 @@ class PyGameGUI: self.draw_background() - self.handle_keys(websocket) + self.handle_keys() # state = self.simulator.get_state() self.draw(state) diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py index 28e35742..2a63df64 100644 --- a/overcooked_simulator/overcooked_environment.py +++ b/overcooked_simulator/overcooked_environment.py @@ -40,7 +40,7 @@ log = logging.getLogger(__name__) class Action: """Action class, specifies player, action type and action itself.""" - def __init__(self, player, act_type, action): + def __init__(self, player, act_type, action, duration=0): self.player = player self.act_type = act_type assert self.act_type in [ @@ -49,6 +49,7 @@ class Action: "interact", ], "Unknown action type" self.action = action + self.duration = duration def __repr__(self): return f"Action({self.player},{self.act_type},{self.action})" @@ -335,8 +336,10 @@ class Environment: player = self.players[action.player] if action.act_type == "movement": - with self.lock: - self.perform_movement(player, action.action) + player.set_movement( + action.action, + self.env_time + datetime.timedelta(seconds=action.duration), + ) else: counter = self.get_facing_counter(player) @@ -351,10 +354,7 @@ class Environment: player.last_interacted_counter = counter if action.action == "keyup": if player.last_interacted_counter: - with self.lock: - player.perform_interact_hold_stop( - player.last_interacted_counter - ) + player.perform_interact_hold_stop(player.last_interacted_counter) def get_closest_counter(self, point: np.ndarray): """Determines the closest counter for a given 2d-coordinate point in the env. @@ -385,7 +385,7 @@ class Environment: facing_counter = self.get_closest_counter(player.facing_point) return facing_counter - def perform_movement(self, player: Player, move_vector: np.array): + def perform_movement(self, player: Player, timedelta): """Moves a player in the direction specified in the action.action. If the player collides with a counter or other player through this movement, then they are not moved. (The extended code with the two ifs is for sliding movement at the counters, which feels a bit smoother. @@ -402,7 +402,11 @@ class Environment: """ old_pos = player.pos.copy() - step = move_vector * player.move_dist + move_vector = player.current_movement + + d_time = timedelta.total_seconds() + step = move_vector * (player.move_speed * d_time) + player.move(step) if self.detect_collision(player): collided_players = self.get_collided_players(player) @@ -412,7 +416,8 @@ class Environment: pushing_vector = pushing_vector / np.linalg.norm(pushing_vector) old_pos_other = collided_player.pos.copy() - self.perform_movement(collided_player, pushing_vector) + collided_player.current_movement = pushing_vector + self.perform_movement(collided_player, timedelta) if self.detect_collision_counters( collided_player ) or self.detect_collision_world_bounds(collided_player): @@ -572,6 +577,10 @@ class Environment: with self.lock: if not self.game_ended: + for player in self.players.values(): + if self.env_time < player.movement_until: + self.perform_movement(player, passed_time) + for counter in self.counters: if isinstance(counter, (CuttingBoard, Stove, Sink, PlateDispenser)): counter.progress(passed_time=passed_time, now=self.env_time) @@ -663,7 +672,6 @@ class Environment: closest_addon.pos - pos ), f"No SinkAddon connected to Sink at pos {pos}" counter.set_addon(closest_addon) - pass @staticmethod diff --git a/overcooked_simulator/player.py b/overcooked_simulator/player.py index 63f803c8..409ef065 100644 --- a/overcooked_simulator/player.py +++ b/overcooked_simulator/player.py @@ -1,3 +1,4 @@ +import datetime import logging from collections import deque from typing import Optional, Any @@ -34,7 +35,7 @@ class Player: self.holding: Optional[Item] = None self.radius: float = self.player_config["radius"] - self.move_dist: int = self.player_config["move_dist"] + self.move_speed: int = self.player_config["player_speed_units_per_seconds"] self.interaction_range: int = self.player_config["interaction_range"] self.facing_direction: npt.NDArray[float] = np.array([0, 1]) self.last_interacted_counter: Optional[ @@ -44,6 +45,13 @@ class Player: self.current_nearest_counter: Optional[Counter] = None self.facing_point: npt.NDArray[float] = np.zeros(2, float) + self.current_movement: npt.NDArray[2] = np.zeros(2, float) + self.movement_until: datetime.datetime = datetime.datetime.now() + + def set_movement(self, move_vector, move_until): + self.current_movement = move_vector + self.movement_until = move_until + def move(self, movement: npt.NDArray[float]): """Moves the player position by the given movement vector. A unit direction vector multiplied by move_dist is added to the player position. diff --git a/tests/test_start.py b/tests/test_start.py index d6010a9e..0f71d0e7 100644 --- a/tests/test_start.py +++ b/tests/test_start.py @@ -95,7 +95,7 @@ def test_movement(): sim.enter_action(move_action) expected = start_pos + do_moves_number * ( - move_direction * sim.env.players[player_name].move_dist + move_direction * sim.env.players[player_name].move_speed ) assert np.isclose( -- GitLab