class_name Figures extends Node2D ## The figures of the other players, with their names, drawn by `art`. A new ## position comes over the network about ten times a second; in between, ## each figure glides toward the latest one, stepping and facing the way it ## goes. ## How quickly a figure closes the gap to its latest position, per second. const GLIDE := 15.0 ## Pixels per frame below which a gliding figure counts as standing. const STANDING := 0.05 ## Size and width of the names, in pixels of the playing field. The names ## are drawn at the size they have on screen, so that they stay sharp. const NAME_SIZE := 8 const NAME_WIDTH := 80.0 const NAME_OUTLINE := Color(0.05, 0.05, 0.06) var session: GwSession var art: Art var _shown: Dictionary[int, Vector2] = {} var _latest: Dictionary[int, Vector2] = {} var _facing: Dictionary[int, float] = {} var _step: Dictionary[int, float] = {} var _moving: Dictionary[int, bool] = {} ## Moves the figure of `player` toward `position`. A figure that was not ## shown yet appears there at once. func place(player: int, position: Vector2) -> void: if not _shown.has(player): _shown[player] = position _latest[player] = position queue_redraw() ## Removes the figure of `player`. func forget(player: int) -> void: for figures: Dictionary in [_shown, _latest, _facing, _step, _moving]: figures.erase(player) queue_redraw() func clear() -> void: for figures: Dictionary in [_shown, _latest, _facing, _step, _moving]: figures.clear() queue_redraw() ## Where the figure of `player` is shown, or Vector2.INF if it is not. func shown_at(player: int) -> Vector2: return _shown.get(player, Vector2.INF) ## 1.0 if the figure of `player` faces right, -1.0 if it faces left. func facing_of(player: int) -> float: return _facing.get(player, 1.0) func _process(delta: float) -> void: if _shown.is_empty(): return var weight := minf(1.0, GLIDE * delta) for player in _shown: var before := _shown[player] _shown[player] = before.lerp(_latest[player], weight) var moved := _shown[player] - before if absf(moved.x) > STANDING: _facing[player] = signf(moved.x) _step[player] = _step.get(player, 0.0) + moved.length() * Avatar.STEP_PER_PIXEL _moving[player] = moved.length() > STANDING queue_redraw() func _draw() -> void: if art == null: return var font := ThemeDB.fallback_font # The camera enlarges the field. Text drawn at its size on screen and then # shrunk stays sharp; text drawn small and enlarged would blur. var zoom := maxf(1.0, get_global_transform_with_canvas().get_scale().x) for player in _shown: var at := _shown[player] var state := {"player": player, "facing": facing_of(player), "step": _step.get(player, 0.0), "moving": _moving.get(player, false)} art.draw(self, Art.FIGURE, Avatar.figure_rect(at), state) if session != null: var label := Vector2(at.x - NAME_WIDTH / 2.0, Avatar.figure_rect(at).position.y - 2.0) var text := session.player_name(player) var size := roundi(NAME_SIZE * zoom) draw_set_transform(label, 0.0, Vector2.ONE / zoom) draw_string_outline(font, Vector2.ZERO, text, HORIZONTAL_ALIGNMENT_CENTER, NAME_WIDTH * zoom, size, maxi(2, roundi(zoom * 1.5)), NAME_OUTLINE) draw_string(font, Vector2.ZERO, text, HORIZONTAL_ALIGNMENT_CENTER, NAME_WIDTH * zoom, size) draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)