i'm building a game for which i the gravity as:
self.physicsworld.gravity = CGVectorMake(gravityX , gravity-Y);
i want to change the forces acting on the sprite node with time.
since i'm a newbie, i'm not able to code a loop(s) so that the gravity changes over time thereby changing the difficulty of the game. if i want the float variable gravityX to increment by 0.5 every 5 seconds what should be the code implemented.
the main problem i'm facing with using the FOR LOOP here is in the CONDITION; I don't know how to make the computer understand actual time since the game started.
This can be done using the SKAction class. Add the following method to your SKScene subclass, and call it when you want gravity to start increasing. I've included both the Objective-C and the Swift version.
Objective-C:
- (void)startGravityIncrease {
SKAction *blockAction = [SKAction runBlock:^{
CGVector gravity = self.physicsWorld.gravity;
gravity.dx += 0.5;
self.physicsWorld.gravity = gravity;
}];
SKAction *waitAction = [SKAction waitForDuration:5];
SKAction *sequenceAction = [SKAction sequence:#[waitAction, blockAction]];
SKAction *repeatAction = [SKAction repeatActionForever:sequenceAction];
[self runAction:repeatAction];
}
Swift:
func startGravityIncrease() {
let blockAction = SKAction.runBlock { () -> Void in
self.physicsWorld.gravity.dx += 0.5
}
let waitAction = SKAction.waitForDuration(5)
let sequenceAction = SKAction.sequence([waitAction, blockAction])
let repeatAction = SKAction.repeatActionForever(sequenceAction)
self.runAction(repeatAction)
}
Related
I think the answer is no, but here’s what I’m doing and I’ll then show what I wish I could do. Overall, what I’m going for is a sequence of dampening rotations that total 24 seconds total.
#define START_ANGLE M_PI/4.0
#define SPRING_TENSION 0.9
#define START_DURATION 1.3 // Hack!
NSMutableArray *animationA = [NSMutableArray array];
double a = START_ANGLE;
double d = START_DURATION;
while (a * a > 0.01) {
// Bounce to the other side.
SKAction *a1 = [SKAction rotateToAngle:a duration:d];
a1.timingMode = SKActionTimingEaseIn;
[animationA addObject:a1];
// Return to rest
SKAction *a2 = [SKAction rotateToAngle:0 duration:d];
a2.timingMode = SKActionTimingEaseOut;
[animationA addObject:a2];
// Reduce the height of the bounce by the spring's tension
a *= -SPRING_TENSION;
d *= SPRING_TENSION;
}
SKAction *seqA = [SKAction sequence:animationA];
[mySprite runAction:seqA];
What I want is for the total sequence duration to be 24 seconds and for the intervening actions to be inferred from that. Since the API doesn’t allow that, in order to arrive at this solution I had to precompute how many iterations of the loop there’d be and track the angle’s attentuation in order to arrive at START_DURATION.
It’s hacky and inflexible -- bad enough that I had to unroll the loop to figure out each component duration, but what if I want to change the SPRING_TENSION later but maintain the total duration? I’d have to unroll the loop again and recompute START_DURATION.
I wish I could do something more flexible like this:
#define START_ANGLE M_PI/4.0
#define SPRING_TENSION 0.9
#define TOTAL_DURATION 24.0
NSMutableArray *animationA = [NSMutableArray array];
double a = START_ANGLE;
while (a * a > 0.01) {
// Bounce to the other side.
SKAction *a1 = [SKAction rotateToAngle:a]; // Duration will be computed from total duration
a1.timingMode = SKActionTimingEaseIn;
[animationA addObject:a1];
// Return to rest
SKAction *a2 = [SKAction rotateToAngle:0]; // Duration will be computed from total duration
a2.timingMode = SKActionTimingEaseOut;
[animationA addObject:a2];
// Reduce the height of the bounce by the spring's tension
a *= -SPRING_TENSION;
d *= SPRING_TENSION;
}
SKAction *seqA = [SKAction sequence:animationA duration:TOTAL_DURATION]; // This computes all undefined component durations
[mySprite runAction:seqA];
That way, I get what I want: I get an animation sequence of a chosen duration and let the framework figure out the intervening rotations. This is akin to how keyframes are inferred by CAKeyframeAnimation. Is there a clever way to do this with SpriteKit that I’ve overlooked?
I am designing a game that changes scenes as the user is playing. The scenes change based on a NSTimer. I can ge everything to change accordingly in regards to graphics. Can even get background music to change.
Where I seem to be hitting a roadblock is changing the score sound and the sound for when the player collides with an object and dies.
As it it's right now I can cancel the sound for scene one but cannot get another one to play.
* on phone don't know how to hand code a code block on here*
If([_timer isValid]) {
//plays correct audio and does not continue when scene changes
}
Else if ([_timer == nil ]){
//suppose to play other sound but does nothing.
}
I would really prefer to call the different sounds based on if a method is called to change something along the lines of
Else if ([ _method1 == TRUE]) {
//play sound
}
Any advice in the right direction appreciated.
EDIT
- (void)didBeginContact:(SKPhysicsContact *)contact {
if( _moving.speed > 0 ) {
if( ( contact.bodyA.categoryBitMask & scoreCategory ) == scoreCategory || ( contact.bodyB.categoryBitMask & scoreCategory ) == scoreCategory ) {
// has contact with score entity
_score++;
_scoreLabelNode.text = [NSString stringWithFormat:#"%d", _score];
//Pick Up Egg
//Sound for Score
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"Point1" waitForCompletion: YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
I have been trying to structure an IF statement based on the background image because that changes with the sounds.
EDIT
_scoreSound = [SKAudioNode node];
SKAudioNode* teslaPoint = [SKAudioNode nodeWithFileNamed:#"Point1"];
[_scoreSound addChild:teslaPoint];
EDIT
THIS IS ADDED IN initwithSize:
_scoreSound = [SKNode node];
[self addChild:_scoreSound];
Then its called in the contacts
EDIT
//Sound for Score
if([_timer isValid]){
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"Point1" waitForCompletion:YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
}
if ([_bgImage isEqual: #"bgimage2.png"]){
SKAction* teslaPoint = [SKAction playSoundFileNamed:#"grassypoint" waitForCompletion:YES];
[self runAction:teslaPoint withKey:#"teslaPoint"];
}
I'm trying to make a cocos2D game in which every time the player touches the screen, the background scrolls to the left thus simulating moving forwards.
The "background" consists of 2 very long rectangular panels called pane1 and pane2 linked together in a chain. When the screen is touched, I use CCActionMoveTo to move both panes to the left, and when one pane is completely off the screen, I move it back around to the other side to create an infinite loop.
The problem is the background scrolling animation takes .2 seconds, and if the player just mashes the screen a lot, it messes up everything. And, sometimes these two panes experience different amounts of lag so that they desync.
How do I set a delay on a function so that it can only be called once per designated time period? In other words, I want to add a "cooldown" to the function that handles player touch.
This function is called every time the scene is touched:
- (void)playerMove {
CCActionMoveTo * actionMove1 = [CCActionEaseOut actionWithAction:
[CCActionMoveTo actionWithDuration:.2
position:ccp(pane1.position.x - 150, 0)]
rate: 1.5];
CCActionMoveTo * actionMove2 = [CCActionEaseOut actionWithAction:
[CCActionMoveTo actionWithDuration:.2
position:ccp(pane2.position.x - 150, 0)]
rate: 1.5];
CCActionCallFunc * actionCallGenerateTerrain = [CCActionCallFunc actionWithTarget: self selector:#selector(generateTerrain)];
counter++;
if(pane1InUse){
[pane1 runAction: [CCActionSequence actionWithArray:#[actionMove1, actionCallGenerateTerrain]]];
[pane2 runAction: actionMove2];
}
else
{
[pane1 runAction: actionMove1];
[pane2 runAction: [CCActionSequence actionWithArray:#[actionMove2, actionCallGenerateTerrain]]];
}
}
-(void) generateTerrain {
if (counter%8 == 0){
pane1InUse ^= YES;
CCLOG(#"%#", pane1InUse ? #"YES" : #"NO");
if (pane1InUse){
CCLOG(#"Generating Terrain 2 ...");
pane2.position = ccp(pane1.position.x+pane1.boundingBox.size.width, 0);
}
else{
CCLOG(#"Generating Terrain 1 ...");
pane1.position = ccp(pane2.position.x+pane2.boundingBox.size.width, 0);
}
}
}
Also you may have noticed I'm using this weird block of code because ideally I would like actionMove1 and actionMove2 to be executed simultaneously, and once they are both done, then execute actionCallGenerateTerrain, but I don't know how to implement that:
if(pane1InUse){
[pane1 runAction: [CCActionSequence actionWithArray:#[actionMove1, actionCallGenerateTerrain]]];
[pane2 runAction: actionMove2];
}
else
{
[pane1 runAction: actionMove1];
[pane2 runAction: [CCActionSequence actionWithArray:#[actionMove2, actionCallGenerateTerrain]]];
}
}
You probably want to put pane1 and pane2 in a ccNode , call it combinedPanes (this will make pane1 and pane2 always move in sync). Then perform a single action on combinedPane. Also add a boolean state property, call it moveEnabled;
id movePane = [CCActionMoveTo ... ]; // whatever you have for pane1 or pane2
id moveComplete = [CCActionBlock actionWithBlock:^{
[self generateTerrain];
self.moveEnabled = YES;
}];
self.moveEnabled = NO;
[movePane runAction:[CCActionSequence actions:movePane,moveComplete,nil]];
and use moveEnabled to allow/deny the touch processing that detected a touch and triggered this move code. During the move, this will drop touches and effectively block your hysterical user tapping like nuts.
-(void) playerMove {
if (self.moveEnabled){
//
// the rest of your logic
// ...
}
}
and in init (or if you detect this condition already, place it there).
self.moveEnabled = YES;
One simple way to prevent a function being called twice within a time period is to wrap it in an if statement like:
if ([[NSDate date] timeIntervalSinceDate:lastUpdated] > minDelay)
{
// call function here
lastUpdated = [NSDate date];
}
where lastUpdated is initialized using [NSDate date] and minDelay is the required minimum delay in seconds.
I have a method that creates a random amount of SKSpriteNodes that are supposed to fall from the roof, sit on the ground a little while, then fade out. They need an SKPhysicsBody so they'll fall and bounce properly, but when I create a lot, it's using a lot of CPU, so I was trying to remove their physicsbody when they'd been sitting on the ground a little while.
I can't figure out how to use a runBlock to just say "do (blah) to the object calling this runBlock", is there a way?
for (int i=0; i < howManyDollars; i++) {
SKSpriteNode *bills = [SKSpriteNode spriteNodeWithImageNamed:#"bills.png"];
int startX = arc4random() % (int)self.size.width/3;
int startY = self.size.height;
bills.position = CGPointMake(startX, startY);
bills.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(bills.size.width/2, bills.size.height/2)];
bills.physicsBody.affectedByGravity = YES;
[self addChild:bills];
SKAction *wait1 = [SKAction waitForDuration:1.5];
SKAction *wait2 = [SKAction waitForDuration:2];
SKAction *removeDynamics = [SKAction runBlock:^(void) {
// this is the spot I am confused at.
//bills.physicsBody = nil;
}];
SKAction *fade = [SKAction fadeOutWithDuration:15];
SKAction *remove = [SKAction removeFromParent];
[bills runAction:[SKAction sequence:#[wait1, removeDynamics, wait2, fade, remove]]];
}
You have it already:
SKAction *removeDynamics = [SKAction runBlock:^(void) {
bills.physicsBody = nil;
NSLog(#"bills is: %# (%p)", bills, bills);
}];
The cool thing about blocks is that they retain (copy) the objects in local scope. So if you create a block like the above and dereference the bills object, then for each block it will use the corresponding bills object that runs the action.
I've added the log statement so you can see for yourself that each block when executed is referencing a different object even though the variable name is the same and even though one would think based on the traditional sequential programming model that each bills object would have long been gone or referencing only the last object created.
I was wondering how to call a method after a CCAction is finished (I would also like to call it every 2.5 seconds after it is first called). I have a sprite that goes to a random position, and I want it to run this method (a shooting bullet one) after it is done moving to its random position. So far the method is called when it is still moving. Can anyone help?
Here is the enemy create method:
(void)enemy1{
gjk= arc4random()%6;
enemy1 = [CCSprite spriteWithFile:#"enemy1.png"];
int d = arc4random()%480+480;
int o = arc4random()%320+320;
x = arc4random()%480;
if( x <= 480 && x>= 460){
x=x-100;
}
if(x <= 100){
x = x+50;
}
y = arc4random()%320;
if(y <=320 && y >= 290){
y = y-100;
}
if(y < 100){
y = y + 100;
}
enemy1.position = ccp(o,d);
[enemy1 runAction:[CCMoveTo actionWithDuration:3 position: ccp(x,y)]];
CCRotateBy *rotation = [CCRotateBy actionWithDuration:20 angle:1080];
CCRepeatForever * repeatforever = [CCRepeatForever actionWithAction:rotation];
[enemy1 runAction:repeatforever];
[self addChild:enemy1];
[enemy addObject :enemy1];
}
And the method for shooting a projectile:
(void)projectileShooting:(ccTime)dt {
projcount++;
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
}
}
The shooting method is called in the init method by the code, so it is called every 2.5 seconds.
[self schedule:#selector(projectileShooting:) interval:2.5];
I know I tried to make the shooting happen by making it so that is shoots when the y position is < 320, but it is still moving when it passes the position of 320.
You can make a sequence of actions and at the end of sequence you can give a callback function which will be executed at the end. Something like this
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(methodToRunAfterAction)],
nil]];
Adding on to Tayyab's answer, you can simply start the actual scheduling of your projectile firing within the startShooting method (or whatever you want to call it) which is fired at the end of your move action:
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(startShooting)],
nil]];
where startShooting is defined as follows:
- (void) startShooting
{
// start scheduling your projectile to fire every 2.5 seconds
[self schedule:#selector(projectileShooting:) interval:2.5];
}