I want to stop an action once my sprite gets to a certain rotation. For example:
CCAction *rotateUp = [CCRotateTo actionWithDuration:0.3 angle:-35];
[player runAction:rotateUp];
if (player.rotation == -35) {
[player stopAction:rotateUp];
[player runAction:[CCRotateTo actionWithDuration:0.5 angle:65]];
}
Once the player gets to the max rotation I want it to run a different action. But this isn't working. What can I do instead?
You cannot get Action output immediately. So it's good to give completion callback for that.
for ex. (in c++)
CCAction *rotateUp = CCRotateTo::create(0.3f, -35f);
CCCallFuncN *pCall = CCCallFuncN::create(callfunc_selector(<#_SELECTOR#>));
player->runAction(CCSequence::create(rotateUp, pCall, NULL));
Here SELECTOR specified get called when rotation action gets completed. Just convert it in Obj C and try.
Related
I am using SpriteKit with Xamarin. I'm currently making a game similar to flappy bird, except the pipes can also 'chomp' down and kill you like that. So at any given time my pipe sprites can be running two actions, one for movement along the X axis (when the player is moving) and one for movement along the Y axis (closing the pipes). When the player stops moving along the X axis I want to stop the running action of the pipes that moves them horizontally, but still keep the chomping action going. I'm having trouble setting up a key for my action though. Currently, this is what I have.
public override void TouchesEnded (NSSet touches, UIEvent evt)
{
base.TouchesEnded (touches, evt);
//TODO: Optimize by removing action by key instead of all of them. Figure out how to do this and impliment.
//This needs to be done before refining chomp as it will flow into the chomp logic
//by not stopping a chomp mid-way because the player lifted their finger.
poletop.RemoveAllActions();
polebottom.RemoveAllActions();
pole2top.RemoveAllActions ();
pole2bottom.RemoveAllActions ();
background.RemoveAllActions ();
//restarting the chomping action if it was stopped before.
chomped_return (false);
chomped_return (true);
}
So I'm basically stopping all actions, then restarting only the chomp if it was running (gets restarted in chomped_return).
This isn't very efficient and also causes some lag in-game as it stops and starts.
Code for starting the action along the X axis (player movement). This is the SKAction that I want to have the key so I can stop it and it alone. resetpoles is the completion function.
SKAction movebottompole;
SKAction movetoppole;
movebottompole = SKAction.MoveToX (edgebottom.X, flTime);
movetoppole = SKAction.MoveToX (edgetop.X, flTime);
polebottom.RunAction (movebottompole, resetpoles);
poletop.RunAction(movetoppole, resetpoles);
Chomping down on the player is an instant teleport of the pipes, but here's the code that runs to start the action of the pipes returning to their original position along the Y axis. This is setting up the action that I don't want to stop until it has completed.
public void chomped_return(bool blFirstPole)
{
//define our two actions
SKAction topreturn;
SKAction botreturn;
//define our floats for the time calculation
float flTime = 0;
float flMoveSpeed = 750;
float flDistance = 0.0f;
if (blFirstPole == true)
{
flDistance = (float)polebottom.Position.Y;
}
else if (blFirstPole == false)
{
flDistance = (float)pole2bottom.Position.Y;
}
//calculate time based on distance and vector units/second desired.
flTime = flDistance / flMoveSpeed;
//setup our moveto actions and use the time calculated above. Start the action.
topreturn = SKAction.MoveToY (750.0f, flTime);
botreturn = SKAction.MoveToY (0.0f, flTime);
if (blFirstPole == true)
{
poletop.RunAction (topreturn);
polebottom.RunAction (botreturn);
}
else if (blFirstPole == false)
{
pole2top.RunAction (topreturn);
pole2bottom.RunAction (botreturn);
}
return;
}
The variable blFirstPole is used to determine whether or not we are running the action on the first set of poles or second set (as there can be two on screen at once)
Would really appreciate any assistance, if you need any more information please do let me know!
Thanks
You can use RemoveActionForKey to remove an action with an certain key.
https://developer.xamarin.com/api/member/MonoTouch.SpriteKit.SKNode.RemoveActionForKey/p/System.String/
polebottom.RunAction(movebottompole, "movebottompole");
// remove it
polebottom.RemoveActionForKey("movebottompole");
But now you don't have your completion handler anymore. You can solve it by combining these two actions into one Sequence.
var poleactionWithCompletion = SKAction.Sequence(movebottompole, restpoles);
polebottom.RunAction(poleactionWithCompletion, "movebottompole");
If you need this more often, you can implement an extension method like:
public static class SKNodeExtension
{
public static void RunAction(this SKNode node, SKAction action, SKAction completion, string key)
{
var sequence = SKAction.Sequence(action, completion);
node.RunAction(sequence, key);
}
}
and then run your action like:
polebottom.RunAction(movebottompole, restpoles, "movebottompole");
I'm working on a game where if there is a collision between two of the same bodies then a sound plays. I've got this part working fine, but I only want the sound to play if the collision happens in the view. I have a scrolling background and collisions further ahead are making a noise.
Is there anyway to limit this sound from only playing when a collision happens in view?
Right now i'm using this code:
let rocksCollide = SKAction.playSoundFileNamed("rocks.wav", waitForCompletion: false)
if nodeB.name == "SMALLASTEROID" && nodeA.name == "SMALLASTEROID"{
runAction(rocksCollide)
}
Any help would be appreciated.
You can check if the node.position is inside the visible frame. For example.
if (CGRectContainsPoint(visibleFrame, nodeA.position)) {
// Play sound.
}
So i am developing a game and have coded the score system. I want it to be so that when my object that is scrolling down is equal to the same y.co-ordinate as the "Box" object, the score system adds one to the score.
However, upon trying this the socre does not update and stays at 0. Im not quite sure why, could you please tell me where I may be going wrong and how i could fix it.
This is the code I am using to code the score system. Any help would be appreciated, thanks.
-(void)Score{
ScoreNumber = ScoreNumber + 1;
ScoreDisplay.text = [NSString stringWithFormat:#"%i", ScoreNumber];
}
[There is also some code in the view did all. but the problem is not with this part of the code as i have used it before and it works fine.]
This is where it is meant to be implemented but is not working...
if (ROne.center.y == IView.center.y) {
[self Score];
}
if (RTwo.center.y == IView.center.y) {
[self Score];
}
if (RThree.center.y == IView.center.y) {
[self Score];
}
if (RFour.center.y == IView.center.y) {
[self Score];
}
if (RFive.center.y == IView.center.y) {
[self Score];
}
if (RSix.center.y == IView.center.y) {
[self Score];
}
Your problem is likely that is the y-increment doesn't add up to the y-coordinate of the target object, then it won't be strictly equal. You'll just sort of pass by it. This is why >= worked, except once you passed it, it will keep adding to the score.
So, what you need to test for is "passing by". That is to say that prior to moving, ROne.center.y < IView.center.y and after moving ROne.center.y >= IView.center.y. That is the only condition that effectively check for "ROne just arrived at the desired height".
Alternatively, you can use >= and use a flag arrived on ROne to tell it to quit counting the score. Set the arrived flag to false when (ROne.center.y >= IView.center.y && ROne.arrived) is true. Then reset the arrived flag to true when you decide to reset the object to the top of the screen.
You need to think through the logic of your game code:
Some object is moving along the Y-axis each frame and that movement is likely to be more than 1 pixel (or point) per frame.
Your tick method is being called each frame (or some other set interval) and it's coded to only work when the Y-coordinate exactly matches the boundary you have set.
What's happening is the object is moving past the boundary between ticks and the method is therefore unable to detect it (i.e. think about a boundary where y == 3 and the object goes from y == 1 to y == 5 in a single tick).
What you need to do is hold a flag showing if the object has gone past the boundary and only check if the boundary condition if this flag is false. The best place to put this flag is in the object itself.
Something like:
if (!ROne.hasScored && ROne.center.y => IView.center.y) {
[self Score];
ROne.scored = YES;
}
You also need to consider using an array of the six objects, rather than 6 individual variables.
I'm relatively new to SpriteKit and am wondering what alternatives there are to using the CACurrentMediaTime() for scheduling events. For example, I may implement an algorithm that prevents a player from firing too many times by adding the last time they fired with some 'cool down' period, comparing it to the current media time:
BOOL canFire = self.lastFireInterval + self.coolPeriod < CACurrentMediaTime();
The problem I have run into is that if I decide to alter the speed of a node, or even the entire scene, this logic falls apart. For example, if I gave the player a speed-up power up, I could slow down all other nodes except for the player, but the timings would be messed up for enemies firing.
What other alternatives are there for CACurrentMediaTime() that factor in the speed of the node?
2 options come to mind regarding the timed to fire:
1) Create an ivar/property BOOL readyNextMove; for your player class. Then when your player shoots set the readyNextMove to false and add this code:
SKAction *wait = [SKAction waitForDuration:2.0];
SKAction *block0 = [SKAction runBlock:^{
readyNextMove = true;
}];
[self runAction:[SKAction sequence:#[wait, block0]]];
2) You can use the update:(CFTimeInterval)currentTime method to see how much time has elapsed from, for example you player shooting, and set readyNextMove accordingly.
It's a matter of taste. But for all games I keep an internal frame based clock which handles everything. I firmly believe this is the best way, but of course it depends on the scope of your project. This method is very rigid and guarantees continuity. If you use timers and your game lags, your continuity is thrown off (in some cases a shooter might shoot more times per frame than other parts of your game gets to move).
In aScene.h
long _currentTime = 0;
In aScene.m
-(void) update {
_currentTime++;
for(shooter * aShooter in self.shooters) {
[aShooter updateWithTime:_currentTime];
}
}
In your shooter.m
-(BOOL) tryShoot:(long)globalTime {
if(_lastShootingTime<globalTime-shootingCooldown) {
_lastShootingTime = globalTime;
[self fireBullet];
return YES;
}
return NO;
}
-(void) updateWithTime:(long)globalTime {
//...perform shooters action for that time frame
_lastUpdatedTime = globalTime;
}
As the question states, in spritekit I am trying to find a way to change my value of a bool from false, too true for x amount of seconds then change it back to false.
the logic is i have this var, isStunned, when my player comes in contact with x sprite i call it and my joystick is disabled since the player is "stunned"
if (controlButtonDown) { // If i pressed joystick
if (!isStunned) { // IF i am not stunned
//move player
When my player touches the glue, i set isStunned to yes and the joystick is disabled, however I am wondering of a way to only disable my joystick or not move my sprite for x amount of seconds. I know of SKAction and the wait sequence, but those are actions and I dont think they will apply here. Any help is appreciated.
Thank you
Since you ask specifically with a spritekit tag.
Don't use NSTimer or actions to work with time in a game. Instead use the update function. If you're running at 60fps that means the update function will be called 120 times for two seconds. Using the frames instead of seconds to keep your game updated will avoid the gameplay being changed by lag. Imagine for instance that the players game lags a little, in other words runs slower. 2 seconds is still 2 seconds regardless of game lag, so now he is affected less by the glue than a person who has no lag. So...
First off you should have a variable keeping track of in game time: int _time;
In every update cycle you add one to time: _time++;
You should have a variable keeping track of when the user touched the glue: int _glueTouchedAtTime;
When the user clicks glue you set: _glueTouchedAtTime = time;
You should have a constant defining how long the glue is active: #define GLUE_TIME 120;
When you initiate the game set _glueTouchedAtTime to: _glueTouchedAtTime = -GLUE_TIME; To prevent the glue from being on when (_time == 0)
Testing if the user has touched glue now works like this:
if(_glueTouchedAtTime+GLUE_TIME>time) {
// Glue is active
} else {
// Glue is not active
}
Different glues for different sprites
To have different glues for different sprites I would suggest first doing a general game object (GameObject) as a subclass of either SKNode or SKSpriteNode depending on your needs. Then create a subclass of GameObject called GameObjectGluable. This should have a property called: #property int gluedAtTime;
You glue the glueable game object by: aGameObjectGluable.gluedAtTime = time;
Now you can test:
if(aGameObjectGluable.gluedAtTime +GLUE_TIME>time) {
// Game Object is glued
} else {
// Game object is not glued
}
Set your value-of-interest to true and then fire a NSTimer after two seconds to set it back to false.
For an explanation of how you might use NSTimer, see this SO thread: How do I use NSTimer?
You can use SKAction also. The runBlock method let's you execute blocks of code as actions.
SKAction *wait = [SKAction waitForDuration:2.0];
SKAction *reenableJoystick = [SKAction runBlock:^{
joystick.userInteractionEnabled = TRUE;
}];
SKAction *sequence = [SKAction sequence:#[wait, reenableJoystick]];
[self runAction:sequence];