ya2 · news · projects · code · about

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