1+ from scene import *
2+ import random
3+ import math
4+
5+
6+ class StickBridgeCN (Scene ):
7+ def setup (self ):
8+ self ._init_game ()
9+ self ._start_new_run ()
10+
11+ def _init_game (self ):
12+ self .sky_color = (0.55 , 0.82 , 0.98 )
13+ self .ocean_color_top = (0.20 , 0.62 , 0.86 )
14+ self .ocean_color_mid = (0.12 , 0.45 , 0.74 )
15+ self .ocean_color_deep = (0.07 , 0.28 , 0.50 )
16+ self .pillar_color = (0.08 , 0.08 , 0.12 )
17+ self .stick_color = (0.03 , 0.03 , 0.03 )
18+
19+ self .hero_skins = [
20+ (0.02 , 0.02 , 0.02 ),
21+ (0.12 , 0.18 , 0.40 ),
22+ (0.08 , 0.34 , 0.20 ),
23+ (0.36 , 0.14 , 0.12 ),
24+ ]
25+ self .skin_index = 0
26+ self .hero_color = self .hero_skins [self .skin_index ]
27+
28+ self .base_y = self .size .h * 0.2
29+ self .pillar_h = self .size .h * 0.3
30+ self .top_y = self .base_y + self .pillar_h
31+
32+ self .start_left_x = self .size .w * 0.12
33+ self .start_left_w = self .size .w * 0.14
34+
35+ self .left_x = self .start_left_x
36+ self .left_w = self .start_left_w
37+ self .right_x = 0.0
38+ self .right_w = 0.0
39+
40+ self .state = 'ready'
41+ self .score = 0
42+ self .best_score = 0
43+ self .last_gain = 0
44+
45+ self .success = False
46+ self .perfect = False
47+
48+ self .grow_speed = self .size .h * 0.95
49+ self .rotate_speed = math .pi * 1.8
50+ self .walk_speed = self .size .w * 0.52
51+ self .fall_g = self .size .h * 2.4
52+
53+ self .stick_len = self .size .h * 0.002
54+ self .stick_angle = math .pi / 2
55+
56+ self .hero_h = self .size .h * 0.085
57+ self .hero_foot_offset = self .hero_h * 0.18
58+
59+ self .hero_x = 0.0
60+ self .hero_y = 0.0
61+ self .hero_tilt = 0.0
62+ self .hero_vy = 0.0
63+
64+ self .bridge_end_x = 0.0
65+ self .walk_target_x = 0.0
66+
67+ self .camera_x = 0.0
68+ self .camera_target_x = 0.0
69+
70+ self .dead_timer = 0.0
71+ self .gain_show_timer = 0.0
72+ self .hint_text = '按住蓄力,松手放桥'
73+
74+ self .ui_top_inset = max (26 , self .size .h * 0.035 )
75+
76+ def _generate_right_pillar (self ):
77+ min_gap = self .size .w * 0.18
78+ max_gap = self .size .w * 0.45
79+ min_w = self .size .w * 0.08
80+ max_w = self .size .w * 0.18
81+
82+ gap = random .uniform (min_gap , max_gap )
83+ w = random .uniform (min_w , max_w )
84+ x = self .left_x + self .left_w + gap
85+ return x , w
86+
87+ def _prepare_round (self , first_round = False ):
88+ if not first_round :
89+ self .left_x = self .right_x
90+ self .left_w = self .right_w
91+
92+ self .right_x , self .right_w = self ._generate_right_pillar ()
93+
94+ self .stick_len = self .size .h * 0.002
95+ self .stick_angle = math .pi / 2
96+
97+ self .hero_x = self .left_x + self .left_w
98+ self .hero_y = self .top_y + self .hero_foot_offset
99+ self .hero_tilt = 0.0
100+ self .hero_vy = 0.0
101+
102+ self .success = False
103+ self .perfect = False
104+ self .state = 'ready'
105+ self .hint_text = '按住蓄力,松手放桥'
106+
107+ def _start_new_run (self ):
108+ self .left_x = self .start_left_x
109+ self .left_w = self .start_left_w
110+ self .camera_x = 0.0
111+ self .camera_target_x = 0.0
112+ self .score = 0
113+ self .last_gain = 0
114+ self .gain_show_timer = 0.0
115+ self ._prepare_round (first_round = True )
116+
117+ def _on_dead_reset (self ):
118+ if self .score > self .best_score :
119+ self .best_score = self .score
120+
121+ self .skin_index = (self .skin_index + 1 ) % len (self .hero_skins )
122+ self .hero_color = self .hero_skins [self .skin_index ]
123+
124+ self ._start_new_run ()
125+
126+ def touch_began (self , touch ):
127+ if self .state == 'ready' :
128+ self .state = 'growing'
129+ self .stick_len = self .size .h * 0.002
130+ self .hint_text = '正在蓄力...'
131+
132+ def touch_ended (self , touch ):
133+ if self .state == 'growing' :
134+ self .state = 'rotating'
135+ self .hint_text = '放桥中...'
136+
137+ def update (self ):
138+ dt = min (self .dt , 1 / 30 )
139+
140+ self .camera_x += (self .camera_target_x - self .camera_x ) * min (1.0 , dt * 6.0 )
141+
142+ if self .gain_show_timer > 0 :
143+ self .gain_show_timer -= dt
144+ if self .gain_show_timer < 0 :
145+ self .gain_show_timer = 0
146+
147+ if self .state == 'growing' :
148+ self .stick_len += self .grow_speed * dt
149+
150+ elif self .state == 'rotating' :
151+ self .stick_angle -= self .rotate_speed * dt
152+ if self .stick_angle <= 0 :
153+ self .stick_angle = 0
154+ self .bridge_end_x = self .left_x + self .left_w + self .stick_len
155+
156+ self .success = self .right_x <= self .bridge_end_x <= (self .right_x + self .right_w )
157+
158+ if self .success :
159+ center = self .right_x + self .right_w * 0.5
160+ perfect_range = max (self .size .w * 0.015 , self .right_w * 0.1 )
161+ self .perfect = abs (self .bridge_end_x - center ) <= perfect_range
162+ else :
163+ self .perfect = False
164+
165+ self .walk_target_x = self .bridge_end_x
166+ self .state = 'walking'
167+ self .hint_text = '前进中...'
168+
169+ elif self .state == 'walking' :
170+ self .hero_x += self .walk_speed * dt
171+ self .camera_target_x = max (0.0 , self .hero_x - self .size .w * 0.35 )
172+
173+ if self .hero_x >= self .walk_target_x :
174+ self .hero_x = self .walk_target_x
175+ if self .success :
176+ self .walk_target_x = self .right_x + self .right_w * 0.5
177+ self .state = 'to_center'
178+ else :
179+ self .state = 'falling'
180+ self .hero_vy = 0.0
181+ self .hint_text = '桥长不合适,掉落中...'
182+
183+ elif self .state == 'to_center' :
184+ self .hero_x += self .walk_speed * dt
185+ self .camera_target_x = max (0.0 , self .hero_x - self .size .w * 0.35 )
186+
187+ if self .hero_x >= self .walk_target_x :
188+ self .hero_x = self .walk_target_x
189+
190+ gain = 1
191+ if self .perfect :
192+ gain += 2
193+
194+ self .last_gain = gain
195+ self .score += gain
196+ self .gain_show_timer = 0.8
197+
198+ if self .perfect :
199+ self .hint_text = '完美落点!+3'
200+ else :
201+ self .hint_text = '过桥成功!+1'
202+
203+ self ._prepare_round (first_round = False )
204+
205+ elif self .state == 'falling' :
206+ self .hero_vy -= self .fall_g * dt
207+ self .hero_y += self .hero_vy * dt
208+ self .hero_tilt -= 2.8 * dt
209+ self .camera_target_x = max (0.0 , self .hero_x - self .size .w * 0.35 )
210+
211+ if self .hero_y < - self .size .h * 0.2 :
212+ self .state = 'dead_pause'
213+ self .dead_timer = 0.55
214+ self .hint_text = '坠落失败,正在重置...'
215+
216+ elif self .state == 'dead_pause' :
217+ self .dead_timer -= dt
218+ if self .dead_timer <= 0 :
219+ self ._on_dead_reset ()
220+
221+ def _draw_hero (self ):
222+ h = self .hero_h
223+ head_r = h * 0.12
224+
225+ push_matrix ()
226+ translate (self .hero_x , self .hero_y )
227+ rotate (self .hero_tilt )
228+
229+ stroke (self .hero_color [0 ], self .hero_color [1 ], self .hero_color [2 ], 1 )
230+ stroke_weight (self .size .w * 0.007 )
231+
232+ line (0 , 0 , 0 , h * 0.45 )
233+ line (0 , h * 0.35 , - h * 0.16 , h * 0.22 )
234+ line (0 , h * 0.35 , h * 0.16 , h * 0.22 )
235+ line (0 , 0 , - h * 0.14 , - h * 0.18 )
236+ line (0 , 0 , h * 0.14 , - h * 0.18 )
237+
238+ no_stroke ()
239+ fill (self .hero_color [0 ], self .hero_color [1 ], self .hero_color [2 ], 1 )
240+ ellipse (- head_r , h * 0.45 , head_r * 2 , head_r * 2 )
241+
242+ pop_matrix ()
243+
244+ def _draw_world (self ):
245+ background (self .sky_color [0 ], self .sky_color [1 ], self .sky_color [2 ])
246+
247+ push_matrix ()
248+ translate (- self .camera_x , 0 )
249+
250+ no_stroke ()
251+ fill (self .ocean_color_deep [0 ], self .ocean_color_deep [1 ], self .ocean_color_deep [2 ], 1 )
252+ rect (self .camera_x - self .size .w , 0 , self .size .w * 4 , self .base_y )
253+
254+ fill (self .ocean_color_mid [0 ], self .ocean_color_mid [1 ], self .ocean_color_mid [2 ], 0.95 )
255+ rect (self .camera_x - self .size .w , self .base_y * 0.35 , self .size .w * 4 , self .base_y * 0.65 )
256+
257+ fill (self .ocean_color_top [0 ], self .ocean_color_top [1 ], self .ocean_color_top [2 ], 0.9 )
258+ rect (self .camera_x - self .size .w , self .base_y * 0.72 , self .size .w * 4 , self .base_y * 0.28 )
259+
260+ stroke (1 , 1 , 1 , 0.32 )
261+ stroke_weight (self .size .h * 0.0035 )
262+ wave_step = self .size .w * 0.09
263+ start_x = self .camera_x - self .size .w
264+ end_x = self .camera_x + self .size .w * 3
265+ phase = self .t * 1.8
266+ x = start_x
267+ while x < end_x :
268+ y1 = self .base_y - self .size .h * 0.008 + math .sin (x * 0.03 + phase ) * self .size .h * 0.003
269+ y2 = y1 + math .sin (x * 0.045 + phase * 1.2 ) * self .size .h * 0.0015
270+ line (x , y1 , x + wave_step * 0.55 , y2 )
271+ x += wave_step
272+
273+ no_stroke ()
274+ fill (self .pillar_color [0 ], self .pillar_color [1 ], self .pillar_color [2 ], 1 )
275+ rect (self .left_x , self .base_y , self .left_w , self .pillar_h )
276+ rect (self .right_x , self .base_y , self .right_w , self .pillar_h )
277+
278+ base_x = self .left_x + self .left_w
279+ end_x = base_x + self .stick_len * math .cos (self .stick_angle )
280+ end_y = self .top_y + self .stick_len * math .sin (self .stick_angle )
281+
282+ stroke (self .stick_color [0 ], self .stick_color [1 ], self .stick_color [2 ], 1 )
283+ stroke_weight (self .size .w * 0.01 )
284+ line (base_x , self .top_y , end_x , end_y )
285+
286+ if self .state in ('ready' , 'growing' , 'rotating' , 'walking' , 'to_center' ):
287+ center = self .right_x + self .right_w * 0.5
288+ mark_w = max (self .size .w * 0.01 , self .right_w * 0.08 )
289+ no_stroke ()
290+ fill (1 , 0.25 , 0.25 , 0.8 )
291+ rect (center - mark_w * 0.5 , self .top_y - self .size .h * 0.01 , mark_w , self .size .h * 0.01 )
292+
293+ self ._draw_hero ()
294+ pop_matrix ()
295+
296+ def _draw_panel (self ):
297+ panel_h = self .size .h * 0.12
298+ panel_y = self .size .h - self .ui_top_inset - panel_h - self .size .h * 0.09
299+
300+ no_stroke ()
301+ fill (0 , 0 , 0 , 0.28 )
302+ rect (0 , panel_y , self .size .w , panel_h )
303+
304+ fill (1 , 1 , 1 , 1 )
305+ text ('当前分' , x = self .size .w * 0.17 , y = panel_y + panel_h * 0.65 , font_size = self .size .h * 0.028 , alignment = 5 )
306+ text (str (self .score ), x = self .size .w * 0.17 , y = panel_y + panel_h * 0.28 , font_size = self .size .h * 0.048 , alignment = 5 )
307+
308+ text ('最高分' , x = self .size .w * 0.83 , y = panel_y + panel_h * 0.65 , font_size = self .size .h * 0.028 , alignment = 5 )
309+ text (str (self .best_score ), x = self .size .w * 0.83 , y = panel_y + panel_h * 0.28 , font_size = self .size .h * 0.048 , alignment = 5 )
310+
311+ fill (1 , 1 , 1 , 0.95 )
312+ hint_y = panel_y - self .size .h * 0.035
313+ text (self .hint_text , x = self .size .w * 0.5 , y = hint_y , font_size = self .size .h * 0.03 , alignment = 5 )
314+
315+ if self .gain_show_timer > 0 :
316+ alpha = min (1.0 , self .gain_show_timer / 0.8 )
317+ fill (1 , 0.93 , 0.2 , alpha )
318+ text (f'+{ self .last_gain } ' , x = self .size .w * 0.5 , y = hint_y - self .size .h * 0.028 , font_size = self .size .h * 0.024 , alignment = 5 )
319+
320+ def draw (self ):
321+ self ._draw_world ()
322+ self ._draw_panel ()
323+
324+
325+ run (StickBridgeCN (), PORTRAIT )
0 commit comments