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
: return ''
93 if not scene_name
in cls
.json_files
:
94 with
open(cls
.filename(scene_name
)) as f
:
95 cls
.json_files
[scene_name
] = loads(f
.read())
96 return cls
.json_files
[scene_name
]['version']
99 def is_done(cls
, scene_name
):
100 if not cls
.scenes_done
or len(cls
.scenes_done
) == 1 and not cls
.scenes_done
[0]:
102 return bool([(name
, version
) for name
, version
in cls
.scenes_done
if scene_name
== name
and cls
.version(scene_name
) == version
])
104 def _instr_txt(self
):
105 txt
= _('Scene: ') + self
.name(self
.__json
_name
) + '\n\n'
106 txt
+= _(self
.__process
_json
_escape
(self
.__class
__.json_files
[self
.__json
_name
]['instructions']))
109 def __process_json_escape(self
, string
):
110 return bytes(string
, 'utf-8').decode('unicode-escape')
112 def _set_items(self
):
113 if not self
.__json
_name
: return
115 self
._test
_items
= []
117 with
open(f
'assets/scenes/{self.__json_name}.json') as f
:
118 self
.json
= loads(f
.read())
119 for item
in self
.json
['start_items']:
121 'world': self
._world
,
122 'plane_node': self
._mouse
_plane
_node
,
123 'cb_inst': self
.cb_inst
,
124 'curr_bottom': self
.current_bottom
,
126 'count': item
['count'],
129 args
['mass'] = item
['mass']
130 if 'friction' in item
:
131 args
['friction'] = item
['friction']
132 self
.items
+= [self
.__code
2class
(item
['class'])(**args
)]
133 for item
in self
.json
['items']:
135 'world': self
._world
,
136 'plane_node': self
._mouse
_plane
_node
,
137 'cb_inst': self
.cb_inst
,
138 'curr_bottom': self
.current_bottom
,
141 args
['pos'] = tuple(item
['position'])
143 args
['mass'] = item
['mass']
144 if 'friction' in item
:
145 args
['friction'] = item
['friction']
147 args
['r'] = item
['roll']
148 if 'model_scale' in item
:
149 args
['model_scale'] = item
['model_scale']
150 if 'restitution' in item
:
151 args
['restitution'] = item
['restitution']
152 if 'friction' in item
:
153 args
['friction'] = item
['friction']
154 self
.items
+= [self
.__code
2class
(item
['class'])(**args
)]
155 if 'strategy' in item
:
156 match item
['strategy']:
158 self
.items
[-1].set_strategy(self
.__code
2class
(item
['strategy'])(self
.items
[-1]._np
, *item
['strategy_args']))
160 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
))
162 def __code2class(self
, code
):
165 'Basketball': Basketball
,
168 'TeeterTooter': TeeterTooter
,
169 'DownStrategy': DownStrategy
,
170 'HitStrategy': HitStrategy
173 def __item_with_id(self
, id):
174 for item
in self
.items
:
175 if 'id' in item
.json
and item
.json
['id'] == id:
178 def screenshot(self
, task
=None):
179 tex
= Texture('screenshot')
180 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
181 cam
= base
.make_camera(buffer)
182 cam
.reparent_to(render
)
183 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
184 cam
.set_pos(0, -20, 0)
190 use_normal_maps
=True,
191 use_emission_maps
=False,
192 use_occlusion_maps
=True,
195 base
.graphicsEngine
.renderFrame()
196 base
.graphicsEngine
.renderFrame()
197 fname
= self
.__json
_name
198 if not exists('assets/images/scenes'):
199 makedirs('assets/images/scenes')
200 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
201 # img = DirectButton(
202 # frameTexture=buffer.get_texture(), relief=FLAT,
203 # frameSize=(-.2, .2, -.2, .2))
204 return buffer.get_texture()
206 def current_bottom(self
):
208 for item
in self
.items
:
210 curr_bottom
= min(curr_bottom
, item
.get_bottom())
214 [itm
.destroy() for itm
in self
.items
]
215 [itm
.remove_node() for itm
in self
._test
_items
]
217 self
._test
_items
= []
219 self
._set
_test
_items
()
222 self
._command
_idx
= 0
223 self
._start
_evt
_time
= None
224 if hasattr(self
, '_success_txt'):
225 self
._success
_txt
.destroy()
226 del self
._success
_txt
227 self
.__right
_btn
['state'] = NORMAL
229 def enforce_res(self
, val
):
230 self
._enforce
_res
= val
231 info('enforce res: ' + val
)
234 self
.__intro
_sequence
.finish()
235 self
.ignore('enforce_res')
239 self
._unset
_mouse
_plane
()
240 [itm
.destroy() for itm
in self
.items
]
241 [itm
.remove_node() for itm
in self
._test
_items
]
243 self
._side
_panel
.destroy()
244 self
._cursor
.destroy()
245 taskMgr
.remove(self
._scene
_tsk
)
246 if hasattr(self
, '_success_txt'):
247 self
._success
_txt
.destroy()
248 self
.ignore('editor-inspector-delete')
250 def _set_camera(self
):
251 base
.camera
.set_pos(0, -20, 0)
252 base
.camera
.look_at(0, 0, 0)
257 start_v
[0] + (end_v
[0] - start_v
[0]) * t
,
258 start_v
[1] + (end_v
[1] - start_v
[1]) * t
,
259 start_v
[2] + (end_v
[2] - start_v
[2]) * t
)
260 base
.camera
.set_pos(*curr_pos
)
262 camera_interval
= LerpFunctionInterval(
267 blendType
='easeInOut')
268 self
.__intro
_sequence
= Sequence(
271 self
.__intro
_sequence
.start()
273 def __load_img_btn(self
, path
, col
):
274 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
275 img
.set_transparency(True)
281 def load_images_btn(path
, col
):
284 (.6, .6, .6, 1), # ready
285 (1, 1, 1, 1), # press
286 (.8, .8, .8, 1), # rollover
292 (.4, .1, .1, .4)]}[col
]
293 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
294 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
296 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
297 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
298 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
299 #('next', self.on_next, DISABLED, abr, 'gray'),
300 #('previous', self.on_prev, DISABLED, abr, 'gray'),
301 #('rewind', self.reset, NORMAL, abr, 'gray')
304 btn_info
.insert(2, ('wrench', self
._set
_editor
, NORMAL
, abl
, 'gray'))
307 for binfo
in btn_info
:
308 imgs
= load_images_btn(binfo
[0], binfo
[4])
309 if binfo
[3] == base
.a2dBottomLeft
:
313 sign
, num
= -1, num_r
315 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
317 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
318 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
319 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
320 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
321 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
322 btn
.set_transparency(True)
323 self
._pos
_mgr
.register(binfo
[0], LibP3d
.wdg_pos(btn
))
326 self
.__home
_btn
, self
.__info
_btn
, self
.__editor
_btn
, self
.__right
_btn
= btns
328 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
= btns
329 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
331 self
._info
_txt
= OnscreenText(
332 '', parent
=base
.a2dTopRight
, scale
=0.04,
333 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
334 if self
._mouse
_coords
:
335 self
._coords
_txt
= OnscreenText(
336 '', parent
=base
.a2dTopRight
, scale
=0.04,
337 pos
=(-.03, -.12), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
338 def update_coords(task
):
340 for hit
in self
._get
_hits
():
341 if hit
.get_node() == self
._mouse
_plane
_node
:
342 pos
= hit
.get_hit_pos()
344 txt
= '%s %s' % (round(pos
.x
, 3),
346 self
._coords
_txt
['text'] = txt
348 self
._coords
_tsk
= taskMgr
.add(update_coords
, 'update_coords')
350 def _unset_gui(self
):
352 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
353 #self.__next_btn, self.__prev_btn, self.__rewind_btn
355 [btn
.destroy() for btn
in btns
]
357 self
._info
_txt
.destroy()
358 if self
._mouse
_coords
:
359 taskMgr
.remove(self
._coords
_tsk
)
360 self
._coords
_txt
.destroy()
362 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
363 light
= Spotlight(name
)
365 light
.setLens(PerspectiveLens())
366 light_np
= render
.attach_new_node(light
)
367 light_np
.set_pos(pos
)
368 light_np
.look_at(look_at
)
369 light
.set_color(color
)
370 render
.set_light(light_np
)
373 def _set_lights(self
):
374 alight
= AmbientLight('alight') # for ao
375 alight
.set_color((.15, .15, .15, 1))
376 self
._alnp
= render
.attach_new_node(alight
)
377 render
.set_light(self
._alnp
)
378 self
._key
_light
= self
._set
_spotlight
(
379 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
380 self
._shadow
_light
= self
._set
_spotlight
(
381 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
382 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
383 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
384 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
385 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
387 def _unset_lights(self
):
388 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
389 render
.clear_light(light
)
392 def _set_input(self
):
393 self
.accept('mouse1', self
.on_click_l
)
394 self
.accept('mouse1-up', self
.on_release
)
395 self
.accept('mouse3', self
.on_click_r
)
396 self
.accept('mouse3-up', self
.on_release
)
398 def _unset_input(self
):
399 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
402 def _set_mouse_plane(self
):
403 shape
= BulletPlaneShape((0, -1, 0), 0)
404 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
405 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
406 self
._mouse
_plane
_node
.addShape(shape
)
407 #np = render.attachNewNode(self._mouse_plane_node)
408 #self._world.attachRigidBody(self._mouse_plane_node)
409 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
411 def _unset_mouse_plane(self
):
412 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
415 if not base
.mouseWatcherNode
.has_mouse(): return []
416 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
417 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
419 def _update_info(self
, item
):
422 txt
= '%.3f %.3f\n%.3f°' % (
423 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
424 self
._info
_txt
['text'] = txt
426 def _on_click(self
, method
):
429 for hit
in self
._get
_hits
():
430 if hit
.get_node() == self
._mouse
_plane
_node
:
431 pos
= hit
.get_hit_pos()
432 for hit
in self
._get
_hits
():
433 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
434 if not self
._item
_active
:
435 self
._item
_active
= item
436 getattr(item
, method
)(pos
)
437 img
= 'move' if method
== 'on_click_l' else 'rotate'
438 if not (img
== 'rotate' and not item
._instantiated
):
439 self
._cursor
.set_image('assets/images/buttons/%s.dds' % img
)
441 def on_click_l(self
):
442 self
._on
_click
('on_click_l')
444 def on_click_r(self
):
445 self
._on
_click
('on_click_r')
447 def on_release(self
):
448 if self
._item
_active
and not self
._item
_active
._first
_command
:
449 self
._commands
= self
._commands
[:self
._command
_idx
]
450 self
._commands
+= [self
._item
_active
]
451 self
._command
_idx
+= 1
452 #self.__prev_btn['state'] = NORMAL
453 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
454 #self.__prev_btn['frameColor'] = fcols[0]
455 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
456 # self.__next_btn['state'] = DISABLED
457 # self.__next_btn['frameColor'] = fcols[1]
458 self
._item
_active
= None
459 [item
.on_release() for item
in self
.items
]
460 self
._cursor
.set_image('assets/images/buttons/arrowUpLeft.dds')
463 for item
in self
.items
:
464 item
.repos_done
= False
465 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
466 [item
.on_aspect_ratio_changed() for item
in self
.items
]
467 self
._side
_panel
.update(self
.items
)
468 max_x
= -float('inf')
469 for item
in self
.items
:
470 if not item
._instantiated
:
471 max_x
= max(item
._np
.get_x(), max_x
)
472 for item
in self
.items
:
473 if not item
._instantiated
:
476 def on_aspect_ratio_changed(self
):
479 def _win_condition(self
):
480 return all(itm
.strategy
.win_condition() for itm
in self
.items
) and not self
._paused
482 def _fail_condition(self
):
483 return all(itm
.fail_condition() for itm
in self
.items
) and not self
._paused
and self
._state
== 'playing'
485 def on_frame(self
, task
):
486 hits
= self
._get
_hits
()
488 for hit
in self
._get
_hits
():
489 if hit
.get_node() == self
._mouse
_plane
_node
:
490 pos
= hit
.get_hit_pos()
491 hit_nodes
= [hit
.get_node() for hit
in hits
]
492 if self
._item
_active
:
493 items_hit
= [self
._item
_active
]
495 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
496 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
497 [itm
.on_mouse_on() for itm
in items_hit
]
498 [itm
.on_mouse_off() for itm
in items_no_hit
]
499 if pos
and self
._item
_active
:
500 self
._item
_active
.on_mouse_move(pos
)
502 self
._update
_info
(items_hit
[0] if items_hit
else None)
503 if not self
.__scene
_editor
and self
._win
_condition
():
504 self
._start
_evt
_time
= None
505 self
._set
_fail
() if self
._enforce
_res
== 'fail' else self
._set
_win
()
506 elif self
._state
== 'playing' and self
._fail
_condition
():
507 self
._start
_evt
_time
= None
508 self
._set
_win
() if self
._enforce
_res
== 'win' else self
._set
_fail
()
509 elif self
._testing
and self
._start
_evt
_time
and globalClock
.getFrameTime() - self
._start
_evt
_time
> 5.0:
510 self
._start
_evt
_time
= None
511 self
._set
_win
() if self
._enforce
_res
== 'win' else self
._set
_fail
()
512 if any(itm
._overlapping
for itm
in self
.items
):
513 self
._cursor
.cursor_img
.img
.set_color(.9, .1, .1, 1)
515 self
._cursor
.cursor_img
.img
.set_color(.9, .9, .9, 1)
518 def cb_inst(self
, item
):
522 self
._state
= 'playing'
523 #self.__prev_btn['state'] = DISABLED
524 #self.__next_btn['state'] = DISABLED
525 self
.__right
_btn
['state'] = DISABLED
526 [itm
.play() for itm
in self
.items
]
527 self
._start
_evt
_time
= globalClock
.getFrameTime()
530 self
._commands
[self
._command
_idx
].redo()
531 self
._command
_idx
+= 1
532 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
533 #self.__prev_btn['state'] = NORMAL
534 #self.__prev_btn['frameColor'] = fcols[0]
535 #more_commands = self._command_idx < len(self._commands)
536 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
537 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
540 self
._command
_idx
-= 1
541 self
._commands
[self
._command
_idx
].undo()
542 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
543 #self.__next_btn['state'] = NORMAL
544 #self.__next_btn['frameColor'] = fcols[0]
545 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
546 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
551 def _set_instructions(self
):
554 mgr
= TextPropertiesManager
.get_global_ptr()
555 for name
in ['mouse_l', 'mouse_r']:
556 graphic
= OnscreenImage('assets/images/buttons/%s.dds' % name
)
557 graphic
.set_scale(.5)
558 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
559 graphic
.get_texture().set_anisotropic_degree(2)
560 mgr
.set_graphic(name
, graphic
)
562 graphic
.set_transparency(True)
563 graphic
.detach_node()
564 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
565 frameSize
=(-.6, .6, -.3, .3))
566 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
568 font
.set_pixels_per_unit(60)
569 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
570 font
.set_outline((0, 0, 0, 1), .8, .2)
571 self
._txt
= OnscreenText(
572 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
573 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
574 u_l
= self
._txt
.textNode
.get_upper_left_3d()
575 l_r
= self
._txt
.textNode
.get_lower_right_3d()
576 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
579 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
580 z
+= (btn_scale
+ 2 * mar
) / 2
581 self
._txt
['pos'] = -w
/ 2, z
582 u_l
= self
._txt
.textNode
.get_upper_left_3d()
583 l_r
= self
._txt
.textNode
.get_lower_right_3d()
584 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
585 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
586 frm
['frameSize'] = fsz
588 (.6, .6, .6, 1), # ready
589 (1, 1, 1, 1), # press
590 (.8, .8, .8, 1), # rollover
592 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
594 image
=imgs
, scale
=btn_scale
,
595 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
596 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
597 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
598 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
599 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
600 btn
.set_transparency(True)
601 self
._pos
_mgr
.register('close_instructions', LibP3d
.wdg_pos(btn
))
604 self
.__persistent
.save_scene(self
.__json
_name
, self
.version(self
.__json
_name
))
605 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
608 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
609 frameSize
=(-.6, .6, -.3, .3))
610 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
612 font
.set_pixels_per_unit(60)
613 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
614 font
.set_outline((0, 0, 0, 1), .8, .2)
615 self
._txt
= OnscreenText(
618 font
=font
, scale
=0.2,
620 u_l
= self
._txt
.textNode
.get_upper_left_3d()
621 l_r
= self
._txt
.textNode
.get_lower_right_3d()
622 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
626 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
627 z
+= (btn_scale
+ 2 * mar
) / 2
628 self
._txt
['pos'] = 0, z
629 u_l
= self
._txt
.textNode
.get_upper_left_3d()
630 l_r
= self
._txt
.textNode
.get_lower_right_3d()
631 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
632 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
633 frm
['frameSize'] = fsz
635 (.6, .6, .6, 1), # ready
636 (1, 1, 1, 1), # press
637 (.8, .8, .8, 1), # rollover
639 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
641 image
=imgs
, scale
=btn_scale
,
642 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
643 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
644 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
645 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
646 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
647 btn
.set_transparency(True)
648 self
._pos
_mgr
.register('home_win', LibP3d
.wdg_pos(btn
))
649 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
651 image
=imgs
, scale
=btn_scale
,
652 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
653 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
654 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
655 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
656 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
657 self
._pos
_mgr
.register('replay', LibP3d
.wdg_pos(btn
))
658 btn
.set_transparency(True)
660 enabled
= self
._scenes
.index(self
.__json
_name
) < len(self
._scenes
) - 1
662 next_scene
= self
._scenes
[self
._scenes
.index(self
.__json
_name
) + 1]
668 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
670 image
=imgs
, scale
=btn_scale
,
671 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
672 parent
=frm
, command
=self
._on
_next
_scene
,
673 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
674 frameColor
=(.6, .6, .6, .08),
675 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
676 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
677 btn
['state'] = NORMAL
if enabled
else DISABLED
678 self
._pos
_mgr
.register('next', LibP3d
.wdg_pos(btn
))
679 btn
.set_transparency(True)
682 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
685 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
686 frameSize
=(-.6, .6, -.3, .3))
687 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
689 font
.set_pixels_per_unit(60)
690 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
691 font
.set_outline((0, 0, 0, 1), .8, .2)
692 self
._txt
= OnscreenText(
693 _('You have failed!'),
695 font
=font
, scale
=0.2,
697 u_l
= self
._txt
.textNode
.get_upper_left_3d()
698 l_r
= self
._txt
.textNode
.get_lower_right_3d()
699 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
703 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
704 z
+= (btn_scale
+ 2 * mar
) / 2
705 self
._txt
['pos'] = 0, z
706 u_l
= self
._txt
.textNode
.get_upper_left_3d()
707 l_r
= self
._txt
.textNode
.get_lower_right_3d()
708 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
709 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
710 frm
['frameSize'] = fsz
712 (.6, .6, .6, 1), # ready
713 (1, 1, 1, 1), # press
714 (.8, .8, .8, 1), # rollover
716 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
718 image
=imgs
, scale
=btn_scale
,
719 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
720 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
721 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
722 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
723 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
724 self
._pos
_mgr
.register('home_win', LibP3d
.wdg_pos(btn
))
725 btn
.set_transparency(True)
726 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
728 image
=imgs
, scale
=btn_scale
,
729 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
730 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
731 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
732 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
733 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
734 self
._pos
_mgr
.register('replay', LibP3d
.wdg_pos(btn
))
735 btn
.set_transparency(True)
737 def _on_restart(self
, frm
):
738 self
.__on
_close
_instructions
(frm
)
741 def _on_end_home(self
, frm
):
742 self
.__on
_close
_instructions
(frm
)
745 def _on_next_scene(self
, frm
, scene
):
746 self
.__on
_close
_instructions
(frm
)
747 self
._reload
_cb
(scene
)
749 def __store_state(self
):
751 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
752 #self.__next_btn, self.__prev_btn, self.__rewind_btn
754 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
756 btn
['state'] = DISABLED
757 [itm
.store_state() for itm
in self
.items
]
759 def __restore_state(self
):
761 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
762 #self.__next_btn, self.__prev_btn, self.__rewind_btn
764 for btn
, state
in zip(btns
, self
.__btn
_state
):
766 [itm
.restore_state() for itm
in self
.items
]
769 def __on_close_instructions(self
, frm
):
771 self
.__restore
_state
()
773 def _set_test_items(self
):
774 def frame_after(task
):
775 self
._define
_test
_items
()
776 for itm
in self
._test
_items
:
777 self
._pos
_mgr
.register(itm
.name
, P3dGfxMgr
.pos2d_p2d(itm
))
778 taskMgr
.doMethodLater(1.4, frame_after
, 'frame after') # after the intro sequence
780 def _define_test_items(self
):
781 if not self
.__json
_name
: return
782 if not self
.__json
_name
in self
.__class
__.json_files
:
783 with
open(self
.__class
__.filename(self
.__json
_name
)) as f
:
784 self
.__class
__.json_files
[self
.__json
_name
] = loads(f
.read())
785 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['pixel_space']:
786 self
._pos
_mgr
.register(item
['id'], tuple(item
['position']))
787 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['world_space']:
788 self
._set
_test
_item
(item
['id'], tuple(item
['position']))
790 def _set_test_item(self
, name
, pos
):
791 self
._test
_items
+= [NodePath(name
)]
792 self
._test
_items
[-1].set_pos(pos
[0], 0, pos
[1])
794 def add_item(self
, item
):
797 def _set_editor(self
):
798 fields
= ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
799 SceneContext
= namedtuple('SceneContext', fields
)
800 context
= SceneContext(
802 self
._mouse
_plane
_node
,
807 self
.__scene
_editor
= SceneEditor(self
.json
, self
.__json
_name
, context
, self
.add_item
, self
.items
)
809 def __on_inspector_delete(self
, item
):
810 self
.items
.remove(item
)
811 self
.json
['items'].remove(item
.json
)