|
| 1 | +local TOUCH = hash("touch") |
| 2 | + |
| 3 | +local START_TARGET = vmath.vector3(360, 420, 0) |
| 4 | +local SOFT_START = vmath.vector3(270, 310, 0) |
| 5 | +local TIGHT_START = vmath.vector3(450, 310, 0) |
| 6 | + |
| 7 | +local SOFT_LINE_COLOR = vmath.vector4(1.0, 0.65, 0.22, 1.0) |
| 8 | +local TIGHT_LINE_COLOR = vmath.vector4(0.35, 0.85, 1.0, 1.0) |
| 9 | +local TARGET_LINE_COLOR = vmath.vector4(0.45, 1.0, 0.65, 1.0) |
| 10 | + |
| 11 | +local function draw_line(from, to, color) |
| 12 | + -- <1> Draw a debug/helper line through the render socket. |
| 13 | + -- The line exists for one frame only, so it must be redrawn every update. |
| 14 | + msg.post("@render:", "draw_line", { |
| 15 | + start_point = from, |
| 16 | + end_point = to, |
| 17 | + color = color |
| 18 | + }) |
| 19 | +end |
| 20 | + |
| 21 | +local function set_target(self, position) |
| 22 | + -- <2> Store the current mouse-joint target as a world-space point. |
| 23 | + -- The mouse/touch pointer is not a Box2D body. It is only a target position |
| 24 | + -- that the mouse joint tries to pull the dynamic body toward. |
| 25 | + self.target = vmath.vector3(position.x, position.y, 0) |
| 26 | + |
| 27 | + -- <3> Move the visible target marker so the user can see the point followed |
| 28 | + -- by both mouse joints. |
| 29 | + go.set_position(self.target, self.target_url) |
| 30 | +end |
| 31 | + |
| 32 | +local function reset_body(body, position) |
| 33 | + -- <4> Set the Box2D body transform directly. |
| 34 | + -- This is good for example setup/reset. Do not use this as normal per-frame |
| 35 | + -- movement, because it teleports the body and can produce non-physical motion. |
| 36 | + b2d.body.set_transform(body, position, 0) |
| 37 | + |
| 38 | + -- <5> Clear previous motion so the example starts from a deterministic state. |
| 39 | + b2d.body.set_linear_velocity(body, vmath.vector3()) |
| 40 | + b2d.body.set_angular_velocity(body, 0) |
| 41 | + |
| 42 | + -- <6> Wake the body so it reacts immediately after reset. |
| 43 | + b2d.body.set_awake(body, true) |
| 44 | +end |
| 45 | + |
| 46 | +local function setup_body(body) |
| 47 | + -- <7> Disable gravity for this body. |
| 48 | + -- This keeps the example focused on the mouse-joint spring behaviour. |
| 49 | + b2d.body.set_gravity_scale(body, 0) |
| 50 | + |
| 51 | + -- <8> Prevent the body from rotating. |
| 52 | + -- Rotation would add noise to the visual explanation, while this example is |
| 53 | + -- about soft versus tight positional following. |
| 54 | + b2d.body.set_fixed_rotation(body, true) |
| 55 | + |
| 56 | + -- <9> Add linear damping. |
| 57 | + -- This reduces endless sliding and helps the body settle after movement. |
| 58 | + b2d.body.set_linear_damping(body, 1.5) |
| 59 | +end |
| 60 | + |
| 61 | +local function create_mouse_joints(self) |
| 62 | + -- <10> Get native Box2D body handles from Defold collision object components. |
| 63 | + -- `target` acts as the static/reference body for the mouse joints. |
| 64 | + local anchor_body = b2d.get_body(msg.url(nil, "target", "collisionobject")) |
| 65 | + self.soft_body = b2d.get_body(msg.url(nil, "soft_body", "collisionobject")) |
| 66 | + self.tight_body = b2d.get_body(msg.url(nil, "tight_body", "collisionobject")) |
| 67 | + |
| 68 | + setup_body(self.soft_body) |
| 69 | + setup_body(self.tight_body) |
| 70 | + |
| 71 | + reset_body(self.soft_body, SOFT_START) |
| 72 | + reset_body(self.tight_body, TIGHT_START) |
| 73 | + |
| 74 | + -- <11> Create a soft V2 mouse joint. |
| 75 | + -- In Box2D V2-style definitions, spring frequency is controlled by |
| 76 | + -- `frequency`, not `hertz`. |
| 77 | + self.soft_joint = b2d.joint.create_mouse(anchor_body, self.soft_body, { |
| 78 | + -- <12> Initial world target. This value is later updated every frame. |
| 79 | + target = self.target, |
| 80 | + |
| 81 | + -- <13> Maximum force the joint can apply. |
| 82 | + -- Lower max force allows more visible lag/stretch. |
| 83 | + max_force = 850, |
| 84 | + |
| 85 | + -- <14> V2 spring frequency. |
| 86 | + -- Lower frequency makes the body follow more softly and slowly. |
| 87 | + frequency = 1.5, |
| 88 | + |
| 89 | + -- <15> Damping ratio. |
| 90 | + -- Lower damping allows more bounce/overshoot. |
| 91 | + damping_ratio = 0.35, |
| 92 | + |
| 93 | + -- <16> The connected bodies should not collide with each other. |
| 94 | + collide_connected = false, |
| 95 | + }) |
| 96 | + |
| 97 | + -- <17> Create a tighter V2 mouse joint. |
| 98 | + -- It follows the same target, but uses stronger/stiffer parameters. |
| 99 | + self.tight_joint = b2d.joint.create_mouse(anchor_body, self.tight_body, { |
| 100 | + target = self.target, |
| 101 | + |
| 102 | + -- <18> Higher max force means the body can be pulled more aggressively. |
| 103 | + max_force = 6500, |
| 104 | + |
| 105 | + -- <19> Higher frequency means a stiffer/faster spring response. |
| 106 | + frequency = 8.0, |
| 107 | + |
| 108 | + -- <20> Higher damping removes more oscillation. |
| 109 | + damping_ratio = 0.9, |
| 110 | + |
| 111 | + collide_connected = false, |
| 112 | + }) |
| 113 | +end |
| 114 | + |
| 115 | +local function update_auto_target(self, dt) |
| 116 | + if self.user_control then |
| 117 | + return |
| 118 | + end |
| 119 | + |
| 120 | + -- <21> Animate the target automatically before user interaction. |
| 121 | + -- This makes the example demonstrate itself on the examples website. |
| 122 | + self.time = self.time + dt |
| 123 | + |
| 124 | + set_target(self, vmath.vector3( |
| 125 | + 360 + math.cos(self.time * 1.35) * 170, |
| 126 | + 395 + math.sin(self.time * 1.10) * 95, |
| 127 | + 0 |
| 128 | +)) |
| 129 | +end |
| 130 | + |
| 131 | +local function update_joints(self) |
| 132 | +-- <22> Update both mouse joints with the current world target. |
| 133 | +-- The joints are created once, but their target can be changed every frame. |
| 134 | +b2d.joint.set_mouse_target(self.soft_joint, self.target) |
| 135 | +b2d.joint.set_mouse_target(self.tight_joint, self.target) |
| 136 | + |
| 137 | +-- <23> Keep the bodies awake while the target moves. |
| 138 | +-- This avoids relying on b2d.joint.wake_bodies(), which may not exist in |
| 139 | +-- the active V2 runtime. |
| 140 | +b2d.body.set_awake(self.soft_body, true) |
| 141 | +b2d.body.set_awake(self.tight_body, true) |
| 142 | +end |
| 143 | + |
| 144 | +local function draw_connections(self) |
| 145 | +-- <24> Read the current simulated positions from Box2D. |
| 146 | +-- These positions are the result of the physics step, not manually animated |
| 147 | +-- sprite positions. |
| 148 | +local soft_position = b2d.body.get_position(self.soft_body) |
| 149 | +local tight_position = b2d.body.get_position(self.tight_body) |
| 150 | + |
| 151 | +-- <25> Draw spring-like helper lines from the target to each body. |
| 152 | +-- The longer the line, the more that body is lagging/stretching. |
| 153 | +draw_line(self.target, soft_position, SOFT_LINE_COLOR) |
| 154 | +draw_line(self.target, tight_position, TIGHT_LINE_COLOR) |
| 155 | + |
| 156 | +-- <26> Draw a small cross at the target point. |
| 157 | +draw_line(self.target + vmath.vector3(-18, 0, 0), self.target + vmath.vector3(18, 0, 0), TARGET_LINE_COLOR) |
| 158 | +draw_line(self.target + vmath.vector3(0, -18, 0), self.target + vmath.vector3(0, 18, 0), TARGET_LINE_COLOR) |
| 159 | +end |
| 160 | + |
| 161 | +function init(self) |
| 162 | +-- <27> Run this script only when the active Box2D backend is V2. |
| 163 | +-- The same collection may contain both V2 and V3 scripts, but only the |
| 164 | +-- matching one should initialize. |
| 165 | +self.active = b2d.get_version().major == 2 |
| 166 | + |
| 167 | +if not self.active then |
| 168 | + return |
| 169 | +end |
| 170 | + |
| 171 | +self.target_url = msg.url(nil, "target", nil) |
| 172 | +self.time = 0 |
| 173 | +self.user_control = false |
| 174 | + |
| 175 | +set_target(self, START_TARGET) |
| 176 | +create_mouse_joints(self) |
| 177 | + |
| 178 | +-- <28> Show which backend-specific script is active. |
| 179 | +label.set_text("/info#version_label", "Box2D V2 mouse joint") |
| 180 | + |
| 181 | +-- <29> Required before this script receives on_input() callbacks. |
| 182 | +msg.post(".", "acquire_input_focus") |
| 183 | +end |
| 184 | + |
| 185 | +function update(self, dt) |
| 186 | +if not self.active then |
| 187 | + return |
| 188 | +end |
| 189 | + |
| 190 | +update_auto_target(self, dt) |
| 191 | +update_joints(self) |
| 192 | +draw_connections(self) |
| 193 | +end |
| 194 | + |
| 195 | +function on_input(self, action_id, action) |
| 196 | +if not self.active then |
| 197 | + return |
| 198 | +end |
| 199 | + |
| 200 | +-- <30> Mouse input usually comes with action_id == nil, while touch input |
| 201 | +-- uses the configured "touch" binding. Both provide x/y screen coordinates. |
| 202 | +if (action_id == TOUCH or action_id == nil) and action.x and action.y then |
| 203 | + self.user_control = true |
| 204 | + set_target(self, vmath.vector3(action.x, action.y, 0)) |
| 205 | +end |
| 206 | +end |
| 207 | + |
| 208 | +function final(self) |
| 209 | +if not self.active then |
| 210 | + return |
| 211 | +end |
| 212 | + |
| 213 | +-- <31> Destroy scripted joints explicitly when the script is finalized. |
| 214 | +b2d.joint.destroy(self.soft_joint) |
| 215 | +b2d.joint.destroy(self.tight_joint) |
| 216 | + |
| 217 | +msg.post(".", "release_input_focus") |
| 218 | +end |
| 219 | + |
| 220 | +--[[ |
| 221 | +1. `@render:` is Defold's render socket. The "draw_line" message draws a temporary helper line. |
| 222 | +2. A mouse joint follows a world-space target point. The pointer itself is not a physics body. |
| 223 | +3. The target marker is only visual. It helps show what the bodies are trying to follow. |
| 224 | +4. `b2d.body.set_transform()` directly changes the body's world transform. Good for setup/reset, not continuous movement. |
| 225 | +5. Clearing linear and angular velocity removes previous momentum. |
| 226 | +6. `b2d.body.set_awake()` wakes the body so it reacts immediately in the simulation. |
| 227 | +7. `b2d.body.set_gravity_scale(body, 0)` disables gravity for this body only. |
| 228 | +8. `b2d.body.set_fixed_rotation(body, true)` prevents rotation and keeps the visual demonstration clean. |
| 229 | +9. `b2d.body.set_linear_damping()` damps velocity over time and reduces endless drift. |
| 230 | +10. `b2d.get_body()` converts a Defold collision object URL into a Box2D body handle used by the b2d API. |
| 231 | +11. `b2d.joint.create_mouse()` creates a mouse joint between two bodies. The second body is the one visibly pulled toward the target. |
| 232 | +12. `target` is the initial world-space target point. |
| 233 | +13. `max_force` caps how strongly the joint can pull. Lower values create more visible stretch. |
| 234 | +14. `frequency` is the V2 spring frequency. Lower values feel softer; higher values feel tighter. |
| 235 | +15. `damping_ratio` controls bounce and overshoot. Lower values oscillate more; higher values settle faster. |
| 236 | +16. `collide_connected = false` prevents the connected bodies from colliding with each other. |
| 237 | +17. The tight joint uses the same mouse-joint API but different parameters. |
| 238 | +18. Higher `max_force` lets the joint correct the body's position more strongly. |
| 239 | +19. Higher `frequency` makes the spring respond faster. |
| 240 | +20. Higher `damping_ratio` removes more oscillation. |
| 241 | +21. Automatic target motion keeps the example animated before the user interacts. |
| 242 | +22. `b2d.joint.set_mouse_target()` updates the world target of an existing mouse joint. |
| 243 | +23. Waking the bodies avoids sleeping-body cases where the target changes but the body does not visibly react immediately. |
| 244 | +24. `b2d.body.get_position()` reads the current simulated world position of a body. |
| 245 | +25. The helper lines visualize how far each body stretches away from the target. |
| 246 | +26. The target cross is a visual helper, not part of the physics simulation. |
| 247 | +27. `b2d.get_version().major` selects the backend-specific script. |
| 248 | +28. `label.set_text()` updates the label. It uses message passing internally, so the URL must be correct. |
| 249 | +29. `acquire_input_focus` is needed to receive `on_input()`. |
| 250 | +30. Pointer input switches the example from automatic motion to direct user-controlled target movement. |
| 251 | +31. `b2d.joint.destroy()` removes joints created through the scripted joint API. |
| 252 | +]] |
0 commit comments