Skip to content

Commit 0f6e288

Browse files
committed
Add ZigZag Example
1 parent 7059fd8 commit 0f6e288

4 files changed

Lines changed: 212 additions & 0 deletions

File tree

examples/zigzag/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/bin/
2+
/lib/
3+
*.lock

examples/zigzag/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# ZigZag
2+
3+
Requirements:
4+
5+
- [Chipmunk](https://github.com/slembcke/Chipmunk2D)
6+
7+
```sh
8+
sudo apt install libchipmunk-dev
9+
```
10+
11+
## build
12+
13+
```sh
14+
shards build
15+
```
16+
17+
## Run
18+
19+
```sh
20+
bin/zigzag
21+
```

examples/zigzag/shard.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: chipmunk
2+
version: 0.1.0
3+
4+
dependencies:
5+
uing:
6+
github: kojix2/uing
7+
branch: main
8+
chipmunk:
9+
github: oprypin/crystal-chipmunk
10+
branch: master
11+
12+
targets:
13+
zigzag:
14+
main: zigzag.cr

examples/zigzag/zigzag.cr

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
require "uing"
2+
require "chipmunk"
3+
4+
PX_PER_M = 32.0
5+
WIN_W = 600
6+
WIN_H = 400
7+
DT = 1.0/120.0
8+
9+
def to_px(v : CP::Vect) : {Float64, Float64}
10+
{v.x * PX_PER_M, WIN_H - v.y * PX_PER_M} # Flip Y because screen Y+ is downward
11+
end
12+
13+
# Collision types
14+
COLL_GROUND = 1
15+
COLL_PLAYER = 2
16+
17+
# Main game class
18+
class Game
19+
getter space : CP::Space
20+
getter! player_body : CP::Body
21+
@player_body : CP::Body
22+
@segments = [] of {CP::Vect, CP::Vect}
23+
24+
def initialize
25+
@space = CP::Space.new
26+
@space.gravity = CP.v(0, -9.8)
27+
@space.iterations = 10
28+
build_terrain
29+
@player_body = build_player
30+
end
31+
32+
# Generate gentle zigzag planks as segments
33+
def build_terrain
34+
sb = @space.static_body
35+
@segments.clear
36+
37+
width_m = WIN_W / PX_PER_M
38+
height_m = WIN_H / PX_PER_M
39+
margin_x = 1.0
40+
top_y = height_m - 1.2
41+
levels = 6
42+
len = (width_m - margin_x * 2) * 0.55
43+
vertical_gap = (height_m - 3.0) / (levels + 1)
44+
slope_drop = 3
45+
46+
# Side walls and floor
47+
add_segment(sb, CP.v(margin_x * 0.2, 0.8), CP.v(margin_x * 0.2, height_m - 0.5))
48+
add_segment(sb, CP.v(width_m - margin_x * 0.2, 0.8), CP.v(width_m - margin_x * 0.2, height_m - 0.5))
49+
add_segment(sb, CP.v(margin_x * 0.2, 0.8), CP.v(width_m - margin_x * 0.2, 0.8))
50+
51+
levels.times do |i|
52+
y = top_y - i * vertical_gap
53+
if i.even?
54+
x0 = margin_x
55+
x1 = (margin_x + len).clamp(margin_x, width_m - margin_x)
56+
y0 = y
57+
y1 = (y - slope_drop).clamp(0.8, height_m - 0.5)
58+
else
59+
x1 = width_m - margin_x
60+
x0 = (x1 - len).clamp(margin_x, width_m - margin_x)
61+
y1 = y
62+
y0 = (y - slope_drop).clamp(0.8, height_m - 0.5)
63+
end
64+
add_segment(sb, CP.v(x0, y0), CP.v(x1, y1))
65+
end
66+
end
67+
68+
# Player is a single circle (simple model)
69+
def build_player : CP::Body
70+
mass = 70.0
71+
radius = 0.30
72+
moment = CP::Shape::Circle.moment(mass, 0.0, radius, CP.v(0, 0))
73+
body = CP::Body.new(mass, moment)
74+
# Start above the first plank on the left
75+
start_x = 1.2
76+
start_y = (WIN_H / PX_PER_M) - 1.0
77+
body.position = CP.v(start_x, start_y)
78+
@space.add body
79+
shape = CP::Shape::Circle.new(body, radius, CP.v(0, 0))
80+
shape.friction = 0.95
81+
shape.elasticity = 1.0
82+
shape.collision_type = COLL_PLAYER
83+
@space.add shape
84+
body
85+
end
86+
87+
# Restart (R key)
88+
def reset!
89+
# Recreate space for a clean state
90+
@space = CP::Space.new
91+
@space.gravity = CP.v(0, -9.8)
92+
@space.iterations = 10
93+
build_terrain
94+
@player_body = build_player
95+
end
96+
97+
# Step physics (twice per frame for ~60FPS)
98+
def update
99+
2.times { @space.step(DT) }
100+
end
101+
102+
# Segments for drawing
103+
def terrain_segments : Array({CP::Vect, CP::Vect})
104+
@segments
105+
end
106+
107+
private def add_segment(sb : CP::Body, a : CP::Vect, b : CP::Vect)
108+
seg = CP::Shape::Segment.new(sb, a, b, 0.05)
109+
seg.friction = 1.1
110+
seg.elasticity = 0.6
111+
seg.collision_type = COLL_GROUND
112+
@space.add seg
113+
@segments << {a, b}
114+
end
115+
end
116+
117+
UIng.init
118+
game = Game.new
119+
120+
window = UIng::Window.new("ZigZag (Chipmunk)", WIN_W, WIN_H, menubar: false)
121+
box = UIng::Box.new(:vertical)
122+
label = UIng::Label.new("Press [R] to reset")
123+
124+
# Area handler: drawing and key input
125+
handler = UIng::Area::Handler.new do
126+
draw do |area, params|
127+
ctx = params.context
128+
bg = UIng::Area::Draw::Brush.new(:solid, 0.96, 0.98, 1.0, 1.0)
129+
ctx.fill_path(bg) { |p| p.add_rectangle(0, 0, WIN_W, WIN_H) }
130+
line = UIng::Area::Draw::Brush.new(:solid, 0.0, 0.0, 0.0, 1.0)
131+
game.terrain_segments.each do |(a, b)|
132+
x0, y0 = to_px(a)
133+
x1, y1 = to_px(b)
134+
ctx.stroke_path(line, thickness: 3.0) do |path|
135+
path.new_figure(x0, y0)
136+
path.line_to(x1, y1)
137+
end
138+
end
139+
pos = game.player_body.position
140+
x, y = to_px(pos)
141+
player = UIng::Area::Draw::Brush.new(:solid, 0.2, 0.2, 0.2, 1.0)
142+
ctx.fill_path(player) do |path|
143+
path.new_figure_with_arc(x, y, 0.30 * PX_PER_M, 0, Math::PI * 2, false)
144+
end
145+
label.text = "Press [R] to reset"
146+
end
147+
key_event do |area, event|
148+
# Ignore key repeat (only handle key down)
149+
next true if event.up != 0
150+
case event.key
151+
when 'r', 'R'
152+
game.reset!
153+
area.queue_redraw_all
154+
true
155+
else
156+
false
157+
end
158+
end
159+
end
160+
area = UIng::Area.new(handler, WIN_W, WIN_H - 40)
161+
162+
box.append(area, stretchy: true)
163+
box.append(label, stretchy: false)
164+
window.child = box
165+
166+
UIng.timer(8) do
167+
game.update
168+
area.queue_redraw_all
169+
1
170+
end
171+
172+
window.on_closing { UIng.quit; true }
173+
window.show
174+
UIng.main

0 commit comments

Comments
 (0)