DNESS / cocos2d-iphone

Automatically exported from code.google.com/p/cocos2d-iphone
1 stars 0 forks source link

[patch] Time Scaling for Actions #236

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
The following patch introduces linearly eased time scaling for all 
IntervalActions.

It basically just intercepts dt in step: and scales it, making it appear 
for the action as though time is progressing slower than it really is but 
keeping the frame rate high.

This can be used to achieve some really neat slow-motion effects.

Original issue reported on code.google.com by lhunath on 3 Mar 2009 at 7:53

Attachments:

GoogleCodeExporter commented 9 years ago
if you have the time, feel free to commit it. thanks.

Original comment by ricardoq...@gmail.com on 4 Mar 2009 at 4:28

GoogleCodeExporter commented 9 years ago
Updated patch - this reworked version does the timescaling in CocosNode's step.

The big advantage is:
 - All actions on the node are affected.
 - Time scale is recursive like normal scale: Parent nodes that are scaled down will 
also cause child nodes' actions to run slower.

In Gorillas I apply the time scale to my game field to slow down the entire 
environment but not my menus etc.

Original comment by lhunath on 10 Mar 2009 at 10:21

Attachments:

GoogleCodeExporter commented 9 years ago
Is it possible to get the same effect by using actions (or a new concept) ?
I would like add the "slow/fast time" by not adding this logic to 
schedule/cocosnode.

For example, what if I want to change the time algorithm? Should I change 
CocosNode ?

I would like to include this feature, but by adding "plugins/actions" to the 
scheulder.

(removing milestone v0.7.1 until we figure how to do it)

Original comment by ricardoq...@gmail.com on 12 Mar 2009 at 1:59

GoogleCodeExporter commented 9 years ago
I'll fix this in trunk first, then feel free to remove it for the moment.

Or leave it in as undocumented for now, it's really up to you.

I was also first thinking on whether I can use the Ease actions, but they're 
not 
built for this.  Actions do something else, and although the ease logic inside 
the 
Ease actions can also be used here, I think reusing that logic would require 
extracting it out of the actions somehow in a more abstract class that can then 
be 
used for things like actions or easing node properties in a more generic way.

Either way, the big problem is indeed the cyclicness of the issue.  Can't 
really 
schedule an action to change the time interval of scheduled actions.  That's 
why the 
node itself, which still has the original frame rate (step rate) needs to 
process 
these.  Or delegate them to something else (plugins, as you call them).

Original comment by lhunath on 12 Mar 2009 at 8:29

GoogleCodeExporter commented 9 years ago
Ok... I have and idea, similar to yours, but instead of modifying CocosNode, it
alters running actions by wrapping them with the Speed action.
Basically this is the idea:

[node runAction:xxx];
[node runAction:yyy];
[node runAction:ooo];

[node runAction:AlterSpeedOfRunningActionsRate:0.2];

for( Action *action in node.runningActions )
{
    [action retain];
    [node stopAction:action];
    speed = [Speed actionWithAction:action rate:newRate];
}

I'll see if this is possible.

Original comment by ricardoq...@gmail.com on 12 Mar 2009 at 11:45

GoogleCodeExporter commented 9 years ago
I've done the following:
 * modified the current Speed action so it can be altered on runtime
 * Speed works with any kind of action: IntervalAction or non IntervalAction like
