CocosNodes
==========

.. contents::
    :local:

CocosNode Functionality
-----------------------

The fact that all objects depicting the scene are CocosNodes subclasses means that they share a common core of characteristics and functionalities. You know for one and you know for all.

The common core can be broken by functionality in

Parent-child
^^^^^^^^^^^^

    - .add(self, child, z=0, name=None ): Adds a child, raises exception Name already exists if duplicated
    - .remove(self, name_or_obj): Removes a child given its name or object, raises exception Child not found if not present
    - .kill(self): Remove itself from its parent
    - .parent: property giving the parent node or None
    - .get_ancestor(self, klass): Returns the first ancestor that isinstance of klass, or None
    - .get_children(self): Returns a list with the node children, order is back to front (ascending z)
    - .get(self, name): Gets a child given its name, raises exception Child not found if not present
    - operator in , as 'in node1 in node2': returns True if and only if node1 is child of node 2

`Parent-Child examples`

Spatial Placement
^^^^^^^^^^^^^^^^^

A node position is defined relative to the parent node, most precisely with respect the parent position.

    - .x, .y .position: properties giving position relative to parent origin; updating x or y automatically updates position, the reverse is also true.
    - .anchor_x, .anchor_y, anchor: properties giving the center for the scale and rotation, they automatically maintain the relation anchor == (anchor_x, anchor_y). Notice that different CocosNodes subclasses can have different default values for anchor.
    - .scale : 1.0 is the default scale, with 2.0 the node will double its apparent size.
    - .rotation : in degrees

`Spatial Placement snippets`; also most of scripts in directory tests do explicit placement.

Going in or out of the active scene
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    - .on_enter(self): called if added to a parent while parent in the active scene or if the entire scene goes active
    - .on_exit(self): called if removed from parent while parent in the active scene or if the entire scene

    
Render
^^^^^^

    - .visit(self): renders itself and its children. Render order is children with z <= 0, itself, children with z > 0
    - .draw(self): draws itself
    - .transform(self): helper for draw and visit, handles the openGL coordinate changes needed to render mandated by position, anchor, scale and rotation
    - .camera: instead of the stock position, anchor, scale and rotation uses a custom gluLookAt that also allows z. This is rarely used, usually by special effects in transitions.

    
Time management
^^^^^^^^^^^^^^^

    - .schedule_interval(self, callback, interval, \*args, \*\*kwargs): Schedule a function to be called every interval seconds.
    - .schedule(self, callback, \*args, \*\*kwargs): Schedule a function to be called every frame.
    - .unschedule(self, callback): Remove a function from the schedule.
    - .pause_scheduler(self): Time will stop passing for this node: scheduled callbacks will not be called, worker actions will not be called
    - .resume_scheduler(self): Time will continue/start passing for this node and callbacks will be called, worker actions will be called

    
Automated changes along the time
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    - .do(self, template_action, target=None): most of the time it is called with target=None, which result in target being self. The action will perform a potentially gradual change in target. Returns the worker action, which is the object doing the changes. You must save a reference to the worker if you plan to call later to .action_remove
    - .action_remove(self, worker_action): the worker action will terminate
    - .pause(self): Suspends the execution of actions.
    - .resume(self): Resumes the execution of actions.
    - .stop(self): All actions scheduled in this node are removed, stop method will be called for each action.
    - .are_actions_running(self): True or False
  
The automated changes are performed by a collaboration between CocosNode instances and `Action` instances.

`Automated changes along the time snippets`
Also the test directory has many samples like test_accel*.py, test_blink.py, test_callfunc*.py and others.


Brief tour of built-in CocosNode subclasses
-------------------------------------------

Scene
^^^^^

The root of the tree depicting the full content to be displayed.
To change between scenes you use director.

TransitionScene
^^^^^^^^^^^^^^^

`TransitionScene`

Base class to implement a gradual change from one screen (scene) to other. Typical examples are fade in and fade out.

Cocos provides a bunch of ready made scene transitions (sample code in test/test_transition_*.py)

Layer
^^^^^

`Layer`

Helps to organize the view in the depth axis.
Subclasses often offer specialized services to the childs (like ScrollingManager), and can be used to build higher order functionality (like Menu)


ColorLayer
^^^^^^^^^^

`ColorLayer`

The primary use is to provide a solid color background to a scene, also used to draw solid color rectangles.


MultiplexLayer
^^^^^^^^^^^^^^

