Module cocos.actions.base_actions

Foundation for all actions

Actions

Actions purpose is to modify along the time some trait of an object. The object that the action will modify is the action's target. Usually the target will be an instance of some CocosNode subclass.

Example:

MoveTo(position, duration)

the target will move smoothly over the segment (target.position, action position parameter), reaching the final position after duration seconds have elapsed.

Cocos also provide some powerful operators to combine or modify actions, the more important s being:

sequence operator: action_1 + action_2 -> action_result

where action_result performs by first doing all that action_1 would do and then perform all that action_2 would do

Example use:

move_2 = MoveTo((100, 100), 10) + MoveTo((200, 200), 15)

When activated, move_2 will move the target first to (100, 100), it will arrive there 10 seconds after departure; then it will move to (200, 200), and will arrive there 10 seconds after having arrived to (100, 100)

spawn operator: action_1 | action_2 -> action_result

where action_result performs by doing what would do action_1 in parallel with what would perform action_2

Example use:

move_rotate = MoveTo((100,100), 10) | RotateBy(360, 5)

When activated, move_rotate will move the target from the position at the time of activation to (100, 100); also in the first 5 seconds target will be rotated 360 degrees

loop operator: action_1 * n -> action_result

Where n non negative integer, and action result would repeat n times in a row the same that action_1 would perform.

Example use:

rotate_3 = RotateBy(360, 5) * 3

When activated, rotate_3 will rotate target 3 times, spending 5 sec in each turn.

Action instance roles

Action subclass: a detailed cualitative description for a change

An Action instance can play one of the following roles

Template Role

The instance knows all the details to perform, except a target has not been set. In this role only __init__ and init should be called. It has no access to the concrete action target. The most usual way to obtain an action in the template mode is by calling the constructor of some Action subclass.

Example:

position = (100, 100); duration = 10
move = MoveTo(position, duration)
move is playing here the template role.

Worker role

Carry on with the changes desired when the action is initiated. You obtain an action in the worker role by calling the method do in a cocosnode instance, like:

worker_action = cocosnode.do(template_action, target=...)

The most usual is to call without the target kw-param, thus by default setting target to the same cocosnode that performs the do. The worker action begins to perform at the do call, and will carry on with the desired modifications to target in subsequent frames. If you want the capabilty to stop the changes midway, then you must retain the worker_action returned by the do and then, when you want stop the changes, call:

cocosnode.remove_action(worker_action)
( the cocosnode must be the same as in the do call )

