I have this method that is supposed to loop through my nodes and check how many tubes I have in my parent, and if it is less than 6, I should add more. The first 6 work, but after they are removed by the remove action [self.children count] still reports that they're are 6 tubes still there. It logs "Tube Amount: 6/6" even though on screen the node count is at 0. I need an accurate way to see how many nodes I have. here is my code:
-(void) checkTubes {
int amt = 0;
SKSpriteNode *last;
for(SKSpriteNode *node in self.children)
if(node.size.height > 100) {
last = node;
amt++;
}
NSLog(#"Tube Amount: %i/%i", amt, [self.children count]);
if(amt < 6) {
for(SKSpriteNode *node in self.children)
if(node.size.height > 100)
last = node;
SKSpriteNode *tube1 = [SKSpriteNode spriteNodeWithImageNamed:#"tube"];
SKSpriteNode *tube2 = [SKSpriteNode spriteNodeWithImageNamed:#"tube2"];
int x1 = 155;
int y1 = -100;
int x2 = x1;
int y2 = y1+tube1.size.height + 68;
if(last != (id)[NSNull null]) {
x1 = last.position.x + x1;
x2 = x1;
}
tube1.position = CGPointMake(x1, y1);
tube2.position = CGPointMake(x2, y2);
[self addChild:tube1];
[self addChild:tube2];
SKAction *actionMove1 = [SKAction moveTo: CGPointMake(tube1.position.x - 1600, tube1.position.y) duration: 15.55f];
SKAction *actionMove2 = [SKAction moveTo: CGPointMake(tube2.position.x - 1600, tube2.position.y) duration: 15.55f];
SKAction *actionMoveDone = [SKAction removeFromParent];
[tube1 runAction:[SKAction sequence:#[actionMove1, actionMoveDone]]];
[tube2 runAction:[SKAction sequence:#[actionMove2, actionMoveDone]]];
}
}
You can not use the same action for multiple nodes without copying the action. This might fix it:
SKAction *actionMoveDone = [SKAction removeFromParent];
[tube1 runAction:[SKAction sequence:#[actionMove1, actionMoveDone]]];
// 2nd and any following use of the same action must be a 'copy'
[tube2 runAction:[SKAction sequence:#[actionMove2, [actionMoveDone copy]]]];
Related
I have a function that adds enemies to my scene that get called by an timing interval
How can I keep track of the number of enemies on the Scene to limit the amount of enemies I have in each level?
**In my update function **
CFTimeInterval timeSinceLastEnemy = currentTime - self.lastEnemyUpdateTime;
self.lastEnemyUpdateTime = currentTime;
if (timeSinceLastEnemy > 1) { // more than a second since last update
timeSinceLastEnemy = 1.0 / 60.0;
self.lastEnemyUpdateTime = currentTime;
}
[self spwanEnemyWithTime:timeSinceLastEnemy];
**The timer and The method to add enemies **
- (void)spwanEnemyWithTime:(CFTimeInterval)timeSinceLast {
self.lastEnemySpawn += timeSinceLast;
if (self.lastEnemySpawn > 0.6) {
self.lastEnemySpawn = 0;
[self spawnEnemy];
}
}
-(void) spawnEnemy {
SKSpriteNode *enemy = [SKSpriteNode spriteNodeWithImageNamed: enemySprite];
int minX = 5;
int maxX = self.frame.size.width;
int rangeX = maxX - minX;
int actualX = (arc4random() % rangeX) + minX;
// Create the enemy slightly off-screen along the upper edge,
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(enemy.size.height - 10 , enemy.size.width)];
enemy.physicsBody.dynamic = YES;
enemy.physicsBody.categoryBitMask = enemyCategory;
enemy.physicsBody.contactTestBitMask = bulletCategory;
enemy.physicsBody.collisionBitMask = 0;
// and along a random position along the X axis as calculated above
enemy.position = CGPointMake(actualX, self.frame.size.height + enemy.size.height);
[self addChild:enemy];
enemy.xScale = 0.2;
enemy.yScale = 0.2;
enemy.zPosition = 4;
// Create the actions
SKAction * actionMove = [SKAction moveToY:(0 - enemy.size.height) duration:4];
SKAction * actionMoveDone = [SKAction removeFromParent];
[enemy runAction:[SKAction sequence:#[actionMove, actionMoveDone]]];
}
I confess I couldn't grasp what you are doing with the double timers, so I will assume that portion works as you want it to. for the enemy count couldn't this be as simple as incrementing a counter on your spawnEnemy and decrementing the counter when the enemy removes itself from the scene?
You'll have to double check my Obj-c I haven't used it in a while.
int enemyCount = 0;
-(void) spawnEnemy {
enemyCount += 1
...
// Create the actions
SKAction *actionMove = [SKAction moveToY:(0 - enemy.size.height) duration:4];
SKAction *actionMoveDone = [SKAction removeFromParent];
SKAction *decrementCount = [SKAction runBlock: ^(void) {
enemyCount -= 1;
}];
[enemy runAction:[SKAction sequence:#[actionMove, actionMoveDone, decrementCount]]];
}
FYI you have a typo in your method "spwanEnemyWithTime"
I'm working on a game that has 3 clouds in the background which are each SKSpriteNodes. In order to create a parallax effect I'm moving them down the screen as the character is moving up (the screen is moving up as well).
I removing them in the update method after they reach the bottom of the screen. I would like to add them back at a Y position 20px above the viewable scene so that would be 500x on an iPhone 4 and 588px on a iPhone 5. Then just repeat over and over.
Then only way I can think of to do this is to chain SKActions and run a sequence, but I don't think that is the best way. I'm trying to find a way to add them back after they have been removed in the update method.
-(id)initWithSize:(CGSize)size {
...
//Moving Clouds
[self performSelector:#selector(createClouds) withObject:nil afterDelay:.2];
}
-(void)createClouds {
SKSpriteNode *cloud1 = [SKSpriteNode spriteNodeWithImageNamed:#"cloud1"];
cloud1.position = CGPointMake(40, 200);
cloud1.xScale = .5;
cloud1.yScale = .5;
cloud1.name = #"Cloud1";
cloud1.zPosition = 0;
SKAction *moveCloud1 = [SKAction moveToY:-20 duration:15];
[cloud1 runAction:[SKAction repeatActionForever:moveCloud1]];
[self addChild:cloud1];
SKSpriteNode *cloud2 = [SKSpriteNode spriteNodeWithImageNamed:#"cloud2"];
cloud2.position = CGPointMake(300, 320);
cloud2.xScale = .5;
cloud2.yScale = .5;
cloud2.name = #"Cloud2";
cloud2.zPosition = 0;
SKAction *moveCloud2 = [SKAction moveToY:-40 duration:15];
[cloud2 runAction:[SKAction repeatActionForever:moveCloud2]];
[self addChild:cloud2];
SKSpriteNode *cloud3 = [SKSpriteNode spriteNodeWithImageNamed:#"cloud3"];
cloud3.position = CGPointMake(200, 450);
cloud3.xScale = .5;
cloud3.yScale = .5;
cloud3.name = #"Cloud3";
cloud3.zPosition = 0;
SKAction *moveCloud3 = [SKAction moveToY:-200 duration:20];
[cloud3 runAction:[SKAction repeatActionForever:moveCloud3]];
[self addChild:cloud3];
}
-(void)update:(CFTimeInterval)currentTime {
....
// Remove Cloud from parent
[self enumerateChildNodesWithName:#"Cloud1" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x < 0 || node.position.y < 0) {
[node removeFromParent];
//Add node back to the scene at a Y position above the screen then move
// the cloud downwards again. Repeat Forever.
// Exp: Move Cloud 1 to X position 40, Y position 500 (20 pixels above the top of the screen for an iPhone 4s)
}
else {
// Do something?
}
}];
You can add an SKAction to move your cloud back to the top:
SKSpriteNode *cloud1 = [SKSpriteNode spriteNodeWithImageNamed:#"cloud1"];
cloud1.position = CGPointMake(40, 200);
cloud1.xScale = .5;
cloud1.yScale = .5;
cloud1.name = #"Cloud1";
cloud1.zPosition = 0;
SKAction *moveCloud1 = [SKAction moveToY:-20 duration:15];
// SKAction to move cloud back to top of screen
SKAction *moveToTop = [SKAction moveToY:CGRectGetHeight(self.frame)+20 duration:0];
SKAction *cloud1Action = [SKAction sequence:#[moveCloud1, moveToTop]];
[cloud1 runAction:[SKAction repeatActionForever:cloud1Action]];
You won't need the code in the update method.
I think you can simply change the position
Also, use CGRectGetMinY to use relative positioning, easier to manage different screen sizes.
And check only the Y position too, because X won't change if they're moving vertically.
This is my idea:
-(void)update:(NSTimeInterval)currentTime {
for(int i = 1; i<=3; i++){
NSString* cloud = [NSString stringWithFormat:#"Cloud%d",i];
[self enumerateChildNodesWithName:cloud usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y < 0) {
node.position = CGPointMake(node.position.x,CGRectGetMaxY(self.frame)+20);
}
}];
}
}
I have a object in the scene which is determine by:
SKSpriteNode* object = [SKSpriteNode spriteNodeWithTexture:_objectTexture];
[object setScale:2];
object.position = CGPointMake(0, y);
object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:object.size];
object.physicsBody.dynamic = NO;
then if my character hit it, it will stay there. Now, I want whenever the character hit the object, the object also move its upper half. How it moves depends on the index.
if index value in range 1, object change texture
if index value in range 2, object and character reflex by normal physical contact law
if index value in range 3, object broken into half
I tried to put
object.physicsBody.dynamic = YES;
then I saw it pulled down and disappear by gravity.
How do I set object dynamic and condition to satisfy the above need?
Thanks a lot for your help !
Added (as requested): My delegate method:
#interface MyScene () <SKPhysicsContactDelegate> {
SKSpriteNode* _character;
SKTexture* _object1;
SKTexture* _object2;
SKAction* _moveObjects;
SKNode* _moving;
SKNode* _objects;
BOOL _canRestart;
NSInteger _index;
SKTexture* characterTexture;
SKSpriteNode* _characterSprite;
}
later part (not sure if it is relevant)
-(id)initWithSize:(CGSize)size{
if (self = [super initWithSize:size]) {
/*Setup Scene here */
self.physicsWorld.gravity = CGVectorMake(0.0, -6.0);
self.physicsWorld.contactDelegate = self;
My objects are moved on scence by:
CGFloat distanceToMove = self.frame.size.width + 2 * _object1.size.width;
SKAction* moveObjects = [SKAction moveByX:-distanceToMove y:0 duration:0.01* distanceToMove];
SKAction* removeObjects = [SKAction removeFromParent];
_moveObjectsAndRemove = [SKAction sequence:#[moveObjects,removeObjects]];
Ps: add didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact{
if (_moving.speed > 0) {
//Score node
if ((contact.bodyA.categoryBitMask & scoreCategory) == scoreCategory || (contact.bodyB.categoryBitMask & scoreCategory) == scoreCategory)
{
_score++;
// Add a little visual
[_scoreLabelNode runAction:[SKAction sequence:#[[SKAction scaleTo:1.5 duration:0.1], [SKAction scaleTo:1.0 duration:0.1]]]];
}
else
//Collided with world
{
_moving.speed = 0;
_character.physicsBody.collisionBitMask = worldCategory;
[_character runAction:[SKAction rotateByAngle:M_PI * _character.position.y*0.01 duration:_character.position.y * 0.03] completion:^{_character.speed = 0; }];
....
}
}
You need to set
object.physicsBody.affectedByGravity = NO;
in my game I am adding enemies to the scene every second , I need to describe an specific distance between pervious object that my main character move through these enemy , here is my codes that add enemy to the scene :
- (void)createEnemy {
int GoOrNot = [self getRandomNumberBetween:0 to:1];
if(GoOrNot == 1){
int randomEnemy = [self getRandomNumberBetween:0 to:1];
if(randomEnemy == 0)
enemy = [[SKSpriteNode alloc]initWithImageNamed:#"car.png"];
else
enemy = [[SKSpriteNode alloc]initWithImageNamed:#"block.png"];
int xPostion = [self placeRandomObject] ;
int yPostion = 1150;
enemy.position = CGPointMake(xPostion, yPostion);
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.size];
enemy.name = #"enemy";
enemy.physicsBody.categoryBitMask = enemyCategory;
enemy.physicsBody.contactTestBitMask = carCategory;
enemy.physicsBody.collisionBitMask = 0;
enemy.physicsBody.dynamic = YES;
enemy.physicsBody.usesPreciseCollisionDetection = YES;
[self addChild:enemy];
SKAction *wait = [SKAction waitForDuration:.20];
SKAction *move = [SKAction moveToY:self.scene.frame.origin.y-10 duration:enemySpeed];
SKAction *remove = [SKAction removeFromParent];
SKAction *runAction = [SKAction sequence:#[wait,move , remove]];
[enemy runAction:[SKAction repeatActionForever:runAction]];
}
}
adding enemy :
- (void)addEnemies {
SKAction *wait = [SKAction waitForDuration:.55];
SKAction *callEnemies = [SKAction runBlock:^{ [self createEnemy];}];
updateEnimies = [SKAction sequence:#[wait,callEnemies]];
[self runAction:[SKAction repeatActionForever:updateEnimies] withKey:#"addEnemy"];
}
You can create new variable to hold the last position of the previous enemy.
CGPoint positionOfLastEnemy;
when you create new enemy assign the position of created enemy to this variable:
positionOfLastEnemy = CGPointMake(xPostion, yPostion);
if you need to update the enemy position you can do it in your collision detection method.
After that just use the positionOfLastEnemy variable to calculate the distance you need.
The other way is to add variable to hold the last enemy
SKSpriteNode *lastEnemy;
in your createEnemy method save reference to your created enemy.
lastEnemy = enemy;
And if you need the distance of the last enemy just use
lastEnemy.position
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
This code spawns a monster, but no enemy.
I expect an enemy to be spawned, why doesn't it?
#import "MyScene.h"
#import "GameOverScene.h"
static const uint32_t projectileCategory = 0x1 << 0;
static const uint32_t monsterCategory = 0x1 << 1;
static const uint32_t enemyCategory = 0x1 << 1;
// 1
#interface MyScene () <SKPhysicsContactDelegate>
#property (nonatomic) SKSpriteNode * player;
#property (nonatomic) NSTimeInterval lastSpawnTimeInterval;
#property (nonatomic) NSTimeInterval lastUpdateTimeInterval;
#property (nonatomic) int monstersDestroyed;
#property (nonatomic) int enemysDestroyed;
#end
static inline CGPoint rwAdd(CGPoint a, CGPoint b) {
return CGPointMake(a.x + b.x, a.y + b.y);
}
static inline CGPoint rwSub(CGPoint a, CGPoint b) {
return CGPointMake(a.x - b.x, a.y - b.y);
}
static inline CGPoint rwMult(CGPoint a, float b) {
return CGPointMake(a.x * b, a.y * b);
}
static inline float rwLength(CGPoint a) {
return sqrtf(a.x * a.x + a.y * a.y);
}
// Makes a vector have a length of 1
static inline CGPoint rwNormalize(CGPoint a) {
float length = rwLength(a);
return CGPointMake(a.x / length, a.y / length);
}
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
// 2
NSLog(#"Size: %#", NSStringFromCGSize(size));
// 3
self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
// 4
self.player = [SKSpriteNode spriteNodeWithImageNamed:#"player"];
self.player.position = CGPointMake(self.player.size.width/2, self.frame.size.height/2);
[self addChild:self.player];
self.physicsWorld.gravity = CGVectorMake(0,0);
self.physicsWorld.contactDelegate = self;
}
return self;
}
-(void)addMonster {
// Create sprite
SKSpriteNode * monster = [SKSpriteNode spriteNodeWithImageNamed:#"monster"];
monster.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:monster.size]; // 1
monster.physicsBody.dynamic = YES; // 2
monster.physicsBody.categoryBitMask = monsterCategory; // 3
monster.physicsBody.contactTestBitMask = projectileCategory; // 4
monster.physicsBody.collisionBitMask = 0; // 5
// Determine where to spawn the monster along the Y axis
int minY = monster.size.height / 2;
int maxY = self.frame.size.height - monster.size.height / 2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
// Create the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster.position = CGPointMake(self.frame.size.width + monster.size.width/2, actualY);
[self addChild:monster];
// Determine speed of the monster
int minDuration = 2.0;
int maxDuration = 4.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(-monster.size.width/2, actualY) duration:actualDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
SKAction * loseAction = [SKAction runBlock:^{
SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:NO];
[self.view presentScene:gameOverScene transition: reveal];
}];
[monster runAction:[SKAction sequence:#[actionMove, loseAction, actionMoveDone]]];
}
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {
self.lastSpawnTimeInterval += timeSinceLast;
if (self.lastSpawnTimeInterval > 1) {
self.lastSpawnTimeInterval = 0;
[self addMonster];
}
}
- (void)update:(NSTimeInterval)currentTime {
// Handle time delta.
// If we drop below 60fps, we still want everything to move the same distance.
CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
self.lastUpdateTimeInterval = currentTime;
if (timeSinceLast > 1) { // more than a second since last update
timeSinceLast = 1.0 / 60.0;
self.lastUpdateTimeInterval = currentTime;
}
[self updateWithTimeSinceLastUpdate:timeSinceLast];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self runAction:[SKAction playSoundFileNamed:#"pew-pew-lei.caf" waitForCompletion:NO]];
// 1 - Choose one of the touches to work with
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
// 2 - Set up initial location of projectile
SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:#"projectile"];
projectile.position = self.player.position;
projectile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:projectile.size.width/2];
projectile.physicsBody.dynamic = YES;
projectile.physicsBody.categoryBitMask = projectileCategory;
projectile.physicsBody.contactTestBitMask = monsterCategory;
projectile.physicsBody.contactTestBitMask = enemyCategory;
projectile.physicsBody.collisionBitMask = 0;
projectile.physicsBody.usesPreciseCollisionDetection = YES;
// 3- Determine offset of location to projectile
CGPoint offset = rwSub(location, projectile.position);
// 4 - Bail out if you are shooting down or backwards
if (offset.x <= 0) return;
// 5 - OK to add now - we've double checked position
[self addChild:projectile];
// 6 - Get the direction of where to shoot
CGPoint direction = rwNormalize(offset);
// 7 - Make it shoot far enough to be guaranteed off screen
CGPoint shootAmount = rwMult(direction, 1000);
// 8 - Add the shoot amount to the current position
CGPoint realDest = rwAdd(shootAmount, projectile.position);
// 9 - Create the actions
float velocity = 480.0/1.0;
float realMoveDuration = self.size.width / velocity;
SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
[projectile runAction:[SKAction sequence:#[actionMove, actionMoveDone]]];
}
- (void)projectile:(SKSpriteNode *)projectile didCollideWithMonster:(SKSpriteNode *)monster {
NSLog(#"Hit");
[projectile removeFromParent];
[monster removeFromParent];
self.monstersDestroyed++;
if (self.monstersDestroyed > 80) {
SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:YES];
[self.view presentScene:gameOverScene transition: reveal];
}
}
- (void)didBeginContact:(SKPhysicsContact *)contact
{
// 1
SKPhysicsBody *firstBody, *secondBody, *thirdBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
thirdBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
thirdBody = contact.bodyA;
}
// 2
if ((firstBody.categoryBitMask & projectileCategory) != 0 &&
(secondBody.categoryBitMask & enemyCategory) != 0 &&
(thirdBody.categoryBitMask & monsterCategory) != 0)
{
[self projectile:(SKSpriteNode *) firstBody.node didCollideWithMonster:(SKSpriteNode *) secondBody.node];
[self projectile:(SKSpriteNode *) firstBody.node didCollideWithEnemy: (SKSpriteNode *) secondBody.node];
}
}
- (void)addEnemy {
// Create sprite
SKSpriteNode * enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.size]; // 1
enemy.physicsBody.dynamic = YES; // 2
enemy.physicsBody.categoryBitMask = enemyCategory; // 3
enemy.physicsBody.contactTestBitMask = projectileCategory; // 4
enemy.physicsBody.collisionBitMask = 0; // 5
// Determine where to spawn the monster along the Y axis
int minY = enemy.size.height / 2;
int maxY = self.frame.size.height - enemy.size.height / 2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
// Create the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
enemy.position = CGPointMake(self.frame.size.width + enemy.size.width/2, actualY);
[self addChild:enemy];
// Determine speed of the monster
int minDuration = 1.0;
int maxDuration = 6.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(-enemy.size.width/2, actualY) duration:actualDuration];
SKAction * actionMoveDone = [SKAction removeFromParent];
SKAction * loseAction = [SKAction runBlock:^{
SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:NO];
[self.view presentScene:gameOverScene transition: reveal];
}];
[enemy runAction:[SKAction sequence:#[actionMove, loseAction, actionMoveDone]]];
}
- (void)projectile:(SKSpriteNode *)projectile didCollideWithEnemy:(SKSpriteNode *)enemy {
NSLog(#"Hit");
[projectile removeFromParent];
[enemy removeFromParent];
self.enemysDestroyed++;
if (self.enemysDestroyed > 80) {
SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:YES];
[self.view presentScene:gameOverScene transition: reveal];
}
}
#end
I am not sure if you realize this, but you never actually call addEnemy. Look through the code. You will find a call to addMonster but never addEnemy. Implementing the method is one thing- without calling the method, whatever is inside will never run.