Alternative spawning method to a timer - ios

I am making a reaction game with a parallax scrolling ground. My enemy spawning timer is located in the didMoveToView and now comes my problem. The game speeds up while playing, but my spawning method stays the same, so its kind of boring if the enemys are spawned with the same frequency.
Here is the code:
//inside the didMoveToView
SKAction *addEnemRand = [SKAction performSelector:#selector(addRandomEnemy) onTarget:self];
SKAction *addEnemRandWait = [SKAction waitForDuration:2];
SKAction *addEnemRandAll = [SKAction sequence:#[addEnemRandWait, addEnemRand]];
SKAction *addEnemRandAllForever = [SKAction repeatActionForever:addEnemRandAll];
[self runAction:addEnemRandAllForever];
Does someone know a way to spawn the enemys in a proper manner ?
Here is the code for moving the ground (there are two). Maybe its possible to relate the spawning speed with the position of the ground ?:
-(void)addGround:(SKSpriteNode *)sprite withSpeed:(int)speed {
if (ground.position.x+ground.frame.size.width/2 < 0) {
ground.position = CGPointMake(self.size.width+ground.size.width/2, ground.size.height/2+1);
}
// movement
ground.position = CGPointMake(ground.position.x-speed, ground.position.y);
}
Thanks for the help ! (code in objective c please)
EDIT: So it seems GamePlayKit can help me but I have no idea how to implement the spawning method with it. I read some tutorials in the internet but it didn't helped me. Can someone help me ?

I think its worth looking into the randomizing that GamePlayKit provides to do this.
Take a look at this tutorial where you implement a "basic rule system in combination with another random distribution to handle the respawning behaviour of enemies"

You can either manage the spawn time using the ground position, or using the time, since the level has been started. That's my code in update method for simular task, using the time passed.
properties in initWithSize method:
self.lastUpdateTimeInterval = 0;
self.timeSinceEnemyAdded = 0;
self.addEnemyTimeInterval = 1.5;
self.totalGameTime = 0;
Update method:
-(void)update:(NSTimeInterval)currentTime {
if (self.lastUpdateTimeInterval ) {
self.timeSinceEnemyAdded += currentTime - self.lastUpdateTimeInterval;
self.totalGameTime += currentTime - self.lastUpdateTimeInterval;
}
if (self.timeSinceEnemyAdded > self.addEnemyTimeInterval && !self.gameOver) {
[self addEnemy];
self.timeSinceEnemyAdded = 0;
}
self.lastUpdateTimeInterval = currentTime;
if (self.totalGameTime > 480) {
self.addEnemyTimeInterval = 0.5;
self.minSpeed = -160;
} else if (self.totalGameTime > 240) {
self.addEnemyTimeInterval = 0.65;
self.minSpeed = -150;
} else {
//Something else
}
}
You can just manage the ground lenght property, if you want

Related

How to have my character "ride" along with moving platforms?

Im experiencing a problem with my Hero Character, when he lands on a moving platform, rather then moving along with it, he literally stands on the same X position until the platform moves offscreen. I searched many answers with no avail.
Here is my methods for my hero character and the moving platforms.
-(void)HeroAdd
{
_Hero = [SKSpriteNode spriteNodeWithImageNamed:#"Hero-1"];
_Hero.name = #"Daniel";
_Hero.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_Hero.size];
_Hero.physicsBody.categoryBitMask = fPlayerCategory;
_Hero.physicsBody.contactTestBitMask = fPlatformCategory | fEnemyCategory;
_Hero.physicsBody.usesPreciseCollisionDetection = YES;
_Hero.physicsBody.affectedByGravity = YES;
_Hero.physicsBody.dynamic = YES;
_Hero.physicsBody.friction = .9;
_Hero.physicsBody.restitution = 0;
_Hero.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
_Hero.position = CGPointMake(_Hero.position.x - 252, _Hero.position.y + 50);
if (self.size.width == 480) {
_Hero.position = CGPointMake(_Hero.position.x + 44, _Hero.position.y);
}
[self addChild:_Hero];
}
My moving platform code
-(void)createPlatform {
SKTexture *objectTexture;
switch (arc4random_uniform(2)) {
case (0):
objectTexture = [SKTexture textureWithImageNamed:#"shortPlatform"];
break;
case (1):
objectTexture = [SKTexture textureWithImageNamed:#"highPlatform"];
default:
break;
}
SKSpriteNode *variaPlatform = [SKSpriteNode spriteNodeWithTexture:objectTexture];
variaPlatform.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
variaPlatform.position = CGPointMake(variaPlatform.position.x + 500, variaPlatform.position.y - 140);
variaPlatform.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:variaPlatform.size];
variaPlatform.physicsBody.usesPreciseCollisionDetection = YES;
variaPlatform.physicsBody.categoryBitMask = fPlatformCategory;
variaPlatform.physicsBody.contactTestBitMask = fPlatformCategory |fPlayerCategory | fEnemyCategory;
variaPlatform.physicsBody.dynamic = NO;
variaPlatform.physicsBody.affectedByGravity = NO;
SKAction *moveLeft = [SKAction moveTo:CGPointMake(180, variaPlatform.position.y) duration:3];
SKAction *moveDown = [SKAction moveTo:CGPointMake(180, -700) duration:4];
SKAction *removeFromParent = [SKAction removeFromParent];
SKAction *AllThree = [SKAction sequence:#[moveLeft, moveDown, removeFromParent]];
[self addChild:variaPlatform];
[variaPlatform runAction:AllThree];
}
Any type of information would be truly appreciated.
I ran into the same issue when I added conveyer belts into a game. Sprite Kit does not drag objects no matter how much resistance is added. You currently have 2 options.
Option 1 - Add a ledge at each end of your platform. This is the easiest to implement but is less graceful and still allows the player to slide off if he lands on the ledge.
Option 2 -
Step 1: Add code which makes the player move in sync with the horizontal moving platform. You can either use something like self.physicsBody.velocity = CGVectorMake(-50, self.physicsBody.velocity.dy); or use self.position = CGPointMake(self.position.x+10, self.position.y);. You will have to play around with the x values to sync them to the platform's speed.
Step 2: Activate the above code whenever the player makes contact with the platform and deactivate when contact is lost.
Step 3: In case the platform switches directions, set up left and right limits which notify you via contact when the platform switches direction. Depending on the platform's direction you apply either +x or -x movement values to your player.
I know this option sounds complicated but it is not. You just need to go step by step.
* EDIT to provide sample code *
This is the logic I have behind the horizontal moving platforms:
PLATFORMS
If you have more than 1 horizontal moving platform, you will need to store them in an array in the GameScene (my Levels). I have created my own class for them but you do not have to do this.
You will have to set left and right limits (invisible SKNodes with contacts) to set a BOOL property for the platform which tells the player class which way to push as each platform will probably not be the same length. This is why you need to keep a reference to each platform (hence the array).
PLAYER
When the player jumps on the platform, set a Player class property BOOL to TRUE which activates the constant left or right push depending on which way the platform is currently moving. On the flip side, losing the contact cancels the push.
// This code in my "Levels class" which is the default GameScene class.
- (void)didBeginContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryPlatformHorizontal | CategoryPlayer))
{
[_player setPlatformHorizontalContact:true];
for(Platform *platformObject in platformArray)
{
if(([platformObject.name isEqualToString:contact.bodyB.node.name]) || ([platformObject.name isEqualToString:contact.bodyA.node.name]))
{
_player.currentPlatform = platformObject;
}
}
}
}
- (void)didEndContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryPlatformHorizontal | CategoryPlayer))
{
[_player setPlatformHorizontalContact:false];
}
}
// This code is in Player.h
#property (strong) Platform *currentPlatform;
#property BOOL platformHorizontalContact;
// This code is in Player.m
- (void)update:(NSTimeInterval)currentTime
{
if(self.platformHorizontalContact == true)
{
if(self.currentPlatform.movingLeft == true)
{
self.physicsBody.velocity = CGVectorMake(-75, self.physicsBody.velocity.dy); // set your own value depending on your platform speed
} else {
self.physicsBody.velocity = CGVectorMake(75, self.physicsBody.velocity.dy); // set your own value depending on your platform speed
}
}
}

