Best way to go about making ccTime function just for a sprite's movement? - iphone

I'm trying to make a simple game and I am currently trying to make an arrow shoot out.
So far I have two functions,
-(void)ccTouchesBegan
Touches began does some math and gets a vector from the initial shooting point of arrow and the thumbpress, then passes this data into another function:
-(void)shatArrow:(CGPoint)cl:(CGPoint)nv{
}
What I want is for the shatArrow function to call a ccTime function that runs solely for the purpose of making the arrow move, and once the arrow is done with it's projection, the ccTime function will stop, and can be called again later when needed.
How would I go about this?

Schedule the update selector (ie in the init method of your class):
[self scheduleUpdate];
Then implement the update method:
-(void) update:(ccTime)delta
{
if (isArrowMoving)
{
// arrow move code here
if (arrow movement should end)
{
isArrowMoving = NO;
}
}
}
You can keep the update method running, unless you have hundreds of arrows it won't affect performance.
-(void) shootArrow:(CGPoint)cl:(CGPoint)nv
{
isArrowMoving = YES;
// other arrow movement init code here
}
Btw, it's "shoot, shot, shot" and not "shoot, shot, shat" or something like that. (I suppose you didn't ask for a #LinguisticsOverflow answer) ;)

Related

SKSpriteNode with physicsBody auto correct orientation

What would be the best way to make a Swift SKSpriteNode auto correct its orientation after it has had a couple of physicsBody knocks and is now not in the default orientation?
For example I have have a 2D stick-man who gets knocked to the side by another object and would like the stick-man to tilt and reorient itself to the default standing position.
The way I have done this in the past is to wait until the physics body comes to rest, turn off the physics engine for the stickman, reorient the stickman, turn the physics engine back on again.
Write yourself an action called standupAction which animates the stickman's reorientation, maybe just a rotate or a jump up. Then do something like the following.
Sorry this Objective-C not Swift, don't know swift, but should be easy to translate.
if (stickman.physicsBody.resting) {
stickman.physicsBody.dynamic = NO;
[stickman runAction:[SKAction sequence:#[
standupAction,
[SKAction runBlock:^{ stickman.physicsBody.dynamic = YES;}]
]]];
}
The only downside of this approach is that sometimes it can take a full second until the stickman comes comes to rest. Box2d takes quite a long time to settle down. But it looks very realistic if you wait.
Alternatively, you could just check the orientation of stickman and if he is >45degrees off balance then reorient him with impulses. I find impulses to be very difficult to know how much force to apply when you need precise movement like this.
Just to update, I couldn't afford to set the physicsBody to not be dynamic.
My scene updates each node, similarly to Apple's Adventure game, as such I implemented the rotation in the subclassed SKSpriteNode's update method as such:
if (isTouchingGround) {
if self.zRotation != 0 && !rotating {
let actions = [
SKAction.runBlock {
self.rotating = true
},
SKAction.rotateToAngle(0, duration: 1.0),
SKAction.runBlock {
self.rotating = false
}]
runAction(SKAction.sequence(actions))
}
}

How to Correctly Destroy ToneUnit after Tone Fades Out?

I'm generating tones on iPhone using AudioUnits based on Matt Gallagher's classic example. In order to avoid the chirps and clicks at the beginning/end, I'm fading the amplitude in/out in the RenderTone callback. I'd like to destroy the ToneUnit at the end of the fade out, that is, after the amplitude reaches zero. The only way I can think to do this is to call an instance method from within the callback:
if (PlayerState == FADING_OUT) {
amplitude -= stepsize;
if (amplitude <= 0) {
amplitude = 0;
PlayerState = OFF;
[viewController destroyToneUnit];
}
}
Unfortunately this is more challenging that I had thought. For one thing, I still get the click at the end that the fadeout was supposed to eliminate. For another, I get this log notice:
<AURemoteIO::IOThread> Someone is deleting an AudioConverter while it is in use.
What does this message mean and why am I getting it?
How should I kill the ToneUnit? I suspect that the click occurs because RenderTone and destroyToneUnit run on different threads. How can I get these synchronized?
In case it's helpful, here's my destroyToneUnit instance method:
- (void) destroyToneUnit {
AudioOutputUnitStop(toneUnit);
AudioUnitUninitialize(toneUnit);
AudioComponentInstanceDispose(toneUnit);
toneUnit = nil;
}
If I NSLog messages right before and right after AudioUnitUninitialize(toneUnit);, the notice appears between them.
I also ran into the same issue. When I called the destroyToneUnit from the main thread, the warning went away.
[viewController performSelectorOnMainThread:#selector(destroyToneUnit) withObject:nil waitUntilDone:NO];

Simulate turn sequence in turn-based game with one human player

I'm working on a turn-based card game. The main loop is simple, I have this structure (it's very simplified to be brief):
while (!gameEnded) {
for (Player *pl in players) {
Action *a = [pl askForAction];
self [manageAction];
}
if (some condition...) {
gameEnded = TRUE;
}
}
If all the player's are computer players there's no problem but if I want to have a human player, the askForAction method should "pause" the loop and let the player to select the action (pushing a button or touching a card...)
How can I do that? I've been "googling" it but I can't find anything. I suppose that the solution should be to use NSThread (or NSRunLoop maybe...) but I can't find an approach. Can anyone help me?
Thank you in advance
The way I would do it (there may be better answers) is to use an event-driven model rather than running in the loop.
In the delegate (or wherever), keep track of the current player (index into the players array). Call [[players objectAtIndex:currentPlayer] askForActionWithDelegate:self];
In the delegate, have a receiver (you can use manageAction), and when the user has performed the action, call manageAction. In manageAction, check for victory condition and advance currentPlayer.
Like Adam says, an event-driven model is probably best. However, I just want to emphasize how simple it is to resolve this, and there's no need for anything like NSThread or NSRunLoop.
You simply stop iterating when you get to a human player, and then start iterating again once you get an action from them:
- (void)nextTurn
while (!gameEnded) {
Player *pl = [self nextPlayer];
if (pl.isHuman) {
break;
} else {
Action *a = [pl getActionFromComputer];
[self manageAction:a];
}
}
if (gameEnded) {
// game over
} else {
// Set up your UI so it can get an action from the player.
// Your UI should be set up so that once it gets an action,
// it does [self manageAction:uiAction] then [self nextTurn]
}
}
Sorry for the delay. I've applied this approach and that's what I want. The way I've made it is as follows:
- (void)consumeTurn:(int)idPlayer {
if (![gameEnded]) {
Player *p = [getPlayer:idPlayer];
if (![p isHuman]) {
/** Do some stuff **/
consumeTurn:[getNextPlayer];
} else {
/** Show UIView with buttons for human player interaction **/
}
} else {
/** Game is ended... **/
}
}
And then in every button, the action associated to each button is:
- (IBAction)playerAction {
/** Do some stuff **/
consumeTurn:[getNextPlayer];
}
I call "consumeTurn" method in my principal ViewController (viewDidLoad).
Thank you every one for your help

