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
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
.utils
.gfx
import GfxTools
, DirectGuiMixin
26 from ya2
.utils
.gui
import GuiTools
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
43 for k
in list(self
._pos
_mgr
.keys()): del self
._pos
_mgr
[k
]
45 self
._start
_evt
_time
= None
46 self
._enforce
_result
= ''
47 self
.__persistent
= persistent
48 self
.__json
_name
= json_name
49 self
.__editor
= editor
50 self
.__scene
_editor
= None
52 self
.accept('enforce_result', self
.enforce_result
)
54 self
._cursor
= MouseCursor(
55 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
61 self
._set
_mouse
_plane
()
67 self
._item
_active
= None
70 self
.__restore
_state
()
72 self
._set
_instructions
()
73 self
._bg
= Background()
74 self
._side
_panel
= SidePanel(world
, self
._mouse
_plane
_node
, (-5, 4), (-3, 1), 1, self
.__items
)
75 self
._scene
_tsk
= taskMgr
.add(self
.on_frame
, 'scene_on_frame')
78 self
.accept('editor-inspector-delete', self
.__on
_inspector
_delete
)
81 def filename(cls
, scene_name
):
82 return f
'assets/scenes/{scene_name}.json'
85 def name(cls
, scene_name
):
86 if not scene_name
in cls
.json_files
:
87 with
open(cls
.filename(scene_name
)) as f
:
88 cls
.json_files
[scene_name
] = loads(f
.read())
89 return _(cls
.json_files
[scene_name
]['name'])
92 def version(cls
, scene_name
):
93 if not scene_name
: return ''
94 if not scene_name
in cls
.json_files
:
95 with
open(cls
.filename(scene_name
)) as f
:
96 cls
.json_files
[scene_name
] = loads(f
.read())
97 return cls
.json_files
[scene_name
]['version']
100 def is_done(cls
, scene_name
):
101 if not cls
.scenes_done
or len(cls
.scenes_done
) == 1 and not cls
.scenes_done
[0]:
103 return bool([(name
, version
) for name
, version
in cls
.scenes_done
if scene_name
== name
and cls
.version(scene_name
) == version
])
105 def _instr_txt(self
):
106 txt
= _('Scene: ') + self
.name(self
.__json
_name
) + '\n\n'
107 txt
+= _(self
.__process
_json
_escape
(self
.__class
__.json_files
[self
.__json
_name
]['instructions']))
110 def __process_json_escape(self
, string
):
111 return bytes(string
, 'utf-8').decode('unicode-escape')
115 items
= self
.__items
[:]
116 if self
.__scene
_editor
:
117 items
+= self
.__scene
_editor
.test_items
120 def _set_items(self
):
121 if not self
.__json
_name
: return
123 self
._test
_items
= []
125 with
open(f
'assets/scenes/{self.__json_name}.json') as f
:
126 self
.json
= loads(f
.read())
127 for item
in self
.json
['start_items']:
129 'world': self
._world
,
130 'plane_node': self
._mouse
_plane
_node
,
131 'cb_inst': self
.cb_inst
,
132 'curr_bottom': self
.current_bottom
,
134 'count': item
['count'],
137 args
['mass'] = item
['mass']
138 if 'friction' in item
:
139 args
['friction'] = item
['friction']
140 self
.__items
+= [self
.__code
2class
(item
['class'])(**args
)]
141 for item
in self
.json
['items']:
143 'world': self
._world
,
144 'plane_node': self
._mouse
_plane
_node
,
145 'cb_inst': self
.cb_inst
,
146 'curr_bottom': self
.current_bottom
,
149 args
['pos'] = tuple(item
['position'])
151 args
['mass'] = item
['mass']
152 if 'friction' in item
:
153 args
['friction'] = item
['friction']
155 args
['r'] = item
['roll']
156 if 'model_scale' in item
:
157 args
['model_scale'] = item
['model_scale']
158 if 'restitution' in item
:
159 args
['restitution'] = item
['restitution']
160 if 'friction' in item
:
161 args
['friction'] = item
['friction']
162 self
.__items
+= [self
.__code
2class
(item
['class'])(**args
)]
163 if 'strategy' in item
:
164 match item
['strategy']:
166 self
.__items
[-1].set_strategy(self
.__code
2class
(item
['strategy'])(self
.__items
[-1]._np
, *item
['strategy_args']))
168 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
))
170 def __code2class(self
, code
):
173 'Basketball': Basketball
,
176 'TeeterTooter': TeeterTooter
,
177 'DownStrategy': DownStrategy
,
178 'HitStrategy': HitStrategy
181 def __item_with_id(self
, id):
182 for item
in self
.__items
:
183 if 'id' in item
.json
and item
.json
['id'] == id:
186 def screenshot(self
, task
=None):
187 tex
= Texture('screenshot')
188 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
189 cam
= base
.make_camera(buffer)
190 cam
.reparent_to(render
)
191 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
192 cam
.set_pos(0, -20, 0)
198 use_normal_maps
=True,
199 use_emission_maps
=False,
200 use_occlusion_maps
=True,
203 base
.graphicsEngine
.renderFrame()
204 base
.graphicsEngine
.renderFrame()
205 fname
= self
.__json
_name
206 if not exists('assets/images/scenes'):
207 makedirs('assets/images/scenes')
208 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
209 # img = DirectButton(
210 # frameTexture=buffer.get_texture(), relief=FLAT,
211 # frameSize=(-.2, .2, -.2, .2))
212 return buffer.get_texture()
214 def current_bottom(self
):
216 for item
in self
.__items
:
218 curr_bottom
= min(curr_bottom
, item
.get_bottom())
222 [itm
.destroy() for itm
in self
.__items
]
223 [itm
.remove_node() for itm
in self
._test
_items
]
225 self
._test
_items
= []
227 self
._set
_test
_items
()
230 self
._command
_idx
= 0
231 self
._start
_evt
_time
= None
232 if hasattr(self
, '_success_txt'):
233 self
._success
_txt
.destroy()
234 del self
._success
_txt
235 self
.__right
_btn
['state'] = NORMAL
237 def enforce_result(self
, val
):
238 self
._enforce
_result
= val
239 info('enforce result: ' + val
)
242 self
.__intro
_sequence
.finish()
243 self
.ignore('enforce_result')
247 self
._unset
_mouse
_plane
()
248 [itm
.destroy() for itm
in self
.__items
]
249 [itm
.remove_node() for itm
in self
._test
_items
]
251 self
._side
_panel
.destroy()
252 self
._cursor
.destroy()
253 taskMgr
.remove(self
._scene
_tsk
)
254 if hasattr(self
, '_success_txt'):
255 self
._success
_txt
.destroy()
256 self
.ignore('editor-inspector-delete')
257 if self
.__scene
_editor
: self
.__scene
_editor
.destroy()
259 def _set_camera(self
):
260 base
.camera
.set_pos(0, -20, 0)
261 base
.camera
.look_at(0, 0, 0)
266 start_v
[0] + (end_v
[0] - start_v
[0]) * t
,
267 start_v
[1] + (end_v
[1] - start_v
[1]) * t
,
268 start_v
[2] + (end_v
[2] - start_v
[2]) * t
)
269 base
.camera
.set_pos(*curr_pos
)
271 camera_interval
= LerpFunctionInterval(
276 blendType
='easeInOut')
277 self
.__intro
_sequence
= Sequence(
280 self
.__intro
_sequence
.start()
282 def __load_img_btn(self
, path
, col
):
283 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
284 img
.set_transparency(True)
290 def load_images_btn(path
, col
):
293 (.6, .6, .6, 1), # ready
294 (1, 1, 1, 1), # press
295 (.8, .8, .8, 1), # rollover
301 (.4, .1, .1, .4)]}[col
]
302 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
303 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
305 ('home', self
.on_home
, NORMAL
, abl
, 'gray', _('Exit'), 'right'),
306 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray', _('Instructions'), 'right'),
307 ('right', self
.on_play
, NORMAL
, abr
, 'green', _('Run'), 'left'),
308 #('next', self.on_next, DISABLED, abr, 'gray'),
309 #('previous', self.on_prev, DISABLED, abr, 'gray'),
310 #('rewind', self.reset, NORMAL, abr, 'gray')
313 btn_info
.insert(2, ('wrench', self
._set
_editor
, NORMAL
, abl
, 'gray', _('Editor'), 'right'))
316 tooltip_args
= self
.__font
, .05, (.93, .93, .93, 1)
317 for binfo
in btn_info
:
318 imgs
= load_images_btn(binfo
[0], binfo
[4])
319 if binfo
[3] == base
.a2dBottomLeft
:
323 sign
, num
= -1, num_r
325 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
327 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
328 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
329 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
330 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
331 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
332 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
333 btn
.set_transparency(True)
334 t
= tooltip_args
+ (binfo
[6],)
335 btn
.set_tooltip(binfo
[5], *t
)
336 self
._pos
_mgr
[binfo
[0]] = btn
.pos_pixel()
339 self
.__home
_btn
, self
.__info
_btn
, self
.__editor
_btn
, self
.__right
_btn
= btns
341 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
= btns
342 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
344 self
._info
_txt
= OnscreenText(
345 '', parent
=base
.a2dTopRight
, scale
=0.04,
346 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
347 if self
._mouse
_coords
:
348 self
._coords
_txt
= OnscreenText(
349 '', parent
=base
.a2dTopRight
, scale
=0.04,
350 pos
=(-.03, -.12), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
351 def update_coords(task
):
353 for hit
in self
._get
_hits
():
354 if hit
.get_node() == self
._mouse
_plane
_node
:
355 pos
= hit
.get_hit_pos()
357 txt
= '%s %s' % (round(pos
.x
, 3),
359 self
._coords
_txt
['text'] = txt
361 self
._coords
_tsk
= taskMgr
.add(update_coords
, 'update_coords')
363 def _unset_gui(self
):
365 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
366 #self.__next_btn, self.__prev_btn, self.__rewind_btn
368 if self
.__editor
: btns
+= [self
.__editor
_btn
]
369 [btn
.destroy() for btn
in btns
]
371 self
._info
_txt
.destroy()
372 if self
._mouse
_coords
:
373 taskMgr
.remove(self
._coords
_tsk
)
374 self
._coords
_txt
.destroy()
376 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
377 light
= Spotlight(name
)
379 light
.setLens(PerspectiveLens())
380 light_np
= render
.attach_new_node(light
)
381 light_np
.set_pos(pos
)
382 light_np
.look_at(look_at
)
383 light
.set_color(color
)
384 render
.set_light(light_np
)
387 def _set_lights(self
):
388 alight
= AmbientLight('alight') # for ao
389 alight
.set_color((.15, .15, .15, 1))
390 self
._alnp
= render
.attach_new_node(alight
)
391 render
.set_light(self
._alnp
)
392 self
._key
_light
= self
._set
_spotlight
(
393 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
394 self
._shadow
_light
= self
._set
_spotlight
(
395 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
396 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
397 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
398 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
399 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
401 def _unset_lights(self
):
402 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
403 render
.clear_light(light
)
406 def _set_input(self
):
407 self
.accept('mouse1', self
.on_click_l
)
408 self
.accept('mouse1-up', self
.on_release
)
409 self
.accept('mouse3', self
.on_click_r
)
410 self
.accept('mouse3-up', self
.on_release
)
412 def _unset_input(self
):
413 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
416 def _set_mouse_plane(self
):
417 shape
= BulletPlaneShape((0, -1, 0), 0)
418 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
419 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
420 self
._mouse
_plane
_node
.addShape(shape
)
421 #np = render.attachNewNode(self._mouse_plane_node)
422 #self._world.attachRigidBody(self._mouse_plane_node)
423 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
425 def _unset_mouse_plane(self
):
426 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
429 if not base
.mouseWatcherNode
.has_mouse(): return []
430 p_from
, p_to
= GuiTools
.get_mouse().from_to_points()
431 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
433 def _update_info(self
, item
):
436 txt
= '%.3f %.3f\n%.3f°' % (
437 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
438 self
._info
_txt
['text'] = txt
440 def _on_click(self
, method
):
443 for hit
in self
._get
_hits
():
444 if hit
.get_node() == self
._mouse
_plane
_node
:
445 pos
= hit
.get_hit_pos()
446 for hit
in self
._get
_hits
():
447 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
448 if not self
._item
_active
:
449 self
._item
_active
= item
450 if item
not in self
.__items
:
451 method
= 'on_click_l'
452 getattr(item
, method
)(pos
)
453 img
= 'move' if method
== 'on_click_l' else 'rotate'
454 if not (img
== 'rotate' and not item
._instantiated
):
455 self
._cursor
.set_image('assets/images/buttons/%s.dds' % img
)
457 def on_click_l(self
):
458 self
._on
_click
('on_click_l')
460 def on_click_r(self
):
461 self
._on
_click
('on_click_r')
463 def on_release(self
):
464 if self
._item
_active
and not self
._item
_active
._first
_command
:
465 self
._commands
= self
._commands
[:self
._command
_idx
]
466 self
._commands
+= [self
._item
_active
]
467 self
._command
_idx
+= 1
468 #self.__prev_btn['state'] = NORMAL
469 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
470 #self.__prev_btn['frameColor'] = fcols[0]
471 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
472 # self.__next_btn['state'] = DISABLED
473 # self.__next_btn['frameColor'] = fcols[1]
474 self
._item
_active
= None
475 [item
.on_release() for item
in self
.__items
]
476 self
._cursor
.set_image('assets/images/buttons/arrowUpLeft.dds')
479 for item
in self
.__items
:
480 item
.repos_done
= False
481 self
.__items
= sorted(self
.__items
, key
=lambda itm
: itm
.__class
__.__name
__)
482 [item
.on_aspect_ratio_changed() for item
in self
.__items
]
483 self
._side
_panel
.update(self
.__items
)
484 max_x
= -float('inf')
485 for item
in self
.__items
:
486 if not item
._instantiated
:
487 max_x
= max(item
._np
.get_x(), max_x
)
488 for item
in self
.__items
:
489 if not item
._instantiated
:
492 def on_aspect_ratio_changed(self
):
495 def _win_condition(self
):
496 return all(itm
.strategy
.win_condition() for itm
in self
.__items
) and not self
._paused
498 def _fail_condition(self
):
499 return all(itm
.fail_condition() for itm
in self
.__items
) and not self
._paused
and self
._state
== 'playing'
501 def on_frame(self
, task
):
502 hits
= self
._get
_hits
()
504 for hit
in self
._get
_hits
():
505 if hit
.get_node() == self
._mouse
_plane
_node
:
506 pos
= hit
.get_hit_pos()
507 hit_nodes
= [hit
.get_node() for hit
in hits
]
508 if self
._item
_active
:
509 items_hit
= [self
._item
_active
]
511 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
512 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
513 [itm
.on_mouse_on() for itm
in items_hit
]
514 [itm
.on_mouse_off() for itm
in items_no_hit
]
515 if pos
and self
._item
_active
:
516 self
._item
_active
.on_mouse_move(pos
)
518 self
._update
_info
(items_hit
[0] if items_hit
else None)
519 if not self
.__scene
_editor
and self
._win
_condition
():
520 self
._start
_evt
_time
= None
521 self
._set
_fail
() if self
._enforce
_result
== 'fail' else self
._set
_win
()
522 elif self
._state
== 'playing' and self
._fail
_condition
():
523 self
._start
_evt
_time
= None
524 self
._set
_win
() if self
._enforce
_result
== 'win' else self
._set
_fail
()
525 elif self
._testing
and self
._start
_evt
_time
and globalClock
.getFrameTime() - self
._start
_evt
_time
> 5.0:
526 self
._start
_evt
_time
= None
527 self
._set
_win
() if self
._enforce
_result
== 'win' else self
._set
_fail
()
528 if any(itm
._overlapping
for itm
in self
.items
):
529 self
._cursor
.set_color((.9, .1, .1, 1))
531 self
._cursor
.set_color((.9, .9, .9, 1))
534 def cb_inst(self
, item
):
535 self
.__items
+= [item
]
538 self
._state
= 'playing'
539 #self.__prev_btn['state'] = DISABLED
540 #self.__next_btn['state'] = DISABLED
541 self
.__right
_btn
['state'] = DISABLED
542 [itm
.play() for itm
in self
.__items
]
543 self
._start
_evt
_time
= globalClock
.getFrameTime()
546 self
._commands
[self
._command
_idx
].redo()
547 self
._command
_idx
+= 1
548 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
549 #self.__prev_btn['state'] = NORMAL
550 #self.__prev_btn['frameColor'] = fcols[0]
551 #more_commands = self._command_idx < len(self._commands)
552 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
553 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
556 self
._command
_idx
-= 1
557 self
._commands
[self
._command
_idx
].undo()
558 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
559 #self.__next_btn['state'] = NORMAL
560 #self.__next_btn['frameColor'] = fcols[0]
561 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
562 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
567 def __set_font(self
):
568 self
.__font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
570 self
.__font
.set_pixels_per_unit(60)
571 self
.__font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
572 self
.__font
.set_outline((0, 0, 0, 1), .8, .2)
575 def _set_instructions(self
):
578 mgr
= TextPropertiesManager
.get_global_ptr()
579 for name
in ['mouse_l', 'mouse_r']:
580 graphic
= OnscreenImage('assets/images/buttons/%s.dds' % name
)
581 graphic
.set_scale(.5)
582 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
583 graphic
.get_texture().set_anisotropic_degree(2)
584 mgr
.set_graphic(name
, graphic
)
586 graphic
.set_transparency(True)
587 graphic
.detach_node()
588 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
589 frameSize
=(-.6, .6, -.3, .3))
590 self
._txt
= OnscreenText(
591 self
._instr
_txt
(), parent
=frm
, font
=self
.__font
, scale
=0.06,
592 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
593 u_l
= self
._txt
.textNode
.get_upper_left_3d()
594 l_r
= self
._txt
.textNode
.get_lower_right_3d()
595 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
598 z
= h
/ 2 - self
.__font
.get_line_height() * self
._txt
['scale'][1]
599 z
+= (btn_scale
+ 2 * mar
) / 2
600 self
._txt
['pos'] = -w
/ 2, z
601 u_l
= self
._txt
.textNode
.get_upper_left_3d()
602 l_r
= self
._txt
.textNode
.get_lower_right_3d()
603 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
604 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
605 frm
['frameSize'] = fsz
607 (.6, .6, .6, 1), # ready
608 (1, 1, 1, 1), # press
609 (.8, .8, .8, 1), # rollover
611 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
613 image
=imgs
, scale
=btn_scale
,
614 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
615 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
616 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
617 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
618 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
619 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
620 btn
.set_transparency(True)
621 self
._pos
_mgr
['close_instructions'] = btn
.pos_pixel()
624 self
.__persistent
.save_scene(self
.__json
_name
, self
.version(self
.__json
_name
))
625 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
628 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
629 frameSize
=(-.6, .6, -.3, .3))
630 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
632 font
.set_pixels_per_unit(60)
633 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
634 font
.set_outline((0, 0, 0, 1), .8, .2)
635 self
._txt
= OnscreenText(
638 font
=font
, scale
=0.2,
640 u_l
= self
._txt
.textNode
.get_upper_left_3d()
641 l_r
= self
._txt
.textNode
.get_lower_right_3d()
642 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
646 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
647 z
+= (btn_scale
+ 2 * mar
) / 2
648 self
._txt
['pos'] = 0, z
649 u_l
= self
._txt
.textNode
.get_upper_left_3d()
650 l_r
= self
._txt
.textNode
.get_lower_right_3d()
651 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
652 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
653 frm
['frameSize'] = fsz
655 (.6, .6, .6, 1), # ready
656 (1, 1, 1, 1), # press
657 (.8, .8, .8, 1), # rollover
659 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
661 image
=imgs
, scale
=btn_scale
,
662 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
663 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
664 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
665 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
666 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
667 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
668 btn
.set_transparency(True)
669 self
._pos
_mgr
['home_win'] = btn
.pos_pixel()
670 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
672 image
=imgs
, scale
=btn_scale
,
673 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
674 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
675 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
676 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
677 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
678 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
679 self
._pos
_mgr
['replay'] = btn
.pos_pixel()
680 btn
.set_transparency(True)
682 enabled
= self
._scenes
.index(self
.__json
_name
) < len(self
._scenes
) - 1
684 next_scene
= self
._scenes
[self
._scenes
.index(self
.__json
_name
) + 1]
690 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
692 image
=imgs
, scale
=btn_scale
,
693 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
694 parent
=frm
, command
=self
._on
_next
_scene
,
695 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
696 frameColor
=(.6, .6, .6, .08),
697 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
698 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
699 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
700 btn
['state'] = NORMAL
if enabled
else DISABLED
701 self
._pos
_mgr
['next'] = btn
.pos_pixel()
702 btn
.set_transparency(True)
705 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
708 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
709 frameSize
=(-.6, .6, -.3, .3))
710 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
712 font
.set_pixels_per_unit(60)
713 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
714 font
.set_outline((0, 0, 0, 1), .8, .2)
715 self
._txt
= OnscreenText(
716 _('You have failed!'),
718 font
=font
, scale
=0.2,
720 u_l
= self
._txt
.textNode
.get_upper_left_3d()
721 l_r
= self
._txt
.textNode
.get_lower_right_3d()
722 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
726 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
727 z
+= (btn_scale
+ 2 * mar
) / 2
728 self
._txt
['pos'] = 0, z
729 u_l
= self
._txt
.textNode
.get_upper_left_3d()
730 l_r
= self
._txt
.textNode
.get_lower_right_3d()
731 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
732 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
733 frm
['frameSize'] = fsz
735 (.6, .6, .6, 1), # ready
736 (1, 1, 1, 1), # press
737 (.8, .8, .8, 1), # rollover
739 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
741 image
=imgs
, scale
=btn_scale
,
742 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
743 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
744 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
745 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
746 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
747 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
748 self
._pos
_mgr
['home_win'] = btn
.pos_pixel()
749 btn
.set_transparency(True)
750 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
752 image
=imgs
, scale
=btn_scale
,
753 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
754 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
755 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
756 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
757 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
758 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
759 self
._pos
_mgr
['replay'] = btn
.pos_pixel()
760 btn
.set_transparency(True)
762 def _on_restart(self
, frm
):
763 self
.__on
_close
_instructions
(frm
)
766 def _on_end_home(self
, frm
):
767 self
.__on
_close
_instructions
(frm
)
770 def _on_next_scene(self
, frm
, scene
):
771 self
.__on
_close
_instructions
(frm
)
772 self
._reload
_cb
(scene
)
774 def __store_state(self
):
776 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
777 #self.__next_btn, self.__prev_btn, self.__rewind_btn
779 if self
.__editor
: btns
+= [self
.__editor
_btn
]
780 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
782 btn
['state'] = DISABLED
783 [itm
.store_state() for itm
in self
.__items
]
785 def __restore_state(self
):
787 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
788 #self.__next_btn, self.__prev_btn, self.__rewind_btn
790 if self
.__editor
: btns
+= [self
.__editor
_btn
]
791 for btn
, state
in zip(btns
, self
.__btn
_state
):
793 [itm
.restore_state() for itm
in self
.__items
]
796 def __on_close_instructions(self
, frm
):
798 self
.__restore
_state
()
800 def _set_test_items(self
):
801 def frame_after(task
):
802 self
._define
_test
_items
()
803 for itm
in self
._test
_items
:
804 self
._pos
_mgr
[itm
.name
] = itm
.pos2d_pixel()
805 taskMgr
.doMethodLater(1.4, frame_after
, 'frame after') # after the intro sequence
807 def _define_test_items(self
):
808 if not self
.__json
_name
: return
809 if not self
.__json
_name
in self
.__class
__.json_files
:
810 with
open(self
.__class
__.filename(self
.__json
_name
)) as f
:
811 self
.__class
__.json_files
[self
.__json
_name
] = loads(f
.read())
812 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['pixel_space']:
813 self
._pos
_mgr
[item
['id']] = tuple(item
['position'])
814 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['world_space']:
815 self
._set
_test
_item
(item
['id'], tuple(item
['position']))
817 def _set_test_item(self
, name
, pos
):
818 self
._test
_items
+= [GfxTools
.build_empty_node(name
)]
819 self
._test
_items
[-1].set_pos(pos
[0], 0, pos
[1])
821 def add_item(self
, item
):
822 self
.__items
+= [item
]
824 def _set_editor(self
):
825 fields
= ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
826 SceneContext
= namedtuple('SceneContext', fields
)
827 context
= SceneContext(
829 self
._mouse
_plane
_node
,
834 self
.__scene
_editor
= SceneEditor(self
.json
, self
.__json
_name
, context
, self
.add_item
, self
.__items
, self
._world
, self
._mouse
_plane
_node
)
836 def __on_inspector_delete(self
, item
):
837 self
.__items
.remove(item
)
838 self
.json
['items'].remove(item
.json
)