e8172f9f85141c8913974303a5238179362bc66e
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
, MouseCursorInfo
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 self
._bg
= Background()
76 self
._side
_panel
= SidePanel(world
, self
._mouse
_plane
_node
, (-5, 4), (-3, 1), 1, self
.__items
)
77 self
._scene
_tsk
= taskMgr
.add(self
.on_frame
, 'scene_on_frame')
80 self
.accept('editor-inspector-delete', self
.__on
_inspector
_delete
)
83 def filename(cls
, scene_name
):
84 return f
'assets/scenes/{scene_name}.json'
87 def name(cls
, scene_name
):
88 if not scene_name
in cls
.json_files
:
89 with
open(cls
.filename(scene_name
)) as f
:
90 cls
.json_files
[scene_name
] = loads(f
.read())
91 return _(cls
.json_files
[scene_name
]['name'])
94 def version(cls
, scene_name
):
95 if not scene_name
: return ''
96 if not scene_name
in cls
.json_files
:
97 with
open(cls
.filename(scene_name
)) as f
:
98 cls
.json_files
[scene_name
] = loads(f
.read())
99 return cls
.json_files
[scene_name
]['version']
102 def is_done(cls
, scene_name
):
103 if not cls
.scenes_done
or len(cls
.scenes_done
) == 1 and not cls
.scenes_done
[0]:
105 return bool([(name
, version
) for name
, version
in cls
.scenes_done
if scene_name
== name
and cls
.version(scene_name
) == version
])
107 def _instr_txt(self
):
108 txt
= _('Scene: ') + self
.name(self
.__json
_name
) + '\n\n'
109 txt
+= _(self
.__process
_json
_escape
(self
.__class
__.json_files
[self
.__json
_name
]['instructions']))
112 def __process_json_escape(self
, string
):
113 return bytes(string
, 'utf-8').decode('unicode-escape')
117 items
= self
.__items
[:]
118 if self
.__scene
_editor
:
119 items
+= self
.__scene
_editor
.test_items
122 def _set_items(self
):
123 if not self
.__json
_name
: return
125 self
._test
_items
= []
127 with
open(f
'assets/scenes/{self.__json_name}.json') as f
:
128 self
.json
= loads(f
.read())
129 for item
in self
.json
['start_items']:
131 'world': self
._world
,
132 'plane_node': self
._mouse
_plane
_node
,
133 'cb_inst': self
.cb_inst
,
134 'curr_bottom': self
.current_bottom
,
136 'count': item
['count'],
139 args
['mass'] = item
['mass']
140 if 'friction' in item
:
141 args
['friction'] = item
['friction']
142 self
.__items
+= [self
.__code
2class
(item
['class'])(**args
)]
143 for item
in self
.json
['items']:
145 'world': self
._world
,
146 'plane_node': self
._mouse
_plane
_node
,
147 'cb_inst': self
.cb_inst
,
148 'curr_bottom': self
.current_bottom
,
151 args
['pos'] = tuple(item
['position'])
153 args
['mass'] = item
['mass']
154 if 'friction' in item
:
155 args
['friction'] = item
['friction']
157 args
['r'] = item
['roll']
158 if 'model_scale' in item
:
159 args
['model_scale'] = item
['model_scale']
160 if 'restitution' in item
:
161 args
['restitution'] = item
['restitution']
162 if 'friction' in item
:
163 args
['friction'] = item
['friction']
164 self
.__items
+= [self
.__code
2class
(item
['class'])(**args
)]
165 if 'strategy' in item
:
166 match item
['strategy']:
168 self
.__items
[-1].set_strategy(self
.__code
2class
(item
['strategy'])(self
.__items
[-1]._np
, *item
['strategy_args']))
170 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
))
172 def __code2class(self
, code
):
175 'Basketball': Basketball
,
178 'TeeterTooter': TeeterTooter
,
179 'DownStrategy': DownStrategy
,
180 'HitStrategy': HitStrategy
183 def __item_with_id(self
, id):
184 for item
in self
.__items
:
185 if 'id' in item
.json
and item
.json
['id'] == id:
188 def screenshot(self
, task
=None):
189 tex
= Texture('screenshot')
190 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
191 cam
= base
.make_camera(buffer)
192 cam
.reparent_to(render
)
193 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
194 cam
.set_pos(0, -20, 0)
200 use_normal_maps
=True,
201 use_emission_maps
=False,
202 use_occlusion_maps
=True,
205 base
.graphicsEngine
.renderFrame()
206 base
.graphicsEngine
.renderFrame()
207 fname
= self
.__json
_name
208 if not exists('assets/images/scenes'):
209 makedirs('assets/images/scenes')
210 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
211 # img = DirectButton(
212 # frameTexture=buffer.get_texture(), relief=FLAT,
213 # frameSize=(-.2, .2, -.2, .2))
214 return buffer.get_texture()
216 def current_bottom(self
):
218 for item
in self
.__items
:
220 curr_bottom
= min(curr_bottom
, item
.get_bottom())
224 [itm
.destroy() for itm
in self
.__items
]
225 [itm
.remove_node() for itm
in self
._test
_items
]
227 self
._test
_items
= []
229 self
._set
_test
_items
()
232 self
._command
_idx
= 0
233 self
._start
_evt
_time
= None
234 if hasattr(self
, '_success_txt'):
235 self
._success
_txt
.destroy()
236 del self
._success
_txt
237 self
.__right
_btn
['state'] = NORMAL
239 def enforce_result(self
, val
):
240 self
._enforce
_result
= val
241 info('enforce result: ' + val
)
244 self
.__intro
_sequence
.finish()
245 self
.ignore('enforce_result')
249 self
._unset
_mouse
_plane
()
250 [itm
.destroy() for itm
in self
.__items
]
251 [itm
.remove_node() for itm
in self
._test
_items
]
253 self
._side
_panel
.destroy()
254 self
._cursor
.destroy()
255 taskMgr
.remove(self
._scene
_tsk
)
256 if hasattr(self
, '_success_txt'):
257 self
._success
_txt
.destroy()
258 self
.ignore('editor-inspector-delete')
259 if self
.__scene
_editor
: self
.__scene
_editor
.destroy()
261 def _set_camera(self
):
262 base
.camera
.set_pos(0, -20, 0)
263 base
.camera
.look_at(0, 0, 0)
268 start_v
[0] + (end_v
[0] - start_v
[0]) * t
,
269 start_v
[1] + (end_v
[1] - start_v
[1]) * t
,
270 start_v
[2] + (end_v
[2] - start_v
[2]) * t
)
271 base
.camera
.set_pos(*curr_pos
)
273 camera_interval
= LerpFunctionInterval(
278 blendType
='easeInOut')
279 self
.__intro
_sequence
= Sequence(
282 self
.__intro
_sequence
.start()
284 def __load_img_btn(self
, path
, col
):
285 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
286 img
.set_transparency(True)
292 def load_images_btn(path
, col
):
295 (.6, .6, .6, 1), # ready
296 (1, 1, 1, 1), # press
297 (.8, .8, .8, 1), # rollover
303 (.4, .1, .1, .4)]}[col
]
304 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
305 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
307 ('home', self
.on_home
, NORMAL
, abl
, 'gray', _('Exit'), 'right'),
308 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray', _('Instructions'), 'right'),
309 ('right', self
.on_play
, NORMAL
, abr
, 'green', _('Run'), 'left'),
310 #('next', self.on_next, DISABLED, abr, 'gray'),
311 #('previous', self.on_prev, DISABLED, abr, 'gray'),
312 #('rewind', self.reset, NORMAL, abr, 'gray')
315 btn_info
.insert(2, ('wrench', self
._set
_editor
, NORMAL
, abl
, 'gray', _('Editor'), 'right'))
318 tooltip_args
= self
.__font
, .05, (.93, .93, .93, 1)
319 for binfo
in btn_info
:
320 imgs
= load_images_btn(binfo
[0], binfo
[4])
321 if binfo
[3] == base
.a2dBottomLeft
:
325 sign
, num
= -1, num_r
327 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
329 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
330 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
331 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
332 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
333 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
334 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
335 btn
.set_transparency(True)
336 t
= tooltip_args
+ (binfo
[6],)
337 btn
.set_tooltip(binfo
[5], *t
)
338 self
._pos
_mgr
[binfo
[0]] = btn
.pos_pixel()
341 self
.__home
_btn
, self
.__info
_btn
, self
.__editor
_btn
, self
.__right
_btn
= btns
343 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
= btns
344 # , self.__next_btn, self.__prev_btn, self.__rewind_btn
346 self
._info
_txt
= OnscreenText(
347 '', parent
=base
.a2dTopRight
, scale
=0.04,
348 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
349 if self
._mouse
_coords
:
350 self
._coords
_txt
= OnscreenText(
351 '', parent
=base
.a2dTopRight
, scale
=0.04,
352 pos
=(-.03, -.12), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
353 def update_coords(task
):
355 for hit
in self
._get
_hits
():
356 if hit
.get_node() == self
._mouse
_plane
_node
:
357 pos
= hit
.get_hit_pos()
359 txt
= '%s %s' % (round(pos
.x
, 3),
361 self
._coords
_txt
['text'] = txt
363 self
._coords
_tsk
= taskMgr
.add(update_coords
, 'update_coords')
365 def _unset_gui(self
):
367 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
368 #self.__next_btn, self.__prev_btn, self.__rewind_btn
370 if self
.__editor
: btns
+= [self
.__editor
_btn
]
371 [btn
.destroy() for btn
in btns
]
373 self
._info
_txt
.destroy()
374 if self
._mouse
_coords
:
375 taskMgr
.remove(self
._coords
_tsk
)
376 self
._coords
_txt
.destroy()
378 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
379 light
= Spotlight(name
)
381 light
.setLens(PerspectiveLens())
382 light_np
= render
.attach_new_node(light
)
383 light_np
.set_pos(pos
)
384 light_np
.look_at(look_at
)
385 light
.set_color(color
)
386 render
.set_light(light_np
)
389 def _set_lights(self
):
390 alight
= AmbientLight('alight') # for ao
391 alight
.set_color((.15, .15, .15, 1))
392 self
._alnp
= render
.attach_new_node(alight
)
393 render
.set_light(self
._alnp
)
394 self
._key
_light
= self
._set
_spotlight
(
395 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
396 self
._shadow
_light
= self
._set
_spotlight
(
397 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
398 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
399 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
400 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
401 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
403 def _unset_lights(self
):
404 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
405 render
.clear_light(light
)
408 def _set_input(self
):
409 self
.accept('mouse1', self
.on_click_l
)
410 self
.accept('mouse1-up', self
.on_release
)
411 self
.accept('mouse3', self
.on_click_r
)
412 self
.accept('mouse3-up', self
.on_release
)
414 def _unset_input(self
):
415 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
418 def _set_mouse_plane(self
):
419 shape
= BulletPlaneShape((0, -1, 0), 0)
420 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
421 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
422 self
._mouse
_plane
_node
.addShape(shape
)
423 #np = render.attachNewNode(self._mouse_plane_node)
424 #self._world.attachRigidBody(self._mouse_plane_node)
425 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
427 def _unset_mouse_plane(self
):
428 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
431 if not base
.mouseWatcherNode
.has_mouse(): return []
432 p_from
, p_to
= GuiTools
.get_mouse().from_to_points()
433 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
435 def _update_info(self
, item
):
438 txt
= '%.3f %.3f\n%.3f°' % (
439 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
440 self
._info
_txt
['text'] = txt
442 def _on_click(self
, method
):
445 for hit
in self
._get
_hits
():
446 if hit
.get_node() == self
._mouse
_plane
_node
:
447 pos
= hit
.get_hit_pos()
448 for hit
in self
._get
_hits
():
449 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
450 if not self
._item
_active
:
451 self
._item
_active
= item
452 if item
not in self
.__items
:
453 method
= 'on_click_l'
454 getattr(item
, method
)(pos
)
455 img
= 'move' if method
== 'on_click_l' else 'rotate'
456 if not (img
== 'rotate' and not item
._instantiated
):
457 self
._cursor
.set_image('assets/images/buttons/%s.dds' % img
)
459 def on_click_l(self
):
460 self
._on
_click
('on_click_l')
462 def on_click_r(self
):
463 self
._on
_click
('on_click_r')
465 def on_release(self
):
466 if self
._item
_active
and not self
._item
_active
._first
_command
:
467 self
._commands
= self
._commands
[:self
._command
_idx
]
468 self
._commands
+= [self
._item
_active
]
469 self
._command
_idx
+= 1
470 #self.__prev_btn['state'] = NORMAL
471 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
472 #self.__prev_btn['frameColor'] = fcols[0]
473 #if self._item_active._command_idx == len(self._item_active._commands) - 1:
474 # self.__next_btn['state'] = DISABLED
475 # self.__next_btn['frameColor'] = fcols[1]
476 self
._item
_active
= None
477 [item
.on_release() for item
in self
.__items
]
478 self
._cursor
.set_image('assets/images/buttons/arrowUpLeft.dds')
481 for item
in self
.__items
:
482 item
.repos_done
= False
483 self
.__items
= sorted(self
.__items
, key
=lambda itm
: itm
.__class
__.__name
__)
484 [item
.on_aspect_ratio_changed() for item
in self
.__items
]
485 self
._side
_panel
.update(self
.__items
)
486 max_x
= -float('inf')
487 for item
in self
.__items
:
488 if not item
._instantiated
:
489 max_x
= max(item
._np
.get_x(), max_x
)
490 for item
in self
.__items
:
491 if not item
._instantiated
:
494 def on_aspect_ratio_changed(self
):
497 def _win_condition(self
):
498 return all(itm
.strategy
.win_condition() for itm
in self
.__items
) and not self
._paused
500 def _fail_condition(self
):
501 return all(itm
.fail_condition() for itm
in self
.__items
) and not self
._paused
and self
._state
== 'playing'
503 def on_frame(self
, task
):
504 hits
= self
._get
_hits
()
506 for hit
in self
._get
_hits
():
507 if hit
.get_node() == self
._mouse
_plane
_node
:
508 pos
= hit
.get_hit_pos()
509 hit_nodes
= [hit
.get_node() for hit
in hits
]
510 if self
._item
_active
:
511 items_hit
= [self
._item
_active
]
513 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
514 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
515 [itm
.on_mouse_on() for itm
in items_hit
]
516 [itm
.on_mouse_off() for itm
in items_no_hit
]
517 if pos
and self
._item
_active
:
518 self
._item
_active
.on_mouse_move(pos
)
520 self
._update
_info
(items_hit
[0] if items_hit
else None)
521 if not self
.__scene
_editor
and self
.__json
_name
and self
._win
_condition
():
522 self
._start
_evt
_time
= None
523 self
._set
_fail
() if self
._enforce
_result
== 'fail' else self
._set
_win
()
524 elif self
._state
== 'playing' and self
._fail
_condition
():
525 self
._start
_evt
_time
= None
526 self
._set
_win
() if self
._enforce
_result
== 'win' else self
._set
_fail
()
527 elif self
._testing
and self
._start
_evt
_time
and globalClock
.getFrameTime() - self
._start
_evt
_time
> 5.0:
528 self
._start
_evt
_time
= None
529 self
._set
_win
() if self
._enforce
_result
== 'win' else self
._set
_fail
()
530 if any(itm
._overlapping
for itm
in self
.items
):
531 self
._cursor
.set_color((.9, .1, .1, 1))
533 self
._cursor
.set_color((.9, .9, .9, 1))
536 def cb_inst(self
, item
):
537 self
.__items
+= [item
]
540 self
._state
= 'playing'
541 #self.__prev_btn['state'] = DISABLED
542 #self.__next_btn['state'] = DISABLED
543 self
.__right
_btn
['state'] = DISABLED
544 [itm
.play() for itm
in self
.__items
]
545 self
._start
_evt
_time
= globalClock
.getFrameTime()
548 self
._commands
[self
._command
_idx
].redo()
549 self
._command
_idx
+= 1
550 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
551 #self.__prev_btn['state'] = NORMAL
552 #self.__prev_btn['frameColor'] = fcols[0]
553 #more_commands = self._command_idx < len(self._commands)
554 #self.__next_btn['state'] = NORMAL if more_commands else DISABLED
555 #self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
558 self
._command
_idx
-= 1
559 self
._commands
[self
._command
_idx
].undo()
560 #fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
561 #self.__next_btn['state'] = NORMAL
562 #self.__next_btn['frameColor'] = fcols[0]
563 #self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
564 #self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
569 def __set_font(self
):
570 self
.__font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
572 self
.__font
.set_pixels_per_unit(60)
573 self
.__font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
574 self
.__font
.set_outline((0, 0, 0, 1), .8, .2)
577 def _set_instructions(self
):
580 mgr
= TextPropertiesManager
.get_global_ptr()
581 for name
in ['mouse_l', 'mouse_r']:
582 graphic
= OnscreenImage('assets/images/buttons/%s.dds' % name
)
583 graphic
.set_scale(.5)
584 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
585 graphic
.get_texture().set_anisotropic_degree(2)
586 mgr
.set_graphic(name
, graphic
)
588 graphic
.set_transparency(True)
589 graphic
.detach_node()
590 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
591 frameSize
=(-.6, .6, -.3, .3))
592 self
._txt
= OnscreenText(
593 self
._instr
_txt
(), parent
=frm
, font
=self
.__font
, scale
=0.06,
594 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
595 u_l
= self
._txt
.textNode
.get_upper_left_3d()
596 l_r
= self
._txt
.textNode
.get_lower_right_3d()
597 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
600 z
= h
/ 2 - self
.__font
.get_line_height() * self
._txt
['scale'][1]
601 z
+= (btn_scale
+ 2 * mar
) / 2
602 self
._txt
['pos'] = -w
/ 2, z
603 u_l
= self
._txt
.textNode
.get_upper_left_3d()
604 l_r
= self
._txt
.textNode
.get_lower_right_3d()
605 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
606 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
607 frm
['frameSize'] = fsz
609 (.6, .6, .6, 1), # ready
610 (1, 1, 1, 1), # press
611 (.8, .8, .8, 1), # rollover
613 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
615 image
=imgs
, scale
=btn_scale
,
616 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
617 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
618 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
619 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
620 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
621 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
622 btn
.set_transparency(True)
623 self
._pos
_mgr
['close_instructions'] = btn
.pos_pixel()
626 self
.__persistent
.save_scene(self
.__json
_name
, self
.version(self
.__json
_name
))
627 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
630 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
631 frameSize
=(-.6, .6, -.3, .3))
632 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
634 font
.set_pixels_per_unit(60)
635 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
636 font
.set_outline((0, 0, 0, 1), .8, .2)
637 self
._txt
= OnscreenText(
640 font
=font
, scale
=0.2,
642 u_l
= self
._txt
.textNode
.get_upper_left_3d()
643 l_r
= self
._txt
.textNode
.get_lower_right_3d()
644 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
648 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
649 z
+= (btn_scale
+ 2 * mar
) / 2
650 self
._txt
['pos'] = 0, z
651 u_l
= self
._txt
.textNode
.get_upper_left_3d()
652 l_r
= self
._txt
.textNode
.get_lower_right_3d()
653 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
654 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
655 frm
['frameSize'] = fsz
657 (.6, .6, .6, 1), # ready
658 (1, 1, 1, 1), # press
659 (.8, .8, .8, 1), # rollover
661 imgs
= [self
.__load
_img
_btn
('home', 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
_end
_home
, extraArgs
=[frm
],
666 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
667 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
668 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
669 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
670 btn
.set_transparency(True)
671 self
._pos
_mgr
['home_win'] = btn
.pos_pixel()
672 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
674 image
=imgs
, scale
=btn_scale
,
675 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
676 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
677 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
678 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
679 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
680 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
681 self
._pos
_mgr
['replay'] = btn
.pos_pixel()
682 btn
.set_transparency(True)
684 enabled
= self
._scenes
.index(self
.__json
_name
) < len(self
._scenes
) - 1
686 next_scene
= self
._scenes
[self
._scenes
.index(self
.__json
_name
) + 1]
692 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
694 image
=imgs
, scale
=btn_scale
,
695 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
696 parent
=frm
, command
=self
._on
_next
_scene
,
697 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
698 frameColor
=(.6, .6, .6, .08),
699 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
700 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
701 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
702 btn
['state'] = NORMAL
if enabled
else DISABLED
703 self
._pos
_mgr
['next'] = btn
.pos_pixel()
704 btn
.set_transparency(True)
707 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
710 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
711 frameSize
=(-.6, .6, -.3, .3))
712 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
714 font
.set_pixels_per_unit(60)
715 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
716 font
.set_outline((0, 0, 0, 1), .8, .2)
717 self
._txt
= OnscreenText(
718 _('You have failed!'),
720 font
=font
, scale
=0.2,
722 u_l
= self
._txt
.textNode
.get_upper_left_3d()
723 l_r
= self
._txt
.textNode
.get_lower_right_3d()
724 #w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
728 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
729 z
+= (btn_scale
+ 2 * mar
) / 2
730 self
._txt
['pos'] = 0, z
731 u_l
= self
._txt
.textNode
.get_upper_left_3d()
732 l_r
= self
._txt
.textNode
.get_lower_right_3d()
733 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
734 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
735 frm
['frameSize'] = fsz
737 (.6, .6, .6, 1), # ready
738 (1, 1, 1, 1), # press
739 (.8, .8, .8, 1), # rollover
741 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
743 image
=imgs
, scale
=btn_scale
,
744 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
745 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
746 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
747 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
748 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
749 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
750 self
._pos
_mgr
['home_win'] = btn
.pos_pixel()
751 btn
.set_transparency(True)
752 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
754 image
=imgs
, scale
=btn_scale
,
755 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
756 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
757 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
758 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
759 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
760 btn
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
761 self
._pos
_mgr
['replay'] = btn
.pos_pixel()
762 btn
.set_transparency(True)
764 def _on_restart(self
, frm
):
765 self
.__on
_close
_instructions
(frm
)
768 def _on_end_home(self
, frm
):
769 self
.__on
_close
_instructions
(frm
)
772 def _on_next_scene(self
, frm
, scene
):
773 self
.__on
_close
_instructions
(frm
)
774 self
._reload
_cb
(scene
)
776 def __store_state(self
):
778 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
779 #self.__next_btn, self.__prev_btn, self.__rewind_btn
781 if self
.__editor
: btns
+= [self
.__editor
_btn
]
782 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
784 btn
['state'] = DISABLED
785 [itm
.store_state() for itm
in self
.__items
]
787 def __restore_state(self
):
789 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
790 #self.__next_btn, self.__prev_btn, self.__rewind_btn
792 if self
.__editor
: btns
+= [self
.__editor
_btn
]
793 for btn
, state
in zip(btns
, self
.__btn
_state
):
795 [itm
.restore_state() for itm
in self
.__items
]
798 def __on_close_instructions(self
, frm
):
800 self
.__restore
_state
()
802 def _set_test_items(self
):
803 def frame_after(task
):
804 self
._define
_test
_items
()
805 for itm
in self
.items
:
807 self
._pos
_mgr
[itm
.id] = itm
._model
.pos2d_pixel()
808 for itm
in self
._test
_items
:
809 self
._pos
_mgr
[itm
.name
] = itm
.pos2d_pixel()
810 taskMgr
.doMethodLater(1.4, frame_after
, 'frame after') # after the intro sequence
812 def _define_test_items(self
):
813 if not self
.__json
_name
: return
814 if not self
.__json
_name
in self
.__class
__.json_files
:
815 with
open(self
.__class
__.filename(self
.__json
_name
)) as f
:
816 self
.__class
__.json_files
[self
.__json
_name
] = loads(f
.read())
817 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['pixel_space']:
818 self
._pos
_mgr
[item
['id']] = tuple(item
['position'])
819 for item
in self
.__class
__.json_files
[self
.__json
_name
]['test_items']['world_space']:
820 self
._set
_test
_item
(item
['id'], tuple(item
['position']))
822 def _set_test_item(self
, name
, pos
):
823 self
._test
_items
+= [GfxTools
.build_empty_node(name
)]
824 self
._test
_items
[-1].set_pos(pos
[0], 0, pos
[1])
826 def add_item(self
, item
):
827 self
.__items
+= [item
]
829 def _set_editor(self
):
830 fields
= ['world', 'plane_node', 'cb_inst', 'curr_bottom', 'repos', 'json']
831 SceneContext
= namedtuple('SceneContext', fields
)
832 context
= SceneContext(
834 self
._mouse
_plane
_node
,
839 self
.__scene
_editor
= SceneEditor(self
.json
, self
.__json
_name
, context
, self
.add_item
, self
.__items
, self
._world
, self
._mouse
_plane
_node
, self
._pos
_mgr
)
841 def __on_inspector_delete(self
, item
):
842 if item
.__class
__ in [WorldSpaceTestItem
, PixelSpaceTestItem
]:
843 for i
in self
._test
_items
:
844 if item
.id == i
.name
:
846 self
._test
_items
.remove(r
)
848 self
.__items
.remove(item
)
849 j
= self
.json
['items']
850 if item
.__class
__ == PixelSpaceTestItem
:
851 j
= self
.json
['test_items']['pixel_space']
852 if item
.__class
__ == WorldSpaceTestItem
:
853 j
= self
.json
['test_items']['world_space']