1 from os
.path
import exists
2 from os
import makedirs
3 from logging
import info
5 from collections
import namedtuple
6 from panda3d
.core
import AmbientLight
, Texture
, TextPropertiesManager
, \
7 TextNode
, Spotlight
, PerspectiveLens
, BitMask32
, NodePath
8 from panda3d
.bullet
import BulletPlaneShape
, BulletGhostNode
9 from direct
.gui
.OnscreenImage
import OnscreenImage
10 from direct
.gui
.OnscreenText
import OnscreenText
11 from direct
.gui
.DirectGui
import DirectButton
, DirectFrame
12 from direct
.gui
.DirectGuiGlobals
import FLAT
, DISABLED
, NORMAL
13 from direct
.showbase
.DirectObject
import DirectObject
14 from direct
.interval
.IntervalGlobal
import Sequence
, Func
15 from direct
.interval
.LerpInterval
import LerpFunctionInterval
16 from pmachines
.items
.background
import Background
17 from pmachines
.gui
.sidepanel
import SidePanel
18 from pmachines
.items
.box
import Box
, HitStrategy
19 from pmachines
.items
.basketball
import Basketball
20 from pmachines
.items
.domino
import Domino
, DownStrategy
21 from pmachines
.items
.shelf
import Shelf
22 from pmachines
.items
.teetertooter
import TeeterTooter
23 from pmachines
.editor
.scene
import SceneEditor
24 from ya2
.utils
.cursor
import MouseCursor
25 from ya2
.p3d
.gfx
import P3dGfxMgr
26 from ya2
.p3d
.p3d
import LibP3d
29 class Scene(DirectObject
):
34 def __init__(self
, world
, exit_cb
, auto_close_instr
, dbg_items
, reload_cb
, scenes
, pos_mgr
, testing
, mouse_coords
, persistent
, json_name
, editor
, auto_start_editor
):
37 self
._exit
_cb
= exit_cb
38 self
._testing
= testing
39 self
._mouse
_coords
= mouse_coords
40 self
._dbg
_items
= dbg_items
41 self
._reload
_cb
= reload_cb
42 self
._pos
_mgr
= pos_mgr
45 self
._start
_evt
_time
= None
46 self
._enforce
_res
= ''
47 self
.__persistent
= persistent
48 self
.__json
_name
= json_name
49 self
.__editor
= editor
50 self
.__scene
_editor
= None
52 self
.accept('enforce_res', self
.enforce_res
)
54 self
._cursor
= MouseCursor(
55 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
60 self
._set
_mouse
_plane
()
66 self
._item
_active
= None
69 self
.__restore
_state
()
71 self
._set
_instructions
()
72 self
._bg
= Background()
73 self
._side
_panel
= SidePanel(world
, self
._mouse
_plane
_node
, (-5, 4), (-3, 1), 1, self
.items
)
74 self
._scene
_tsk
= taskMgr
.add(self
.on_frame
, 'on_frame')
77 self
.accept('editor-inspector-delete', self
.__on
_inspector
_delete
)
80 def filename(cls
, scene_name
):
81 return f
'assets/scenes/{scene_name}.json'
84 def name(cls
, scene_name
):
85 if not scene_name
in cls
.json_files
:
86 with
open(cls
.filename(scene_name
)) as f
:
87 cls
.json_files
[scene_name
] = loads(f
.read())
88 return _(cls
.json_files
[scene_name
]['name'])
91 def version(cls
, scene_name
):
92 if not scene_name
in cls
.json_files
:
93 with
open(cls
.filename(scene_name
)) as f
:
94 cls
.json_files
[scene_name
] = loads(f
.read())
95 return cls
.json_files
[scene_name
]['version']
98 def is_done(cls
, scene_name
):
99 if not cls
.scenes_done
or len(cls
.scenes_done
) == 1 and not cls
.scenes_done
[0]:
101 return bool([(name
, version
) for name
, version
in cls
.scenes_done
if scene_name
== name
and cls
.version(scene_name
) == version
])
103 def _instr_txt(self
):
104 txt
= _('Scene: ') + self
.name(self
.__json
_name
) + '\n\n'
105 txt
+= _(self
.__process
_json
_escape
(self
.__class
__.json_files
[self
.__json
_name
]['instructions']))
108 def __process_json_escape(self
, string
):
109 return bytes(string
, 'utf-8').decode('unicode-escape')
111 def _set_items(self
):
113 self
._test
_items
= []
115 with
open(f
'assets/scenes/{self.__json_name}.json') as f
:
116 self
.json
= loads(f
.read())
117 for item
in self
.json
['start_items']:
119 'world': self
._world
,
120 'plane_node': self
._mouse
_plane
_node
,
121 'cb_inst': self
.cb_inst
,
122 'curr_bottom': self
.current_bottom
,
124 'count': item
['count'],
127 args
['mass'] = item
['mass']
128 if 'friction' in item
:
129 args
['friction'] = item
['friction']
130 self
.items
+= [self
.__code
2class
(item
['class'])(**args
)]
131 for item
in self
.json
['items']:
133 'world': self
._world
,
134 'plane_node': self
._mouse
_plane
_node
,
135 'cb_inst': self
.cb_inst
,
136 'curr_bottom': self
.current_bottom
,
139 args
['pos'] = tuple(item
['position'])
141 args
['mass'] = item
['mass']
142 if 'friction' in item
:
143 args
['friction'] = item
['friction']
145 args
['r'] = item
['roll']
146 if 'model_scale' in item
:
147 args
['model_scale'] = item
['model_scale']
148 if 'restitution' in item
:
149 args
['restitution'] = item
['restitution']
150 if 'friction' in item
:
151 args
['friction'] = item
['friction']
152 self
.items
+= [self
.__code
2class
(item
['class'])(**args
)]
153 if 'strategy' in item
:
154 match item
['strategy']:
156 self
.items
[-1].set_strategy(self
.__code
2class
(item
['strategy'])(self
.items
[-1]._np
, *item
['strategy_args']))
158 self
.items
[-1].set_strategy(self
.__code
2class
(item
['strategy'])(self
.__item
_with
_id
(item
['strategy_args'][0]), self
.items
[-1].node
, self
.items
[-1]._world
))
160 def __code2class(self
, code
):
163 'Basketball': Basketball
,
166 'TeeterTooter': TeeterTooter
,
167 'DownStrategy': DownStrategy
,
168 'HitStrategy': HitStrategy
171 def __item_with_id(self
, id):
172 for item
in self
.items
:
173 if 'id' in item
.json
and item
.json
['id'] == id:
176 def screenshot(self
, task
=None):
177 tex
= Texture('screenshot')
178 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
179 cam
= base
.make_camera(buffer)
180 cam
.reparent_to(render
)
181 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
182 cam
.set_pos(0, -20, 0)
188 use_normal_maps
=True,
189 use_emission_maps
=False,
190 use_occlusion_maps
=True,
193 base
.graphicsEngine
.renderFrame()
194 base
.graphicsEngine
.renderFrame()
195 fname
= self
.__json
_name
196 if not exists('assets/images/scenes'):
197 makedirs('assets/images/scenes')
198 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
199 # img = DirectButton(
200 # frameTexture=buffer.get_texture(), relief=FLAT,
201 # frameSize=(-.2, .2, -.2, .2))
202 return buffer.get_texture()
204 def current_bottom(self
):
206 for item
in self
.items
:
208 curr_bottom
= min(curr_bottom
, item
.get_bottom())
212 [itm
.destroy() for itm
in self
.items
]
213 [itm
.remove_node() for itm
in self
._test
_items
]
215 self
._test
_items
= []
217 self
._set
_test
_items
()
220 self
._command
_idx
= 0
221 self
._start
_evt
_time
= None
222 if hasattr(self
, '_success_txt'):
223 self
._success
_txt
.destroy()
224 del self
._success
_txt
225 self
.__right
_btn
['state'] = NORMAL
227 def enforce_res(self
, val
):
228 self
._enforce
_res
= val
229 info('enforce res: ' + val
)
232 self
.__intro
_sequence
.finish()
233 self
.ignore('enforce_res')
237 self
._unset
_mouse
_plane
()
238 [itm
.destroy() for itm
in self
.items
]
239 [itm
.remove_node() for itm
in self
._test
_items
]
241 self
._side
_panel
.destroy()
242 self
._cursor
.destroy()
243 taskMgr
.remove(self
._scene
_tsk
)
244 if hasattr(self
, '_success_txt'):
245 self
._success
_txt
.destroy()
247 def _set_camera(self
):
248 base
.camera
.set_pos(0, -20, 0)
249 base
.camera
.look_at(0, 0, 0)
254 start_v
[0] + (end_v
[0] - start_v
[0]) * t
,
255 start_v
[1] + (end_v
[1] - start_v
[1]) * t
,
256 start_v
[2] + (end_v
[2] - start_v
[2]) * t
)
257 base
.camera
.set_pos(*curr_pos
)
259 camera_interval
= LerpFunctionInterval(
264 blendType
='easeInOut')
265 self
.__intro
_sequence
= Sequence(
268 self
.__intro
_sequence
.start()
270 def __load_img_btn(self
, path
, col
):
271 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
272 img
.set_transparency(True)
278 def load_images_btn(path
, col
):
281 (.6, .6, .6, 1), # ready
282 (1, 1, 1, 1), # press
283 (.8, .8, .8, 1), # rollover
289 (.4, .1, .1, .4)]}[col
]
290 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
291 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
293 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
294 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
295 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
296 #('next', self.on_next, DISABLED, abr, 'gray'),
297 #('previous', self.on_prev, DISABLED, abr, 'gray'),
298 #('rewind', self.reset, NORMAL, abr, 'gray')
301 btn_info
.insert(2, ('wrench', self
._set
_editor
, NORMAL
, abl
, 'gray'))
304 for binfo
in btn_info
:
305 imgs
= load_images_btn(binfo
[0], binfo
[4])
306 if binfo
[3] == base
.a2dBottomLeft
:
310 sign
, num
= -1, num_r
312 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
314 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
315 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
316 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
317 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
318 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
319 btn
.set_transparency(True)
320 self
._pos
_mgr
.register(binfo
[0], LibP3d
.wdg_pos(btn
))
323 self
.__home
_btn
, self
.__info
_btn
, self
.__editor
_btn
, self
.__right
_btn
= btns
325 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
= btns
326 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
328 self
._info
_txt
= OnscreenText(
329 '', parent
=base
.a2dTopRight
, scale
=0.04,
330 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
331 if self
._mouse
_coords
:
332 self
._coords
_txt
= OnscreenText(
333 '', parent
=base
.a2dTopRight
, scale
=0.04,
334 pos
=(-.03, -.12), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
335 def update_coords(task
):
337 for hit
in self
._get
_hits
():
338 if hit
.get_node() == self
._mouse
_plane
_node
:
339 pos
= hit
.get_hit_pos()
341 txt
= '%s %s' % (round(pos
.x
, 3),
343 self
._coords
_txt
['text'] = txt
345 self
._coords
_tsk
= taskMgr
.add(update_coords
, 'update_coords')
347 def _unset_gui(self
):
349 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
350 #self.__next_btn, self.__prev_btn, self.__rewind_btn
352 [btn
.destroy() for btn
in btns
]
354 self
._info
_txt
.destroy()
355 if self
._mouse
_coords
:
356 taskMgr
.remove(self
._coords
_tsk
)
357 self
._coords
_txt
.destroy()
359 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
360 light
= Spotlight(name
)
362 light
.setLens(PerspectiveLens())
363 light_np
= render
.attach_new_node(light
)
364 light_np
.set_pos(pos
)
365 light_np
.look_at(look_at
)
366 light
.set_color(color
)
367 render
.set_light(light_np
)
370 def _set_lights(self
):
371 alight
= AmbientLight('alight') # for ao
372 alight
.set_color((.15, .15, .15, 1))
373 self
._alnp
= render
.attach_new_node(alight
)
374 render
.set_light(self
._alnp
)
375 self
._key
_light
= self
._set
_spotlight
(
376 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
377 self
._shadow
_light
= self
._set
_spotlight
(
378 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
379 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
380 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
381 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
382 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
384 def _unset_lights(self
):
385 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
386 render
.clear_light(light
)
389 def _set_input(self
):
390 self
.accept('mouse1', self
.on_click_l
)
391 self
.accept('mouse1-up', self
.on_release
)
392 self
.accept('mouse3', self
.on_click_r
)
393 self
.accept('mouse3-up', self
.on_release
)
395 def _unset_input(self
):
396 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
399 def _set_mouse_plane(self
):
400 shape
= BulletPlaneShape((0, -1, 0), 0)
401 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
402 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
403 self
._mouse
_plane
_node
.addShape(shape
)
404 #np = render.attachNewNode(self._mouse_plane_node)
405 #self._world.attachRigidBody(self._mouse_plane_node)
406 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
408 def _unset_mouse_plane(self
):
409 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
412 if not base
.mouseWatcherNode
.has_mouse(): return []
413 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
414 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
416 def _update_info(self
, item
):
419 txt
= '%.3f %.3f\n%.3f°' % (
420 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
421 self
._info
_txt
['text'] = txt
423 def _on_click(self
, method
):
426 for hit
in self
._get
_hits
():
427 if hit
.get_node() == self
._mouse
_plane
_node
:
428 pos
= hit
.get_hit_pos()
429 for hit
in self
._get
_hits
():
430 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
431 if not self
._item
_active
:
432 self
._item
_active
= item
433 getattr(item
, method
)(pos
)
434 img
= 'move' if method
== 'on_click_l' else 'rotate'
435 if not (img
== 'rotate' and not item
._instantiated
):
436 self
._cursor
.set_image('assets/images/buttons/%s.dds' % img
)
438 def on_click_l(self
):
439 self
._on
_click
('on_click_l')
441 def on_click_r(self
):
442 self
._on
_click
('on_click_r')
444 def on_release(self
):
445 if self
._item
_active
and not self
._item
_active
._first
_command
:
446 self
._commands
= self
._commands
[:self
._command
_idx
]
447 self
._commands
+= [self
._item
_active
]
448 self
._command
_idx
+= 1
449 #self.__prev_btn['state'] = NORMAL
450 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
451 #self.__prev_btn['frameColor'] = fcols[0]
452 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
453 # self.__next_btn['state'] = DISABLED
454 # self.__next_btn['frameColor'] = fcols[1]
455 self
._item
_active
= None
456 [item
.on_release() for item
in self
.items
]
457 self
._cursor
.set_image('assets/images/buttons/arrowUpLeft.dds')
460 for item
in self
.items
:
461 item
.repos_done
= False
462 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
463 [item
.on_aspect_ratio_changed() for item
in self
.items
]
464 self
._side
_panel
.update(self
.items
)
465 max_x
= -float('inf')
466 for item
in self
.items
:
467 if not item
._instantiated
:
468 max_x
= max(item
._np
.get_x(), max_x
)
469 for item
in self
.items
:
470 if not item
._instantiated
:
473 def on_aspect_ratio_changed(self
):
476 def _win_condition(self
):
477 return all(itm
.strategy
.win_condition() for itm
in self
.items
) and not self
._paused
479 def _fail_condition(self
):
480 return all(itm
.fail_condition() for itm
in self
.items
) and not self
._paused
and self
._state
== 'playing'
482 def on_frame(self
, task
):
483 hits
= self
._get
_hits
()
485 for hit
in self
._get
_hits
():
486 if hit
.get_node() == self
._mouse
_plane
_node
:
487 pos
= hit
.get_hit_pos()
488 hit_nodes
= [hit
.get_node() for hit
in hits
]
489 if self
._item
_active
:
490 items_hit
= [self
._item
_active
]
492 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
493 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
494 [itm
.on_mouse_on() for itm
in items_hit
]
495 [itm
.on_mouse_off() for itm
in items_no_hit
]
496 if pos
and self
._item
_active
:
497 self
._item
_active
.on_mouse_move(pos
)
499 self
._update
_info
(items_hit
[0] if items_hit
else None)
500 if self
._win
_condition
():
501 self
._start
_evt
_time
= None
502 self
._set
_fail
() if self
._enforce
_res
== 'fail' else self
._set
_win
()
503 elif self
._state
== 'playing' and self
._fail
_condition
():
504 self
._start
_evt
_time
= None
505 self
._set
_win
() if self
._enforce
_res
== 'win' else self
._set
_fail
()
506 elif self
._testing
and self
._start
_evt
_time
and globalClock
.getFrameTime() - self
._start
_evt
_time
> 5.0:
507 self
._start
_evt
_time
= None
508 self
._set
_win
() if self
._enforce
_res
== 'win' else self
._set
_fail
()
509 if any(itm
._overlapping
for itm
in self
.items
):
510 self
._cursor
.cursor_img
.img
.set_color(.9, .1, .1, 1)
512 self
._cursor
.cursor_img
.img
.set_color(.9, .9, .9, 1)
515 def cb_inst(self
, item
):
519 self
._state
= 'playing'
520 #self.__prev_btn['state'] = DISABLED
521 #self.__next_btn['state'] = DISABLED
522 self
.__right
_btn
['state'] = DISABLED
523 [itm
.play() for itm
in self
.items
]
524 self
._start
_evt
_time
= globalClock
.getFrameTime()
527 self
._commands
[self
._command
_idx
].redo()
528 self
._command
_idx
+= 1
529 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
530 #self.__prev_btn['state'] = NORMAL
531 #self.__prev_btn['frameColor'] = fcols[0]
532 #more_commands = self._command_idx < len(self._commands)
533 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
534 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
537 self
._command
_idx
-= 1
538 self
._commands
[self
._command
_idx
].undo()
539 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
540 #self.__next_btn['state'] = NORMAL
541 #self.__next_btn['frameColor'] = fcols[0]
542 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
543 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
548 def _set_instructions(self
):
551 mgr
= TextPropertiesManager
.get_global_ptr()
552 for name
in ['mouse_l', 'mouse_r']:
553 graphic
= OnscreenImage('assets/images/buttons/%s.dds' % name
)
554 graphic
.set_scale(.5)
555 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
556 graphic
.get_texture().set_anisotropic_degree(2)
557 mgr
.set_graphic(name
, graphic
)
559 graphic
.set_transparency(True)
560 graphic
.detach_node()
561 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
562 frameSize
=(-.6, .6, -.3, .3))
563 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
565 font
.set_pixels_per_unit(60)
566 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
567 font
.set_outline((0, 0, 0, 1), .8, .2)
568 self
._txt
= OnscreenText(
569 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
570 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
571 u_l
= self
._txt
.textNode
.get_upper_left_3d()
572 l_r
= self
._txt
.textNode
.get_lower_right_3d()
573 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
576 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
577 z
+= (btn_scale
+ 2 * mar
) / 2
578 self
._txt
['pos'] = -w
/ 2, z
579 u_l
= self
._txt
.textNode
.get_upper_left_3d()
580 l_r
= self
._txt
.textNode
.get_lower_right_3d()
581 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
582 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
583 frm
['frameSize'] = fsz
585 (.6, .6, .6, 1), # ready
586 (1, 1, 1, 1), # press
587 (.8, .8, .8, 1), # rollover
589 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
591 image
=imgs
, scale
=btn_scale
,
592 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
593 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
594 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
595 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
596 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
597 btn
.set_transparency(True)
598 self
._pos
_mgr
.register('close_instructions', LibP3d
.wdg_pos(btn
))
601 self
.__persistent
.save_scene(self
.__json
_name
, self
.version(self
.__json
_name
))
602 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
605 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
606 frameSize
=(-.6, .6, -.3, .3))
607 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
609 font
.set_pixels_per_unit(60)
610 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
611 font
.set_outline((0, 0, 0, 1), .8, .2)
612 self
._txt
= OnscreenText(
615 font
=font
, scale
=0.2,
617 u_l
= self
._txt
.textNode
.get_upper_left_3d()
618 l_r
= self
._txt
.textNode
.get_lower_right_3d()
619 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
623 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
624 z
+= (btn_scale
+ 2 * mar
) / 2
625 self
._txt
['pos'] = 0, z
626 u_l
= self
._txt
.textNode
.get_upper_left_3d()
627 l_r
= self
._txt
.textNode
.get_lower_right_3d()
628 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
629 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
630 frm
['frameSize'] = fsz
632 (.6, .6, .6, 1), # ready
633 (1, 1, 1, 1), # press
634 (.8, .8, .8, 1), # rollover
636 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
638 image
=imgs
, scale
=btn_scale
,
639 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
640 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
641 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
642 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
643 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
644 btn
.set_transparency(True)
645 self
._pos
_mgr
.register('home_win', LibP3d
.wdg_pos(btn
))
646 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
648 image
=imgs
, scale
=btn_scale
,
649 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
650 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
651 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
652 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
653 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
654 self
._pos
_mgr
.register('replay', LibP3d
.wdg_pos(btn
))
655 btn
.set_transparency(True)
656 enabled
= self
._scenes
.index(self
.__json
_name
) < len(self
._scenes
) - 1
658 next_scene
= self
._scenes
[self
._scenes
.index(self
.__json
_name
) + 1]
661 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
663 image
=imgs
, scale
=btn_scale
,
664 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
665 parent
=frm
, command
=self
._on
_next
_scene
,
666 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
667 frameColor
=(.6, .6, .6, .08),
668 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
669 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
670 btn
['state'] = NORMAL
if enabled
else DISABLED
671 self
._pos
_mgr
.register('next', LibP3d
.wdg_pos(btn
))
672 btn
.set_transparency(True)
675 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
678 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
679 frameSize
=(-.6, .6, -.3, .3))
680 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
682 font
.set_pixels_per_unit(60)
683 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
684 font
.set_outline((0, 0, 0, 1), .8, .2)
685 self
._txt
= OnscreenText(
686 _('You have failed!'),
688 font
=font
, scale
=0.2,
690 u_l
= self
._txt
.textNode
.get_upper_left_3d()
691 l_r
= self
._txt
.textNode
.get_lower_right_3d()
692 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
696 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
697 z
+= (btn_scale
+ 2 * mar
) / 2
698 self
._txt
['pos'] = 0, z
699 u_l
= self
._txt
.textNode
.get_upper_left_3d()
700 l_r
= self
._txt
.textNode
.get_lower_right_3d()
701 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
702 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
703 frm
['frameSize'] = fsz
705 (.6, .6, .6, 1), # ready
706 (1, 1, 1, 1), # press
707 (.8, .8, .8, 1), # rollover
709 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
711 image
=imgs
, scale
=btn_scale
,
712 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
713 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
714 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
715 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
716 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
717 self
._pos
_mgr
.register('home_win', LibP3d
.wdg_pos(btn
))
718 btn
.set_transparency(True)
719 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
721 image
=imgs
, scale
=btn_scale
,
722 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
723 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
724 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
725 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
726 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
727 self
._pos
_mgr
.register('replay', LibP3d
.wdg_pos(btn
))
728 btn
.set_transparency(True)
730 def _on_restart(self
, frm
):
731 self
.__on
_close
_instructions
(frm
)
734 def _on_end_home(self
, frm
):
735 self
.__on
_close
_instructions
(frm
)
738 def _on_next_scene(self
, frm
, scene
):
739 self
.__on
_close
_instructions
(frm
)
740 self
._reload
_cb
(scene
)
742 def __store_state(self
):
744 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
745 #self.__next_btn, self.__prev_btn, self.__rewind_btn
747 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
749 btn
['state'] = DISABLED
750 [itm
.store_state() for itm
in self
.items
]
752 def __restore_state(self
):
754 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
755 #self.__next_btn, self.__prev_btn, self.__rewind_btn
757 for btn
, state
in zip(btns
, self
.__btn
_state
):
759 [itm
.restore_state() for itm
in self
.items
]
762 def __on_close_instructions(self
, frm
):
764 self
.__restore
_state
()
766 def _set_test_items(self
):
767 def frame_after(task
):
768 self
._define
_test
_items
()
769 for itm
in self
._test
_items
:
770 self
._pos
_mgr
.register(itm
.name
, P3dGfxMgr
.pos2d_p2d(itm
))
771 taskMgr
.doMethodLater(1.4, frame_after
, 'frame after') # after the intro sequence
773 def _define_test_items(self
):
774 if not self
.__json
_name
in self
.__class
__.json_files
:
775 with
open(self
.__class
__.filename(self
.__json
_name
)) as f
:
776 self
.__class
__.json_files
[self
.__json
_name
] = loads(f
.read())
777 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['pixel_space']:
778 self
._pos
_mgr
.register(item
['id'], tuple(item
['position']))
779 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['world_space']:
780 self
._set
_test
_item
(item
['id'], tuple(item
['position']))
782 def _set_test_item(self
, name
, pos
):
783 self
._test
_items
+= [NodePath(name
)]
784 self
._test
_items
[-1].set_pos(pos
[0], 0, pos
[1])
786 def add_item(self
, item
):
789 def _set_editor(self
):
790 fields
= ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
791 SceneContext
= namedtuple('SceneContext', fields
)
792 context
= SceneContext(
794 self
._mouse
_plane
_node
,
799 self
.__scene
_editor
= SceneEditor(self
.json
, self
.__json
_name
, context
, self
.add_item
, self
.items
)
801 def __on_inspector_delete(self
, item
):
802 self
.items
.remove(item
)
803 self
.json
['items'].remove(item
.json
)