H! I'm using SKVideoNode to play a video on a project. The thing is that when I try to stop it and get back to the current actions sometimes it works but sometimes not.
Here is the code where I play it. Thanks in advance.
SKAction *actionPlayVideo = [SKAction runBlock:^{
SKVideoNode *introVideoLevel1 = [SKVideoNode videoNodeWithVideoFileNamed:#"escenario_intermedio.mov"];
introVideoLevel1.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[introVideoLevel1 setName:#"IntroVideo"];
introVideoLevel1.size = CGSizeMake(ipad_2_width, ipad_2_height);
[self addChild: introVideoLevel1];
[introVideoLevel1 play];
}];
SKAction *actionStopVideo = [SKAction runBlock:^{
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
usleep(6900000);
//[[self childNodeWithName:#"IntroVideo"] stop];
[[self childNodeWithName:#"IntroVideo"] removeFromParent];
});
}];
[self runAction:[SKAction sequence:#[actionPlayVideo, actionStopVideo]]];
Solved it! Just added another action (waitTime) between play and stop and removed the dispatch_async and the usleep
Related
I have an SKAction:
SKAction *myAction = [SKAction performSelector:#selector(methodA) onTarget:self];
I want to repeat this action 50 times before calling methodB upon completion of the 50 actions.
[[self runAction:[SKAction repeatAction:myAction count:50]
withKey:#"myActionKey"]
completion:^{
[self methodB];
}];
It is giving me a bad receiver type 'void' error. The error goes away if I take out the withKey:#"myActionKey" part but I need to get the key because I might need to call removeActionForKey:#"myActionKey" at some point.
Is there any way to work around this?
The command you are going for does not exist but you can do this:
SKAction *callMethodA = [SKAction runBlock:^{
[self methodA];
}];
SKAction *myAction = [SKAction repeatAction:callMethodA count:50];
SKAction *callMethodB = [SKAction runBlock:^{
[self methodB];
}];
SKAction *sequence = [SKAction sequence:#[myAction, callMethodB]];
[self runAction:sequence withKey:#"myKey"];
I am currently coding a game for the iPhone and I sometimes get random crashes with the error in the title.
I've researched quite a bit and it probably has to do with leaking(?) memory issues. The people I asked told me, that it may has to do with adding a nil object. I set up exception breakpoints and NSZombies but neither of those recognize the crash, nor give me the exact code line where the crash occurs.
After some crash-testing I noticed that most of the time it happens when either the touchesBegan method or the didBeganContact method is active.
Here's the code of the touchesBegan method and the didBeginContact method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (touchEnabled == YES){
firstTouch++;
if (firstTouch == 1){
SKAction* removeFade = [SKAction removeFromParent];
SKAction* startFade = [SKAction fadeAlphaTo:0 duration:0.5];
SKAction* fadeSeq = [SKAction sequence:#[startFade, removeFade]];
[startScreen runAction:fadeSeq completion:^{
startScreenRemoved = YES;
[self gameCountdown];
touchToShoot = YES;
}];
}
if (firstTouch == 2){
weaponActivated = YES;
}
if (gameOver == YES){
[self removeAllActions];
[self removeAllChildren];
[bgPlayer stop];
touchToShoot = NO;
SKScene* gameScene = [[GameScene alloc] initWithSize:self.size];
SKTransition *doors = [SKTransition doorsOpenVerticalWithDuration:1];
[self.view presentScene:gameScene transition:doors];
gameOver = NO;
}
}
if (touchToShoot == YES) {
[self weaponParticle];
touchToShoot = NO;
[self performSelector:#selector(enableTouchToShoot) withObject:nil afterDelay:0.3]; //-enableTouchToShoot{} = touchToShoot = YES;
}
//Pause Button
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"pauseButton"]) {
[self pauseMenu];
}
else if ([node.name isEqualToString:#"continue"]) {
self.paused = NO;
pauseBG.hidden = YES;
pauseText.hidden = YES;
backm.hidden = YES;
cont.hidden = YES;
//Damit unsichtbare Buttons nicht während dem Spiel gedrückt werden
backm.zPosition = -100;
cont.zPosition = -100;
}
else if ([node.name isEqualToString:#"backtomenu"]) {
self.paused = NO;
[self displayAlert];
}
}
-(void)didBeginContact:(SKPhysicsContact *)contact{
if (contact.bodyA.categoryBitMask == shipCategory) {
//Explosion Animation
SKAction* wait =[SKAction waitForDuration:0.5];
SKAction* fadeOut = [SKAction scaleTo:0.0 duration:1];
remove = [SKAction removeFromParent];
explodeSequenceGO =[SKAction sequence:#[fadeOut,wait,remove]];
ExplosionPath = [[NSBundle mainBundle] pathForResource:#"Explosion" ofType:#"sks"];
Explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:ExplosionPath];
Explosion.position = CGPointMake(contact.bodyA.node.position.x, contact.bodyA.node.position.y);
[self addChild:Explosion];
[Explosion runAction:explodeSequenceGO];
[contact.bodyA.node removeFromParent];
[contact.bodyB.node removeFromParent];
[shipSmoke removeFromParent];
[player1 removeFromParent];
touchEnabled = NO;
[self gameOver];
}
if (contact.bodyA.categoryBitMask == enemyCategory || contact.bodyB.categoryBitMask == enemyCategory) {
//Explosion Animation
SKAction* wait =[SKAction waitForDuration:0.5];
SKAction* fadeOut = [SKAction scaleTo:0.0 duration:1];
remove = [SKAction removeFromParent];
explodeSequenceGO =[SKAction sequence:#[fadeOut,wait,remove]];
ExplosionPath = [[NSBundle mainBundle] pathForResource:#"Explosion" ofType:#"sks"];
Explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:ExplosionPath];
Explosion.position = CGPointMake(contact.bodyA.node.position.x, contact.bodyA.node.position.y);
[Explosion runAction:explodeSequenceGO];
[self addChild:Explosion];
[contact.bodyA.node removeFromParent];
[contact.bodyB.node removeFromParent];
hitCount++;
[self scoreChange:100];
}
if (hitCount>39) {
[self eLevel2];
}
}
Does anyone see a fault? I greatly appreciate any tip, since I am searching for this bug for weeks....
EDIT: The crash just points to the "main" function, which doesn't help at all
And in each of the Thread "actions" it just points to Assembly(?) code:
And like I said, I tried to analyze the crashes by various debug tools (NSZombies, MemoryTool, Exceptional Breakouts, etc.) but none of them give me useful infos. When the app crashes, the debugging tools just stop recording, but they don't show me any faults or crash results.
I know that this is a few months old, but I too was experiencing the EXC_BAD_ACCESS error when transitioning from one scene to another. After reading multiple posts, and commenting out almost every line of code in my game, what fixed it for me was the removing all of the objects from the existing scene before returning from the method and bang error was gone.
- (void)returnToMenuScene {
SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:1.0];
MenuScene *menuScene = [MenuScene sceneWithSize:self.scene.size];
[self.scene.view presentScene:menuScene transition:reveal];
[self.gameScene removeAllChildren];
return;
}
- (GameScene *)gameScene {
return (GameScene *)self.scene;
}
I hope this helps someone else in the future
One thing I found early on is that transitioning between scenes caused crashes if the original scene was killed too early by SpriteKit. - Crazy talk I know.
To help this I created a base class scene which included a cleanup op.
-(void)cleanUp
{
[self cleanUpChildrenAndRemove:self];
}
- (void)cleanUpChildrenAndRemove:(SKNode*)node {
for (SKNode* child in node.children) {
[self cleanUpChildrenAndRemove:child];
}
[node removeFromParent];
}
Then when i transition scenes, I let the old scene live for a while before destroying it.
Notice the use of an action on the old scene to clean itself up later on.
Call me paranoid, delusional, but it fixed my crash. I also needed to call cleanup when I received Terminate events on the App.
if( oldscene!=nil )
{
SKTransition* doors = [SKTransition doorsOpenVerticalWithDuration:1.5];
doors.pausesIncomingScene = NO;
[[self view] presentScene:newscene transition:doors];
SKNode* dummynode = [SKNode node];
// wait for the transition to complete before we give up the referene to oldscene
[dummynode runAction:[SKAction waitForDuration:2.0] completion:^{
if ([oldscene isKindOfClass:[MyBaseScene class]])
{
// failing to clean up nodes can result in crashes... nice.
[((MyBaseScene*)oldscene) cleanUp];
}}];
[oldscene addChild:dummynode];
}
else
{
[[self view] presentScene:newscene];
}
I made a game with SpriteKit. I structured my game by coding a GameScene (SKScene) and a separate enemy class.
I want the spawning enemies to shoot a particle e.g. every 2 seconds (just moving an SKEmitterNode by the y axis) I tried to do it with a timer, but it doesn't really work.
I call my Enemy class from the gaming Scene with this code:
GameScene.m
-(void)enemiesLevel1{
EnemyClass* wave1 = [[EnemyClass alloc] init];
[wave1 enemiesLevel1:self];
}
And I am basically calling this method from EnemyClass.m
-(void)enemiesLevel1:(SKScene *)scene
{
enemy = [SKSpriteNode spriteNodeWithImageNamed:Enemy];
//Enemy Path
(...)
SKAction *followPath2 = [SKAction followPath:pathRef2
asOffset:NO
orientToPath:YES
duration: pathSpeed];
SKAction *forever = [SKAction repeatActionForever:followPath2];
//PhysicsBody Eigenshaften
enemy.physicsBody =[SKPhysicsBody bodyWithCircleOfRadius:enemy.size.width];
enemy.physicsBody.dynamic = YES;
enemy.physicsBody.categoryBitMask = enemyCategory;
[enemy runAction:forever];
[scene addChild:enemy];
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(weaponParticle)
userInfo:nil
repeats:YES];
}
-(void)weaponParticle{
screenHeight = self.frame.size.height;
screenWidth = self.frame.size.width;
//Schuss-Particles
enemyParticlePath = [[NSBundle mainBundle] pathForResource:#"ShootFire" ofType:#"sks"];
enemyParticle = [NSKeyedUnarchiver unarchiveObjectWithFile:enemyParticlePath];
enemyParticle.physicsBody =[SKPhysicsBody bodyWithCircleOfRadius:0.2];
enemyParticle.physicsBody.categoryBitMask = shootCategory;
enemyParticle.physicsBody.contactTestBitMask = playerCategory;
//Schuss-Action
moveDown = [SKAction moveByX:0.0 y:-screenHeight duration:1.0];
remove = [SKAction removeFromParent];
weaponShot = [SKAction sequence:#[moveDown, remove]];
enemyParticle.position = CGPointMake(enemy.position.x, enemy.position.y+10);
[self addChild:enemyParticle];
[enemyParticle runAction: weaponShot];
}
The enemies are spawning 1 by 1 just how I wanted, but they can't shoot. Can anyone help me out?
Instead of scheduling NSTimer try something like this:
SKAction *shoot = [SKAction runBlock:^{
// add code that shoots here
}];
SKAction *wait = [SKAction waitForDuration:0.5];
[enemy runAction:[SKAction repeatActionForever:[SKAction sequence:#[shoot, wait]]]];
I was trying to implement an animated start button in my main menu. Because my scene takes a while to load, I want to bridge the waiting time with that button animation. Unfortunately the animation does not start. What is the problem with my code?
-(void)buttonAnimation{
SKAction *HUDzoom = [SKAction scaleTo:3 duration:1];
SKAction *HUDzoomOut = [SKAction scaleTo:1.0 duration:1];
SKAction *HUDAnimation = [SKAction sequence:#[HUDzoom, HUDzoomOut]];
[self.startButton runAction:[SKAction repeatActionForever:HUDAnimation]];
}
-(void)loadScene{
SKScene *restart = [[Level_1 alloc] initWithSize:self.size];
[self.view presentScene:restart];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"startLevel1"]){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self loadScene];
dispatch_async(dispatch_get_main_queue(), ^{
[self buttonAnimation];
});
});
}
}
That's because you're loading the scene asynchronically, and only after that is done you start the button animation asynchronically:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// loading the scene
[self loadScene];
// when scene has finished loading, animate the button asynchronically
// (this makes no sense)
dispatch_async(dispatch_get_main_queue(), ^{
[self buttonAnimation];
});
});
Instead you should start the animation, then load the scene asynchronically.
[self buttonAnimation];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self loadScene];
});
The button is animated by Sprite Kit actions, while you can start the animation asynchronically it won't make the entire animation asynchronically. Instead you just need to ensure that any blocking methods like loadScene run asynchronically.
I want that every object from the NSMuttableArray to appear for 4 sec. and than disappear.And on this way to iterate all items from the array.Instead the result i got is that all items appears and after the period disappear together.
-(void)showTreasures{
for (int i = 0; i < _treasures.count; i++)
{
SKSpriteNode *obj = [_treasures objectAtIndex:i];
SKAction *show = [SKAction runBlock:^{
obj.hidden = NO;
}];
SKAction *wait = [SKAction waitForDuration:4];
SKAction *hide = [SKAction runBlock:^{
obj.hidden = YES;
}];
SKAction *sequence = [SKAction sequence:#[show, wait, hide]];
[obj runAction:sequence completion:^{
NSLog(#"Item %d", i);
}];
}
}
I think it's good job for recursive method, method which call itself.
You can created array of all of your objects (SKSpriteNode) and pass it to the method which takes first (or last) object and run appropriate action, remove the object and call the method again:
NSMutableArray *arrOfObject = //arrat with all of the sprites you want to show
[self runShowAction:arrOfObject];
-(void)runShowAction:(NSMutableArray*)array {
//if no object in the array return
if(array.count <= 0) return;
SKSpriteNode *obj = [array firstObject];
//Run your code here
//...
//On completion remove object from array and run this method again
[obj runAction:sequence completion:^{
NSLog(#"Item %d", i);
[array removeObject:obj];
[self runShowAction:array];
}];
}
Why don't you use an array to build up a list of all the actions, then run a sequence of all of those actions?
-(void)showTreasures
{
NSMutableArray *actions = [NSMutableArray array];
for (int i = 0; i < _treasures.count; i++)
{
SKSpriteNode *obj = [_treasures objectAtIndex:i];
SKAction *show = [SKAction runBlock:^{
obj.hidden = NO;
}];
SKAction *wait = [SKAction waitForDuration:4];
SKAction *hide = [SKAction runBlock:^{
obj.hidden = YES;
}];
SKAction *finish = [SKAction runBlock:^{
NSLog(#"Item %d", i);
}];
[actions addObjectsFromArray:#[show, wait, hide, finish]];
}
SKAction *sequence = [SKAction sequence:actions];
[obj runAction:sequence completion:^{
NSLog(#"Finished all items");
}];
}