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
.items
.test_item
import PixelSpaceTestItem
, WorldSpaceTestItem
24 from pmachines
.editor
.scene
import SceneEditor
25 from ya2
.utils
.gui
.cursor
import MouseCursor
, MouseCursorArgs
26 from ya2
.utils
.gfx
import GfxTools
, DirectGuiMixin
27 from ya2
.utils
.gui
.gui
import GuiTools
30 class Scene(DirectObject
):
35 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
):
38 self
._exit
_cb
= exit_cb
39 self
._testing
= testing
40 self
._mouse
_coords
= mouse_coords
41 self
._dbg
_items
= dbg_items
42 self
._reload
_cb
= reload_cb
43 self
._pos
_mgr
= pos_mgr
44 for k
in list(self
._pos
_mgr
.keys()): del self
._pos
_mgr
[k
]
46 self
._start
_evt
_time
= None
47 self
._enforce
_result
= ''
48 self
.__persistent
= persistent
49 self
.__json
_name
= json_name
50 self
.__editor
= editor
51 self
.__scene
_editor
= None
53 self
.accept('enforce_result', self
.enforce_result
)
56 'assets/images/buttons/arrowUpLeft.dds', testing
, (.04, 1, .04), (.5, .5, .5, 1),
58 self
._cursor
= MouseCursor(c
)
63 self
._set
_mouse
_plane
()
69 self
._item
_active
= None
72 self
.__restore
_state
()
73 elif self
.__json
_name
:
74 self
._set
_instructions
()
75 if not self
.__json
_name
:
76 self
.json
= {'background': 'wood'}
77 self
._bg
= Background(self
.json
['background'])
78 self
._side
_panel
= SidePanel(world
, self
._mouse
_plane
_node
, (-5, 4), (-3, 1), 1, self
.__items
)
79 self
._scene
_tsk
= taskMgr
.add(self
.on_frame
, 'scene_on_frame')
82 self
.accept('editor-inspector-delete', self
.__on
_inspector
_delete
)
85 def filename(cls
, scene_name
):
86 return f
'assets/scenes/{scene_name}.json'
89 def name(cls
, scene_name
):
90 if not scene_name
in cls
.json_files
:
91 with
open(cls
.filename(scene_name
)) as f
:
92 cls
.json_files
[scene_name
] = loads(f
.read())
93 return _(cls
.json_files
[scene_name
]['name'])
96 def version(cls
, scene_name
):
97 if not scene_name
: return ''
98 if not scene_name
in cls
.json_files
:
99 with
open(cls
.filename(scene_name
)) as f
:
100 cls
.json_files
[scene_name
] = loads(f
.read())
101 return cls
.json_files
[scene_name
]['version']
104 def is_done(cls
, scene_name
):
105 if not cls
.scenes_done
or len(cls
.scenes_done
) == 1 and not cls
.scenes_done
[0]:
107 return bool([(name
, version
) for name
, version
in cls
.scenes_done
if scene_name
== name
and cls
.version(scene_name
) == version
])
109 def _instr_txt(self
):
110 txt
= _('Scene: ') + self
.name(self
.__json
_name
) + '\n\n'
111 txt
+= _(self
.__process
_json
_escape
(self
.__class
__.json_files
[self
.__json
_name
]['instructions']))
114 def __process_json_escape(self
, string
):
115 return bytes(string
, 'utf-8').decode('unicode-escape')
119 items
= self
.__items
[:]
120 if self
.__scene
_editor
:
121 items
+= self
.__scene
_editor
.test_items
124 def _set_items(self
):
125 info(f
'{self.__json_name=}')
126 if not self
.__json
_name
: return
128 self
._test
_items
= []
130 with
open(f
'assets/scenes/{self.__json_name}.json') as f
:
131 self
.json
= loads(f
.read())
132 info(f
'{self.json=}')
133 for item
in self
.json
['start_items']:
135 'world': self
._world
,
136 'plane_node': self
._mouse
_plane
_node
,
137 'cb_inst': self
.cb_inst
,
138 'curr_bottom': self
.current_bottom
,
140 'count': item
['count'],
143 args
['mass'] = item
['mass']
144 if 'friction' in item
:
145 args
['friction'] = item
['friction']
146 self
.__items
+= [self
.__code
2class
(item
['class'])(**args
)]
147 for item
in self
.json
['items']:
149 'world': self
._world
,
150 'plane_node': self
._mouse
_plane
_node
,
151 'cb_inst': self
.cb_inst
,
152 'curr_bottom': self
.current_bottom
,
155 args
['pos'] = tuple(item
['position'])
157 args
['mass'] = item
['mass']
158 if 'friction' in item
:
159 args
['friction'] = item
['friction']
161 args
['r'] = item
['roll']
162 if 'model_scale' in item
:
163 args
['model_scale'] = item
['model_scale']
164 if 'restitution' in item
:
165 args
['restitution'] = item
['restitution']
166 if 'friction' in item
:
167 args
['friction'] = item
['friction']
168 self
.__items
+= [self
.__code
2class
(item
['class'])(**args
)]
169 if 'strategy' in item
:
170 match item
['strategy']:
172 self
.__items
[-1].set_strategy(self
.__code
2class
(item
['strategy'])(self
.__items
[-1]._np
, *item
['strategy_args']))
174 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
))
176 def __code2class(self
, code
):
179 'Basketball': Basketball
,
182 'TeeterTooter': TeeterTooter
,
183 'DownStrategy': DownStrategy
,
184 'HitStrategy': HitStrategy
187 def __item_with_id(self
, id):
188 for item
in self
.__items
:
189 if 'id' in item
.json
and item
.json
['id'] == id:
192 def screenshot(self
, task
=None):
193 tex
= Texture('screenshot')
194 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
195 cam
= base
.make_camera(buffer)
196 cam
.reparent_to(render
)
197 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
198 cam
.set_pos(0, -20, 0)
204 use_normal_maps
=True,
205 use_emission_maps
=False,
206 use_occlusion_maps
=True,
209 base
.graphicsEngine
.renderFrame()
210 base
.graphicsEngine
.renderFrame()
211 fname
= self
.__json
_name
212 if not exists('assets/images/scenes'):
213 makedirs('assets/images/scenes')
214 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
215 # img = DirectButton(
216 # frameTexture=buffer.get_texture(), relief=FLAT,
217 # frameSize=(-.2, .2, -.2, .2))
218 return buffer.get_texture()
220 def current_bottom(self
):
222 for item
in self
.__items
:
224 curr_bottom
= min(curr_bottom
, item
.get_bottom())
228 [itm
.destroy() for itm
in self
.__items
]
229 [itm
.remove_node() for itm
in self
._test
_items
]
231 self
._test
_items
= []
233 self
._set
_test
_items
()
236 self
._command
_idx
= 0
237 self
._start
_evt
_time
= None
238 if hasattr(self
, '_success_txt'):
239 self
._success
_txt
.destroy()
240 del self
._success
_txt
241 self
.__right
_btn
['state'] = NORMAL
243 def enforce_result(self
, val
):
244 self
._enforce
_result
= val
245 info('enforce result: ' + val
)
248 self
.__intro
_sequence
.finish()
249 self
.ignore('enforce_result')
253 self
._unset
_mouse
_plane
()
254 [itm
.destroy() for itm
in self
.__items
]
255 [itm
.remove_node() for itm
in self
._test
_items
]
257 self
._side
_panel
.destroy()
258 self
._cursor
.destroy()
259 taskMgr
.remove(self
._scene
_tsk
)
260 if hasattr(self
, '_success_txt'):
261 self
._success
_txt
.destroy()
262 self
.ignore('editor-inspector-delete')
263 if self
.__scene
_editor
: self
.__scene
_editor
.destroy()
265 def _set_camera(self
):
266 base
.camera
.set_pos(0, -20, 0)
267 base
.camera
.look_at(0, 0, 0)
272 start_v
[0] + (end_v
[0] - start_v
[0]) * t
,
273 start_v
[1] + (end_v
[1] - start_v
[1]) * t
,
274 start_v
[2] + (end_v
[2] - start_v
[2]) * t
)
275 base
.camera
.set_pos(*curr_pos
)
277 camera_interval
= LerpFunctionInterval(
282 blendType
='easeInOut')
283 self
.__intro
_sequence
= Sequence(
286 self
.__intro
_sequence
.start()
288 def __load_img_btn(self
, path
, col
):
289 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
290 img
.set_transparency(True)
296 def load_images_btn(path
, col
):
299 (.6, .6, .6, 1), # ready
300 (1, 1, 1, 1), # press
301 (.8, .8, .8, 1), # rollover
307 (.4, .1, .1, .4)]}[col
]
308 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
309 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
311 ('home', self
.on_home
, NORMAL
, abl
, 'gray', _('Exit'), 'right'),
312 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray', _('Instructions'), 'right'),
313 ('right', self
.on_play
, NORMAL
, abr
, 'green', _('Run'), 'left'),
314 #('next', self.on_next, DISABLED, abr, 'gray'),
315 #('previous', self.on_prev, DISABLED, abr, 'gray'),
316 #('rewind', self.reset, NORMAL, abr, 'gray')
318 info(f
'{self.__editor=}')
320 btn_info
.insert(2, ('wrench', self
._set
_editor
, NORMAL
, abl
, 'gray', _('Editor'), 'right'))
323 tooltip_args
= self
.__font
, .05, (.93, .93, .93, 1)
324 for binfo
in btn_info
:
325 imgs
= load_images_btn(binfo
[0], binfo
[4])
326 if binfo
[3] == base
.a2dBottomLeft
:
330 sign
, num
= -1, num_r
332 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
334 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
335 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
336 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
337 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
338 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
339 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
340 btn
.set_transparency(True)
341 t
= tooltip_args
+ (binfo
[6],)
342 btn
.set_tooltip(binfo
[5], *t
)
343 self
._pos
_mgr
[binfo
[0]] = btn
.pos_pixel()
346 self
.__home
_btn
, self
.__info
_btn
, self
.__editor
_btn
, self
.__right
_btn
= btns
348 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
= btns
349 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
351 self
._info
_txt
= OnscreenText(
352 '', parent
=base
.a2dTopRight
, scale
=0.04,
353 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
354 if self
._mouse
_coords
:
355 self
._coords
_txt
= OnscreenText(
356 '', parent
=base
.a2dTopRight
, scale
=0.04,
357 pos
=(-.03, -.12), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
358 def update_coords(task
):
360 for hit
in self
._get
_hits
():
361 if hit
.get_node() == self
._mouse
_plane
_node
:
362 pos
= hit
.get_hit_pos()
364 txt
= '%s %s' % (round(pos
.x
, 3),
366 self
._coords
_txt
['text'] = txt
368 self
._coords
_tsk
= taskMgr
.add(update_coords
, 'update_coords')
370 def _unset_gui(self
):
372 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
373 #self.__next_btn, self.__prev_btn, self.__rewind_btn
375 if self
.__editor
: btns
+= [self
.__editor
_btn
]
376 [btn
.destroy() for btn
in btns
]
378 self
._info
_txt
.destroy()
379 if self
._mouse
_coords
:
380 taskMgr
.remove(self
._coords
_tsk
)
381 self
._coords
_txt
.destroy()
383 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
384 light
= Spotlight(name
)
386 light
.setLens(PerspectiveLens())
387 light_np
= render
.attach_new_node(light
)
388 light_np
.set_pos(pos
)
389 light_np
.look_at(look_at
)
390 light
.set_color(color
)
391 render
.set_light(light_np
)
394 def _set_lights(self
):
395 alight
= AmbientLight('alight') # for ao
396 alight
.set_color((.15, .15, .15, 1))
397 self
._alnp
= render
.attach_new_node(alight
)
398 render
.set_light(self
._alnp
)
399 self
._key
_light
= self
._set
_spotlight
(
400 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
401 self
._shadow
_light
= self
._set
_spotlight
(
402 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
403 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
404 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
405 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
406 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
408 def _unset_lights(self
):
409 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
410 render
.clear_light(light
)
413 def _set_input(self
):
414 self
.accept('mouse1', self
.on_click_l
)
415 self
.accept('mouse1-up', self
.on_release
)
416 self
.accept('mouse3', self
.on_click_r
)
417 self
.accept('mouse3-up', self
.on_release
)
419 def _unset_input(self
):
420 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
423 def _set_mouse_plane(self
):
424 shape
= BulletPlaneShape((0, -1, 0), 0)
425 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
426 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
427 self
._mouse
_plane
_node
.addShape(shape
)
428 #np = render.attachNewNode(self._mouse_plane_node)
429 #self._world.attachRigidBody(self._mouse_plane_node)
430 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
432 def _unset_mouse_plane(self
):
433 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
436 if not base
.mouseWatcherNode
.has_mouse(): return []
437 p_from
, p_to
= GuiTools
.get_mouse().from_to_points()
438 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
440 def _update_info(self
, item
):
443 txt
= '%.3f %.3f\n%.3f°' % (
444 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
445 self
._info
_txt
['text'] = txt
447 def _on_click(self
, method
):
450 for hit
in self
._get
_hits
():
451 if hit
.get_node() == self
._mouse
_plane
_node
:
452 pos
= hit
.get_hit_pos()
453 for hit
in self
._get
_hits
():
454 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
455 if not self
._item
_active
:
456 self
._item
_active
= item
457 if item
not in self
.__items
:
458 method
= 'on_click_l'
459 getattr(item
, method
)(pos
)
460 img
= 'move' if method
== 'on_click_l' else 'rotate'
461 if not (img
== 'rotate' and not item
._instantiated
):
462 self
._cursor
.set_image('assets/images/buttons/%s.dds' % img
)
464 def on_click_l(self
):
465 self
._on
_click
('on_click_l')
467 def on_click_r(self
):
468 self
._on
_click
('on_click_r')
470 def on_release(self
):
471 if self
._item
_active
and not self
._item
_active
._first
_command
:
472 self
._commands
= self
._commands
[:self
._command
_idx
]
473 self
._commands
+= [self
._item
_active
]
474 self
._command
_idx
+= 1
475 #self.__prev_btn['state'] = NORMAL
476 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
477 #self.__prev_btn['frameColor'] = fcols[0]
478 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
479 # self.__next_btn['state'] = DISABLED
480 # self.__next_btn['frameColor'] = fcols[1]
481 self
._item
_active
= None
482 [item
.on_release() for item
in self
.__items
]
483 self
._cursor
.set_image('assets/images/buttons/arrowUpLeft.dds')
486 for item
in self
.__items
:
487 item
.repos_done
= False
488 self
.__items
= sorted(self
.__items
, key
=lambda itm
: itm
.__class
__.__name
__)
489 [item
.on_aspect_ratio_changed() for item
in self
.__items
]
490 self
._side
_panel
.update(self
.__items
)
491 max_x
= -float('inf')
492 for item
in self
.__items
:
493 if not item
._instantiated
:
494 max_x
= max(item
._np
.get_x(), max_x
)
495 for item
in self
.__items
:
496 if not item
._instantiated
:
499 def on_aspect_ratio_changed(self
):
502 def _win_condition(self
):
503 return all(itm
.strategy
.win_condition() for itm
in self
.__items
) and not self
._paused
505 def _fail_condition(self
):
506 return all(itm
.fail_condition() for itm
in self
.__items
) and not self
._paused
and self
._state
== 'playing'
508 def on_frame(self
, task
):
509 hits
= self
._get
_hits
()
511 for hit
in self
._get
_hits
():
512 if hit
.get_node() == self
._mouse
_plane
_node
:
513 pos
= hit
.get_hit_pos()
514 hit_nodes
= [hit
.get_node() for hit
in hits
]
515 if self
._item
_active
:
516 items_hit
= [self
._item
_active
]
518 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
519 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
520 [itm
.on_mouse_on() for itm
in items_hit
]
521 [itm
.on_mouse_off() for itm
in items_no_hit
]
522 if pos
and self
._item
_active
:
523 self
._item
_active
.on_mouse_move(pos
)
525 self
._update
_info
(items_hit
[0] if items_hit
else None)
526 if not self
.__scene
_editor
and self
.__json
_name
and self
._win
_condition
():
527 self
._start
_evt
_time
= None
528 self
._set
_fail
() if self
._enforce
_result
== 'fail' else self
._set
_win
()
529 elif self
._state
== 'playing' and self
._fail
_condition
():
530 self
._start
_evt
_time
= None
531 self
._set
_win
() if self
._enforce
_result
== 'win' else self
._set
_fail
()
532 elif self
._testing
and self
._start
_evt
_time
and globalClock
.getFrameTime() - self
._start
_evt
_time
> 5.0:
533 self
._start
_evt
_time
= None
534 self
._set
_win
() if self
._enforce
_result
== 'win' else self
._set
_fail
()
535 if any(itm
._overlapping
for itm
in self
.items
):
536 self
._cursor
.set_color((.9, .1, .1, 1))
538 self
._cursor
.set_color((.9, .9, .9, 1))
541 def cb_inst(self
, item
):
542 self
.__items
+= [item
]
545 self
._state
= 'playing'
546 #self.__prev_btn['state'] = DISABLED
547 #self.__next_btn['state'] = DISABLED
548 self
.__right
_btn
['state'] = DISABLED
549 [itm
.play() for itm
in self
.__items
]
550 self
._start
_evt
_time
= globalClock
.getFrameTime()
553 self
._commands
[self
._command
_idx
].redo()
554 self
._command
_idx
+= 1
555 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
556 #self.__prev_btn['state'] = NORMAL
557 #self.__prev_btn['frameColor'] = fcols[0]
558 #more_commands = self._command_idx < len(self._commands)
559 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
560 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
563 self
._command
_idx
-= 1
564 self
._commands
[self
._command
_idx
].undo()
565 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
566 #self.__next_btn['state'] = NORMAL
567 #self.__next_btn['frameColor'] = fcols[0]
568 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
569 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
574 def __set_font(self
):
575 self
.__font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
577 self
.__font
.set_pixels_per_unit(60)
578 self
.__font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
579 self
.__font
.set_outline((0, 0, 0, 1), .8, .2)
582 def _set_instructions(self
):
585 mgr
= TextPropertiesManager
.get_global_ptr()
586 for name
in ['mouse_l', 'mouse_r']:
587 graphic
= OnscreenImage('assets/images/buttons/%s.dds' % name
)
588 graphic
.set_scale(.5)
589 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
590 graphic
.get_texture().set_anisotropic_degree(2)
591 mgr
.set_graphic(name
, graphic
)
593 graphic
.set_transparency(True)
594 graphic
.detach_node()
595 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
596 frameSize
=(-.6, .6, -.3, .3))
597 self
._txt
= OnscreenText(
598 self
._instr
_txt
(), parent
=frm
, font
=self
.__font
, scale
=0.06,
599 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
600 u_l
= self
._txt
.textNode
.get_upper_left_3d()
601 l_r
= self
._txt
.textNode
.get_lower_right_3d()
602 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
605 z
= h
/ 2 - self
.__font
.get_line_height() * self
._txt
['scale'][1]
606 z
+= (btn_scale
+ 2 * mar
) / 2
607 self
._txt
['pos'] = -w
/ 2, z
608 u_l
= self
._txt
.textNode
.get_upper_left_3d()
609 l_r
= self
._txt
.textNode
.get_lower_right_3d()
610 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
611 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
612 frm
['frameSize'] = fsz
614 (.6, .6, .6, 1), # ready
615 (1, 1, 1, 1), # press
616 (.8, .8, .8, 1), # rollover
618 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
620 image
=imgs
, scale
=btn_scale
,
621 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
622 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
623 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
624 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
625 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
626 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
627 btn
.set_transparency(True)
628 self
._pos
_mgr
['close_instructions'] = btn
.pos_pixel()
631 self
.__persistent
.save_scene(self
.__json
_name
, self
.version(self
.__json
_name
))
632 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
635 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
636 frameSize
=(-.6, .6, -.3, .3))
637 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
639 font
.set_pixels_per_unit(60)
640 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
641 font
.set_outline((0, 0, 0, 1), .8, .2)
642 self
._txt
= OnscreenText(
645 font
=font
, scale
=0.2,
647 u_l
= self
._txt
.textNode
.get_upper_left_3d()
648 l_r
= self
._txt
.textNode
.get_lower_right_3d()
649 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
653 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
654 z
+= (btn_scale
+ 2 * mar
) / 2
655 self
._txt
['pos'] = 0, z
656 u_l
= self
._txt
.textNode
.get_upper_left_3d()
657 l_r
= self
._txt
.textNode
.get_lower_right_3d()
658 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
659 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
660 frm
['frameSize'] = fsz
662 (.6, .6, .6, 1), # ready
663 (1, 1, 1, 1), # press
664 (.8, .8, .8, 1), # rollover
666 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
668 image
=imgs
, scale
=btn_scale
,
669 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
670 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
671 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
672 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
673 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
674 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
675 btn
.set_transparency(True)
676 self
._pos
_mgr
['home_win'] = btn
.pos_pixel()
677 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
679 image
=imgs
, scale
=btn_scale
,
680 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
681 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
682 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
683 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
684 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
685 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
686 self
._pos
_mgr
['replay'] = btn
.pos_pixel()
687 btn
.set_transparency(True)
689 enabled
= self
._scenes
.index(self
.__json
_name
) < len(self
._scenes
) - 1
691 next_scene
= self
._scenes
[self
._scenes
.index(self
.__json
_name
) + 1]
697 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
699 image
=imgs
, scale
=btn_scale
,
700 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
701 parent
=frm
, command
=self
._on
_next
_scene
,
702 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
703 frameColor
=(.6, .6, .6, .08),
704 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
705 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
706 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
707 btn
['state'] = NORMAL
if enabled
else DISABLED
708 self
._pos
_mgr
['next'] = btn
.pos_pixel()
709 btn
.set_transparency(True)
712 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
715 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
716 frameSize
=(-.6, .6, -.3, .3))
717 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
719 font
.set_pixels_per_unit(60)
720 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
721 font
.set_outline((0, 0, 0, 1), .8, .2)
722 self
._txt
= OnscreenText(
723 _('You have failed!'),
725 font
=font
, scale
=0.2,
727 u_l
= self
._txt
.textNode
.get_upper_left_3d()
728 l_r
= self
._txt
.textNode
.get_lower_right_3d()
729 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
733 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
734 z
+= (btn_scale
+ 2 * mar
) / 2
735 self
._txt
['pos'] = 0, z
736 u_l
= self
._txt
.textNode
.get_upper_left_3d()
737 l_r
= self
._txt
.textNode
.get_lower_right_3d()
738 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
739 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
740 frm
['frameSize'] = fsz
742 (.6, .6, .6, 1), # ready
743 (1, 1, 1, 1), # press
744 (.8, .8, .8, 1), # rollover
746 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
748 image
=imgs
, scale
=btn_scale
,
749 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
750 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
751 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
752 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
753 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
754 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
755 self
._pos
_mgr
['home_win'] = btn
.pos_pixel()
756 btn
.set_transparency(True)
757 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
759 image
=imgs
, scale
=btn_scale
,
760 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
761 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
762 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
763 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
764 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
765 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
766 self
._pos
_mgr
['replay'] = btn
.pos_pixel()
767 btn
.set_transparency(True)
769 def _on_restart(self
, frm
):
770 self
.__on
_close
_instructions
(frm
)
773 def _on_end_home(self
, frm
):
774 self
.__on
_close
_instructions
(frm
)
777 def _on_next_scene(self
, frm
, scene
):
778 self
.__on
_close
_instructions
(frm
)
779 self
._reload
_cb
(scene
)
781 def __store_state(self
):
783 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
784 #self.__next_btn, self.__prev_btn, self.__rewind_btn
786 if self
.__editor
: btns
+= [self
.__editor
_btn
]
787 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
789 btn
['state'] = DISABLED
790 [itm
.store_state() for itm
in self
.__items
]
792 def __restore_state(self
):
794 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
795 #self.__next_btn, self.__prev_btn, self.__rewind_btn
797 if self
.__editor
: btns
+= [self
.__editor
_btn
]
798 for btn
, state
in zip(btns
, self
.__btn
_state
):
800 [itm
.restore_state() for itm
in self
.__items
]
803 def __on_close_instructions(self
, frm
):
805 self
.__restore
_state
()
807 def _set_test_items(self
):
808 def frame_after(task
):
809 self
._define
_test
_items
()
810 for itm
in self
.items
:
812 self
._pos
_mgr
[itm
.id] = itm
._model
.pos2d_pixel()
813 for itm
in self
._test
_items
:
814 self
._pos
_mgr
[itm
.name
] = itm
.pos2d_pixel()
815 taskMgr
.doMethodLater(1.4, frame_after
, 'frame after') # after the intro sequence
817 def _define_test_items(self
):
818 if not self
.__json
_name
: return
819 if not self
.__json
_name
in self
.__class
__.json_files
:
820 with
open(self
.__class
__.filename(self
.__json
_name
)) as f
:
821 self
.__class
__.json_files
[self
.__json
_name
] = loads(f
.read())
822 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['pixel_space']:
823 self
._pos
_mgr
[item
['id']] = tuple(item
['position'])
824 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['world_space']:
825 self
._set
_test
_item
(item
['id'], tuple(item
['position']))
827 def _set_test_item(self
, name
, pos
):
828 self
._test
_items
+= [GfxTools
.build_empty_node(name
)]
829 self
._test
_items
[-1].set_pos(pos
[0], 0, pos
[1])
831 def add_item(self
, item
):
832 self
.__items
+= [item
]
834 def _set_editor(self
):
835 fields
= ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
836 SceneContext
= namedtuple('SceneContext', fields
)
837 context
= SceneContext(
839 self
._mouse
_plane
_node
,
844 self
.__scene
_editor
= SceneEditor(self
.json
, self
.__json
_name
, context
, self
.add_item
, self
.__items
, self
._world
, self
._mouse
_plane
_node
, self
._pos
_mgr
)
846 def __on_inspector_delete(self
, item
):
847 if item
.__class
__ in [WorldSpaceTestItem
, PixelSpaceTestItem
]:
848 for i
in self
._test
_items
:
849 if item
.id == i
.name
:
851 self
._test
_items
.remove(r
)
853 self
.__items
.remove(item
)
854 j
= self
.json
['items']
855 if item
.__class
__ == PixelSpaceTestItem
:
856 j
= self
.json
['test_items']['pixel_space']
857 if item
.__class
__ == WorldSpaceTestItem
:
858 j
= self
.json
['test_items']['world_space']