Sprite Kit - Set position not doing anything

I am trying to reset the ball after it hit the goal to the middle of the screen, but it seems I can no longer set it's position anymore after i declare it's position during declaration.
This is how i define my ball:
-(void)setUpBall {
self.ball = [SKSpriteNode spriteNodeWithImageNamed:#"Ball"];
self.ball.name = bName;
self.ball.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
self.ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.ball.frame.size.width/2];
self.ball.physicsBody.restitution = 1.0;
self.ball.physicsBody.angularDamping = 0.0;
self.ball.physicsBody.linearDamping = 0.0;
self.ball.physicsBody.friction = 0.0;
self.ball.physicsBody.dynamic = true;
self.ball.physicsBody.allowsRotation = false;
self.ball.physicsBody.usesPreciseCollisionDetection = true;
self.ball.physicsBody.categoryBitMask = bCategory;
self.ball.physicsBody.collisionBitMask = pCategory | borderCategory;
self.ball.physicsBody.contactTestBitMask = borderCategory;
[self addChild:self.ball];
[self.ball.physicsBody applyImpulse:CGVectorMake(15, 10)];
}
This is my code to reset the ball to middle of the screen
-(void)reset {
NSLog(#"reset method ran");
[self.ball setPosition: CGPointMake(self.size.width/2, self.size.height/2)];
}
It seems that the reset method is definitely ran since there was output from the NSLog(#"reset method ran"); but the ball always continue moving like the setPosition never work at all
Doing things like setting position might not work in Sprite Kit if it's a result of a call from testing collisions or other things. You may need to add a BOOL property to your ball to flag it if you need to reset it, and then call the reset when you are updating all your sprites.
I have an asteroids type game where I was trying to replace one large asteroid with 3 smaller rocks when the collision detection against a player's missile fired – it did not work because the position was being set during the collision check part of the Sprite Kit update loop. When I added the new rocks during the standard part of the update loop, it worked fine.
Yopu could simply set up the ball again.
-(void)reset
{
NSLog(#"reset method ran");
[self.ball removeFromParent];
[self setUpBall];
}
Could you not replace your reset method with:
-(void)reset {
NSLog(#"reset method ran");
self.ball.position = CGPointMake(self.size.width/2, self.size.height/2)];
}
I tend to use this and not the setPosition method to relocate a sprite.

Sprite Kit device lags on first collision

I have a problem in which my Sprite Kit game lags only when the first collision occurs between the main character and any other sprite. After the first collision occurs, every other collision is smooth and runs at 60.0 fps. The odd thing is that when the first collision, the fps only drops to 49-51, but the actual game freezes for a half second. This is also not an issue of setup lag as this occurs no matter how long I wait to start. Does anyone know what the issue is?
-(void)checkForCollisions {
if (_invincible) return;
[self enumerateChildNodesWithName:#"enemy"
usingBlock:^(SKNode *node, BOOL *stop){
SKSpriteNode *enemy = (SKSpriteNode *)node;
CGRect smallerFrame = CGRectInset(enemy.frame, 22, 22);
if (CGRectIntersectsRect(smallerFrame, _sloth.frame)) {
[enemy removeFromParent];
[self runAction:[SKAction playSoundFileNamed:#"eagle.wav" waitForCompletion:NO]];
NSString *burstPath =
[[NSBundle mainBundle] pathForResource:#"ExplosionParticle" ofType:#"sks"];
SKEmitterNode *burstEmitter =
[NSKeyedUnarchiver unarchiveObjectWithFile:burstPath];
burstEmitter.position = _sloth.position;
[self addChild:burstEmitter];
[self changeInLives:-1];
_invincible = YES;
float blinkTimes = 10;
float blinkDuration = 3.0;
SKAction *blinkAction =
[SKAction customActionWithDuration:blinkDuration
actionBlock:
^(SKNode *node, CGFloat elapsedTime) {
float slice = blinkDuration / blinkTimes;
float remainder = fmodf(elapsedTime, slice);
node.hidden = remainder > slice / 2;
}];
SKAction *sequence = [SKAction sequence:#[blinkAction, [SKAction runBlock:^{
_sloth.hidden = NO;
_invincible = NO;
}]]];
[_sloth runAction:sequence];
}
}];
}
The lag is not linked to the emitter node as the game still lags whenever it is commented out.
Let me know if you need any additional information. Thanks in advance!
Here is a link for my Instruments's trace file: https://www.dropbox.com/sh/xvd1xdti37d76au/ySL4UaHuOS
If you look at the trace file, note that the collision occurs when the Time Profiler reaches 118%.
As Cocos mentioned, it is probably the initial loading of your sound file which is causing the one time delay.
In your implementation add the sound action:
SKAction *eagleSound;
In your init method add this:
eagleSound = [SKAction playSoundFileNamed:#"eagle.wav" waitForCompletion:NO];
Then whenever you need to play the sound, use this:
[self runAction:eagleSound];

sprite kit removing specific nodes

I am trying to create a game where a character runs forever to the right (the game is landscape). On the ground there are spikes that the character can jump over. Currently, I am creating a new (and somewhat random) set of spikes in almost a checkpoint-like style where once the character reaches a certain distance, the next set of randomly organized spikes are created and the checkpoint distance gets pushed back and so on. Along with the spikes, I have a separate but very similar checkpoint-like system that is used to create the tiles that make up the ground.
This is my code for that portion, 'endlessX' and 'endlessGroundX' are the checkpoint value:
- (void) didSimulatePhysics {
if (player.position.x > endlessX) {
int random = player.position.x + self.frame.size.width;
[self createSpike:random];
endlessX += self.frame.size.width/2.2 + arc4random_uniform(30);
}
if (player.position.x + self.frame.size.width > endlessGroundX) {
[self createGround:endlessGroundX];
endlessGroundX += tile1.frame.size.width;
}
[self centerOnNode: player];
}
The parameter of the createSpike and createGround method is just the 'x' value for the SKSpriteNodes.
I am currently having it as the character itself is the one moving and the spikes and tiles are stationary. This is how I am creating the character:
-(void) createPlayer {
player = [SKSpriteNode spriteNodeWithImageNamed:#"base"];
player.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
player.name = #"player";
player.zPosition = 60;
player.xScale = 0.8;
player.yScale = 0.8;
player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player.frame.size.height/2];
player.physicsBody.mass = 1;
player.physicsBody.linearDamping = 0.0;
player.physicsBody.angularDamping = 0.0;
player.physicsBody.friction = 0.0;
player.physicsBody.restitution = 0.0;
player.physicsBody.allowsRotation = NO;
player.physicsBody.dynamic = YES;
player.physicsBody.velocity = CGVectorMake(400, 0);
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.collisionBitMask = wallCategory;
player.physicsBody.contactTestBitMask = wallCategory | spikeCategory;
[myWorld addChild:player];
}
With that, the character will never lose any of its kinetic energy to friction or any other force like that. Then, I am using the 'center on node' method that apple used in their adventure game so that the character will always remain in the same x-position on the screen:
- (void) centerOnNode: (SKSpriteNode *) node {
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(self.frame.size.width/5 + node.parent.position.x - cameraPositionInScene.x, node.parent.position.y);
}
I am calling this method in 'didSimulatePhysics.'
When I run this for some time, the programs gets slower and slower. I am guessing that that is due to the fact that I am never removing these nodes and they are always being added. However, to fix this problem, I tried doing something like this:
-(void)update:(CFTimeInterval)currentTime {
[self enumerateChildNodesWithName:#"*" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x + 50 < player.position.x) {
[node removeFromParent];
}
}];
}
(the +50 would be just to make sure that the node is off the screen before removing it)
However, when I did this, instead of removing the specific node that satisfies the 'if' statement, the program removes all of the sprite nodes. Is there a different method or something that I am missing to fix this? Or are there any other simple ways to remove the specific nodes?
Lacking quite a few details, like how you are animating the spikes for instance, makes it a bit hard to be too specific. Nevertheless, from what you are sharing I guess you might be looking for something a little like this:
SKAction *moveSpikeAction = [SKAction moveToX:-50 duration:5];
SKAction *removeSpikeAction = [SKAction removeFromParent];
SKAction *spikeSequence = [SKAction sequence:#[moveSpikeAction, removeSpikeAction]];
[yourSpikeSpriteNode runAction:spikeSequence];
The idea simply being that when the spike has animated to the off screen position you use the removeFromParent action to clear it.

My spritekit game crashes when I am reloading the game?

In my spritekit game I am creating Stone randomly through update method. Here is my code for random creation of stone
//Create random island
-(void)createRandomStone:(NSMutableArray *)imageArray
{
int getRandomNumberCoordinate = [self getRandomNumberBetween:(int)0 to:(int)768];
int getRandomStoneImage = [self getRandomNumberBetween:0 to:(int)([imageArray count] - 1)];
NSLog(#"Stone Image name = %d", getRandomStoneImage);
SKSpriteNode *createStone = [SKSpriteNode spriteNodeWithTexture:[imageArray objectAtIndex:getRandomStoneImage]];
if((getRandomNumberCoordinate + createStone.size.height / 2) > 768 )
createIsland.position = CGPointMake(_myScreenSize.width + createStone.size.width, 768 - createStone.size.height / 2);
else if((getRandomNumberCoordinate - createStone.size.height / 2) < 0 )
createStone.position = CGPointMake(_myScreenSize.width + createStone.size.width, 0 + createIsland.size.height / 2);
else
createStone.position = CGPointMake(_myScreenSize.width + createStone.size.width, getRandomNumberCoordinate);
createStone.name = #"Stone";
createStone.zPosition = 3;
[self addChild:createStone];
//Apply physics on the Stone
createStone.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: CGSizeMake(createStone.size.width - createStone.size.width / 4, createStone.size.height - createStone.size.height / 6)];
createStone.physicsBody.categoryBitMask = CollisionTypeStone;
createStone.physicsBody.contactTestBitMask = CollisionTypeMan;
createStone.physicsBody.usesPreciseCollisionDetection = YES;
createStone.physicsBody.collisionBitMask = 0;
}
Stones are moving from the coordinate 1024 to 0 and if any stone cross the 0 coordinate the the stone will remove by using the code
-(void)updateStonePosition:(NSString *)whichDirection andMoveAmount:(float)speed
{
for (SKNode* node in self.children)
{
if([node.name isEqualToString:#"Stone"])
{
node.position = CGPointMake(node.position.x - speed, node.position.y);
if(node.position.x < -node.frame.size.width / 2)
[node removeFromParent];
}
}
}
Both of the above two methods are calling from the update method. And if the man hit by any 5 stone then the game will again reload. The reloading code is:
[self.view presentScene:[[MyScene alloc] initWithSize:self.size] transition:[SKTransition doorsCloseHorizontalWithDuration:0.5f]];
the game is reloaded but after few second an error message shows EXC_BAD_ACCESS(code=2, address = 0x0) on the line
SKSpriteNode *createStone = [SKSpriteNode spriteNodeWithTexture:[imageArray objectAtIndex:getRandomStoneImage]];
Please help. Thanks in advance.
To me, and with the code/information you are providing, it seems that when your scene is reloaded the array you are passing to "createRandomStone:" is nil.
Try checking for nil before calling the method:
if (imageArray != nil) {
// call createRandomStone
} else {
NSLog(#"imageArray is nil")
}
see if that helps identifying the root cause of the error.
I was also trying to find out some information how to reset/restart the SpriteKit game but I wasn't successful. I didn't spend enough time to find out why this is happening in my case, probably should try to do some cleaning before the transition happens like removing all actions and maybe all nodes...but I found a workaround for myself..
So it seems the crashing is happening when you call/do a transition to the same scene where you are at the moment so what I did is that:
1) I make a transition to my ResetScene which is just an empty scene
- with some parameters so I could identify a previous scene
2) In ResetScene in DidMoveToView I make transition back to my previous scene
This is how I fixed this problem but I don't think it is an ideal solution. Note, you can't really spot a difference that there are 2 transitions instead of one.
Let's see if somebody would join this thread with more wisdom :)
If your imageArray is not nil, and you've got more than one item, and it's still crashing, then you could be experiencing one of SpriteKit's bugs with the physics bodies (or skshapenodes elsewhere).
See this thread..: https://stackoverflow.com/a/25007468/557362
I've had this sort of problem myself, and I've got code in my common scene base class to clean up scenes, but I don't use physics bodies - which could be your problem (see link).
-(void)cleanUp
{
[self cleanUpChildrenAndRemove:self];
}
- (void)cleanUpChildrenAndRemove:(SKNode*)node {
for (SKNode *child in node.children) {
[self cleanUpChildrenAndRemove:child];
}
[node removeFromParent];
}
I also found that crashes were happening on transitions - and that if I delayed cleanup for a second after transition (and kept the scene alive just long enough), I had no crashes.
E.g.
SKTransition* doors = [SKTransition fadeWithDuration:0.75];
[[self view] presentScene:newscene transition:doors];
SKNode* dummynode = [SKNode node];
// wait for the transition to complete before we give up the reference to oldscene
[dummynode runAction:[SKAction waitForDuration:2.0] completion:^{
// failing to clean up nodes can result in crashes... nice.
[((MySceneBaseClass*)oldscene) cleanUp];
}];
[newscene addChild:dummynode];

Resources