I have been working on a SpriteKit game for a while now and I have an annoying problem that I can't get rid of. I don't want to tell too much about the game, but it is very simple. I generate some objects (SKSpriteNodes) each second that are falling down from the top of the screen using SpriteKits physics and the player is interacting with them.
Most of the time the game runs perfectly (constant 60 FPS). The problem is that sometimes (maybe once per minute or something like that), the game starts to lag a little bit for about 3-5 seconds (still at 60 FPS) and then it runs perfectly again (note: i'm running the game on an iPhone 5s). It seems to be because of the physics, because if I add a normal move-action on an object, it runs very smoothly while the nodes affected by the physics are lagging.
I tried to remove some particles and effects that I have and I reuse my objects, but I can't remove the lag. I decided to create a very simple test project to see if the lag would be gone but it is still there. Here is the code:
#import "GameScene.h"
static const uint32_t groundCategory = 1 << 0;
static const uint32_t objectCategory = 1 << 1;
#implementation GameScene
-(void)didMoveToView:(SKView *)view
{
// Init
self.backgroundColor = [UIColor blackColor];
self.physicsWorld.gravity = CGVectorMake(0.0f, -3.2f);
self.physicsWorld.contactDelegate = self;
// Ground
SKPhysicsBody* ground = [SKPhysicsBody bodyWithEdgeFromPoint:CGPointMake(0, -88) toPoint:CGPointMake(self.size.width, -88)];
ground.categoryBitMask = groundCategory;
self.physicsBody = ground;
// Start game loop
SKAction* waitAction = [SKAction waitForDuration:1];
SKAction* sequence = [SKAction sequence:#[[SKAction performSelector:#selector(addObject) onTarget:self], waitAction]];
SKAction* repeatAction = [SKAction repeatActionForever:sequence];
[self runAction:repeatAction withKey:#"fallingObjectsAction"];
}
- (void)addObject
{
SKSpriteNode* newObject = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(88, 88)];
newObject.name = #"Object";
newObject.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:newObject.size];
newObject.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height + newObject.size.height);
newObject.physicsBody.categoryBitMask = objectCategory;
newObject.physicsBody.contactTestBitMask = groundCategory;
newObject.physicsBody.collisionBitMask = 0;
[self addChild:newObject];
}
- (void)didBeginContact:(SKPhysicsContact *)contact
{
if ([contact.bodyA.node.name isEqualToString:#"Object"])
[contact.bodyA.node removeFromParent];
else if ([contact.bodyB.node.name isEqualToString:#"Object"])
[contact.bodyB.node removeFromParent];
}
#end
I generate one object each second and let it fall down from the top of the screen. When it hits the ground, it is removed.
Am I using the physics wrong or is it SpriteKit's fault that it is lagging? It seems strange, because I'm running a very simple project using an iPhone 5s with iOS 8.
Okay I've been having the same issue with physics. The issue was objects are jittery despite low CPU usage and a consistent FPS. I've finally figured out why. The solution I found is do not set physicsWorld.speed to 1.0; set it to a .9999. Now everything runs smoothly.
I hope this helps.
Related
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];
I am making a 2D game where a character stands stationary at the left hand side of the screen and objects fly towards him from the right. He needs to be able to slap these flying enemies down. I have a sprite animation that contains the "slap" animation frames. During the slap, his body moves slightly and his arm rotates from resting on the ground, to fully extended above his head and then slaps down to the ground. Here is what it looks like:
SumoSmash Animation GIF
For these purposes I have a class called SumoWarrior which is a SKSpriteNode subclass. The SumoWarrior sprite node has a child sprite node called warriorArm. My idea was to have the main sumo warrior sprite node display the animation, and to use this warriorArm sprite node only for the purposes of a physics body in the shape of the warriors arm. I need to somehow rotate this arm body to follow the sprite animation, in order to detect collisions with the flying objects.
Here is how the arm is created:
sumoWarrior.warriorArm = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"warriorArm"]];
sumoWarrior.warriorArm.position = CGPointMake(15, 25);
sumoWarrior.warriorArm.anchorPoint = CGPointMake(0.16, 0.7);
sumoWarrior.warriorArm.texture = nil;
sumoWarrior.warriorArm.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:[sumoWarrior createArmBody]];
sumoWarrior.warriorArm.physicsBody.mass = 9999;
sumoWarrior.warriorArm.physicsBody.categoryBitMask = CollisionPlayer;
sumoWarrior.warriorArm.physicsBody.collisionBitMask = CollisionEnemy;
sumoWarrior.warriorArm.physicsBody.contactTestBitMask = CollisionEnemy | CollisionAlly;
sumoWarrior.warriorArm.physicsBody.allowsRotation = YES;
sumoWarrior.warriorArm.physicsBody.dynamic = YES;
Is it possible to rotate and extend this arm somehow, in order for it to follow the animation precisely? Is it also possible to alter the main physics body of the warrior (which is just a polygon around the body, without the arm) so that IT also follow the animation? Or am I completely missing the way that this should be done?
I used Texture Packer for the texture and animation. I created an Texture Atlas called sumoAnimations and Texture Packer created a .h file for me which I then imported into the project.
You can get a free copy if you do not already have it.
Before I launch into the code, you might want to reconsider using the animation you have. Going by your previous comments the only relevant frames in the animation are frame 15, 16 and 17. I am not even sure about frame 17 because the sumo already has his hand down. That only give you 3 frames which by the animation you provided is equal to 0.1 seconds as each frame has a time of 0.05 seconds.
Take a look at the 3 pics I included to see what I mean. You might want to consider getting a new animation or allowing for greater time in between frames. I used 0.25 seconds per frame so you can see it more clearly. You can change it to anything you like.
As for the player being missing and being hit, you can create a clearColor sprite rect around the player (behind the arm of course) to detect contact of a missed object.
#import "MyScene.h"
#import "sumoAnimation.h"
#interface MyScene()<SKPhysicsContactDelegate>
#end
#implementation MyScene
{
SKSpriteNode *sumo;
SKSpriteNode *arm;
SKAction *block0;
SKAction *block1;
SKAction *block2;
SKAction *block3;
SKAction *block4;
SKAction *slapHappy;
SKAction *wait0;
SKAction *wait1;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
sumo = [SKSpriteNode spriteNodeWithTexture:SUMOANIMATION_TEX_SUMO_001];
sumo.anchorPoint = CGPointMake(0, 0);
sumo.position = CGPointMake(0, 0);
[self addChild:sumo];
arm = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(34, 14)];
arm.anchorPoint = CGPointMake(0, 0);
arm.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(34,14) center:CGPointMake(17, 7)];
arm.physicsBody.dynamic = NO;
slapHappy = [SKAction animateWithTextures:SUMOANIMATION_ANIM_SUMO timePerFrame:0.25];
// start the animation
block0 = [SKAction runBlock:^{
[sumo runAction:slapHappy];
}];
// time until frame 15 is reached
wait0 = [SKAction waitForDuration:3.50];
// add arm at frame 15 positon
block1 = [SKAction runBlock:^{
arm.position = CGPointMake(205, 125);
arm.zRotation = 1.3;
[self addChild:arm];
}];
// wait until next frame
wait1 = [SKAction waitForDuration:0.25]; // time in between frames
// move arm and rotate to frame 16 position
block2 = [SKAction runBlock:^{
arm.position = CGPointMake(224, 105);
arm.zRotation = 0.4;
}];
// move arm and rotate to frame 17 position
block3 = [SKAction runBlock:^{
arm.position = CGPointMake(215, 68);
arm.zRotation = -0.65;
}];
// remove arm from view
block4 = [SKAction runBlock:^{
[arm removeFromParent];
}];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[sumo runAction:[SKAction sequence:#[block0, wait0, block1, wait1, block2, wait1, block3, wait1, block4]]];
}
-(void)update:(CFTimeInterval)currentTime
{
//
}
#end
Updated based on additional comments
The pic below outlines my suggestion. Add a physics body rect for the frames the sumo is swatting. This will allow you to not have to deal with adding a body for every frame in the precise position. It will also make the swatting more effective.
Your object can still fall to the ground and have the crushed animation play. Remember that your sumo animation moves very fast and the player will not see precise locations for each frame.
Your idea of having the arm "push" the object would take a much more precise animation. Something like the arm's position changing by a single increment. Then you would have to precisely position a body on the hand. I am not saying its impossible but its certainly A LOT of work and difficult to do.
I'm creating an ongoing learning project (basically I code while learning to use the framework in hope it will be useful for me and for someone else) for Sprite Kit (you can find it here if you are interested) but I'm facing some performance problems with my code.
The project puts cubes on the screen and make them falling. Here's the class that creates the piece
// The phisics for our falling piece:
// It will be a square, subject to gravity of 5: check common.h (9.8 is way too much for us)
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(self.size.width, self.size.height)];
self.physicsBody.dynamic = YES;
self.physicsBody.mass = 100;
self.physicsBody.collisionBitMask = piecesCollisionBitmask;
self.physicsBody.allowsRotation = NO;
and here is the scene file with the loop.
//schedule pieces
SKAction *wait = [SKAction waitForDuration:1];
SKAction *pieceIsFalling = [SKAction runBlock:^{
FLPPiece *piece = [[FLPPiece alloc] init];
[self addChild:piece];
}];
SKAction *fallingPieces = [SKAction sequence:#[wait,pieceIsFalling]];
[self runAction:[SKAction repeatActionForever:fallingPieces]];
I suspect that physics is behind my frame rate drop. I would like to stop physics execution on node while keeping it on the screen as soon as it collides with something else.
Is that possible? How can I do that?
I'm working in IOS/spritekit where I have a sprite that rotates. The rotation works fine, but in addition I want that the sprite jumps. Therefore I added dynamic and restitution to my physicsBody of my sprite:
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"bigball"];
sprite.position = location;
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
sprite.physicsBody.restitution = 0.7;
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
This also works, but the sprite only jumps 2 times and then comes to a standstill, which is logical because I've added the dynamic, but I want that the sprite jumps infinitely.
Can anyone explain how I can do it?
Thanks in advance
Try this:
sprite.physicsBody.restitution = 1.0;
If the restitution property is at 1.0, its bounciness will be at the maximum level - the physics body will not lose any energy while bouncing, so it should bounce indefinitely.
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.