1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
|
# ----------------------------------------------------------------------------
# cocos2d
# Copyright (c) 2008 Daniel Moisset, Ricardo Quesada, Rayentray Tappa, Lucio Torre
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of cocos2d nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
'''Singleton that handles the logic behind the Scenes
Director
========
Initializing
------------
The director is the singleton that creates and handles the main ``Window``
and manages the logic behind the ``Scenes``.
The first thing to do, is to initialize the ``director``::
from cocos.director import *
director.init( list_of_arguments )
This will initialize the director, and will create a display area
(a 640x480 window by default).
The parameters that are supported by director.init() are the same
parameters that are supported by pyglet.window.Window(), plus a few
cocos exclusive ones.
director.init cocos exclusive parameters:
* ``do_not_scale``: Boleean. Defaults to False, when your app can think
that the windows size dont change despite resize events.
When True, your app must include logic to deal with diferent window
sizes along the session.
* ``audio_backend``: one in ['pyglet','sdl']. Defaults to 'pyglet' for
legacy support.
* ``audio``: None or a dict providing parameters for the sdl audio backend.
* None: in this case a "null" audio system will be used, where all the
sdl sound operations will be no-ops. This may be useful if you do not
want to depend on SDL_mixer
* A dictionary with string keys; these are the arguments for setting up
the audio output (sample rate and bit-width, channels, buffer size).
The key names/values should match the positional arguments of
http://www.pygame.org/docs/ref/mixer.html#pygame.mixer.init
* The default value is {}, which means sound enabled with default
settings
director.init parameters passes to pyglet.window.Window (partial list):
* ``fullscreen``: Boolean. Window is created in fullscreen. Default is False
* ``resizable``: Boolean. Window is resizable. Default is False
* ``vsync``: Boolean. Sync with the vertical retrace. Default is True
* ``width``: Integer. Window width size. Default is 640
* ``height``: Integer. Window height size. Default is 480
* ``caption``: String. Window title.
* ``visible``: Boolean. Window is visible or not. Default is True.
The full list of valid video arguments can be found at the pyglet Window
documentation:
- http://www.pyglet.org/doc/1.1/api/pyglet.window.Window-class.html
Example::
director.init( caption="Hello World", fullscreen=True )
Running a Scene
----------------
Once you have initialized the director, you can run your first ``Scene``::
director.run( Scene( MyLayer() ) )
This will run a scene that has only 1 layer: ``MyLayer()``. You can run a scene
that has multiple layers. For more information about ``Layers`` and ``Scenes``
refer to the ``Layers`` and ``Scene`` documentation.
`cocos.director.Director`
Once a scene is running you can do the following actions:
* ``director.replace( new_scene ):``
Replaces the running scene with the new_scene
You could also use a transition. For example:
director.replace( SplitRowsTransition( new_scene, duration=2 ) )
* ``director.push( new_scene ):``
The running scene will be pushed to a queue of scenes to run,
and new_scene will be executed.
* ``director.pop():``
Will pop out a scene from the queue, and it will replace the running scene.
* ``director.scene.end( end_value ):``
Finishes the current scene with an end value of ``end_value``. The next scene
to be run will be popped from the queue.
Other functions you can use are:
* ``director.get_window_size():``
Returns an (x,y) pair with the _logical_ dimensions of the display.
The display might have been resized, but coordinates are always relative
to this size. If you need the _physical_ dimensions, check the dimensions
of ``director.window``
* ``get_virtual_coordinates(self, x, y):``
Transforms coordinates that belongs the real (physical) window size, to
the coordinates that belongs to the virtual (logical) window. Returns
an x,y pair in logical coordinates.
The director also has some useful attributes:
* ``director.return_value``: The value returned by the last scene that
called ``director.scene.end``. This is useful to use scenes somewhat like
function calls: you push a scene to call it, and check the return value
when the director returns control to you.
* ``director.window``: This is the pyglet window handled by this director,
if you happen to need low level access to it.
* ``self.show_FPS``: You can set this to a boolean value to enable, disable
the framerate indicator.
* ``self.scene``: The scene currently active
'''
__docformat__ = 'restructuredtext'
from os import getenv
import pyglet
from pyglet import window, event
from pyglet import clock
#from pyglet import media
from pyglet.gl import *
import cocos, cocos.audio
__all__ = ['director', 'DefaultHandler']
class DefaultHandler( object ):
def __init__(self):
super(DefaultHandler,self).__init__()
self.wired = False
def on_key_press( self, symbol, modifiers ):
if symbol == pyglet.window.key.F and (modifiers & pyglet.window.key.MOD_ACCEL):
director.window.set_fullscreen( not director.window.fullscreen )
return True
elif symbol == pyglet.window.key.P and (modifiers & pyglet.window.key.MOD_ACCEL):
import scenes.pause as pause
pause_sc = pause.get_pause_scene()
if pause:
director.push( pause_sc )
return True
elif symbol == pyglet.window.key.W and (modifiers & pyglet.window.key.MOD_ACCEL):
# import wired
if self.wired == False:
glDisable(GL_TEXTURE_2D);
glPolygonMode(GL_FRONT, GL_LINE);
glPolygonMode(GL_BACK, GL_LINE);
# wired.wired.install()
# wired.wired.uset4F('color', 1.0, 1.0, 1.0, 1.0 )
self.wired = True
else:
glEnable(GL_TEXTURE_2D);
glPolygonMode(GL_FRONT, GL_FILL);
glPolygonMode(GL_BACK, GL_FILL);
self.wired = False
# wired.wired.uninstall()
return True
elif symbol == pyglet.window.key.X and (modifiers & pyglet.window.key.MOD_ACCEL):
director.show_FPS = not director.show_FPS
return True
elif symbol == pyglet.window.key.I and (modifiers & pyglet.window.key.MOD_ACCEL):
from layer import PythonInterpreterLayer
if not director.show_interpreter:
if director.python_interpreter == None:
director.python_interpreter = cocos.scene.Scene( PythonInterpreterLayer() )
director.python_interpreter.enable_handlers( True )
director.python_interpreter.on_enter()
director.show_interpreter = True
else:
director.python_interpreter.on_exit()
director.show_interpreter= False
return True
elif symbol == pyglet.window.key.S and (modifiers & pyglet.window.key.MOD_ACCEL):
import time
pyglet.image.get_buffer_manager().get_color_buffer().save('screenshot-%d.png' % (int( time.time() ) ) )
return True
if symbol == pyglet.window.key.ESCAPE:
director.pop()
return True
class ScreenReaderClock(pyglet.clock.Clock):
''' Make frames happen every 1/framerate and takes screenshots '''
def __init__(self, framerate, template, duration):
super(ScreenReaderClock, self).__init__()
self.framerate = framerate
self.template = template
self.duration = duration
self.frameno = 0
self.fake_time = 0
def tick(self, poll=False):
'''Signify that one frame has passed.
'''
# take screenshot
pyglet.image.get_buffer_manager().get_color_buffer().save(self.template % (self.frameno) )
self.frameno += 1
# end?
if self.duration:
if self.fake_time > self.duration:
raise SystemExit()
# fake time.time
ts = self.fake_time
self.fake_time += 1.0/self.framerate
if self.last_ts is None:
delta_t = 0
else:
delta_t = ts - self.last_ts
self.times.insert(0, delta_t)
if len(self.times) > self.window_size:
self.cumulative_time -= self.times.pop()
self.cumulative_time += delta_t
self.last_ts = ts
# Call functions scheduled for every frame
# Dupe list just in case one of the items unchedules itself
for item in list(self._schedule_items):
item.func(delta_t, *item.args, **item.kwargs)
# Call all scheduled interval functions and reschedule for future.
need_resort = False
# Dupe list just in case one of the items unchedules itself
for item in list(self._schedule_interval_items):
if item.next_ts > ts:
break
item.func(ts - item.last_ts, *item.args, **item.kwargs)
if item.interval:
# Try to keep timing regular, even if overslept this time;
# but don't schedule in the past (which could lead to
# infinitely-worsing error).
item.next_ts = item.last_ts + item.interval
item.last_ts = ts
if item.next_ts <= ts:
if ts - item.next_ts < 0.05:
# Only missed by a little bit, keep the same schedule
item.next_ts = ts + item.interval
else:
# Missed by heaps, do a soft reschedule to avoid
# lumping everything together.
item.next_ts = self._get_soft_next_ts(ts, item.interval)
# Fake last_ts to avoid repeatedly over-scheduling in
# future. Unfortunately means the next reported dt is
# incorrect (looks like interval but actually isn't).
item.last_ts = item.next_ts - item.interval
need_resort = True
# Remove finished one-shots.
self._schedule_interval_items = \
[item for item in self._schedule_interval_items \
if item.next_ts > ts]
if need_resort:
# TODO bubble up changed items might be faster
self._schedule_interval_items.sort(key=lambda a: a.next_ts)
return delta_t
class Director(event.EventDispatcher):
"""Class that creates and handle the main Window and manages how
and when to execute the Scenes"""
#: a dict with locals for the interactive python interpreter (fill with what you need)
interpreter_locals = {}
def init(self, *args, **kwargs):
"""Initializes the Director creating the main window.
Keyword arguments are passed to pyglet.window.Window().
All the valid arguments can be found here:
- http://www.pyglet.org/doc/1.1/api/pyglet.window.Window-class.html
:rtype: pyglet.window.Window
:returns: The main window, an instance of pyglet.window.Window class.
"""
# pop out the Cocos-specific flags
do_not_scale_window = kwargs.pop('do_not_scale', False)
audio_backend = kwargs.pop('audio_backend', 'pyglet')
audio_settings = kwargs.pop('audio', {})
# Environment variable COCOS2d_NOSOUND=1 overrides audio settings
if getenv('COCOS2D_NOSOUND', None) == '1' or audio_backend == 'pyglet':
audio_settings = None
# if audio is not working, better to not work at all. Except if
# explicitely instructed to continue
if not cocos.audio._working and audio_settings is not None:
from cocos.audio.exceptions import NoAudioError
msg = "cocos.audio isn't able to work without needed dependencies. " \
"Try installing pygame for fixing it, or forcing no audio " \
"mode by calling director.init with audio=None, or setting the " \
"COCOS2D_NOSOUND=1 variable in your env."
raise NoAudioError(msg)
#: pyglet's window object
self.window = window.Window( *args, **kwargs )
#: whether or not the FPS are displayed
self.show_FPS = False
#: stack of scenes
self.scene_stack = []
#: scene that is being run
self.scene = None
#: this is the next scene that will be shown
self.next_scene = None
# save resolution and aspect for resize / fullscreen
if do_not_scale_window:
self.window.push_handlers(on_resize=self.unscaled_resize_window)
else:
self.window.push_handlers(on_resize=self.scaled_resize_window)
self.window.push_handlers(self.on_draw)
self._window_virtual_width = self.window.width
self._window_virtual_height = self.window.height
self._window_aspect = self.window.width / float( self.window.height )
self._offset_x = 0
self._offset_y = 0
# opengl settings
self.set_alpha_blending()
# python interpreter
self.python_interpreter = None
#: whether or not to show the python interpreter
self.show_interpreter = False
# default handler
self.window.push_handlers( DefaultHandler() )
# Audio setup:
#TODO: reshape audio to not screw unittests
import os
if not os.environ.get('cocos_utest', False):
cocos.audio.initialize(audio_settings)
return self.window
fps_display = None
def set_show_FPS(self, value):
if value and self.fps_display is None:
self.fps_display = clock.ClockDisplay()
elif not value and self.fps_display is not None:
self.fps_display.unschedule()
self.fps_display.label.delete()
self.fps_display = None
show_FPS = property(lambda self: self.fps_display is not None,
set_show_FPS)
def run(self, scene):
"""Runs a scene, entering in the Director's main loop.
:Parameters:
`scene` : `Scene`
The scene that will be run.
"""
self.scene_stack.append( self.scene )
self._set_scene( scene )
event_loop.run()
def set_recorder(self, framerate, template="frame-%d.png", duration=None):
'''Will replace the system clock so that now we can ensure a steady
frame rate and save one image per frame
:Parameters
`framerate`: int
the number of frames per second
`template`: str
the template that will be completed with an in for the name of the files
`duration`: float
the amount of seconds to record, or 0 for infinite
'''
pyglet.clock._default = ScreenReaderClock(framerate, template, duration)
def on_draw( self ):
"""Callback to draw the window.
It propagates the event to the running scene."""
if self.window.width==0 or self.window.height==0: #
return
self.window.clear()
if self.next_scene is not None:
self._set_scene( self.next_scene )
if not self.scene_stack:
pyglet.app.exit()
# draw all the objects
self.scene.visit()
# finally show the FPS
if self.show_FPS:
self.fps_display.draw()
if self.show_interpreter:
self.python_interpreter.visit()
def push(self, scene):
"""Suspends the execution of the running scene, pushing it
on the stack of suspended scenes. The new scene will be executed.
:Parameters:
`scene` : `Scene`
It is the scene that will be run.
"""
self.dispatch_event("on_push", scene )
def on_push( self, scene ):
self.next_scene = scene
self.scene_stack.append( self.scene )
def pop(self):
"""Pops out a scene from the queue. This scene will replace the running one.
The running scene will be deleted. If there are no more scenes in the stack
the execution is terminated.
"""
self.dispatch_event("on_pop")
def on_pop(self):
self.next_scene = self.scene_stack.pop()
def replace(self, scene):
"""Replaces the running scene with a new one. The running scene is terminated.
:Parameters:
`scene` : `Scene`
It is the scene that will be run.
"""
self.next_scene = scene
def _set_scene(self, scene ):
"""Change to a new scene.
"""
self.next_scene = None
if self.scene is not None:
self.scene.on_exit()
self.scene.enable_handlers( False )
old = self.scene
self.scene = scene
self.scene.enable_handlers( True )
scene.on_enter()
return old
#
# Window Helper Functions
#
def get_window_size( self ):
"""Returns the size of the window when it was created, and not the
actual size of the window.
Usually you don't want to know the current window size, because the
Director() hides the complexity of rescaling your objects when
the Window is resized or if the window is made fullscreen.
If you created a window of 640x480, the you should continue to place
your objects in a 640x480 world, no matter if your window is resized or not.
Director will do the magic for you.
:rtype: (x,y)
:returns: The size of the window when it was created
"""
if not hasattr(self, "_window_virtual_width"):
import pdb;pdb.set_trace()
return ( self._window_virtual_width, self._window_virtual_height)
def get_virtual_coordinates( self, x, y ):
"""Transforms coordinates that belongs the *real* window size, to the
coordinates that belongs to the *virtual* window.
For example, if you created a window of 640x480, and it was resized
to 640x1000, then if you move your mouse over that window,
it will return the coordinates that belongs to the newly resized window.
Probably you are not interested in those coordinates, but in the coordinates
that belongs to your *virtual* window.
:rtype: (x,y)
:returns: Transformed coordinates from the *real* window to the *virtual* window
"""
x_diff = self._window_virtual_width / float( self.window.width - self._offset_x * 2 )
y_diff = self._window_virtual_height / float( self.window.height - self._offset_y * 2 )
adjust_x = (self.window.width * x_diff - self._window_virtual_width ) / 2
adjust_y = (self.window.height * y_diff - self._window_virtual_height ) / 2
return ( int( x_diff * x) - adjust_x, int( y_diff * y ) - adjust_y )
def scaled_resize_window( self, width, height):
"""One of two possible methods that are called when the main window is resized.
This implementation scales the display such that the initial resolution
requested by the programmer (the "logical" resolution) is always retained
and the content scaled to fit the physical display.
This implementation also sets up a 3D projection for compatibility with the
largest set of Cocos transforms.
The other implementation is `unscaled_resize_window`.
:Parameters:
`width` : Integer
New width
`height` : Integer
New height
"""
# physical view size
pw, ph = width, height
# virtual (desired) view size
vw, vh = self.get_window_size()
# desired aspect ratio
v_ar = vw/float(vh)
# usable width, heigh
uw = int(min(pw, ph*v_ar))
uh = int(min(ph, pw/v_ar))
ox = (pw-uw)//2
oy = (ph-uh)//2
self._offset_x = ox
self._offset_y = oy
self._usable_width = uw
self._usable_height = uh
self.set_projection()
self.dispatch_event("on_resize", width, height)
return pyglet.event.EVENT_HANDLED
def unscaled_resize_window(self, width, height):
"""One of two possible methods that are called when the main window is resized.
This implementation does not scale the display but rather forces the logical
resolution to match the physical one.
This implementation sets up a 2D projection, resulting in the best pixel
alignment possible. This is good for text and other detailed 2d graphics
rendering.
The other implementation is `scaled_resize_window`.
:Parameters:
`width` : Integer
New width
`height` : Integer
New height
"""
self._usable_width = width
self._usable_height = height
self.dispatch_event("on_resize", width, height)
def set_projection(self):
'''Sets a 3D projection mantaining the aspect ratio of the original window size'''
# virtual (desired) view size
vw, vh = self.get_window_size()
glViewport(self._offset_x, self._offset_y, self._usable_width, self._usable_height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(60, self._usable_width/float(self._usable_height), 0.1, 3000.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
gluLookAt( vw/2.0, vh/2.0, vh/1.1566, # eye
vw/2.0, vh/2.0, 0, # center
0.0, 1.0, 0.0 # up vector
)
#
# Misc functions
#
def set_alpha_blending( self, on=True ):
"""
Enables/Disables alpha blending in OpenGL
using the GL_ONE_MINUS_SRC_ALPHA algorithm.
On by default.
"""
if on:
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
else:
glDisable(GL_BLEND)
def set_depth_test( sefl, on=True ):
'''Enables z test. On by default
'''
if on:
glClearDepth(1.0)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
else:
glDisable( GL_DEPTH_TEST )
event_loop = pyglet.app.event_loop
if not hasattr(event_loop, "event"):
event_loop = pyglet.app.EventLoop()
director = Director()
director.event = event_loop.event
"""The singleton; check `cocos.director.Director` for details on usage.
Don't instantiate Director(). Just use this singleton."""
director.interpreter_locals["director"] = director
director.interpreter_locals["cocos"] = cocos
Director.register_event_type('on_push')
Director.register_event_type('on_pop')
Director.register_event_type('on_resize')
|