`MultiplexLayer`

Allows to show only one of his childs, and to select which one.
An use case is when you want to show one of a number of menus.

ScrollingManager
^^^^^^^^^^^^^^^^

`ScrollingManager`

Coordinates a number of ScrollableLayer to do scroll and parallax so that the scroll don't goes out of the the world.

ScrollableLayer
^^^^^^^^^^^^^^^

Used as childs of ScrollingManager, helps to manage scroll limits and parallax.
You must set attributes px_with and px_height in a `ScrollableLayer` instance so restrictions in the scroll are enforced.
If using parallax, it must be provided in the constructor.

RectMapLayer and HexMapLayer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A tilemap is a world description where
 - the space is partitioned in a regular grid (rectangular or hexagonal in cocos)
 - it is known which associated image and game properties has each cell in the grid.

`RectMapLayer` and `HexMapLayer` are usually created by tiles.load, and they care to display the world and give access to the cells properties.

Examples are test/test_tiles.py and test/test_platformer.py

InterpreterLayer
^^^^^^^^^^^^^^^^

Used by director to show a python console that allows to inspect and modify the objects in the application using director.
You don't instantiate manually an InterpreterLayer, simply press ctrl + i (cmd + i on mac) to toggle the layer's visibility.

Sprite
^^^^^^

`Sprite`

Sprites allows to display an image in a rectangular area, which can be rotated,
scaled and moved.


Animating a sprite
++++++++++++++++++

Animation as in cartoon style animation, that is, replacing the image fast
enough to give the illusion of movement, can be accomplished by:

- using an animated .gif file as source for the image
- passing a pyglet.image.Animation as image, which collects a number of images
- have an array of images and let your code assign to the sprite image member

Performance Considerations
++++++++++++++++++++++++++

Sprites are not suitable for effects that piles a lot of semitransparent images
to render things like smoke or poisoning fog. Use particle systems or custom
CocosNode s managing vertex lists for that.

If your scene has less than 25 sprites, you can add directly to the
scene, like::

    class TLayer(cocos.layer.Layer):
        is_event_handler = True
        def __init__(self):
            cocos.layer.Layer.__init__(self)
            world_width, world_height = director.get_window_size()
            rand_color = [255, 0, 0]
            icolor = 0
            for i in range(qty_balls):
                ball = Ball((world_width*random.random(), world_height*random.random()), color=rand_color)
                rand_color[icolor] = random.randint(50, 255)
                icolor = (icolor + 1)%len(rand_color)
                self.add(ball)
    self.time = 0.0
    self.schedule(self.update)

When you have between 25 and 100 visible sprites, moving at each frame, you
should add one or more BatchNode s at some level in the scene and add the
sprites to them. Even only one batch can give you 4x fps::

    class TLayer(cocos.layer.Layer):
        is_event_handler = True
            def __init__(self):
                cocos.layer.Layer.__init__(self)
                batch = cocos.batch.BatchNode()
                self.add(batch) # we add a batch to the layer ...
                world_width, world_height = director.get_window_size()
                rand_color = [255, 0, 0]
                icolor = 0
                for i in range(100):
                    ball = Ball((world_width*random.random(), world_height*random.random()), color=rand_color)
                    rand_color[icolor] = random.randint(50, 255)
                    icolor = (icolor + 1)%len(rand_color)
                    batch.add(ball) # see ? we add sprites to the batch, not the layer
                self.batch = batch
                self.time = 0.0
                self.schedule(self.update)

With more than 100 visible, moving at each frame sprites, you will need to
optimize further to run at 60 fps in weak hardware like netbooks, old desktops,
cheap new desktops.
You can look at cocos particle systems and the section graphics in pyglet
programing guide for ideas.

For a ballpark data point, a 2008 low gamming spec machine (athon 5200, radeon 4650) gives::

    numballs    fps
        250     98
        500     54
        750     36

Notice that sprites out of view has a fraction of the cost that visible sprites: they are discarded at the openGL geometry stage (in most of openGL implementations)

    
ParticleSystems
^^^^^^^^^^^^^^^

`ParticleSystems`

Certain effects are best rendered as a big number of translucent, colored images, by example explosions and smoke.
Cocos provides the base class `ParticleSystems` to efficiently render those entities, and some specialized subclasses like `Fireworks`, `Spiral`, `Meteor`, `Sun`, `Fire`, `Galaxy`, `Flower`, `Explosion`, `Smoke`.