Cocos2D getting progress of CCAction

I have a Cocos2D game with Box2D physics. In my GameScene.mm, I'm working on a method to zoom to a given scale:
-(void) zoomToScale:(float)zoom withDuration:(ccTime)duration
{
id action = [CCScaleTo actionWithDuration:duration scale:zoom];
[scrollNode runAction:action];
currentZoomLevel = zoom;
}
The problem that I'm having is that currentZoomLevel (which is used in the Scene's update() method) is set to the zoom immediately, and isn't gradually adjusted as per the animation. So while the animation is in progress, the currentZoomLevel variable is totally wrong.
I'm trying to figure out a way to have the currentZoomLevel variable match the progress of the animation as it's happening. According to the CCAction API Reference, the CCAction's update method takes a ccTime that's between 0 and 1 based on the progress of the animation (0 is just started, 1 is just finished).
How can I access this ccTime from outside of the action? I want to have something in my Scene's update method like this:
if(animating)
{
float progress = [action getProgress]; // How do I do this?
// Do math to update currentZoomLevel based on progress
}
Am I missing something obvious here, or am I going to have to subclass CCScaleTo?
You should be able to access the scale directly as it animates.
instead of
float progress = [action getProgress];
try
float current_scale = some_node.scale ;
where "some_node" is the thing you're animating/scaling.
Actually, your best bet is to use the new Cocos2D extension "CCLayerPanZoom", which handles all of this marvellously for you! It should be part of any new cocos2D install (v.1.0+).

Placing camera with animation

I want to know that how to place camera near and far (By increasing and decreasing value of eyeZ self.camera setEyeX:0 eyeY:0 eyeZ:1]; self.camera setEyeX:0 eyeY:0 eyeZ:180];) including animation (for smoothness), as normally it provide jerky zooming.
My suggestion is creating your own subclass of CCActionInterval, say CCCameraZoomAnimation and override its update method. The main advantage of having an action, aside from being able to control the camera movement finely, is also the possibility of using this action through CCEaseOut/CCEaseIn (etc.) to obtain nice graphical effects.
CCCameraZoomAnimation would have the node whose camera you want to modify as a target and another parameter to the constructor specifying the final Z value.
#interface CCActionEase : CCActionInterval <NSCopying>
{
CCActionInterval * other;
}
/** creates the action */
+(id) actionWithDuration:(ccTime)t finalZ:(float)finalZ;
/** initializes the action */
-(id) initWithDuration:(ccTime)t finalZ:(float)finalZ;
#end
The update method is called with an argument dt which represents the elapsed time since the start of the action and would allow you to easily calculate the current Z position:
-(void) update: (ccTime) t
{
// Get the camera's current values.
float centerX, centerY, centerZ;
float eyeX, eyeY, eyeZ;
[_target.camera centerX:&centerX centerY:&centerY centerZ:&centerZ];
[_target.camera eyeX:&eyeX eyeY:&eyeY eyeZ:&eyeZ];
eyeZ = _intialZ + _delta * t //-- just a try at modifying the camera
// Set values.
[_target.camera setCenterX:newX centerY:newY centerZ:0];
[_target.camera setEyeX:newX eyeY:newY eyeZ:eyeZ];
}
You would also need to implement copyWithZone:
-(id) copyWithZone: (NSZone*) zone
{
CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] finalZ:_finalZ];
return copy;
}
and make use of startWithTarget to
-(void) startWithTarget:(CCNode *)aTarget
{
[super startWithTarget:aTarget];
_initialZ = _target.camera....; //-- get the current value for eyeZ
_delta = ccpSub( _finalZ, _initialZ );
}
Nothing more, nothing less.
Sorry if copy/paste/modify produced some bugs, but I hope that the general idea is clear.
if you increase the 'z' by 180 from the off, you are bound to get a jerky animation, try running this in an animation context loop,increasing the value over a period of time will allow you to have a smooth 'zoom'.