ya2 · news · projects · code · about

31660ece45ebdf563fcafc44ecca778d4310a68b
[pmachines.git] / pmachines / scene.py
1 from os.path import exists
2 from os import makedirs
3 from logging import info
4 from json import loads
5 from panda3d.core import AmbientLight, Texture, TextPropertiesManager, \
6 TextNode, Spotlight, PerspectiveLens, BitMask32, NodePath
7 from panda3d.bullet import BulletPlaneShape, BulletGhostNode
8 from direct.gui.OnscreenImage import OnscreenImage
9 from direct.gui.OnscreenText import OnscreenText
10 from direct.gui.DirectGui import DirectButton, DirectFrame
11 from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
12 from direct.showbase.DirectObject import DirectObject
13 from direct.interval.IntervalGlobal import Sequence, Func
14 from direct.interval.LerpInterval import LerpFunctionInterval
15 from pmachines.items.background import Background
16 from pmachines.gui.sidepanel import SidePanel
17 from ya2.utils.cursor import MouseCursor
18 from ya2.p3d.gfx import P3dGfxMgr
19 from ya2.p3d.p3d import LibP3d
20
21
22 class Scene(DirectObject):
23
24 scenes_done = []
25 json = {}
26
27 def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb, scenes, pos_mgr, testing, mouse_coords, persistent):
28 super().__init__()
29 self._world = world
30 self._exit_cb = exit_cb
31 self._testing = testing
32 self._mouse_coords = mouse_coords
33 self._dbg_items = dbg_items
34 self._reload_cb = reload_cb
35 self._pos_mgr = pos_mgr
36 self._pos_mgr.reset()
37 self._scenes = scenes
38 self._start_evt_time = None
39 self._enforce_res = ''
40 self.__persistent = persistent
41 self.accept('enforce_res', self.enforce_res)
42 self._set_camera()
43 self._cursor = MouseCursor(
44 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
45 (.01, .01))
46 self._set_gui()
47 self._set_lights()
48 self._set_input()
49 self._set_mouse_plane()
50 self.items = []
51 self._test_items = []
52 self.reset()
53 self._state = 'init'
54 self._paused = False
55 self._item_active = None
56 if auto_close_instr:
57 self.__store_state()
58 self.__restore_state()
59 else:
60 self._set_instructions()
61 self._bg = Background()
62 self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
63 self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
64
65 @staticmethod
66 def name():
67 return ''
68
69 @classmethod
70 def version(cls):
71 if not cls.json:
72 with open(cls.filename) as f:
73 cls.json = loads(f.read())
74 return cls.json['version']
75
76 @classmethod
77 def is_done(cls):
78 return bool([(name, version) for name, version in cls.scenes_done if cls.__name__ == name and cls.version() == version])
79
80 def _instr_txt(self):
81 return ''
82
83 def _set_items(self):
84 self.items = []
85 self._test_items = []
86
87 def screenshot(self, task=None):
88 tex = Texture('screenshot')
89 buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
90 cam = base.make_camera(buffer)
91 cam.reparent_to(render)
92 cam.node().get_lens().set_fov(base.camLens.get_fov())
93 cam.set_pos(0, -20, 0)
94 cam.look_at(0, 0, 0)
95 import simplepbr
96 simplepbr.init(
97 window=buffer,
98 camera_node=cam,
99 use_normal_maps=True,
100 use_emission_maps=False,
101 use_occlusion_maps=True,
102 msaa_samples=4,
103 enable_shadows=True)
104 base.graphicsEngine.renderFrame()
105 base.graphicsEngine.renderFrame()
106 fname = self.__class__.__name__
107 if not exists('assets/images/scenes'):
108 makedirs('assets/images/scenes')
109 buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
110 # img = DirectButton(
111 # frameTexture=buffer.get_texture(), relief=FLAT,
112 # frameSize=(-.2, .2, -.2, .2))
113 return buffer.get_texture()
114
115 def current_bottom(self):
116 curr_bottom = 1
117 for item in self.items:
118 if item.repos_done:
119 curr_bottom = min(curr_bottom, item.get_bottom())
120 return curr_bottom
121
122 def reset(self):
123 [itm.destroy() for itm in self.items]
124 [itm.remove_node() for itm in self._test_items]
125 self.items = []
126 self._test_items = []
127 self._set_items()
128 self._set_test_items()
129 self._state = 'init'
130 self._commands = []
131 self._command_idx = 0
132 self._start_evt_time = None
133 if hasattr(self, '_success_txt'):
134 self._success_txt.destroy()
135 del self._success_txt
136 self.__right_btn['state'] = NORMAL
137
138 def enforce_res(self, val):
139 self._enforce_res = val
140 info('enforce res: ' + val)
141
142 def destroy(self):
143 self.__intro_sequence.finish()
144 self.ignore('enforce_res')
145 self._unset_gui()
146 self._unset_lights()
147 self._unset_input()
148 self._unset_mouse_plane()
149 [itm.destroy() for itm in self.items]
150 [itm.remove_node() for itm in self._test_items]
151 self._bg.destroy()
152 self._side_panel.destroy()
153 self._cursor.destroy()
154 taskMgr.remove(self._scene_tsk)
155 if hasattr(self, '_success_txt'):
156 self._success_txt.destroy()
157
158 def _set_camera(self):
159 base.camera.set_pos(0, -20, 0)
160 base.camera.look_at(0, 0, 0)
161 def camera_ani(t):
162 start_v = (1, -5, 1)
163 end_v = (0, -20, 0)
164 curr_pos = (
165 start_v[0] + (end_v[0] - start_v[0]) * t,
166 start_v[1] + (end_v[1] - start_v[1]) * t,
167 start_v[2] + (end_v[2] - start_v[2]) * t)
168 base.camera.set_pos(*curr_pos)
169 self.repos()
170 camera_interval = LerpFunctionInterval(
171 camera_ani,
172 1.2,
173 0,
174 1,
175 blendType='easeInOut')
176 self.__intro_sequence = Sequence(
177 camera_interval,
178 Func(self.repos))
179 self.__intro_sequence.start()
180
181 def __load_img_btn(self, path, col):
182 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
183 img.set_transparency(True)
184 img.set_color(col)
185 img.detach_node()
186 return img
187
188 def _set_gui(self):
189 def load_images_btn(path, col):
190 colors = {
191 'gray': [
192 (.6, .6, .6, 1), # ready
193 (1, 1, 1, 1), # press
194 (.8, .8, .8, 1), # rollover
195 (.4, .4, .4, .4)],
196 'green': [
197 (.1, .68, .1, 1),
198 (.1, 1, .1, 1),
199 (.1, .84, .1, 1),
200 (.4, .1, .1, .4)]}[col]
201 return [self.__load_img_btn(path, col) for col in colors]
202 abl, abr = base.a2dBottomLeft, base.a2dBottomRight
203 btn_info = [
204 ('home', self.on_home, NORMAL, abl, 'gray'),
205 ('information', self._set_instructions, NORMAL, abl, 'gray'),
206 ('right', self.on_play, NORMAL, abr, 'green'),
207 #('next', self.on_next, DISABLED, abr, 'gray'),
208 #('previous', self.on_prev, DISABLED, abr, 'gray'),
209 #('rewind', self.reset, NORMAL, abr, 'gray')
210 ]
211 num_l = num_r = 0
212 btns = []
213 for binfo in btn_info:
214 imgs = load_images_btn(binfo[0], binfo[4])
215 if binfo[3] == base.a2dBottomLeft:
216 sign, num = 1, num_l
217 num_l += 1
218 else:
219 sign, num = -1, num_r
220 num_r += 1
221 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
222 btn = DirectButton(
223 image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
224 parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
225 frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
226 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
227 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
228 btn.set_transparency(True)
229 self._pos_mgr.register(binfo[0], LibP3d.wdg_pos(btn))
230 btns += [btn]
231 self.__home_btn, self.__info_btn, self.__right_btn = btns
232 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
233 if self._dbg_items:
234 self._info_txt = OnscreenText(
235 '', parent=base.a2dTopRight, scale=0.04,
236 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
237 if self._mouse_coords:
238 self._coords_txt = OnscreenText(
239 '', parent=base.a2dTopRight, scale=0.04,
240 pos=(-.03, -.12), fg=(.9, .9, .9, 1), align=TextNode.A_right)
241 def update_coords(task):
242 pos = None
243 for hit in self._get_hits():
244 if hit.get_node() == self._mouse_plane_node:
245 pos = hit.get_hit_pos()
246 if pos:
247 txt = '%s %s' % (round(pos.x, 3),
248 round(pos.z, 3))
249 self._coords_txt['text'] = txt
250 return task.cont
251 self._coords_tsk = taskMgr.add(update_coords, 'update_coords')
252
253 def _unset_gui(self):
254 btns = [
255 self.__home_btn, self.__info_btn, self.__right_btn,
256 #self.__next_btn, self.__prev_btn, self.__rewind_btn
257 ]
258 [btn.destroy() for btn in btns]
259 if self._dbg_items:
260 self._info_txt.destroy()
261 if self._mouse_coords:
262 taskMgr.remove(self._coords_tsk)
263 self._coords_txt.destroy()
264
265 def _set_spotlight(self, name, pos, look_at, color, shadows=False):
266 light = Spotlight(name)
267 if shadows:
268 light.setLens(PerspectiveLens())
269 light_np = render.attach_new_node(light)
270 light_np.set_pos(pos)
271 light_np.look_at(look_at)
272 light.set_color(color)
273 render.set_light(light_np)
274 return light_np
275
276 def _set_lights(self):
277 alight = AmbientLight('alight') # for ao
278 alight.set_color((.15, .15, .15, 1))
279 self._alnp = render.attach_new_node(alight)
280 render.set_light(self._alnp)
281 self._key_light = self._set_spotlight(
282 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
283 self._shadow_light = self._set_spotlight(
284 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
285 self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
286 self._shadow_light.node().get_lens().set_film_size(2048, 2048)
287 self._shadow_light.node().get_lens().set_near_far(1, 256)
288 self._shadow_light.node().set_camera_mask(BitMask32(0x01))
289
290 def _unset_lights(self):
291 for light in [self._alnp, self._key_light, self._shadow_light]:
292 render.clear_light(light)
293 light.remove_node()
294
295 def _set_input(self):
296 self.accept('mouse1', self.on_click_l)
297 self.accept('mouse1-up', self.on_release)
298 self.accept('mouse3', self.on_click_r)
299 self.accept('mouse3-up', self.on_release)
300
301 def _unset_input(self):
302 for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
303 self.ignore(evt)
304
305 def _set_mouse_plane(self):
306 shape = BulletPlaneShape((0, -1, 0), 0)
307 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
308 self._mouse_plane_node = BulletGhostNode('mouse plane')
309 self._mouse_plane_node.addShape(shape)
310 #np = render.attachNewNode(self._mouse_plane_node)
311 #self._world.attachRigidBody(self._mouse_plane_node)
312 self._world.attach_ghost(self._mouse_plane_node)
313
314 def _unset_mouse_plane(self):
315 self._world.remove_ghost(self._mouse_plane_node)
316
317 def _get_hits(self):
318 if not base.mouseWatcherNode.has_mouse(): return []
319 p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
320 return self._world.ray_test_all(p_from, p_to).get_hits()
321
322 def _update_info(self, item):
323 txt = ''
324 if item:
325 txt = '%.3f %.3f\n%.3f°' % (
326 item._np.get_x(), item._np.get_z(), item._np.get_r())
327 self._info_txt['text'] = txt
328
329 def _on_click(self, method):
330 if self._paused:
331 return
332 for hit in self._get_hits():
333 if hit.get_node() == self._mouse_plane_node:
334 pos = hit.get_hit_pos()
335 for hit in self._get_hits():
336 for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
337 if not self._item_active:
338 self._item_active = item
339 getattr(item, method)(pos)
340 img = 'move' if method == 'on_click_l' else 'rotate'
341 if not (img == 'rotate' and not item._instantiated):
342 self._cursor.set_image('assets/images/buttons/%s.dds' % img)
343
344 def on_click_l(self):
345 self._on_click('on_click_l')
346
347 def on_click_r(self):
348 self._on_click('on_click_r')
349
350 def on_release(self):
351 if self._item_active and not self._item_active._first_command:
352 self._commands = self._commands[:self._command_idx]
353 self._commands += [self._item_active]
354 self._command_idx += 1
355 #self.__prev_btn['state'] = NORMAL
356 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
357 #self.__prev_btn['frameColor'] = fcols[0]
358 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
359 # self.__next_btn['state'] = DISABLED
360 # self.__next_btn['frameColor'] = fcols[1]
361 self._item_active = None
362 [item.on_release() for item in self.items]
363 self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
364
365 def repos(self):
366 for item in self.items:
367 item.repos_done = False
368 self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
369 [item.on_aspect_ratio_changed() for item in self.items]
370 self._side_panel.update(self.items)
371 max_x = -float('inf')
372 for item in self.items:
373 if not item._instantiated:
374 max_x = max(item._np.get_x(), max_x)
375 for item in self.items:
376 if not item._instantiated:
377 item.repos_x(max_x)
378
379 def on_aspect_ratio_changed(self):
380 self.repos()
381
382 def _win_condition(self):
383 pass
384
385 def _fail_condition(self):
386 return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
387
388 def on_frame(self, task):
389 hits = self._get_hits()
390 pos = None
391 for hit in self._get_hits():
392 if hit.get_node() == self._mouse_plane_node:
393 pos = hit.get_hit_pos()
394 hit_nodes = [hit.get_node() for hit in hits]
395 if self._item_active:
396 items_hit = [self._item_active]
397 else:
398 items_hit = [itm for itm in self.items if itm.node in hit_nodes]
399 items_no_hit = [itm for itm in self.items if itm not in items_hit]
400 [itm.on_mouse_on() for itm in items_hit]
401 [itm.on_mouse_off() for itm in items_no_hit]
402 if pos and self._item_active:
403 self._item_active.on_mouse_move(pos)
404 if self._dbg_items:
405 self._update_info(items_hit[0] if items_hit else None)
406 if self._win_condition():
407 self._start_evt_time = None
408 self._set_fail() if self._enforce_res == 'fail' else self._set_win()
409 elif self._state == 'playing' and self._fail_condition():
410 self._start_evt_time = None
411 self._set_win() if self._enforce_res == 'win' else self._set_fail()
412 elif self._testing and self._start_evt_time and globalClock.getFrameTime() - self._start_evt_time > 5.0:
413 self._start_evt_time = None
414 self._set_win() if self._enforce_res == 'win' else self._set_fail()
415 if any(itm._overlapping for itm in self.items):
416 self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
417 else:
418 self._cursor.cursor_img.img.set_color(.9, .9, .9, 1)
419 return task.cont
420
421 def cb_inst(self, item):
422 self.items += [item]
423
424 def on_play(self):
425 self._state = 'playing'
426 #self.__prev_btn['state'] = DISABLED
427 #self.__next_btn['state'] = DISABLED
428 self.__right_btn['state'] = DISABLED
429 [itm.play() for itm in self.items]
430 self._start_evt_time = globalClock.getFrameTime()
431
432 def on_next(self):
433 self._commands[self._command_idx].redo()
434 self._command_idx += 1
435 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
436 #self.__prev_btn['state'] = NORMAL
437 #self.__prev_btn['frameColor'] = fcols[0]
438 #more_commands = self._command_idx < len(self._commands)
439 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
440 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
441
442 def on_prev(self):
443 self._command_idx -= 1
444 self._commands[self._command_idx].undo()
445 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
446 #self.__next_btn['state'] = NORMAL
447 #self.__next_btn['frameColor'] = fcols[0]
448 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
449 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
450
451 def on_home(self):
452 self._exit_cb()
453
454 def _set_instructions(self):
455 self._paused = True
456 self.__store_state()
457 mgr = TextPropertiesManager.get_global_ptr()
458 for name in ['mouse_l', 'mouse_r']:
459 graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
460 graphic.set_scale(.5)
461 graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
462 graphic.get_texture().set_anisotropic_degree(2)
463 mgr.set_graphic(name, graphic)
464 graphic.set_z(-.2)
465 graphic.set_transparency(True)
466 graphic.detach_node()
467 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
468 frameSize=(-.6, .6, -.3, .3))
469 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
470 font.clear()
471 font.set_pixels_per_unit(60)
472 font.set_minfilter(Texture.FTLinearMipmapLinear)
473 font.set_outline((0, 0, 0, 1), .8, .2)
474 self._txt = OnscreenText(
475 self._instr_txt(), parent=frm, font=font, scale=0.06,
476 fg=(.9, .9, .9, 1), align=TextNode.A_left)
477 u_l = self._txt.textNode.get_upper_left_3d()
478 l_r = self._txt.textNode.get_lower_right_3d()
479 w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
480 btn_scale = .05
481 mar = .06 # margin
482 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
483 z += (btn_scale + 2 * mar) / 2
484 self._txt['pos'] = -w / 2, z
485 u_l = self._txt.textNode.get_upper_left_3d()
486 l_r = self._txt.textNode.get_lower_right_3d()
487 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
488 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
489 frm['frameSize'] = fsz
490 colors = [
491 (.6, .6, .6, 1), # ready
492 (1, 1, 1, 1), # press
493 (.8, .8, .8, 1), # rollover
494 (.4, .4, .4, .4)]
495 imgs = [self.__load_img_btn('exitRight', col) for col in colors]
496 btn = DirectButton(
497 image=imgs, scale=btn_scale,
498 pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
499 parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
500 relief=FLAT, frameColor=(.6, .6, .6, .08),
501 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
502 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
503 btn.set_transparency(True)
504 self._pos_mgr.register('close_instructions', LibP3d.wdg_pos(btn))
505
506 def _set_win(self):
507 self.__persistent.save_scene(self.__class__.__name__, self.version())
508 loader.load_sfx('assets/audio/sfx/success.ogg').play()
509 self._paused = True
510 self.__store_state()
511 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
512 frameSize=(-.6, .6, -.3, .3))
513 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
514 font.clear()
515 font.set_pixels_per_unit(60)
516 font.set_minfilter(Texture.FTLinearMipmapLinear)
517 font.set_outline((0, 0, 0, 1), .8, .2)
518 self._txt = OnscreenText(
519 _('You win!'),
520 parent=frm,
521 font=font, scale=0.2,
522 fg=(.9, .9, .9, 1))
523 u_l = self._txt.textNode.get_upper_left_3d()
524 l_r = self._txt.textNode.get_lower_right_3d()
525 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
526 h = u_l[2] - l_r[2]
527 btn_scale = .05
528 mar = .06 # margin
529 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
530 z += (btn_scale + 2 * mar) / 2
531 self._txt['pos'] = 0, z
532 u_l = self._txt.textNode.get_upper_left_3d()
533 l_r = self._txt.textNode.get_lower_right_3d()
534 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
535 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
536 frm['frameSize'] = fsz
537 colors = [
538 (.6, .6, .6, 1), # ready
539 (1, 1, 1, 1), # press
540 (.8, .8, .8, 1), # rollover
541 (.4, .4, .4, .4)]
542 imgs = [self.__load_img_btn('home', col) for col in colors]
543 btn = DirectButton(
544 image=imgs, scale=btn_scale,
545 pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
546 parent=frm, command=self._on_end_home, extraArgs=[frm],
547 relief=FLAT, frameColor=(.6, .6, .6, .08),
548 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
549 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
550 btn.set_transparency(True)
551 self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
552 imgs = [self.__load_img_btn('rewind', col) for col in colors]
553 btn = DirectButton(
554 image=imgs, scale=btn_scale,
555 pos=(0, 1, l_r[2] - mar - btn_scale),
556 parent=frm, command=self._on_restart, extraArgs=[frm],
557 relief=FLAT, frameColor=(.6, .6, .6, .08),
558 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
559 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
560 self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
561 btn.set_transparency(True)
562 enabled = self._scenes.index(self.__class__) < len(self._scenes) - 1
563 if enabled:
564 next_scene = self._scenes[self._scenes.index(self.__class__) + 1]
565 else:
566 next_scene = None
567 imgs = [self.__load_img_btn('right', col) for col in colors]
568 btn = DirectButton(
569 image=imgs, scale=btn_scale,
570 pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
571 parent=frm, command=self._on_next_scene,
572 extraArgs=[frm, next_scene], relief=FLAT,
573 frameColor=(.6, .6, .6, .08),
574 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
575 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
576 btn['state'] = NORMAL if enabled else DISABLED
577 self._pos_mgr.register('next', LibP3d.wdg_pos(btn))
578 btn.set_transparency(True)
579
580 def _set_fail(self):
581 loader.load_sfx('assets/audio/sfx/success.ogg').play()
582 self._paused = True
583 self.__store_state()
584 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
585 frameSize=(-.6, .6, -.3, .3))
586 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
587 font.clear()
588 font.set_pixels_per_unit(60)
589 font.set_minfilter(Texture.FTLinearMipmapLinear)
590 font.set_outline((0, 0, 0, 1), .8, .2)
591 self._txt = OnscreenText(
592 _('You have failed!'),
593 parent=frm,
594 font=font, scale=0.2,
595 fg=(.9, .9, .9, 1))
596 u_l = self._txt.textNode.get_upper_left_3d()
597 l_r = self._txt.textNode.get_lower_right_3d()
598 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
599 h = u_l[2] - l_r[2]
600 btn_scale = .05
601 mar = .06 # margin
602 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
603 z += (btn_scale + 2 * mar) / 2
604 self._txt['pos'] = 0, z
605 u_l = self._txt.textNode.get_upper_left_3d()
606 l_r = self._txt.textNode.get_lower_right_3d()
607 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
608 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
609 frm['frameSize'] = fsz
610 colors = [
611 (.6, .6, .6, 1), # ready
612 (1, 1, 1, 1), # press
613 (.8, .8, .8, 1), # rollover
614 (.4, .4, .4, .4)]
615 imgs = [self.__load_img_btn('home', col) for col in colors]
616 btn = DirectButton(
617 image=imgs, scale=btn_scale,
618 pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
619 parent=frm, command=self._on_end_home, extraArgs=[frm],
620 relief=FLAT, frameColor=(.6, .6, .6, .08),
621 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
622 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
623 self._pos_mgr.register('home_win', LibP3d.wdg_pos(btn))
624 btn.set_transparency(True)
625 imgs = [self.__load_img_btn('rewind', col) for col in colors]
626 btn = DirectButton(
627 image=imgs, scale=btn_scale,
628 pos=(0, 1, l_r[2] - mar - btn_scale),
629 parent=frm, command=self._on_restart, extraArgs=[frm],
630 relief=FLAT, frameColor=(.6, .6, .6, .08),
631 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
632 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
633 self._pos_mgr.register('replay', LibP3d.wdg_pos(btn))
634 btn.set_transparency(True)
635
636 def _on_restart(self, frm):
637 self.__on_close_instructions(frm)
638 self.reset()
639
640 def _on_end_home(self, frm):
641 self.__on_close_instructions(frm)
642 self.on_home()
643
644 def _on_next_scene(self, frm, scene):
645 self.__on_close_instructions(frm)
646 self._reload_cb(scene)
647
648 def __store_state(self):
649 btns = [
650 self.__home_btn, self.__info_btn, self.__right_btn,
651 #self.__next_btn, self.__prev_btn, self.__rewind_btn
652 ]
653 self.__btn_state = [btn['state'] for btn in btns]
654 for btn in btns:
655 btn['state'] = DISABLED
656 [itm.store_state() for itm in self.items]
657
658 def __restore_state(self):
659 btns = [
660 self.__home_btn, self.__info_btn, self.__right_btn,
661 #self.__next_btn, self.__prev_btn, self.__rewind_btn
662 ]
663 for btn, state in zip(btns, self.__btn_state):
664 btn['state'] = state
665 [itm.restore_state() for itm in self.items]
666 self._paused = False
667
668 def __on_close_instructions(self, frm):
669 frm.remove_node()
670 self.__restore_state()
671
672 def _set_test_items(self):
673 def frame_after(task):
674 self._define_test_items()
675 for itm in self._test_items:
676 self._pos_mgr.register(itm.name, P3dGfxMgr.pos2d_p2d(itm))
677 taskMgr.doMethodLater(.01, frame_after, 'frame after')
678
679 def _set_test_item(self, name, pos):
680 self._test_items += [NodePath(name)]
681 self._test_items[-1].set_pos(pos[0], 0, pos[1])