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;
}
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");
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];
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.
What would be the best way to do this? I see the CCEaseSineInOut action but it doesn't look like that could be used to do this.
I need to move from one side of the screen to the other. The sprite should move in a sine-wave pattern across the screen.
I always like to have complete control over CCNode motion. I only use CCActions to do very basic things. While your case sounds simple enough to possibly do with CCActions, I will show you how to move a CCNode according to any function over time. You can also change scale, color, opacity, rotation, and even anchor point with the same technique.
#interface SomeLayer : CCLayer
{
CCNode *nodeToMove;
float t; // time elapsed
}
#end
#implementation SomeLayer
// Assumes nodeToMove has been created somewhere else
-(void)startAction
{
t = 0;
// updateNodeProperties: gets called at the framerate
// The exact time between calls is passed to the selector
[self schedule:#selector(updateNodeProperties:)];
}
-(void)updateNodeProperties:(ccTime)dt
{
t += dt;
// Option 1: Update properties "differentially"
CGPoint velocity = ccp( Vx(t), Vy(t) ); // You have to provide Vx(t), and Vy(t)
nodeToMove.position = ccpAdd(nodeToMove.position, ccpMult(velocity, dt));
nodeToMove.rotation = ...
nodeToMove.scale = ...
...
// Option 2: Update properties non-differentially
nodeToMove.position = ccp( x(t), y(t) ); // You have to provide x(t) and y(t)
nodeToMove.rotation = ...
nodeToMove.scale = ...
...
// In either case, you may want to stop the action if some condition is met
// i.e.)
if(nodeToMove.position.x > [[CCDirector sharedDirector] winSize].width){
[self unschedule:#selector(updateNodeProperties:)];
// Perhaps you also want to call some other method now
[self callToSomeMethod];
}
}
#end
For your specific problem, you could use Option 2 with x(t) = k * t + c, and y(t) = A * sin(w * t) + d.
Math note #1: x(t) and y(t) are called position parameterizations. Vx(t) and Vy(t) are velocity parameterizations.
Math note #2: If you have studied calculus, it will be readily apparent that Option 2 prevents accumulation of positional errors over time (especially for low framerates). When possible, use Option 2. However, it is often easier to use Option 1 when accuracy is not a concern or when user input is actively changing the parameterizations.
There are many advantages to using CCActions. They handle calling other functions at specific times for you. They are kept track of so that you can easily pause them and restart them, or count them.
But when you really need to manage nodes generally, this is the way to do it. For complex or intricate formulas for position, for example, it is much easier to change the parameterizations than to figure out how to implement that parameterization in CCActions.
I already know how to check for collisions with the doesintersectNode-method in Cocos3d, but in my case, I want to avoid obstacles, before I get in touch with them. In example, I want to stop in front of a wall, before I crash against it.
For this reasons I wrote the methods getNodeAtLocation in my subclass of CC3Scene and -(BOOL)shouldMoveDirectionallywithDistance:(float)distance in the class of my person, which should move around.
Unfortunately, I have some problems with the algorithm of the last method. Here the code:
-(BOOL)shouldMoveDirectionallywithDistance:(float)distance
{
BOOL shouldMove = NO;
float x = self.person.globalLocation.x;
float z = self.person.globalLocation.z;
int times = 5;
for (int i = 0; i < times; i++) {
CC3Vector newPos = cc3v(x, 0.5, z);
CC3PODResourceNode *obstacle = (CC3PODResourceNode *)[myScene getNodeAtLocation:newPos];
if (obstacle) {
return NO;
}else{
shouldMove = YES;
}
x += self.person.globalForwardDirection.x * distance / times;
z += self.person.globalForwardDirection.z * distance / times;
}
return shouldMove;
}
In this method, I get the important parts of the coordinates (for my proposal just the x- and z-values) and increase them by a fifth of the forwardDirection. I decided, that this makes sense, when the obstacle is i.e. a thin wall. But for reasons I don't know, this method doesn't work, and the person is able to walk through this wall. So where is the problem in my code?
I strongly believe, that the getNodeAtLocation-method works correctly, as I tested it multiple times, but maybe there are my mistakes:
-(CC3Node *)getNodeAtLocation:(CC3Vector )position
{
CC3Node *node = nil;
for (CC3PODResourceNode *aNode in self.children) {
if ([aNode isKindOfClass:[CC3PODResourceNode class]] ) {
for (CC3PODResourceNode *child in aNode.children) {
if (CC3BoundingBoxContainsLocation(child.globalBoundingBox, position)) {
node = aNode;
}
}
}
}
return node;
}
To conclude, in my view the mistake is in the -(BOOL)shouldMoveDirectionallywithDistance:(float)distance-method. I suppose, that something is wrong with the increase of the x- and z-values, but I couldn't figure out, what exactly is incorrect.
If you are still interested in finding an answer to this problem. I may be able to provide you with an alternative solution. I am about to release free source for a 3d collision engine I ported to cocos3d, and it will give you more flexibility than simply stoping an object in front of another.
I am currently polishing out the code a little for easy use, but if you are interested you can email me to: waywardson07#aol.com
you could also get a little preview of the engine in action here: http://www.youtube.com/watch?v=QpYZlF7EktU
Note: the video is a little dated.
After several attempts, it appears to be easier as I thought:
Just using the doesIntersectNode method has the right effect for me.
But please notice, that this is not a real solution to the problem of stopping in front of obstacles.