RepeatForEver
 * Speed can't be part of a Sequence because it is not an interval action.
 * fixed spawn to be 'Speed'ed (issue #257)
 * merged issue #222 (actions and tags)
 * added example in EaseAction demo

this way is not as complete as your patch (it doesn't cover Particles), and 
also you
must know before hand that you want to "slow motion" something, but I think this
approach is more "cocos2d"-like.

let me know if you are Ok with it, and if you need further changes so that you 
can
use it in your game.

thanks.

r666

Original comment by ricardoq...@gmail.com on 12 Mar 2009 at 3:12

GoogleCodeExporter commented 9 years ago
The biggest issue it has (other than the fact that snow/rain/fire/etc run at 
full 
speed) is that you have to apply it per action.

That really makes it quite useless for slowing down the gameplay.  In my game I 
slow 
down the game field node; everything else is a child of that in the hierarchy.  
As a 
result; just doing [gameLayer scaleTimeTo:] can be used for bullet-time 
slowmotion 
of everything that's going on in the game.  This implementation makes such use 
impossible, AFAIK.

That's the main reason why I moved my implementation out of IntervalAction and 
into 
CocosNode.

I think the only way to implement this in a manner that's useful in the 
scenario I 
described is to provide some kind of plugin architecture to CocosNode;
OR
Leave the timeScale property in CocosNode (it's not out of place there at all), 
and 
use actions to ease the property from 1 to 0.5f for example (EaseTime or 
something).

I think the second option is probably the most useful one.  The plugin 
architecture 
one might be more generic but I'm not really sure how it would work and what 
exactly 
it would even do.

Original comment by lhunath on 12 Mar 2009 at 5:51

GoogleCodeExporter commented 9 years ago
yes, your patch has more features than mine, and I like the "slow motion" 
concept.
My biggest concern regarding your patch is the "scope/spread" ratio.

What do I mean by that ?
To implement 'the slow motion' effect in a generic way we have to patch 
different files:
   * CocosNode (to hook actions)
   * ParticleNode (to hook particle update selector)
If tomorrow we decided to add a new node with it's own scheduler, we need to 
add this
same logic to that file.
And even if we patch all the needed files we can't say that cocos2d supports 
'slow
motion' because all the custom schedules are not affected by this logic, and 
every
developer needs to add this logic to it's scheduled selectors if they want to 
support it.

Perhaps, this can be achieved my modifying the scheduled selectors' timer.
Every node contains an array of scheduled selectors. so I'm thinking of adding 
the
scaletime logic in the scheduler... something like this:

[node alterSpeed:0.2];

-(void) alterSpeed:(float)ratio
{
  // apply alter speed to it's timers
  [scheduledSelectors makeObjectsPerformSelector:@selector(alterSpeed:)
withObject:ratio];

  // apply alter speed to it's children as well
  [children makeObjectsPerformSelector:@selector(alterSpeed:) withObject:ratio];
}

what do you think ?

Original comment by ricardoq...@gmail.com on 12 Mar 2009 at 8:17

GoogleCodeExporter commented 9 years ago
Hmm; I see what you mean.

Before I got started with this, I thought only the root node(s) were scheduled 
with 
the scheduler and they delegated their "time" to the children.  That way, the 
children were completely dependant on their parents for receiving "time".

After looking through the code I now know that every node has its own 
responsibility 
for scheduling and unscheduling itself (activating and deactivating its timers).

With that comes some issues that are caused when nodes get scheduled twice 
(I've ran 
into this issue several times, such as when onEnter runs twice but no onExit 
happens 
inbetween, etc.) or when a node is removed but still scheduled.

Either way - this cleanup happens quite OK, now, but the point is that each 
node 
still needs to unschedule/deactivate itself.  It's a bit of a code bloat and 
it's a 
possible cause for bugs to creap in (like scheduled methods called on 
deallocated 
nodes - I've encountered this issue when removing a node that isn't my child in 
a 
scheduled method; during the Scheduler's timer loop, the removed node's timer 
is 
further down the timer list and is still ran even though it's been added to the 
remove queue.)

If "time" is delegated from node to node, we could also address the issue of 
time 
scaling easily.  the parent node would scale its time, and pass the scaled time 
to 
the children.

Basically, a CocosNode would have a
-(void) tick:(ccTime)dt
{
    // <-- Scale dt here for time scaling.

    for(CocosNode *child in children)
        [child tick:dt];

    // <-- here, iterate over all method scheduled for this node, and pass them dt.
}

That would account for all the issues, I think.  It would mean all scheduled 
methods 
are scaled, not just step: (which also fixes ParticleSystem and custom 
scheduled 
methods).

So all the logic would be in CocosNode, but only in this one method.  Nowhere 
else.

And for easing the timeScale, we can still use a "TimeScaleTo" action that just 
modifies the timeScale property of a CocosNode (that way developers can choose 
what 
algorithm to use for time scale easing).

Original comment by lhunath on 13 Mar 2009 at 8:43

GoogleCodeExporter commented 9 years ago
Moreover, the dt scaling wouldn't be recursive anymore, which would be a 
performance 
gain.

And the alterSpeed you suggested would be a lot more work to perform than just 
altering a float property in one node.

Original comment by lhunath on 13 Mar 2009 at 8:47

GoogleCodeExporter commented 9 years ago
ok. let me think about it.
rescheduled: v0.7.2 (or v0.8 in case API or performance is affected)

Original comment by ricardoq...@gmail.com on 17 Mar 2009 at 12:26

GoogleCodeExporter commented 9 years ago

Original comment by ricardoq...@gmail.com on 17 Mar 2009 at 12:27

GoogleCodeExporter commented 9 years ago
 - All actions on the node are affected.
 - All scheduled are affected
 - The high performance.
 - Same frame rate.
 - Controlling with Director. Only one function. only one variable. 

Director.h:
{
float speed;
}
@property (readwrite, assign) float speed;

///////////////////////////
Director.m:
- (void) mainLoop
{
    // dispatch missing events
    while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES) == kCFRunLoopRunHandledSource) {};

    /* clear window */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* calculate "global" dt */
    [self calculateDeltaTime];
    if( ! paused )
        [[Scheduler sharedScheduler] tick: (dt*speed)];//"*speed". this is the modify

    /* to avoid flickr, nextScene MUST be here: after tick and before draw */
    if( nextScene )
        [self setNextScene];

    glPushMatrix();

    [self applyLandscape];

    /* draw the scene */
    [runningScene visit];
    if( displayFPS )
        [self showFPS];

        glPopMatrix();

    /* swap buffers */
    [_openGLView swapBuffers];  
}
Example:
[[Director shareDirector] setSpeed:0.5];//slow motion
[[Director shareDirector] setSpeed:1]; //Real Time
[[Director shareDirector] setSpeed:1.5];//quick motion

More:
You can add other functions.
  - Speed related to frame rate
etc.

Original comment by manu.valladolid on 29 Mar 2009 at 8:59

GoogleCodeExporter commented 9 years ago
1. Please use diffs instead of huge code dumps where only tiny changes are 
made.  
It's far easier to understand what you mean to change.

2. The suggestion made in this issue is
  - Just as simplistic as yours.
  - Far more potent: Yours slows down *everything*, where as the suggestions made 
here allow you to slow down just parts of the hierarchy.
  - The suggestions made here allow for Ease actions to ease the time scaling in or 
out.

To retort on your list of advantages:
 - All actions on the node are affected.
    -> This is just not true.  You're affecting the scheduler's source of time 
directly; which means all schedules, all actions on all nodes are affected; not 
just 
a node or a hierarchy of nodes.  My suggestion does however only affect a given 
node, and everything if you apply it on the root node.
 - All scheduled are affected
    -> True, and this is a disadvantage.  My suggestion allows your game menu to run 
at normal speed while your game dynamics run slower or are paused (frozen).
 - The high performance.
    -> My suggestion has no noticable lower performance than yours.  Maybe one FLOP 
per affected node more.
 - Same frame rate.
    -> Same thing with my suggestion.
 - Controlling with Director. Only one function. only one variable. 
    -> My suggestion would be controlled with the node, only one function/property.

I think my suggestion is overall favorable unless I've missed something 
important 
here; feel free to point it out.

Original comment by lhunath on 29 Mar 2009 at 9:14

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
rescheduling for v0.8

Original comment by ricardoq...@gmail.com on 16 Apr 2009 at 7:13

GoogleCodeExporter commented 9 years ago
I have a idea.
@interface Timer
float scaling;
@property(readwrite,assign) float scaling
@implementation Timer

-(void) fire: (ccTime) dt
{
    elapsed += dt;
    if( elapsed >= interval && scaling>0.0) { // pause if scaling is 0.0
        impMethod(target, selector, elapsed*scaling); //<<
        elapsed = 0;
    }
}
CocosNode.m
-(void)alterSpeed:(float)speed{
      for(Timer *t inscheduledSelectors){
           [t setScaling:speed];
      }
      //And step_ timer
}
I didn't try it.

Original comment by manu.valladolid on 27 May 2009 at 10:06

GoogleCodeExporter commented 9 years ago
I've implemented my proposed changes.  As a result, the following modifications 
were 
made:

 - Scheduler & Timer have been completely deleted.
 - Their function has been replaced by a far simpler & far less bugprone (see my 
earlier explanation) implementation of recursively passing time to running 
nodes.  
There is absolutely no need to do any activating/deactivating of timers logic 
when 
nodes enter/exit meaning calling these methods multiple times, playing with 
scheduling/unscheduling methods and actions from child methods/actions and all 
that 
sort of goodness will not cause any more issues.
 - I've also added ways of adding actions, scheduling methods and adding child nodes 
that are immune to their parent's timeScale.  This is an optional feature I 
introduced for the sake of completeness which causes 80% of the noise in this 
patch.
 - I've updated and renamed the Speed action to the ScaleTime action which linearly 
modifies the node's timeScale property.  It's an IntervalAction so it's easily 
wrappable by an Ease action to customize the way you want to apply time scaling 
to 
your node.

The net result of this patch:

 - Existing behavior remains completely the same. (AFAIK)
 - No API changes other than added methods you can optionally use.
 - Far cleaner, simpler and less bug-prone way of scheduling time.
 - Ability to scale a node's time so that it and all its children will (without any 
performance impact) scale up or down in time.  The effect can easily be 
combined in 
a tree such as a -> b -> c -> d -> e, so that timeScale 0.5f on b and 0.5f on d 
will 
result in a dt of 1 getting distributed like so:  a (1) -> b (0.5) -> c (0.5) 
-> d 
(0.25) -> e (0.25)

