+from os.path import exists
+from glob import glob
+from importlib import import_module
+from inspect import isclass
from panda3d.core import AmbientLight, DirectionalLight, Point3, Texture, \
TextPropertiesManager, TextNode, Spotlight, PerspectiveLens, BitMask32
from panda3d.bullet import BulletPlaneShape, BulletGhostNode
from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
from direct.showbase.DirectObject import DirectObject
from pmachines.items.background import Background
-from pmachines.items.box import Box
from pmachines.sidepanel import SidePanel
from lib.engine.gui.cursor import MouseCursor
from lib.lib.p3d.gfx import P3dGfxMgr
class Scene(DirectObject):
- def __init__(self, world, exit_cb, auto_close_instr):
+ def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb):
super().__init__()
self._world = world
self._exit_cb = exit_cb
+ self._dbg_items = dbg_items
+ self._reload_cb = reload_cb
self._set_camera()
self._cursor = MouseCursor(
- 'assets/buttons/arrowUpLeft.png', (.04, 1, .04), (.5, .5, .5, 1),
+ 'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
(.01, .01))
self._set_gui()
self._set_lights()
self._set_input()
self._set_mouse_plane()
- self.items = [Box(world, self._mouse_plane_node, 3, self.cb_inst)]
- self._paused = True
+ self.items = []
+ self.reset()
+ self._state = 'init'
+ self._paused = False
self._item_active = None
- self.__store_state()
if auto_close_instr:
+ self.__store_state()
self.__restore_state()
else:
self._set_instructions()
self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
+ @staticmethod
+ def name():
+ return ''
+
+ def _instr_txt(self):
+ return ''
+
+ def _set_items(self):
+ self.items = []
+
+ def screenshot(self, task=None):
+ tex = Texture('screenshot')
+ buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
+ cam = base.make_camera(buffer)
+ cam.reparent_to(render)
+ cam.node().get_lens().set_fov(base.camLens.get_fov())
+ cam.set_pos(0, -20, 0)
+ cam.look_at(0, 0, 0)
+ import simplepbr
+ simplepbr.init(
+ window=buffer,
+ camera_node=cam,
+ use_normal_maps=True,
+ use_emission_maps=False,
+ use_occlusion_maps=True,
+ msaa_samples=4,
+ enable_shadows=True)
+ base.graphicsEngine.renderFrame()
+ base.graphicsEngine.renderFrame()
+ fname = self.__class__.__name__
+ if not exists('assets/images/scenes'):
+ makedirs('assets/images/scenes')
+ buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
+ # img = DirectButton(
+ # frameTexture=buffer.get_texture(), relief=FLAT,
+ # frameSize=(-.2, .2, -.2, .2))
+ return buffer.get_texture()
+
+ def current_bottom(self):
+ curr_bottom = 1
+ for item in self.items:
+ if item.repos_done:
+ curr_bottom = min(curr_bottom, item.get_bottom())
+ return curr_bottom
+
+ def reset(self):
+ [itm.destroy() for itm in self.items]
+ self._set_items()
+ self._state = 'init'
+ self._commands = []
+ self._command_idx = 0
+ if hasattr(self, '_success_txt'):
+ self._success_txt.destroy()
+ del self._success_txt
+ self.__right_btn['state'] = NORMAL
+
def destroy(self):
self._unset_gui()
self._unset_lights()
self._side_panel.destroy()
self._cursor.destroy()
taskMgr.remove(self._scene_tsk)
+ if hasattr(self, '_success_txt'):
+ self._success_txt.destroy()
def _set_camera(self):
base.camera.set_pos(0, -20, 0)
base.camera.look_at(0, 0, 0)
def __load_img_btn(self, path, col):
- img = OnscreenImage('assets/buttons/%s.png' % path)
+ img = OnscreenImage('assets/images/buttons/%s.dds' % path)
img.set_transparency(True)
img.set_color(col)
img.detach_node()
abl, abr = base.a2dBottomLeft, base.a2dBottomRight
btn_info = [
('home', self.on_home, NORMAL, abl, 'gray'),
- ('information', self.on_information, DISABLED, abl, 'gray'),
+ ('information', self._set_instructions, NORMAL, abl, 'gray'),
('right', self.on_play, NORMAL, abr, 'green'),
('next', self.on_next, DISABLED, abr, 'gray'),
('previous', self.on_prev, DISABLED, abr, 'gray'),
- ('rewind', self.on_rewind, DISABLED, abr, 'gray')]
+ ('rewind', self.reset, NORMAL, abr, 'gray')]
num_l = num_r = 0
btns = []
for binfo in btn_info:
btns += [btn]
self.__home_btn, self.__info_btn, self.__right_btn, self.__next_btn, \
self.__prev_btn, self.__rewind_btn = btns
+ if self._dbg_items:
+ self._info_txt = OnscreenText(
+ '', parent=base.a2dTopRight, scale=0.04,
+ pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
def _unset_gui(self):
btns = [
self.__home_btn, self.__info_btn, self.__right_btn,
self.__next_btn, self.__prev_btn, self.__rewind_btn]
[btn.destroy() for btn in btns]
+ if self._dbg_items:
+ self._info_txt.destroy()
def _set_spotlight(self, name, pos, look_at, color, shadows=False):
light = Spotlight(name)
p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
return self._world.ray_test_all(p_from, p_to).get_hits()
+ def _update_info(self, item):
+ txt = ''
+ if item:
+ txt = '%.3f %.3f\n%.3f°' % (
+ item._np.get_x(), item._np.get_z(), item._np.get_r())
+ self._info_txt['text'] = txt
+
def _on_click(self, method):
if self._paused:
return
if hit.get_node() == self._mouse_plane_node:
pos = hit.get_hit_pos()
for hit in self._get_hits():
- for item in [i for i in self.items if hit.get_node() == i.node]:
+ for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
if not self._item_active:
self._item_active = item
getattr(item, method)(pos)
img = 'move' if method == 'on_click_l' else 'rotate'
- self._cursor.set_image('assets/buttons/%s.png' % img)
+ if not (img == 'rotate' and not item._instantiated):
+ self._cursor.set_image('assets/images/buttons/%s.dds' % img)
def on_click_l(self):
self._on_click('on_click_l')
self._on_click('on_click_r')
def on_release(self):
+ if self._item_active and not self._item_active._first_command:
+ self._commands = self._commands[:self._command_idx]
+ self._commands += [self._item_active]
+ self._command_idx += 1
+ self.__prev_btn['state'] = NORMAL
+ fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+ self.__prev_btn['frameColor'] = fcols[0]
+ if self._item_active._command_idx == len(self._item_active._commands) - 1:
+ self.__next_btn['state'] = DISABLED
+ self.__next_btn['frameColor'] = fcols[1]
self._item_active = None
[item.on_release() for item in self.items]
- self._cursor.set_image('assets/buttons/arrowUpLeft.png')
+ self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
- def on_aspect_ratio_changed(self):
+ def repos(self):
+ for item in self.items:
+ item.repos_done = False
+ self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
[item.on_aspect_ratio_changed() for item in self.items]
self._side_panel.update(self.items)
+ max_x = -float('inf')
+ for item in self.items:
+ if not item._instantiated:
+ max_x = max(item._np.get_x(), max_x)
+ for item in self.items:
+ if not item._instantiated:
+ item.repos_x(max_x)
+
+ def on_aspect_ratio_changed(self):
+ self.repos()
+
+ def _win_condition(self):
+ pass
+
+ def _fail_condition(self):
+ return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
def on_frame(self, task):
hits = self._get_hits()
[itm.on_mouse_off() for itm in items_no_hit]
if pos and self._item_active:
self._item_active.on_mouse_move(pos)
+ if self._dbg_items:
+ self._update_info(items_hit[0] if items_hit else None)
+ if self._win_condition():
+ self._set_win()
+ elif self._state == 'playing' and self._fail_condition():
+ self._set_fail()
+ if any(itm._overlapping for itm in self.items):
+ self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
+ else:
+ self._cursor.cursor_img.img.set_color(.9, .9, .9, 1)
return task.cont
def cb_inst(self, item):
self.items += [item]
def on_play(self):
+ self._state = 'playing'
+ self.__prev_btn['state'] = DISABLED
+ self.__next_btn['state'] = DISABLED
+ self.__right_btn['state'] = DISABLED
[itm.play() for itm in self.items]
def on_next(self):
- print('on_next')
+ self._commands[self._command_idx].redo()
+ self._command_idx += 1
+ fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+ self.__prev_btn['state'] = NORMAL
+ self.__prev_btn['frameColor'] = fcols[0]
+ more_commands = self._command_idx < len(self._commands)
+ self.__next_btn['state'] = NORMAL if more_commands else DISABLED
+ self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
def on_prev(self):
- print('on_prev')
-
- def on_rewind(self):
- print('on_rewind')
+ self._command_idx -= 1
+ self._commands[self._command_idx].undo()
+ fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+ self.__next_btn['state'] = NORMAL
+ self.__next_btn['frameColor'] = fcols[0]
+ self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
+ self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
def on_home(self):
self._exit_cb()
- def on_information(self):
- print('on_information')
-
def _set_instructions(self):
+ self._paused = True
+ self.__store_state()
mgr = TextPropertiesManager.get_global_ptr()
for name in ['mouse_l', 'mouse_r']:
- graphic = OnscreenImage('assets/buttons/%s.png' % name)
+ graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
graphic.set_scale(.5)
graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
graphic.get_texture().set_anisotropic_degree(2)
font.set_pixels_per_unit(60)
font.set_minfilter(Texture.FTLinearMipmapLinear)
font.set_outline((0, 0, 0, 1), .8, .2)
- txt = _('keep \5mouse_l\5 pressed to drag an item\n\n'
- 'keep \5mouse_r\5 pressed to rotate an item')
self._txt = OnscreenText(
- txt, parent=frm, font=font, scale=0.06, fg=(.9, .9, .9, 1),
- align=TextNode.A_left)
+ self._instr_txt(), parent=frm, font=font, scale=0.06,
+ fg=(.9, .9, .9, 1), align=TextNode.A_left)
u_l = self._txt.textNode.get_upper_left_3d()
l_r = self._txt.textNode.get_lower_right_3d()
w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
btn.set_transparency(True)
+ def _set_win(self):
+ loader.load_sfx('assets/audio/sfx/success.ogg').play()
+ self._paused = True
+ self.__store_state()
+ frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+ frameSize=(-.6, .6, -.3, .3))
+ font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+ font.clear()
+ font.set_pixels_per_unit(60)
+ font.set_minfilter(Texture.FTLinearMipmapLinear)
+ font.set_outline((0, 0, 0, 1), .8, .2)
+ self._txt = OnscreenText(
+ _('You win!'),
+ parent=frm,
+ font=font, scale=0.2,
+ fg=(.9, .9, .9, 1))
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+ btn_scale = .05
+ mar = .06 # margin
+ z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+ z += (btn_scale + 2 * mar) / 2
+ self._txt['pos'] = 0, z
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+ fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+ frm['frameSize'] = fsz
+ colors = [
+ (.6, .6, .6, 1), # ready
+ (1, 1, 1, 1), # press
+ (.8, .8, .8, 1), # rollover
+ (.4, .4, .4, .4)]
+ imgs = [self.__load_img_btn('home', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_end_home, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.set_transparency(True)
+ imgs = [self.__load_img_btn('rewind', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(0, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_restart, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.set_transparency(True)
+ scenes = []
+ for _file in glob('pmachines/scenes/*.py'):
+ _fn = _file.replace('.py', '').replace('/', '.')
+ for member in import_module(_fn).__dict__.values():
+ if isclass(member) and issubclass(member, Scene) and \
+ member != Scene:
+ scenes += [member]
+ scenes = sorted(scenes, key=lambda elm: elm.sorting)
+ enabled = scenes.index(self.__class__) < len(scenes) - 1
+ if enabled:
+ next_scene = scenes[scenes.index(self.__class__) + 1]
+ else:
+ next_scene = None
+ imgs = [self.__load_img_btn('right', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_next_scene,
+ extraArgs=[frm, next_scene], relief=FLAT,
+ frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn['state'] = NORMAL if enabled else DISABLED
+ btn.set_transparency(True)
+
+ def _set_fail(self):
+ loader.load_sfx('assets/audio/sfx/success.ogg').play()
+ self._paused = True
+ self.__store_state()
+ frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+ frameSize=(-.6, .6, -.3, .3))
+ font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+ font.clear()
+ font.set_pixels_per_unit(60)
+ font.set_minfilter(Texture.FTLinearMipmapLinear)
+ font.set_outline((0, 0, 0, 1), .8, .2)
+ self._txt = OnscreenText(
+ _('You have failed!'),
+ parent=frm,
+ font=font, scale=0.2,
+ fg=(.9, .9, .9, 1))
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+ btn_scale = .05
+ mar = .06 # margin
+ z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+ z += (btn_scale + 2 * mar) / 2
+ self._txt['pos'] = 0, z
+ u_l = self._txt.textNode.get_upper_left_3d()
+ l_r = self._txt.textNode.get_lower_right_3d()
+ c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+ fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+ frm['frameSize'] = fsz
+ colors = [
+ (.6, .6, .6, 1), # ready
+ (1, 1, 1, 1), # press
+ (.8, .8, .8, 1), # rollover
+ (.4, .4, .4, .4)]
+ imgs = [self.__load_img_btn('home', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_end_home, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.set_transparency(True)
+ imgs = [self.__load_img_btn('rewind', col) for col in colors]
+ btn = DirectButton(
+ image=imgs, scale=btn_scale,
+ pos=(0, 1, l_r[2] - mar - btn_scale),
+ parent=frm, command=self._on_restart, extraArgs=[frm],
+ relief=FLAT, frameColor=(.6, .6, .6, .08),
+ rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+ clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+ btn.set_transparency(True)
+
+ def _on_restart(self, frm):
+ self.__on_close_instructions(frm)
+ self.reset()
+
+ def _on_end_home(self, frm):
+ self.__on_close_instructions(frm)
+ self.on_home()
+
+ def _on_next_scene(self, frm, scene):
+ self.__on_close_instructions(frm)
+ self._reload_cb(scene)
+
def __store_state(self):
btns = [
self.__home_btn, self.__info_btn, self.__right_btn,