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]]]];
Related
I have a circle spawning every few seconds and I have five lanes that I want them to spawn into but I want it to go to a different one at random. The code I'm using works properly but it won't let me use my five x positions? I've tried multiple solutions but they won't work. I'm still new to objective c so any help would be appreciated. (Also the code below isn't my whole method obviously but it's the only part I need to show.)
#import "GameScene.h"
int xPos1;
int xPos2;
int xPos3;
int xPos4;
int xPos5;
#implementation GameScene
-(void) addBall:(CGSize) size{
xPos1 = 8.5;
xPos2 = 74;
xPos3 = 138;
xPos4 = 203;
xPos5 = 267.5;
CGRect box = CGRectMake( arc4random() , self.frame.size.height, //pos
45, 45); //size
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:box];
SKShapeNode *circle = [SKShapeNode node];
circle.path = circlePath.CGPath;
circle.fillColor = [SKColor colorWithRed:(243.0f/255) green:(134.0f/255) blue:(48.0f/255) alpha:1.0];
circle.strokeColor = nil;
circle.lineWidth = 3;
//add physics
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:circle.frame.size.width/2];
//appy actions to move sprite
SKAction *wait = [SKAction waitForDuration:2];
SKAction *move = [SKAction moveToY:-self.size.height - 50 duration:2];
SKAction *remove = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:#[wait, move, remove]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
SKAction *spawn = [SKAction performSelector:#selector(addBall:) onTarget:self];
SKAction *delay = [SKAction waitForDuration:2.5];
SKAction *spawnThenDelay = [SKAction sequence:#[delay, spawn]];
[self runAction:spawnThenDelay];
[circle runAction:repeat];
[self addChild:circle];
}
Here are the different things I've tried using the x positions in arc4random:
CGRect box = CGRectMake( arc4random() %(xPos1, xPos2, xPos3, xPos4, xPos5) , self.frame.size.height, //pos
45, 45);
CGRect box = CGRectMake( arc4random(xPos1, xPos2, xPos3, xPos4, xPos5) , self.frame.size.height, //pos
45, 45);
I've also tried just putting the integers directly in there...
Try by creating an array of X Positions and selecting from that using a random index.
CGFloat xPositions[5] = {8.5,74,138,203,267.5};
int randomIndex = arc4random() % 5;
int randomXPosition = xPositions[randomIndex]
CGRect box = CGRectMake(randomXPosition, self.frame.size.height, 45, 45);
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:box];
How to repeat this action 3 or 2 times instead of repeating it forever
SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:#"AmericanTypewriter-Bold"];
label.text = #"Boom";
label.fontColor = [SKColor blackColor];
label.fontSize = 90;
label.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame)+25);
SKAction *disappear = [SKAction fadeAlphaTo:0.0 duration:0.2];
SKAction *appear = [SKAction fadeAlphaTo:1.0 duration:0.2];
SKAction *pulse = [SKAction sequence:#[disappear,appear]];
[label runAction:[SKAction repeatActionForever:pulse]];
[self addChild:label];
You need to use SKAction's repeatAction:count: method documented here.
[label runAction:[SKAction repeatAction:pulse count:3]];
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");
}];
}
Okay, so I tried the orbivoid tutorial, however I want that the elapsed time (in seconds) would be the score of the player and not the number of enemy respawned. I tried so many things with regards to NSTimer, NSTimeInterval and CFTimeInterval but needless to say, I failed. Can somebody help me with regards to this -- What code to add, tips or something? BTW, this is the GameScene.m in the orbivoid tutorial.
Thank you in advance!
#implementation GameScene
{
BOOL _dead;
SKNode *_player;
NSMutableArray *_enemies;
SKLabelNode *_scoreLabel;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
self.physicsWorld.contactDelegate = self;
_enemies = [NSMutableArray new];
_player = [SKNode node];
SKShapeNode *circle = [SKShapeNode node];
circle.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-10, -10, 20, 20)].CGPath;`
circle.fillColor = [UIColor blueColor];
circle.glowWidth = 5;
SKEmitterNode *trail = [SKEmitterNode orb_emitterNamed:#"Trail"];
trail.targetNode = self;
trail.position = CGPointMake(CGRectGetMidX(circle.frame), CGRectGetMidY(circle.frame));
_player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10];
_player.physicsBody.mass = 100000;
_player.physicsBody.categoryBitMask = CollisionPlayer;
_player.physicsBody.contactTestBitMask = CollisionEnemy;
[_player addChild:trail];
_player.position = CGPointMake(size.width/2, size.height/2);
[self addChild:_player];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
[self performSelector:#selector(spawnEnemy) withObject:nil afterDelay:1.0];
}
-(void)spawnEnemy
{
[self runAction:[SKAction playSoundFileNamed:#"Spawn.wav" waitForCompletion:NO]];
SKNode *enemy = [SKNode node];
SKEmitterNode *trail = [SKEmitterNode orb_emitterNamed:#"Trail"];
trail.targetNode = self;
trail.particleScale /= 2;
trail.position = CGPointMake(10, 10);
trail.particleColorSequence = [[SKKeyframeSequence alloc] initWithKeyframeValues:#[
[SKColor redColor],
[SKColor colorWithHue:0.1 saturation:.5 brightness:1 alpha:1],
[SKColor redColor],
] times:#[#0, #0.02, #0.2]];
[enemy addChild:trail];
enemy.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:6];
enemy.physicsBody.categoryBitMask = CollisionEnemy;
enemy.physicsBody.allowsRotation = NO;
enemy.position = CGPointMake(50, 50);
[_enemies addObject:enemy];
[self addChild:enemy];
if(!_scoreLabel) {
_scoreLabel = [SKLabelNode labelNodeWithFontNamed:#"Courier-Bold"];
_scoreLabel.fontSize = 200;
_scoreLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
_scoreLabel.fontColor = [SKColor colorWithHue:0 saturation:0 brightness:1 alpha:0.5];
[self addChild:_scoreLabel];
}
_scoreLabel.text = [NSString stringWithFormat:#"%02d", _enemies.count];
// Next spawn
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:5],
[SKAction performSelector:#selector(spawnEnemy) onTarget:self],
]]];
}
-(void)dieFrom: (SKNode*)killingEnemy
{
_dead = YES;
SKEmitterNode *explosion = [SKEmitterNode orb_emitterNamed:#"Explosion"];
explosion.position = _player.position;
[self addChild:explosion];
[explosion runAction:[SKAction sequence:#[
[SKAction playSoundFileNamed:#"Explosion.wav" waitForCompletion:NO],
[SKAction waitForDuration:0.4],
[SKAction runBlock:^{
// TODO: Revove these more nicely
[killingEnemy removeFromParent];
[_player removeFromParent];
}],
[SKAction waitForDuration:0.4],
[SKAction runBlock:^{
explosion.particleBirthRate = 0;
}],
[SKAction waitForDuration: 1.2],
[SKAction runBlock:^{
ORBMenuScene *menu = [[ORBMenuScene alloc] initWithSize:self.size];
[self.view presentScene:menu transition:[SKTransition doorsCloseHorizontalWithDuration:0.4]];
}],
]]];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];
}
-(void)touchesMoved: (NSSet *) touches withEvent:(UIEvent *)event
{
[_player runAction:[SKAction moveTo:[[touches anyObject] locationInNode:self] duration:.01]];
}
-(void)update:(CFTimeInterval)currentTime
{
CGPoint playerPos = _player.position;
for(SKNode *enemyNode in _enemies)
{
CGPoint enemyPos = enemyNode.position;
/* Uniform speed: */
CGVector diff = TCVectorMinus(playerPos, enemyPos);
CGVector normalized = TCVectorUnit(diff);
CGVector force = TCVectorMultiply(normalized, 4);
[enemyNode.physicsBody applyForce:force];
}
_player.physicsBody.velocity = CGVectorMake (0, 0);
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if(_dead)
return;
[self dieFrom:contact.bodyB.node];
contact.bodyB.node.physicsBody = nil;
}
#end
Simple Version: create a score integer (_block needed because you're going to use it in a block)
__block int score123=0;
Run an action that increases your integer then waits 1.0 seconds before doing it again. This sequence repeats forever. Give the action the key "scoreIncrease" so you can stop it later.
SKAction *scoreIncrease = [SKAction repeatActionForever:[SKAction sequence:#[[SKAction runBlock:^{ score123++; }],[SKAction waitForDuration:1.0f]]]];
[self runAction:scoreIncrease withKey:#"scoreIncrease"];
When you want to stop the counting, use this:
[self removeActionForKey:#"scoreIncrease"];
I don't really know where to start. I have an image of a circle stored in an SKSpriteNode and a physicsBody that mirrors the size when it is created.
I am using an SKAction to scale down the size of the image though, and the physicsBody remains the same size. How can I scale down the physicsBody?
My code:
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"whiteball"];
sprite.size = CGSizeMake(20, 20);
sprite.position = CGPointMake(dx, CGRectGetHeight(self.frame)-dy);
sprite.name = #"ball";
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
[self addChild:sprite];
SKNode *ballNode = [self childNodeWithName:#"ball"];
if (ballNode != Nil){
ballNode.name = nil;
SKAction *delay = [SKAction waitForDuration:3];
SKAction *scale = [SKAction scaleTo:0 duration:1]; // I want this to scale the physicsBody as well as the spriteNode...
SKAction *remove = [SKAction removeFromParent];
//put actions in sequence
SKAction *moveSequence = [SKAction sequence:#[delay, scale, remove]];
//run action from node (child of SKLabelNode)
[ballNode runAction:moveSequence];
}
If you set the size of the physicsbody to the size of the circle, you may achieve what you seek.
_circle = [SKSpriteNode spriteNodeWithImageNamed:#"cirlce"];
_circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_circle.size.width/2];