I can commit it after I receive your approval.

Original comment by lhunath on 1 Jul 2009 at 7:26

Attachments:

GoogleCodeExporter commented 9 years ago
here's my feedback:
 a) The scheduler is simplified (and I like simple classes), but I don't like to
couple it inside CocosNode. I prefer not to assign CocosNode new 
responsibilities
(when possible).

 b) I like that you can chain multiple nodes (eg: if you slow down an ancestor, all
it's children will be slowed down), and I think this is a nice feature.

The disadvantage is that you need to walk all the node's tree, no matter if 
they have
or not something scheduled (this can be expensive if you use several nodes).

And I don't know if it's because of b), but the 'Sprite performance test' is 
running
MUCH MUCH slower.

Performance of v0.8-beta:
http://www.cocos2d-iphone.org/wiki/doku.php/perf_test:0_8_beta

eg:
With your patch tests F9, F10, F11 and F12 with 450 sprites run at ~14 FPS. 
Without
your patch they run at: 21, 57, 60, 60 FPS respectively.

Original comment by ricardoq...@gmail.com on 2 Jul 2009 at 6:29

GoogleCodeExporter commented 9 years ago
in r1081 I've committed a small patch to the scheduler.
you can create a slow motion / fast forward effect.
It affects all scheduled actions.
I've also added a new test in EaseActionsTest.m that tests this feature.

