Writing a cocos2d application ============================= Getting started with a new library or framework can be daunting, especially when presented with a large amount of reference material to read. This chapter gives a very quick introduction to cocos2d without covering any of the details. .. contents:: :local: Hello, World ------------ We'll begin with the requisite "Hello, World" introduction. This program will open a window with some text in it and wait to be closed. You can find the entire program in the `samples/hello_world.py` file. .. image:: hello_world.py.png Begin by importing the cocos package:: import cocos Subclass a `Layer` and define the logic of you program here:: class HelloWorld(cocos.layer.Layer): Always call ``super`` in the constructor:: def __init__(self): super(HelloWorld, self).__init__() To display the text, we'll create a `Label`. Keyword arguments are used to set the font, position and alignment of the label:: label = cocos.text.Label( 'Hello, world', font_name='Times New Roman', font_size=32, anchor_x='center', anchor_y='center' ) The label position will be the center of the screen:: label.position = 320, 240 Since `Label` is a subclass of `CocosNode` it can be added as a child. All `CocosNode` objects know how to render itself, perform actions and transformations. To add it as a layer's child, use the `CocosNode.add` method:: self.add(label) After defining the ``HelloWorld`` class, we need to initialize and create a window. To do this, we initialize the `Director`:: cocos.director.director.init() Then we create a ``HelloWorld`` instance:: hello_layer = HelloWorld() Then we create an `Scene` that contains the ``HelloWorld`` layer as a child:: main_scene = cocos.scene.Scene (hello_layer) And finally we run the scene:: cocos.director.director.run(main_scene) A shorter way to write the last 3 statements is this:: #cocos.director.director.run(cocos.scene.Scene(HelloWorld())) Hello Actions ------------- .. figure:: hello_world_actions.py.png This example is very similar to example #1, with the difference that it introduces us to the world of actions. An action is like an order. You can tell **any** `CocosNode` object to execute an action. You can find the entire program in the `samples/hello_world_actions.py` file. Like in our previous example, we import the cocos package:: import cocos If you're going to use several actions, you can import all the available actions into the namespace with this import:: from cocos.actions import * We subclass `ColorLayer` to have a background color, and then we call super() with a blueish color:: class HelloWorld(cocos.layer.ColorLayer): def __init__(self): # blueish color super( HelloWorld, self ).__init__( 64, 64, 224, 255 ) As in the previous example, we create and add a label:: label = cocos.text.Label('Hello, World!', font_name='Times New Roman', font_size=32, anchor_x='center', anchor_y='center') # set the label in the center of the screen label.position = 320, 240 self.add( label ) In this example we also create and add an sprite as a child. In cocos2d sprites are `Sprite` objects:: sprite = cocos.sprite.Sprite('grossini.png') We place the sprite in the center of the screen. Default position is (0, 0):: sprite.position = 320, 240 We set the scale attribute to 3. This means that our sprite will be 3 times bigger. The default scale attribute is 1:: sprite.scale = 3 We add the sprite as a child but on top of the label by setting the z-value to 1, since the default z-value is 0:: self.add( sprite, z=1 ) We create a `ScaleBy` action. It will scale 3 times the object in 2 seconds:: scale = ScaleBy(3, duration=2) We tell the label to: 1. scale 3 times in 2 seconds 2. then to scale back 3 times in 2 seconds 3. and we repeat these 2 actions forever Notice that the '+' operator is the `Sequence` action:: label.do( Repeat( scale + Reverse( scale) ) ) And we tell the sprite to do the same actions but starting with the 'scale back' action:: sprite.do( Repeat( Reverse(scale) + scale ) ) Then we initialize the director, like in the previous example:: cocos.director.director.init() hello_layer = HelloWorld () And... we tell the `Layer` (yes, all `CocosNode` objects can execute actions) to execute a `RotateBy` action of 360 degrees in 10 seconds:: hello_layer.do( RotateBy(360, duration=10) ) Finally we start the execution:: # A scene that contains the layer hello_layer main_scene = cocos.scene.Scene (hello_layer) # And now, start the application, starting with main_scene cocos.director.director.run (main_scene) Handling Events --------------- All our previous examples are non-interactive. They display something, but do not respond to user input (except for quitting when you press **ESC** or close the window). Cocos obtains inputs by listening to director.window events, and conveniently cocos.layer can automatically listen to director.window events: in your layer subclass set the is_event_handler class member to True and cocos will take care. In this section we will build step by step the demo provided in `samples/handling_events.py`; this is a very simple cocos app which shows which keys are pressed, and reacts to mouse motion and clicks. Run the app before reading on to get a clearer idea of what we are trying to build. .. figure:: event_demo.py.png This demo has a scene with two layers; one shows which keys are currently pressed (none, one, or maybe several at the same time), the other one shows text with the mouse position, and clicking moves the text. We start defining the KeyDisplay layer class. As always, we put some initialization on ``__init__`` and the code for displaying it in step:: class KeyDisplay(cocos.layer.Layer): # If you want that your layer receives director.window events # you must set this variable to 'True' is_event_handler = True def __init__(self): super( KeyDisplay, self ).__init__() self.text = cocos.text.Label("", x=100, y=280 ) # To keep track of which keys are pressed: self.keys_pressed = set() self.update_text() self.add(self.text) def update_text(self): key_names = [pyglet.window.key.symbol_string (k) for k in self.keys_pressed] text = 'Keys: '+','.join (key_names) # Update self.text self.text.element.text = text This class defines a key_pressed set, which should be the set of keys pressed at any time. However, this code as it is still does nothing. We need to tell this layer to update this set when a key is pressed or released. In other words, we need to add event handlers to the layer. Adding event handlers to a layer is just a matter of adding methods to it called on_. The two events that interest us now are ``on_key_press`` and ``on_key_release``:: def on_key_press (self, key, modifiers): """This function is called when a key is pressed. 'key' is a constant indicating which key was pressed. 'modifiers' is a bitwise or of several constants indicating which modifiers are active at the time of the press (ctrl, shift, capslock, etc.) """ self.keys_pressed.add (key) self.update_text() def on_key_release (self, key, modifiers): """This function is called when a key is released. 'key' is a constant indicating which key was pressed. 'modifiers' is a bitwise or of several constants indicating which modifiers are active at the time of the press (ctrl, shift, capslock, etc.) Constants are the ones from pyglet.window.key """ self.keys_pressed.remove (key) self.update_text() def update_text(self): key_names = [pyglet.window.key.symbol_string (k) for k in self.keys_pressed] text = 'Keys: '+','.join (key_names) # Update self.text self.text.element.text = text With that code, the layer is now fully working. You can press and release keys or key combinations, and you will see how the display is updated telling you which keys are pressed at any time. Handling mouse input is similar. You have three events of interest: on_mouse_press, on_mouse_motion and on_mouse_drag. With that, we can define our layer:: class MouseDisplay(cocos.layer.Layer): is_event_handler = True #: enable director.window events def __init__(self): super( MouseDisplay, self ).__init__() self.posx = 100 self.posy = 240 self.text = cocos.text.Label('No mouse events yet', font_size=18, x=self.posx, y=self.posy ) self.add( self.text ) def update_text (self, x, y): text = 'Mouse @ %d,%d' % (x, y) self.text.element.text = text self.text.element.x = self.posx self.text.element.y = self.posy And then add event handlers to update the text when the mouse is moved, and change the text position when any button is clicked:: def on_mouse_motion (self, x, y, dx, dy): """Called when the mouse moves over the app window with no button pressed (x, y) are the physical coordinates of the mouse (dx, dy) is the distance vector covered by the mouse pointer since the last call. """ self.update_text (x, y) def on_mouse_drag (self, x, y, dx, dy, buttons, modifiers): """Called when the mouse moves over the app window with some button(s) pressed (x, y) are the physical coordinates of the mouse (dx, dy) is the distance vector covered by the mouse pointer since the last call. 'buttons' is a bitwise or of pyglet.window.mouse constants LEFT, MIDDLE, RIGHT 'modifiers' is a bitwise or of pyglet.window.key modifier constants (values like 'SHIFT', 'OPTION', 'ALT') """ self.update_text (x, y) def on_mouse_press (self, x, y, buttons, modifiers): """This function is called when any mouse button is pressed (x, y) are the physical coordinates of the mouse 'buttons' is a bitwise or of pyglet.window.mouse constants LEFT, MIDDLE, RIGHT 'modifiers' is a bitwise or of pyglet.window.key modifier constants (values like 'SHIFT', 'OPTION', 'ALT') """ self.posx, self.posy = director.get_virtual_coordinates (x, y) self.update_text (x, y) The only thing a bit unusual here is the call to `director.get_virtual_coordinates` (x, y). As explained in the example before, cocos has two coordinates systems, a physical one and a virtual one. The mouse event handlers receive their arguments from pyglet in physical coordinates. If you want to map that to virtual coordinates, you need to use the director.get_virtual_coordinates method, which does the correct mapping. If you put instead ``self.posx``, ``self.posy = x, y`` in the on_mouse_press handler above, you will see that the app seems to work, but if you resize the window, the clicks will move the text to the wrong place. For completneddes, there are other mouse events that can be of interest: on_mouse_release : called when a button is released on_mouse_scroll : called when the mouse wheel moved on_mouse_leave : called when the mouse cursor goes out of the window on_mouse_enter : called when the mouse cursor enters the window The demo does not have much more code, just creating a scene with these two layers and running it:: director.init(resizable=True) # Run a scene with our event displayers: director.run( cocos.scene.Scene( KeyDisplay(), MouseDisplay() ) ) You can now play to the demo and change it. Some things you can try are: * Change the on_mouse_press handler and remove the mapping to virtual coordinates; note how it behaves strangely after resizing the window * Note that the mouse coordinates on screen are physical coordinates, so their range changes when resizing the window; modify the demo to show virtual coordinates. * Change the code to be able to move the mouse coordinates label when you drag the mouse * Change the code so the keyboard display also shows the modifiers set at each time Where to next? -------------- The examples presented in this chapter should have given you enough information to get started writing simple arcade and point-and-click-based games. The remainder of this programming guide goes into quite technical detail regarding some of cocos's features. While getting started, it's recommended that you skim the beginning of each chapter but not attempt to read through the entire guide from start to finish. To achieve optimal performance in your 2D applications you'll need to work with OpenGL directly. The canonical references for OpenGL are `The OpenGL Programming Guide`_ and `The OpenGL Shading Language`_. Since cocos2d uses pyglet, you shall also check `pyglet Programming Guide`_ and `pyglet API Reference`_. There are numerous examples of cocos2d applications in the ``samples/`` directory of the documentation and source distributions. Keep checking http://los-cocos.github.io/cocos-site for more examples and tutorials as they are written. .. _The OpenGL Programming Guide: http://opengl.org/documentation/books/#the_opengl_programming_guide_the_official_guide_to_learning_opengl_version .. _The OpenGL Shading Language: http://opengl.org/documentation/books/#the_opengl_shading_language_2nd_edition .. _pyglet Programming Guide: https://pyglet.readthedocs.io/en/stable/#programming-guide .. _pyglet API Reference: https://pyglet.readthedocs.io/en/stable/#api-reference .. _The pyglet Event Framework: http://www.pyglet.org/doc/programming_guide/the_pyglet_event_framework.html