nodes/mob.pyΒΆ
Part of Dodge the Creeps.
1"""Mob: fly/walk/swim creep that drifts across the screen at a fixed velocity."""
2
3from __future__ import annotations
4
5import math
6import random
7
8from simvx.core import (
9 AnimatedSprite2D,
10 CharacterBody2D,
11 CircleShape2D,
12 Vec2,
13)
14
15from .sprite_sheets import mob_sheet
16
17MOB_TYPES = ("fly", "walk", "swim")
18
19
20class Mob(CharacterBody2D):
21 """Random-type creep with a constant linear velocity.
22
23 A ``CharacterBody2D`` is an ordinary ``KINEMATIC`` physics body, so two of them
24 block each other when their layers and masks mutually opt in. This project
25 never calls ``move_and_slide``, so no character here sweeps and none can block
26 another: the collider exists for the group / overlap queries only, which is why
27 the default layer and mask need no retune.
28
29 The Godot original uses a Path2D + RigidBody2D + VisibleOnScreenNotifier.
30 Here the spawning logic is replicated in Main (random edge + random
31 rotation), and the mob just drifts in a straight line and despawns when
32 it leaves a generous off-screen margin.
33 """
34
35 DESPAWN_MARGIN = 200.0
36
37 def __init__(self, **kwargs):
38 super().__init__(shape=CircleShape2D(30.0), **kwargs)
39 self.add_to_group("mobs")
40
41 kind = random.choice(MOB_TYPES)
42 sheet, fw, fh = mob_sheet(kind)
43 self.sprite = self.add_child(
44 AnimatedSprite2D(
45 texture=sheet,
46 frames_h=2,
47 frames_v=1,
48 width=fw,
49 height=fh,
50 scale=Vec2(0.75, 0.75),
51 name="AnimatedSprite2D",
52 )
53 )
54 # Each kind ships a 2-frame loop. Speeds taken from upstream
55 # mob.tscn: walk/swim 4 fps, fly 3 fps.
56 fps = 3.0 if kind == "fly" else 4.0
57 self.sprite.add_animation(kind, frames=[0, 1], fps=fps, loop=True)
58 self.sprite.play(kind)
59
60 def configure(self, position: Vec2, rotation: float, speed: float):
61 """Set spawn pose + velocity. Called by Main after instancing."""
62 self.position = Vec2(position)
63 self.rotation = rotation
64 self.velocity = Vec2(math.cos(rotation), math.sin(rotation)) * speed
65
66 def on_fixed_update(self, dt: float):
67 self.position += self.velocity * dt
68 # Off-screen despawn (Godot uses VisibleOnScreenNotifier2D).
69 x, y = float(self.position.x), float(self.position.y)
70 m = self.DESPAWN_MARGIN
71 width, height = self.viewport_size
72 if x < -m or x > float(width) + m or y < -m or y > float(height) + m:
73 self.destroy()