Original comment by ricardoq...@gmail.com on 3 Jul 2009 at 3:30

GoogleCodeExporter commented 9 years ago
lhunath: I'm closing this bug as fixed cocos2d now have the Speed action and the
Scheduler#scaleTime method.

If you think we should implement another feature, please re-open it. thanks.

fixed in r1081

Original comment by ricardoq...@gmail.com on 6 Jul 2009 at 5:57

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
While your patch introduces a way of scaling time, I find it rather useless.  
You're 
simply affecting the global time scheduling process which means that with time 
scaled down *everything* goes slow.  This is almost never the intention.  
You'll 
always have components that *shouldn't* be affected by game time, such as HUDs 
and 
menus.

Moreover, the Scheduler is a lump of convoluted complexity that serves nothing 
but 
to introduce unnecessary complexity in both cocos2d and for the developer.  The 
suggestion of recursive time distribution is splendid in its simplicity both in 
cocos2d and for the developer.  No lists of actions to add and lists of actions 
to 
remove and mess to keep those in sync.

In reply to your feedback:

a) The scheduler is not simplified; it's simply gone.  Currently nodes manage 
their 
own actions AND the scheduler manages each node.  That's useless.  I suggest 
the 
node manages itself and its children.  That keeps all the logic that goes 
together, 
together.  Nodes now already scale their size, why shouldn't they scale their 
*own* 
time?  Moreover, there is no other way to take care of this decently.  A 
Scheduler 
simply just can't do the job right, which is why your suggestions are always 
lacking 
in functionality.  Personally, I think timekeeping of a node's actions and a 
node's 
schedules are right in place in the node's tick:, which is there either way.

b) Agreed, you do walk the whole tree here.  And while many nodes are always a 
performance issue and a bad idea; it does make it a bit slower in this case.

The proposed implementation is not ideal either.  There's a few things that can 
definitely be simplified; esp. if we don't care about being able to exempt 
certain 
actions/nodes/schedules from time scaling.

I'll be back with a better performing implementation if that is what is keeping 
this 
out of the tree.  If you simply just don't want to loose the Scheduler and 
don't 
want to add time scaling to CocosNode then I think all is said and I will live 
on at 
http://github.com/lhunath/Cocos2D-iPhone/tree/master - my personal 
cocos2d-iphone 
fork, currently just for the purpose of SCM'ing this patch :-)

Original comment by lhunath on 6 Jul 2009 at 7:12

GoogleCodeExporter commented 9 years ago
Sure. If you could make a patch that has a good performance I will be happy to 
review
it again.

BTW, 
 - I think it is a *good* thing (design-wise) to have an standalone scheduler.
 - I don't think the timeScale patch for the scheduler is useless. On the contrary, I
think that lots of games can profit from this feature.

Original comment by ricardoq...@gmail.com on 6 Jul 2009 at 7:32

GoogleCodeExporter commented 9 years ago
Just applied a simple patch to interval action to make it work on individual 
action. Hope this is helpful