The most common method to customize particle systems is to subclass one of the cocos particle systems and modify some of the class members defining its behavior.
If you change the particle texture remember to load it with pyglet.image.load, not pyglet.resource.image
Of course, deeper customization can be achieved with subclassing and code customization.

Look at test/test_particle_*.py for smaple code.

TextElement, Label, HTMLLabel, RichLabel
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Overall opacity, Family and Font size can be specified for all.

`TextElement`: base class for all cocos text. Holds internally (member 'element') a suitable pyglet text object, providing the CocosNode interface and pyglet Batch to store parts.
Features other than opacity and the common cocosnode ones should be accessed trough the instance.element.   

`Label`: provide the most simple text display; color can also be set.
 
`HTMLLabel`: a slightly more powerful label, it can use mixed styles. Uses HTML syntax for text and styles; opacity as a whole can be set.

`RichTextLabel`: Allows rich text attributes.

Look for examples at test/test_label_*.py, test/test_htmllabel_*.py, test/test_rich_label.py

Menu
^^^^

`Menu`

- Does the layout of menu items
- Handles navigation between menu items
- Passes to the items the desired animation to play when the focus changes from one to another item

Look for examples at test/test_menu_*.py
 
Menu items
^^^^^^^^^^

`MenuItem`: A menu item that shows text

`ImageMenuItem`: A menu item that shows a image and optionally text 

`MultipleMenuItem`: Allows to cycle trough a customizable list of options

`ToggleMenuItem`: Exposes a on/off selection

`EntryMenuItem`: Exposes a label and a text entry field

`ColorMenuItem`: Allows to select a color

Look at test/test_menu_items.py for sample code.

Creating your CocosNode subclasses
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

TODO: explain how to build custom cocosnode objects,
how to use `transform` , etc.

CocosNodes Examples
-------------------

Parent-Child examples
^^^^^^^^^^^^^^^^^^^^^

Building a scene, object instantiation omitted ::

    # a scene with 2 layers; the background would be seen behind the game layer because of z values
    scene.add( background_layer, z=0 )
    scene.add( game_layer, z=1 )

    # the layer with 1 bloody pacman sprite
    blody_pacman_sprite.position = (100, 100)
    game_layer.add( bloody_pacman_sprite )

    # the bloody pacman has two daggers !!!
    sprite.add( dagger_far_sprite, z=-1 )
    sprite.add( dagger_near_sprite, z=1  )
    # the draw order is dagger_far, bloody_pacman, dagger_near

    # also they are zombie alien cowboys in the game,
    # so we add a bunch of them to the game_layer
    ...
 

A dagger has touched a zombie alien cowboy so it wants to spawn a nasty blotch of green blood.
We could add the blotch_sprite to the zac_sprite, an then the blotch will follow the zombie::

    zac_zprite.add(blotch_sprite, z = 1)

or we could add to the game_layer, and the blotch will not follow the zombie::

    layer = zac_sprite.parent # should be game_layer, because our staggering
    layer.add(blotch_sprite)

If for some ridiculous reason a dagger needs to grab a reference to the game_layer we could use 'get_ancestor'.
Due to to way we staged the scene, we know game_layer is parent of bloody_pacman_sprite which is parent of the dagger. So::

    layer = dagger_near.get_ancestor(Layer)

should produce game_layer.

A zombie is killed, we can retire from the scene with::

    zac_sprite.kill()

or with::

    game_layer.remove(zac_sprite)

or if we named it "T'chkss" when we added to the game_layer, like in::

    game_layer.add(zac_sprite, name = "T'chkss", z=4)

then we can retire it with::

    game_layer.remove("T'chkss")

Note that in current cocos if you add with name then you should remove by name,
else the name will become unusable for new additions.

Spatial Placement snippets
^^^^^^^^^^^^^^^^^^^^^^^^^^ 

Example::

    # place the sprite in 320,240
    sprite.position = (320,240)

or by using actions.
Example::

    # move the sprite to 320,240 in 5 seconds
    sprite.do( MoveTo( (320,240, duration=2 ) )

Automated changes along the time snippets
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
You can modify attributes to **any** `CocosNode` object.
By example::

    scene.do( ScaleTo(2, duration=2) )

    layer.do( RotateBy(360, duration=3) )

    sprite.do( Blink(5, duration=1 ) )

    label.do( JumpBy( (100,100), 50, 5, duration=3) )
