I'm working a "bullet time" effect into my game. For objects that use the update:(ccTime)dt function, I can just scale dt by a float to get the desired effect, but I'm not sure how to do something similar for object that are moving via action. I see that CCActionManager also uses update. What would be the best way to deal with this?
Slowing down an action
(27 posts) (4 voices)-
Posted 6 months ago #
-
Posted 6 months ago #
-
CCSpeed will help, but unfortunately you won't be able to just arbitrarily apply it to actions that were already running. You'll need to stop all the "normal speed" actions and replace them with CCSpeed'd actions.
Offhand, it occurs to me that you could potentially create your own custom action similar to CCSpeed, but which has a static multiplication value it is constantly applying. That way you can nest all of your other actions inside of this new action class, and it can respond automatically to the slowdown events as you change the static variable.
Posted 6 months ago # -
I was thinking he would create his actions as 1X CCSpeed actions, and then change the action speed as needed in his update or wherever.
Posted 6 months ago # -
Thanks, ciarus. Yes, I'm creating them as CCSpeed actions, and am going to iterate through the CCActionManager and just set the speed whenever I want to change the game speed. Only thing is, not quite sure how to iterate through the list yet. Hmm.
EDIT:
Doesn't look there's very good support for just grabbing all the actions. Am I missing something? I guess I'll just have my enemy objects keep an NSArray of actions and iterate through that. But then, how wold I know when to remove completed actions and prevent a memory leak?Posted 6 months ago # -
While I'd like us to continue figuring out a good way to change the speed of groups of CCActions, I found a great solution for when you just want to slow EVERYTHING:
float speed = 0.5f;
[[CCScheduler sharedScheduler] setTimeScale:speed];Posted 6 months ago # -
How about if you create the actions you want to slow-mo with a special tag, and then use getActionByTag on any nodes (sprites) you're interested in? If that returns non-nil, set the speed of those as needed. That way you'll only change the actions that you want to.
Posted 6 months ago # -
This sounds similar to what I did in Swipe Soccer. I set the speed of the ball animation to match the speed of a Box2D body.
Here's an exact snippet of my code, hope it helps you determine how to set the speed of an animation when it's already running:
-->This is in the interface:
CCSpeed *speedAction;-->This is in the implementation where I set up the animation:
CCAnimation *rollAnim = [CCAnimation animationWithFrames:rollFrames delay:0.1];</p> <p>CCAnimateIndexed *action = [CCAnimateIndexed actionWithAnimation: rollAnim];</p> <p>speedAction = [CCSpeed actionWithAction:[CCRepeatForever actionWithAction: action] speed:4.0];<br />-->This is in the tick method (I set the speed based on the ball box2d velocity):
float32 speed = b->GetLinearVelocity().Length(); // you could put whatever you like here</p> <p>[speedAction setSpeed:speed];<br />Is that what you were after or do I misunderstand? If you want to do multiple then you could shove more CCSpeed pointers in an array and iterate through that.
Cheers
Posted 6 months ago # -
I'm not sure if that would work. For instance, if I have a movement action and an action for drawing sprite frames. There's two actions I'd need to scale time for for that sprite. If I give them the same tag (is that even allowed?), I won't be able to manipulate them independently of each other. Would it be possible to set the tags to something like "speed_position" and "speed_animation", and find them by a substring in the tag (in this case "speed")?
Posted 6 months ago # -
You can have the same tags, but in this case it would probably make sense to have several tags that you handle as you need to. They're numeric tags.
So, you define something like
typedef enum {
e_action_tag_unknown = 0,
e_action_tag_speed_position,
e_action_tag_speed_animation
} ActionTags;Then, create your actions with the appropriate tag (or don't set any if you don't care).
Then, adjust speeds for whichever tagged actions as you need to, using the getActionByTag method on a sprite.
There are other ways you could do things as well (using arrays to the actions, etc.) but I think this is a pretty clean approach.
Posted 6 months ago # -
Thanks, ciarus. I'll go with that for now. But now I have the problem of dealing with timers in my game. CCTimer doesn't have a speed property. If I subclass CCTimer and add a speed property, then override update so that it multiples dt by the speed, well...I'm not sure if it's working. It's not calling the selectors I set at all. Hmm.
Posted 6 months ago # -
Is there some reason that you can't just put the speed in an instance variable and then access that in your update method, multiplying dt by it or whatever?
Posted 6 months ago # -
That's what I thought too, but for some reason, I can't get a CCTimer to fire. If I put a breakpoint in update(), it never reaches it. Maybe I'm losing my mind? Here's the code:
// In my enemy object [self setCleanupTimer:[JTimer timerWithTarget:self selector:@selector(removeFromParentAndCleanup:) interval:[self movementActionDuration]]]; // JTimer.h #import <Foundation/Foundation.h> #import "cocos2d.h" @interface JTimer : CCTimer @property(nonatomic, assign)float speed; -(void)update:(ccTime)dt; @end // JTimer.c #import "JTimer.h" @implementation JTimer @synthesize speed; -(id)init { if((self=[super init])) { [self setSpeed:1.0f]; } return self; } -(id)initWithTarget:(id)t selector:(SEL)s { if((self=[super initWithTarget:t selector:s])) { [self setSpeed:1.0f]; } return self; } -(id)initWithTarget:(id)t selector:(SEL)s interval:(ccTime)seconds { if((self=[super initWithTarget:t selector:s interval:seconds])) { [self setSpeed:1.0f]; } return self; } -(void)update:(ccTime)dt { // Never gets called [super update:dt*[self speed]]; } @endPosted 6 months ago # -
I meant, can't you put a speed instance variable in your layer and avoid subclassing CCTimer at all?
Posted 6 months ago # -
I'm not sure what you mean. The actions are fine now. I just have a new problem. I have to use timers to spawn/destroy some objects, but right now I have no way of slowing down those timers along with everything else in the game
I think I'm using or subclassing CCTimer wrong. I can't get it to fire. Do I have to schedule it somehow?
Posted 6 months ago # -
I think this is why you're not getting updates:
CCTimer init:
- (id) init
{
// blah blah blah removed// used to trigger CCTimer#update
updateSelector = @selector(update:);
impMethod = (TICK_IMP) [CCTimer instanceMethodForSelector:updateSelector];// blah blah blah removed
}It looks like it's grabbing the CCTimer update method, not your subclassed version, unless I'm misreading that. I've never used instanceMethodForSelector so I might be wrong.
Posted 6 months ago # -
Oh, I see. I didn't understand what you were doing with the timers. Can't you just change the interval of them?
Posted 6 months ago # -
I can't even get them to work at all. I was using NSTimers before. I'm totally lost.
Changing the interval might not be a good solution. These are no-repeat timers that should only fire once. I need them to not get desynced from the rest of the game if I modify the time. I mean, it could work...I'm just not sure how to get the elapsed time, and it seems like there should be an easier way.
For example, say I'm supposed to spawn an enemy in 10 seconds.
- After 4 seconds, I modify the game speed to 0.5.
- Assuming I don't change the game speed, the timer should fire 16 seconds from the start (4 + 6/0.5)Posted 6 months ago # -
Where do you set these timers up? Can you avoid the timers completely and just do the enemy spawning in your layer's update method, based on elapsed time and speed? You could track the elapsed time, or the time since the last spawn.
Your layer does have updates scheduled, yes? If not, the timers won't fire.
Posted 6 months ago # -
I set the timer up in the enemy's init method.
I've been going nuts over this for the last few hours...I can confirm that the problem is that for whatever reason, the timer just isn't being scheduled (update is never called). If I hack it by calling the timer's update function from within my enemy's update function, it works. So, I guess I can hack it if worse comes to worse. It just seems to me like the correct design pattern here is to have the timers act independently of a parent layer.
Posted 6 months ago # -
Does the enemy [[CCScheduler sharedScheduler] scheduleUpdateForTarget:priority:paused:]?
By the way, I'd probably be doing all of this in onEnter and tearing it down in onExit rather than in init.
Posted 6 months ago # -
Sorry, forget the first part of that. I'm getting sleepy.
Posted 6 months ago # -
So, yeah, I think the problem is that you can't override the update method based on what I posted about instanceMethodForSelector.
Posted 6 months ago # -
The enemy does this:
[self scheduleUpdate];I might do away with that and just have the main game loop call update on each enemy...that will also prevent desyncing issues.
Calling:
[[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:0 paused:NO];
in the JTimer init() did get update going, but then I had trouble stopping it. :-/What would you tear down in onExit?
One last question. NSTimer has a userInfo (id) parameter. You can use it to pass in a dictionary with values that you can use as arguments for functions that may require them. How would I do that with my JTimer class? Does the timer pass itself as the only argument when it sends the selector to the target?
Posted 6 months ago # -
Haha, I'm sleepy too. Let's pick up again tomorrow. Thanks so much for trying to help tonight.
Posted 6 months ago # -
Your update is firing because you've scheduled updates for it, not because of the timer. So, pull the scheduleUpdate out. Sorry, the two things are separate, I got my wires crossed.
CCTimer does not have a userinfo param, but there was a thread about that a while back.
See ToadKick's custom timer class link in the thick of the discussion here:
http://www.cocos2d-iphone.org/forum/topic/20743onEnter/onExit, I'd set up/tear down scheduled selectors and call their supers, but if you pull the scheduleUpdate per above, you don't need those.
Posted 6 months ago # -
It would probably be a lot easier to use the scheduler to schedule a method at some interval like a second and then have that method determine if an enemy should be created based on the time elapsed and the current play speed.
Posted 6 months ago #
Reply
You must log in to post.