1- local BALL_START = vmath.vector3(650, 545, 0)
1+ local BALL_START_POSITION = vmath.vector3(650, 545, 0)
22local BALL_VELOCITY = vmath.vector3(-140, 0, 0)
33local CHAIN_COLOR = vmath.vector4(0.1, 0.8, 0.9, 1.0)
4- local PREV_GHOST_VERTEX = vmath.vector3(715, 505, 0)
5- local NEXT_GHOST_VERTEX = vmath.vector3(35, 235, 0)
4+ local PREV_GHOST_VERTEX = vmath.vector3(715, 505, 0) -- <1>
5+ local NEXT_GHOST_VERTEX = vmath.vector3(35, 235, 0) -- <2>
66
7- local TERRAIN_VERTICES = {
7+ local TERRAIN_VERTICES = { -- <3>
88 vmath.vector3(650, 480, 0),
99 vmath.vector3(560, 455, 0),
1010 vmath.vector3(470, 395, 0),
@@ -14,135 +14,130 @@ local TERRAIN_VERTICES = {
1414 vmath.vector3(95, 265, 0),
1515}
1616
17- local function draw_line(from, to, color)
18- msg.post("@render :", "draw_line", { start_point = from, end_point = to, color = color }) -- <1>
19- end
20-
21- local function get_major_version()
22- local version = b2d.get_version()
23- if type(version) == "table" then
24- return version.major
25- end
26- return tonumber(string.match(version, "^(%d+)"))
27- end
28-
29- local function clear_fixtures(body)
30- local fixtures = b2d.body.get_fixtures(body) -- <2>
31- for i = #fixtures, 1, -1 do
32- b2d.body.destroy_fixture(body, fixtures[ i] .index) -- <3>
33- end
34- end
35-
3617local function create_chain(body)
37- clear_fixtures(body)
38-
39- return b2d.body.create_fixture(body, {
18+ return b2d.body.create_fixture(body, { -- <4>
4019 density = 0.0,
4120 friction = 0.65,
4221 restitution = 0.2,
4322 shape = {
44- type = b2d.shape.SHAPE_TYPE_CHAIN,
23+ type = b2d.shape.SHAPE_TYPE_CHAIN, -- <5>
4524 vertices = TERRAIN_VERTICES,
4625 prev_vertex = PREV_GHOST_VERTEX,
4726 next_vertex = NEXT_GHOST_VERTEX,
4827 },
49- }) -- <4>
28+ })
5029end
5130
5231local function delete_ball(self)
53- if self.ball_id then
32+ if self.ball_id then -- <6>
5433 go.delete(self.ball_id)
34+ self.ball_id = nil
5535 end
5636end
5737
5838local function spawn_ball(self)
59- delete_ball(self)
60- self.ball_id = factory.create("#ball_factory", BALL_START) -- <5>
39+ delete_ball(self) -- <7>
6140
62- local ball_body = b2d.get_body(msg.url(nil, self.ball_id, "collisionobject"))
63- if b2d.body.set_active then
64- b2d.body.set_active(ball_body, true) -- <6>
65- end
66- b2d.body.set_linear_velocity(ball_body, BALL_VELOCITY) -- <7>
67- b2d.body.set_angular_velocity(ball_body, -4.0)
68- end
41+ self.ball_id = factory.create("#ball_factory", BALL_START_POSITION) -- <8>
6942
70- local function draw_chain()
71- for i = 1, #TERRAIN_VERTICES - 1 do
72- draw_line(TERRAIN_VERTICES [ i ] , TERRAIN_VERTICES [ i + 1 ] , CHAIN_COLOR ) -- <8 >
43+ local ball_body = b2d.get_body(msg.url(nil, self.ball_id, "collisionobject")) -- <9>
44+ if b2d.body.set_active then -- <10>
45+ b2d.body.set_active(ball_body, true ) -- <11 >
7346 end
47+ b2d.body.set_linear_velocity(ball_body, BALL_VELOCITY) -- <12>
48+ b2d.body.set_angular_velocity(ball_body, -4.0) -- <13>
7449end
7550
7651local function start_reset_timer(self)
77- self.reset_timer = timer.delay(2.5, true, function() spawn_ball(self) end) -- <9>
78- end
79-
80- local function cancel_reset_timer(self)
81- if self.reset_timer then
52+ if self.reset_timer then -- <14>
8253 timer.cancel(self.reset_timer)
83- self.reset_timer = nil
8454 end
55+
56+ self.reset_timer = timer.delay(2.5, true, function() -- <15>
57+ spawn_ball(self)
58+ end)
8559end
8660
8761function init(self)
88- self.active = get_major_version() == 2 -- <10>
62+ local b2d_version = b2d.get_version() -- <16>
63+ self.active = b2d_version.major == 2 -- <17>
8964
90- if not self.active then -- <11 >
65+ if not self.active then -- <18 >
9166 return
9267 end
9368
94- msg.post(".", "acquire_input_focus")
95- local terrain_body = b2d.get_body(msg.url(nil, "terrain", "collisionobject")) -- <12>
96- self.chain = create_chain(terrain_body) -- <13>
97- label.set_text("#label", "Box2D V2 chain\nClick or touch to reset")
69+ msg.post(".", "acquire_input_focus") -- <19>
70+ label.set_text("#label", "Box2D V2 chain terrain\nClick or touch to reset") -- <20>
71+
72+ local terrain_body = b2d.get_body(msg.url(nil, "terrain", "collisionobject")) -- <21>
73+ self.chain = create_chain(terrain_body) -- <22>
74+
9875 spawn_ball(self)
9976 start_reset_timer(self)
10077end
10178
102- function update(self, dt)
103- if self.active then
104- draw_chain()
105- end
106- end
79+ -------------------
80+ -- Input handling:
10781
10882local TOUCH = hash("touch")
10983
11084function on_input(self, action_id, action)
111- if not self.active then
85+ if not self.active then -- <23>
11286 return
11387 end
11488
115- if action_id == TOUCH and action.pressed then -- <14 >
89+ if action_id == TOUCH and action.pressed then -- <24 >
11690 spawn_ball(self)
117- cancel_reset_timer(self) -- <15>
118- start_reset_timer(self)
91+ start_reset_timer(self) -- <25>
11992 end
12093end
12194
122- function final(self)
123- if not self.active then
124- return
95+ -------------------
96+ -- Debug draw only:
97+
98+ local function draw_line(from, to, color)
99+ msg.post("@render :", "draw_line", { start_point = from, end_point = to, color = color }) -- <26>
100+ end
101+
102+ local function draw_chain()
103+ for i = 1, #TERRAIN_VERTICES - 1 do -- <27>
104+ draw_line(TERRAIN_VERTICES[ i] , TERRAIN_VERTICES[ i + 1] , CHAIN_COLOR)
125105 end
106+ end
126107
127- delete_ball(self)
128- cancel_reset_timer(self)
129- msg.post(".", "release_input_focus")
108+ function update(self, dt)
109+ if self.active then
110+ draw_chain() -- <28>
111+ end
130112end
131113
132114--[[
133- 1 . Draw each terrain segment through the render socket. The lines are transient, so the chain is redrawn every frame.
134- 2 . Read the existing Box2D V2 fixtures from the placeholder body.
135- 3 . Remove the placeholder fixture so only the runtime chain remains.
136- 4 . Attach a V2 chain fixture to the terrain body with ` b2d.body.create_fixture() ` and ` b2d.shape.SHAPE_TYPE_CHAIN ` .
137- 5 . Spawn one dynamic ball from the local factory at the start of the chain.
138- 6 . Explicitly activate the spawned body on backends that expose body activation.
139- 7 . Give the ball an initial velocity so it rolls across the terrain immediately.
140- 8 . Draw the runtime terrain again each frame because the render line messages do not persist.
141- 9 . Replay the ball automatically so the example stays active without input.
142- 10 . Detect whether the running engine uses the Box2D V2 backend. The helper accepts both table and string version formats.
143- 11 . Leave this script as a no-op when the project uses another backend.
144- 12 . Get the Box2D body owned by the hidden ` terrain ` collision object in the collection.
145- 13 . Build the V2 chain terrain.
146- 14 . Clicks and taps reset the ball manually.
147- 15 . Reset the repeating timer after manual input so the next automatic reset waits for a full interval.
148- ]]
115+ 1 . Defines the ghost vertex before the first chain vertex. Box2D uses it to calculate smoother contacts at the open start of the chain.
116+ 2 . Defines the ghost vertex after the last chain vertex. It helps avoid unwanted edge catching at the open end of the chain.
117+ 3 . Defines the terrain path. The same points are used for the Box2D chain shape and the debug line visualization.
118+ 4 . Creates a Box2D V2 fixture on the terrain body. In Box2D V2, a fixture attaches a collision shape and material properties to a body.
119+ 5 . Selects a chain shape. A chain is an open sequence of connected line segments, useful for terrain collision.
120+ 6 . Checks whether a previously spawned ball exists before deleting it.
121+ 7 . Removes the previous ball before spawning a new one, so the example only has one active ball at a time.
122+ 8 . Spawns a new ball from the factory at the configured start position.
123+ 9 . Gets the Box2D body from the spawned ball’s ` collisionobject ` component.
124+ 10 . Checks whether this Box2D V2 build exposes explicit body activation.
125+ 11 . Activates the spawned body through the Box2D V2 API when ` set_active() ` is available.
126+ 12 . Sets the ball’s linear velocity through the Box2D V2 body API.
127+ 13 . Sets the ball’s angular velocity through the Box2D V2 body API so it starts spinning.
128+ 14 . Cancels the previous reset timer before creating a new one. This prevents multiple repeating timers from running at the same time.
129+ 15 . Starts a repeating timer that respawns the ball, so the chain interaction keeps replaying without input.
130+ 16 . Reads the active Box2D backend version.
131+ 17 . Enables this example only when the project is running the Box2D V2 backend.
132+ 18 . Stops the script early when Box2D V2 is not active, because the example uses V2-specific API calls.
133+ 19 . Acquires input focus so this script can receive click or touch input.
134+ 20 . Updates the label with a short description and reset instruction.
135+ 21 . Gets the Box2D body from the ` terrain ` collision object placed in the collection.
136+ 22 . Adds the runtime chain fixture to the terrain body.
137+ 23 . Skips input handling if this script is inactive.
138+ 24 . Handles a click or touch press and uses it as a manual reset for the ball.
139+ 25 . Restarts the repeating timer after manual input, so the next automatic reset waits for a full interval.
140+ 26 . Draws one debug line through the render socket. These lines are temporary and must be sent every frame.
141+ 27 . Draws each segment of the terrain path as a debug line so the invisible chain shape is visible.
142+ 28 . If this script is active, draws the chain each frame.
143+ ]]
0 commit comments