1
# ----------------------------------------------------------------------------
3
# Copyright (c) 2008 Daniel Moisset, Ricardo Quesada, Rayentray Tappa, Lucio Torre
6
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions are met:
9
# * Redistributions of source code must retain the above copyright
10
# notice, this list of conditions and the following disclaimer.
11
# * Redistributions in binary form must reproduce the above copyright
12
# notice, this list of conditions and the following disclaimer in
13
# the documentation and/or other materials provided with the
15
# * Neither the name of cocos2d nor the names of its
16
# contributors may be used to endorse or promote products
17
# derived from this software without specific prior written
20
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
# POSSIBILITY OF SUCH DAMAGE.
32
# ----------------------------------------------------------------------------
33
'''Singleton that handles the logic behind the Scenes
41
The director is the singleton that creates and handles the main ``Window``
42
and manages the logic behind the ``Scenes``.
44
The first thing to do, is to initialize the ``director``::
46
from cocos.director import *
47
director.init( list_of_arguments )
49
This will initialize the director, and will create a display area
50
(a 640x480 window by default).
51
The parameters that are supported by director.init() are the same
52
parameters that are supported by pyglet.window.Window(), plus a few
55
director.init cocos exclusive parameters:
56
* ``do_not_scale``: Boleean. Defaults to False, when your app can think
57
that the windows size dont change despite resize events.
58
When True, your app must include logic to deal with diferent window
59
sizes along the session.
60
* ``audio_backend``: one in ['pyglet','sdl']. Defaults to 'pyglet' for
62
* ``audio``: None or a dict providing parameters for the sdl audio backend.
63
* None: in this case a "null" audio system will be used, where all the
64
sdl sound operations will be no-ops. This may be useful if you do not
65
want to depend on SDL_mixer
66
* A dictionary with string keys; these are the arguments for setting up
67
the audio output (sample rate and bit-width, channels, buffer size).
68
The key names/values should match the positional arguments of
69
http://www.pygame.org/docs/ref/mixer.html#pygame.mixer.init
70
* The default value is {}, which means sound enabled with default
73
director.init parameters passes to pyglet.window.Window (partial list):
75
* ``fullscreen``: Boolean. Window is created in fullscreen. Default is False
76
* ``resizable``: Boolean. Window is resizable. Default is False
77
* ``vsync``: Boolean. Sync with the vertical retrace. Default is True
78
* ``width``: Integer. Window width size. Default is 640
79
* ``height``: Integer. Window height size. Default is 480
80
* ``caption``: String. Window title.
81
* ``visible``: Boolean. Window is visible or not. Default is True.
83
The full list of valid video arguments can be found at the pyglet Window
86
- http://www.pyglet.org/doc/1.1/api/pyglet.window.Window-class.html
90
director.init( caption="Hello World", fullscreen=True )
95
Once you have initialized the director, you can run your first ``Scene``::
97
director.run( Scene( MyLayer() ) )
99
This will run a scene that has only 1 layer: ``MyLayer()``. You can run a scene
100
that has multiple layers. For more information about ``Layers`` and ``Scenes``
101
refer to the ``Layers`` and ``Scene`` documentation.
103
`cocos.director.Director`
105
Once a scene is running you can do the following actions:
107
* ``director.replace( new_scene ):``
108
Replaces the running scene with the new_scene
109
You could also use a transition. For example:
110
director.replace( SplitRowsTransition( new_scene, duration=2 ) )
112
* ``director.push( new_scene ):``
113
The running scene will be pushed to a queue of scenes to run,
114
and new_scene will be executed.
116
* ``director.pop():``
117
Will pop out a scene from the queue, and it will replace the running scene.
119
* ``director.scene.end( end_value ):``
120
Finishes the current scene with an end value of ``end_value``. The next scene
121
to be run will be popped from the queue.
123
Other functions you can use are:
125
* ``director.get_window_size():``
126
Returns an (x,y) pair with the _logical_ dimensions of the display.
127
The display might have been resized, but coordinates are always relative
128
to this size. If you need the _physical_ dimensions, check the dimensions
129
of ``director.window``
132
* ``get_virtual_coordinates(self, x, y):``
133
Transforms coordinates that belongs the real (physical) window size, to
134
the coordinates that belongs to the virtual (logical) window. Returns
135
an x,y pair in logical coordinates.
137
The director also has some useful attributes:
139
* ``director.return_value``: The value returned by the last scene that
140
called ``director.scene.end``. This is useful to use scenes somewhat like
141
function calls: you push a scene to call it, and check the return value
142
when the director returns control to you.
144
* ``director.window``: This is the pyglet window handled by this director,
145
if you happen to need low level access to it.
147
* ``self.show_FPS``: You can set this to a boolean value to enable, disable
148
the framerate indicator.
150
* ``self.scene``: The scene currently active
154
__docformat__ = 'restructuredtext'
156
from os import getenv
158
from pyglet import window, event
159
from pyglet import clock
160
#from pyglet import media
161
from pyglet.gl import *
163
import cocos, cocos.audio
165
__all__ = ['director', 'DefaultHandler']
167
class DefaultHandler( object ):
169
super(DefaultHandler,self).__init__()
172
def on_key_press( self, symbol, modifiers ):
173
if symbol == pyglet.window.key.F and (modifiers & pyglet.window.key.MOD_ACCEL):
174
director.window.set_fullscreen( not director.window.fullscreen )
177
elif symbol == pyglet.window.key.P and (modifiers & pyglet.window.key.MOD_ACCEL):
178
import scenes.pause as pause
179
pause_sc = pause.get_pause_scene()
181
director.push( pause_sc )
184
elif symbol == pyglet.window.key.W and (modifiers & pyglet.window.key.MOD_ACCEL):
186
if self.wired == False:
187
glDisable(GL_TEXTURE_2D);
188
glPolygonMode(GL_FRONT, GL_LINE);
189
glPolygonMode(GL_BACK, GL_LINE);
190
# wired.wired.install()
191
# wired.wired.uset4F('color', 1.0, 1.0, 1.0, 1.0 )
194
glEnable(GL_TEXTURE_2D);
195
glPolygonMode(GL_FRONT, GL_FILL);
196
glPolygonMode(GL_BACK, GL_FILL);
198
# wired.wired.uninstall()
201
elif symbol == pyglet.window.key.X and (modifiers & pyglet.window.key.MOD_ACCEL):
202
director.show_FPS = not director.show_FPS
205
elif symbol == pyglet.window.key.I and (modifiers & pyglet.window.key.MOD_ACCEL):
206
from layer import PythonInterpreterLayer
208
if not director.show_interpreter:
209
if director.python_interpreter == None:
210
director.python_interpreter = cocos.scene.Scene( PythonInterpreterLayer() )
211
director.python_interpreter.enable_handlers( True )
212
director.python_interpreter.on_enter()
213
director.show_interpreter = True
215
director.python_interpreter.on_exit()
216
director.show_interpreter= False
219
elif symbol == pyglet.window.key.S and (modifiers & pyglet.window.key.MOD_ACCEL):
221
pyglet.image.get_buffer_manager().get_color_buffer().save('screenshot-%d.png' % (int( time.time() ) ) )
224
if symbol == pyglet.window.key.ESCAPE:
228
class ScreenReaderClock(pyglet.clock.Clock):
229
''' Make frames happen every 1/framerate and takes screenshots '''
231
def __init__(self, framerate, template, duration):
232
super(ScreenReaderClock, self).__init__()
233
self.framerate = framerate
234
self.template = template
235
self.duration = duration
239
def tick(self, poll=False):
240
'''Signify that one frame has passed.
244
pyglet.image.get_buffer_manager().get_color_buffer().save(self.template % (self.frameno) )
249
if self.fake_time > self.duration:
254
self.fake_time += 1.0/self.framerate
256
if self.last_ts is None:
259
delta_t = ts - self.last_ts
260
self.times.insert(0, delta_t)
261
if len(self.times) > self.window_size:
262
self.cumulative_time -= self.times.pop()
263
self.cumulative_time += delta_t
266
# Call functions scheduled for every frame
267
# Dupe list just in case one of the items unchedules itself
268
for item in list(self._schedule_items):
269
item.func(delta_t, *item.args, **item.kwargs)
271
# Call all scheduled interval functions and reschedule for future.
273
# Dupe list just in case one of the items unchedules itself
274
for item in list(self._schedule_interval_items):
275
if item.next_ts > ts:
277
item.func(ts - item.last_ts, *item.args, **item.kwargs)
279
# Try to keep timing regular, even if overslept this time;
280
# but don't schedule in the past (which could lead to
281
# infinitely-worsing error).
282
item.next_ts = item.last_ts + item.interval
284
if item.next_ts <= ts:
285
if ts - item.next_ts < 0.05:
286
# Only missed by a little bit, keep the same schedule
287
item.next_ts = ts + item.interval
289
# Missed by heaps, do a soft reschedule to avoid
290
# lumping everything together.
291
item.next_ts = self._get_soft_next_ts(ts, item.interval)
292
# Fake last_ts to avoid repeatedly over-scheduling in
293
# future. Unfortunately means the next reported dt is
294
# incorrect (looks like interval but actually isn't).
295
item.last_ts = item.next_ts - item.interval
298
# Remove finished one-shots.
299
self._schedule_interval_items = \
300
[item for item in self._schedule_interval_items \
301
if item.next_ts > ts]
304
# TODO bubble up changed items might be faster
305
self._schedule_interval_items.sort(key=lambda a: a.next_ts)
309
class Director(event.EventDispatcher):
310
"""Class that creates and handle the main Window and manages how
311
and when to execute the Scenes"""
312
#: a dict with locals for the interactive python interpreter (fill with what you need)
313
interpreter_locals = {}
315
def init(self, *args, **kwargs):
316
"""Initializes the Director creating the main window.
317
Keyword arguments are passed to pyglet.window.Window().
319
All the valid arguments can be found here:
321
- http://www.pyglet.org/doc/1.1/api/pyglet.window.Window-class.html
323
:rtype: pyglet.window.Window
324
:returns: The main window, an instance of pyglet.window.Window class.
327
# pop out the Cocos-specific flags
328
do_not_scale_window = kwargs.pop('do_not_scale', False)
329
audio_backend = kwargs.pop('audio_backend', 'pyglet')
330
audio_settings = kwargs.pop('audio', {})
332
# Environment variable COCOS2d_NOSOUND=1 overrides audio settings
333
if getenv('COCOS2D_NOSOUND', None) == '1' or audio_backend == 'pyglet':
334
audio_settings = None
335
# if audio is not working, better to not work at all. Except if
336
# explicitely instructed to continue
337
if not cocos.audio._working and audio_settings is not None:
338
from cocos.audio.exceptions import NoAudioError
339
msg = "cocos.audio isn't able to work without needed dependencies. " \
340
"Try installing pygame for fixing it, or forcing no audio " \
341
"mode by calling director.init with audio=None, or setting the " \
342
"COCOS2D_NOSOUND=1 variable in your env."
343
raise NoAudioError(msg)
345
#: pyglet's window object
346
self.window = window.Window( *args, **kwargs )
348
#: whether or not the FPS are displayed
349
self.show_FPS = False
352
self.scene_stack = []
354
#: scene that is being run
357
#: this is the next scene that will be shown
358
self.next_scene = None
360
# save resolution and aspect for resize / fullscreen
361
if do_not_scale_window:
362
self.window.push_handlers(on_resize=self.unscaled_resize_window)
364
self.window.push_handlers(on_resize=self.scaled_resize_window)
365
self.window.push_handlers(self.on_draw)
366
self._window_virtual_width = self.window.width
367
self._window_virtual_height = self.window.height
368
self._window_aspect = self.window.width / float( self.window.height )
373
self.set_alpha_blending()
376
self.python_interpreter = None
378
#: whether or not to show the python interpreter
379
self.show_interpreter = False
382
self.window.push_handlers( DefaultHandler() )
385
#TODO: reshape audio to not screw unittests
387
if not os.environ.get('cocos_utest', False):
388
cocos.audio.initialize(audio_settings)
393
def set_show_FPS(self, value):
394
if value and self.fps_display is None:
395
self.fps_display = clock.ClockDisplay()
396
elif not value and self.fps_display is not None:
397
self.fps_display.unschedule()
398
self.fps_display.label.delete()
399
self.fps_display = None
401
show_FPS = property(lambda self: self.fps_display is not None,
404
def run(self, scene):
405
"""Runs a scene, entering in the Director's main loop.
409
The scene that will be run.
412
self.scene_stack.append( self.scene )
413
self._set_scene( scene )
418
def set_recorder(self, framerate, template="frame-%d.png", duration=None):
419
'''Will replace the system clock so that now we can ensure a steady
420
frame rate and save one image per frame
424
the number of frames per second
426
the template that will be completed with an in for the name of the files
428
the amount of seconds to record, or 0 for infinite
430
pyglet.clock._default = ScreenReaderClock(framerate, template, duration)
434
"""Callback to draw the window.
435
It propagates the event to the running scene."""
437
if self.window.width==0 or self.window.height==0: #
441
if self.next_scene is not None:
442
self._set_scene( self.next_scene )
444
if not self.scene_stack:
447
# draw all the objects
450
# finally show the FPS
452
self.fps_display.draw()
454
if self.show_interpreter:
455
self.python_interpreter.visit()
458
def push(self, scene):
459
"""Suspends the execution of the running scene, pushing it
460
on the stack of suspended scenes. The new scene will be executed.
464
It is the scene that will be run.
466
self.dispatch_event("on_push", scene )
468
def on_push( self, scene ):
469
self.next_scene = scene
470
self.scene_stack.append( self.scene )
473
"""Pops out a scene from the queue. This scene will replace the running one.
474
The running scene will be deleted. If there are no more scenes in the stack
475
the execution is terminated.
477
self.dispatch_event("on_pop")
480
self.next_scene = self.scene_stack.pop()
482
def replace(self, scene):
483
"""Replaces the running scene with a new one. The running scene is terminated.
487
It is the scene that will be run.
489
self.next_scene = scene
491
def _set_scene(self, scene ):
492
"""Change to a new scene.
495
self.next_scene = None
497
if self.scene is not None:
499
self.scene.enable_handlers( False )
504
self.scene.enable_handlers( True )
511
# Window Helper Functions
513
def get_window_size( self ):
514
"""Returns the size of the window when it was created, and not the
515
actual size of the window.
517
Usually you don't want to know the current window size, because the
518
Director() hides the complexity of rescaling your objects when
519
the Window is resized or if the window is made fullscreen.
521
If you created a window of 640x480, the you should continue to place
522
your objects in a 640x480 world, no matter if your window is resized or not.
523
Director will do the magic for you.
526
:returns: The size of the window when it was created
528
if not hasattr(self, "_window_virtual_width"):
529
import pdb;pdb.set_trace()
530
return ( self._window_virtual_width, self._window_virtual_height)
533
def get_virtual_coordinates( self, x, y ):
534
"""Transforms coordinates that belongs the *real* window size, to the
535
coordinates that belongs to the *virtual* window.
537
For example, if you created a window of 640x480, and it was resized
538
to 640x1000, then if you move your mouse over that window,
539
it will return the coordinates that belongs to the newly resized window.
540
Probably you are not interested in those coordinates, but in the coordinates
541
that belongs to your *virtual* window.
544
:returns: Transformed coordinates from the *real* window to the *virtual* window
547
x_diff = self._window_virtual_width / float( self.window.width - self._offset_x * 2 )
548
y_diff = self._window_virtual_height / float( self.window.height - self._offset_y * 2 )
550
adjust_x = (self.window.width * x_diff - self._window_virtual_width ) / 2
551
adjust_y = (self.window.height * y_diff - self._window_virtual_height ) / 2
553
return ( int( x_diff * x) - adjust_x, int( y_diff * y ) - adjust_y )
556
def scaled_resize_window( self, width, height):
557
"""One of two possible methods that are called when the main window is resized.
559
This implementation scales the display such that the initial resolution
560
requested by the programmer (the "logical" resolution) is always retained
561
and the content scaled to fit the physical display.
563
This implementation also sets up a 3D projection for compatibility with the
564
largest set of Cocos transforms.
566
The other implementation is `unscaled_resize_window`.
575
pw, ph = width, height
576
# virtual (desired) view size
577
vw, vh = self.get_window_size()
578
# desired aspect ratio
580
# usable width, heigh
581
uw = int(min(pw, ph*v_ar))
582
uh = int(min(ph, pw/v_ar))
587
self._usable_width = uw
588
self._usable_height = uh
589
self.set_projection()
590
self.dispatch_event("on_resize", width, height)
591
return pyglet.event.EVENT_HANDLED
593
def unscaled_resize_window(self, width, height):
594
"""One of two possible methods that are called when the main window is resized.
596
This implementation does not scale the display but rather forces the logical
597
resolution to match the physical one.
599
This implementation sets up a 2D projection, resulting in the best pixel
600
alignment possible. This is good for text and other detailed 2d graphics
603
The other implementation is `scaled_resize_window`.
611
self._usable_width = width
612
self._usable_height = height
613
self.dispatch_event("on_resize", width, height)
615
def set_projection(self):
616
'''Sets a 3D projection mantaining the aspect ratio of the original window size'''
617
# virtual (desired) view size
618
vw, vh = self.get_window_size()
620
glViewport(self._offset_x, self._offset_y, self._usable_width, self._usable_height)
621
glMatrixMode(GL_PROJECTION)
623
gluPerspective(60, self._usable_width/float(self._usable_height), 0.1, 3000.0)
624
glMatrixMode(GL_MODELVIEW)
627
gluLookAt( vw/2.0, vh/2.0, vh/1.1566, # eye
628
vw/2.0, vh/2.0, 0, # center
629
0.0, 1.0, 0.0 # up vector
636
def set_alpha_blending( self, on=True ):
638
Enables/Disables alpha blending in OpenGL
639
using the GL_ONE_MINUS_SRC_ALPHA algorithm.
644
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
648
def set_depth_test( sefl, on=True ):
649
'''Enables z test. On by default
653
glEnable(GL_DEPTH_TEST)
654
glDepthFunc(GL_LEQUAL)
655
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
657
glDisable( GL_DEPTH_TEST )
659
event_loop = pyglet.app.event_loop
660
if not hasattr(event_loop, "event"):
661
event_loop = pyglet.app.EventLoop()
662
director = Director()
663
director.event = event_loop.event
664
"""The singleton; check `cocos.director.Director` for details on usage.
665
Don't instantiate Director(). Just use this singleton."""
667
director.interpreter_locals["director"] = director
668
director.interpreter_locals["cocos"] = cocos
670
Director.register_event_type('on_push')
671
Director.register_event_type('on_pop')
672
Director.register_event_type('on_resize')