Also, if your code need access to the action that performs the changes, have in mind that you want the worker_action (but this is discouraged,

Example:

position = (100, 100); duration = 10
move = MoveTo(position, duration)
blue_bird = Bird_CocosNode_subclass(...)
blue_move = blue_bird.do(move)

Here move plays the template role and blue_move plays the worker role. The target for blue_move has been set for the do method. When the do call omits the target parameter it defaults to the cocosnode where the do is called, so in the example the target for blue_move is blue_bird. In subsequents frames after this call, the blue_bird will move to the position (100, 100), arriving there 10 seconds after the do was executed.

From the point of view of a worker role action, the actions life can be mimicked by:

worker_action = deepcopy(template_action)
worker_action.target = some_obj
worker_action.start()
for dt in frame.next():
    worker_action.step(dt)
    if premature_termination() or worker_action.done():
        break
worker_action.stop()

Component role

Such an instance is created and stored into an Action class instance that implements an Action operator (a composite action). Carries on with the changes desired on behalf of the composite action. When the composite action is not instance of IntervalAction, the perceived life can be mimicked as in the worker role. When the composite action is instance of IntervalAction, special rules apply. For examples look at code used in the implementation of any operator, like Sequence_Action or Sequence_IntervalAction.

Restrictions and Capabilities for the current design and implementation

Worker Independence

Multiple worker actions can be obtained from a single template action, and they wont interfere between them when applied to different targets.

Example:

 position = (100, 0); duration = 10
 move = MoveBy(position, duration)

 blue_bird = Sprite("blue_bird.png")
 blue_bird.position = (0, 100)
 blue_move = blue_bird.do(move)

 red_bird = Sprite("red_bird.png")
 red_bird.position = (0, 200)
 red_move = blue_bird.do(move)

Here we placed two birds at the left screen border, separated vertically
by 100.
move is the template_action: full details on changes, still no target
blue_move, red_move are worker_action 's: obtained by a node.do, have all
the details plus the target; they will perform the changes along the time.
What we see is both birds moving smooth to right by 100, taking 10 seconds
to arrive at final position.
Note that even if both worker actions derive for the same template, they
don't interfere one with the other.

A worker action instance should not be used as a template

You will not get tracebacks, but the second worker action most surelly will have a corrupt workspace, that will produce unexpected behavior.

Posible fights between worker actions over a target member

If two actions that are active at the same time try to change the same target's member(s), the resulting change is computationally well defined, but can be somewhat unexpected by the programmer.

Example:

guy = Sprite("grossini.png")
guy.position = (100, 100)
worker1 = guy.do(MoveTo((400, 100), 3))
worker2 = guy.do(MoveBy((0, 300), 3))
layer.add(guy)

Here the worker1 action will try to move to (400, 100), while the worker2 action will try to move 300 in the up direction. Both are changing guy.position in each frame.

What we see on screen, in the current cocos implementation, is the guy moving up, like if only worker2 were active. And, by physics, the programmer expectation probably guessed more like a combination of both movements.

Note that the unexpected comes from two actions trying to control the same target member. If the actions were changing diferent members, like position and rotation, then no unexpected can happen.

The fighting can result in a behavior that is a combination of both workers, not one a 'winning' one. It entirely depends on the implementation from each action. It is possible to write actions than in a fight will show additive behavoir, by example:

import cocos.euclid as eu
class MoveByAdditive(ac.Action):
    def init( self, delta_pos, duration ):
        try:
            self.delta_pos = eu.Vector2(*delta_pos)/float(duration)
        except ZeroDivisionError:
            duration = 0.0
            self.delta_pos = eu.Vector2(*delta_pos)
        self.duration = duration

    def start(self):
        if self.duration==0.0:
            self.target.position += self.delta_pos
            self._done = True

    def step(self, dt):
        old_elapsed = self._elapsed
        self._elapsed += dt
        if self._elapsed > self.duration:
            dt = self.duration - old_elapsed
            self._done = True
        self.target.position += dt*self.delta_pos

guy = Sprite("grossini.png")
guy.position = (100, 100)
worker1 = guy.do(MoveByAdditive((300, 0), 3))
worker2 = guy.do(MoveByAdditive((0, 300), 3))
layer.add(guy)

Here the guy will mode in diagonal, ending 300 right and 300 up, the two actions have combined.

Action's instances in the template role must be (really) deepcopyiable

Beginers note: if you pass in init only floats, ints, strings, dicts or tuples of the former you can skip this section and revisit later.

If the action template is not deepcopyiable, you will get a deepcopy exception, complaining it can't copy something

If you cheat deepcopy by overriding __deepcopy__ in your class like:

def __deepcopy__(self):
    return self

you will not get a traceback, but the Worker Independence will broke, the Loop and Repeat operators will broke, and maybe some more.

The section name states a precise requeriment, but it is a bit concise. Let see some common situations where you can be in trouble and how to manage them.

  • you try to pass a CocosNode instance in init, and init stores that in an action member
  • you try to pass a callback f = some_cocosnode.a_method, with the idea that it shoud be called when some condition is meet, and init stores it in an action member
  • You want the action access some big decision table, static in the sense it will not change over program execution. Even if is deepcopyable, there's no need to deepcopy.

Workarounds:

  • store the data that you do not want to deepcopy in some member in the cocosnode

  • use an init2 fuction to pass the params you want to not deepcopy:

    worker = node.do(template)
    worker.init2(another_cocosnode)

    (see test_action_non_interval.py for an example)

Future: Next cocos version probably will provide an easier mechanism to designate some parameters as references.

Overview main subclasses

All action classes in cocos must be subclass of one off the following:

  • Action
  • IntervalAction (is itself subclass of Action)
  • InstantAction (is itself subclass of IntervalAction)

InstantAction

The task that must perform happens in only one call, the start method. The duration member has the value zero. Examples:

Place(position) : does target.position <- position
CallFunc(f, *args, **kwargs) : performs the call f(*args,**kwargs)

IntervalAction

The task that must perform is spanned over a number of frames. The total time needed to complete the task is stored in the member duration. The action will cease to perform when the time elapsed from the start call reachs duration. A proper IntervalAction must adhere to extra rules, look in the details section Examples:

MoveTo(position, duration)
RotateBy(angle, duration)

Action

The most general posible action class. The task that must perform is spanned over a number of frames. The time that the action would perfom is undefined, and member duration has value None. Examples:

RandomWalk(fastness)

Performs:

  • selects a random point in the screen
  • moves to it with the required fastness
  • repeat

This action will last forever.

Chase(fastness, chasee)

Performs:

  • at each frame, move the target toward the chasee with the specified fastness.
  • Declare the action as done when the distance from target to chasee is less than 10.

If fastness is greather than the chasee fastness this action will certainly terminate, but we dont know how much time when the action starts.

Classes

  Action
The most general action
  IntervalAction
IntervalAction()
  InstantAction
Instant actions are actions that promises to do nothing when the methods step, update, and stop are called.
  Repeat
Applied to InstantAction s means once per frame.
  _ReverseTime
Executes an action in reverse order, from time=duration to time=0

Functions

  Reverse(action)
Reverses the behavior of the action
  loop(action, times)
  sequence(action_1, action_2)
Returns an action that runs first action_1 and then action_2 The returned action will be instance of the most narrow class posible in InstantAction, IntervalAction, Action
  spawn(action_1, action_2)
Returns an action that runs action_1 and action_2 in paralel.

Function Details

Reverse

Reverse(action)

Reverses the behavior of the action

Example:

# rotates the sprite 180 degrees in 2 seconds counter clockwise
action = Reverse( RotateBy( 180, 2 ) )
sprite.do( action )

spawn

spawn(action_1, action_2)
Returns an action that runs action_1 and action_2 in paralel. The returned action will be instance of the most narrow class posible in InstantAction, IntervalAction, Action