Index: Classes/cocos2d/IntervalAction.h
===================================================================
--- Classes/cocos2d/IntervalAction.h    (revision 55)
+++ Classes/cocos2d/IntervalAction.h    (working copy)
@@ -37,9 +37,11 @@
 {
    struct timeval lastUpdate;
    ccTime elapsed;
+    float timeScale;
 }

 @property (readonly) ccTime elapsed;
+@property (assign) float timeScale;

 /** creates the action */
 +(id) actionWithDuration: (ccTime) d;
Index: Classes/cocos2d/IntervalAction.m
===================================================================
--- Classes/cocos2d/IntervalAction.m    (revision 55)
+++ Classes/cocos2d/IntervalAction.m    (working copy)
@@ -25,7 +25,7 @@
 #pragma mark IntervalAction
 @implementation IntervalAction

-@synthesize elapsed;
+@synthesize elapsed, timeScale;

 -(id) init
 {
@@ -49,6 +49,7 @@

    duration = d;
    elapsed = 0.0f;
+    timeScale = 1.0f;
    return self;
 }

@@ -66,7 +67,7 @@

 -(void) step: (ccTime) dt
 {
-   elapsed += dt;
+   elapsed += dt * timeScale;
    [self update: MIN(1, elapsed/duration)];
 }

Original comment by zhayup...@gmail.com on 18 Jul 2009 at 8:31

GoogleCodeExporter commented 9 years ago
I agree with lhunath, scaling time on a node basis would be much more useful 
than just scaling the global time. 
But from reading the above discussion, I understand there would be a 
performance hit. Would the performance 
hit be significant if only one type of node could have time scaled (say Layer) 
? Or what if the developer could 
choose to have "scalable time nodes" by conforming to a "scale time protocol" ? 
Sorry if these are stupid ideas, I 
haven't looked at how time is handled in cocos. I'm just throwing some thoughts 
as I believe time scaling for 
different nodes is a great idea.

Original comment by kur...@gmail.com on 9 Aug 2009 at 3:24

GoogleCodeExporter commented 9 years ago
No, implementing this in a limited basis is not possible because what is 
required to
do this completely changes how "time" is passed from the main run loop into node
schedules and actions.  Schedulers and Timers have completely disappeared and 
time is
just given to the showing Scene.  That one gives its time to its children (the
CocosNodes) those children to their children, etc.

Original comment by lhunath on 9 Aug 2009 at 8:23

GoogleCodeExporter commented 9 years ago
Have been made some improvements on this issue? Maybe planned for 0.9 
milestone? I
would need the functionality proposed by lhunath (in my case: slowing down the 
whole
game layer while user interacts with some menus), but actual implementation 
(slowing
down the whole system) seems useless.

Can be this issue re-opened?

Original comment by daniele.benegiamo@gmail.com on 30 Dec 2009 at 12:54

GoogleCodeExporter commented 9 years ago
The whole action system has changed significantly since my proposed patch; 
there is no 
way to implement this the way it had been before now.

Perhaps with the ActionManager a better solution can be found; it would need to 
be 
investigated.  I still think the Scheduler is a very broken approach to time 
scheduling.

Original comment by lhunath on 30 Dec 2009 at 1:03

GoogleCodeExporter commented 9 years ago
Unfortunately I don't know much the internals of Cocos. As I would ship this 
feature
in my game, is your patch in comment 2 still usable? Or the changes in Cocos 
have
invalidated it?

Original comment by daniele.benegiamo@gmail.com on 30 Dec 2009 at 1:42

GoogleCodeExporter commented 9 years ago
No, as I just said; "The whole action system has changed significantly since my 
proposed patch; there is no way to implement this the way it had been before 
now."

The patch will not work.

You'll have to either find another way; or use Cocos2D 0.7.2 with the patch.

If you want to do the last thing; get the cocos2d code from here:
http://github.com/lhunath/Cocos2D-iPhone/tree/Gorillas-131

Or download it from here:
http://github.com/lhunath/Cocos2D-iPhone/downloads (Gorillas-131 tag)

That has the patch already applied to it.

Original comment by lhunath on 30 Dec 2009 at 1:50

GoogleCodeExporter commented 9 years ago
https://github.com/cocos2d/cocos2d-iphone/pull/132

Original comment by lhunath@lyndir.com on 19 Dec 2011 at 11:26

GoogleCodeExporter commented 9 years ago
Thanks. re-fixed in v2.0 by using multiple Scheduler + ActionManagers.

Original comment by ricardoq...@gmail.com on 10 Jan 2012 at 6:10