Reposition sprite nodes after SKAction is completed - ios7

I am trying to reposition my sprites after an SKAction moveTo action is completed. I have programmed the enemies to enter the screen from (self.frame.size.width/2, 0). They are moving only on the y axis. I want to reposition them on the initial position when their y position is bigger than (self.frame.size.height) and move them again and again in the same way until the player kills all of the enemies. I am struggling on this point.What part of code should I add? Any ideas? This code might help you understand my implementation :
-(void) addEnemies {
for (int j = 0; j < 6; j++) {
SKSpriteNode* enemy = [SKSpriteNode spriteNodeWithImageNamed:#"player"];
enemy.position = CGPointMake(((self.frame.size.width) -20) - j * (enemy.frame.size.width) , 0);
CGPoint realDest = CGPointMake((enemy.position.x), (self.frame.size.height));
float velocity = 50/1.0;
float realMoveDuration = self.size.height / velocity;
SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
[enemy runAction:actionMove];
[self addChild:enemy];
}
}

-(void)addEnemies{
for (int j = 0; j < 6; j++) {
SKSpriteNode* enemy = [SKSpriteNode spriteNodeWithImageNamed:#"player"];
enemy.position = CGPointMake(((self.frame.size.width) -20) - j * (enemy.frame.size.width) , 0);
[self addChild:enemy];
[self moveEnemyNode:enemy];
}
}
-(void)moveEnemyNode:(SKSpriteNode *)enemy{
enemy.position = CGPointMake(enemy.position.x, 0);
float velocity = 50/1.0;
float realMoveDuration = self.size.height / velocity;
SKAction *actionMove = [SKAction moveToY:self.frame.size.height + enemy.frame.size.height duration:realMoveDuration];
[self runAction:actionMove completion:^{
[self moveEnemyNode:enemy];
}];
}

Create a SKSpriteNode that replicates a roof -> Rectangle(self.frame.size.width,1)
Have it sit along the top edge of the screen with a non-dynamic physics body
Set the "roof" and enemy to contact each other
Call a method on contact that resets there position and calls the SKaction
This allows you to have direct access to each enemy exactly when it hits the top of the screen.
Here is an example of how to create a roof and have enemies interact with it. https://stackoverflow.com/a/24195006/2494064

Related

SpriteKit texture change according to sprite x-axis direction

This seems like a simple problem to solve, yet I can't. As the title suggests, I need my sprite to change to a different texture depending on which way it's going - left or right. Here's what I tried doing:
if (self.sprite.position.x > 0) //also tried "self.sprite.position.x" instead of 0.
{
[self.sprite setTexture:[SKTexture textureWithImageNamed:#"X"]];
}
if (self.sprite.position.x < 0)
{
[self.sprite setTexture:[SKTexture textureWithImageNamed:#"XX"]];
}
Neither worked (Except when the sprite's x-axis == 0 -_-).
I've read this: SpriteKit: Change texture based on direction
It didn't help.
EDIT:
Pardon, I forgot to mention the movement of the sprite is completely random in 2D space. I use arc4random() for that. I don't need any texture changes when it's going up or down. But left & right are essential.
I must be writing something wrong:
if (self.sprite.physicsBody.velocity.dx > 0)
if (self.sprite.physicsBody.velocity.dx < 0)
isn't working for me. Neither is
if (self.sprite.physicsBody.velocity.dx > 5)
if (self.sprite.physicsBody.velocity.dx < -5)
Just to be clear about this sprite's movement one more time: it is randomly moving with the help of the
-(void)update:(CFTimeInterval)currentTime
method. This is achieved like so:
-(void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
if (!spritehMoving)
{
CGFloat fX = [self getRandomNumberBetweenMin:-5 andMax:5];
CGFloat fY = [self getRandomNumberBetweenMin:-5 andMax:7];
int waitDuration = arc4random() %3;
SKAction *moveXTo = [SKAction moveBy:CGVectorMake(fX, fY) duration:1.0];
SKAction *wait = [SKAction waitForDuration:waitDuration];
SKAction *sequence = [SKAction sequence:#[moveXTo, wait]];
[self.sprite runAction:sequence];
}
}
You could just check to see if fX is greater or lower than 0, meaning the 'force' on the x-axis is either positive (right movement) or negative (left movement).
Here is a modified code that should do the trick-
BTW- I've also set the SpritehMoving BOOL at the start and at the end of the movement, otherwise, this method will be called even if the sprite is moving (which there is nothing wrong with it, but it seems to beat the purpose of checking the BOOL value.
if (!spritehMoving)
{
spritehMoving = YES;
CGFloat fX = [self getRandomNumberBetweenMin:-5 andMax:5];
CGFloat fY = [self getRandomNumberBetweenMin:-5 andMax:7];
int waitDuration = arc4random() %3;
SKAction *changeTexture;
if(fX > 0) { // Sprite will move to the right
changeTexture = [SKAction setTexture:[SKTexture textureWithImageNamed:#"RightTexture.png"]];
} else if (fX < 0) { // Sprite will move to the left
changeTexture = [SKAction setTexture:[SKTexture textureWithImageNamed:#"LeftTexture.png"]];
} else {
// Here I'm creating an action that doesn't do anything, so in case
// fX == 0 the texture won't change direction, and the app won't crash
// because of nil action
changeTexture = [SKAction runBlock:(dispatch_block_t)^(){}];
}
SKAction *moveXTo = [SKAction moveBy:CGVectorMake(fX, fY) duration:1.0];
SKAction *wait = [SKAction waitForDuration:waitDuration];
SKAction *completion = [SKAction runBlock:(dispatch_block_t)^(){ SpritehMoving = NO; }];
SKAction *sequence = [SKAction sequence:#[changeTexture, moveXTo, wait, completion]];
[self.sprite runAction:sequence];
}

SpriteKit SKSpriteNode slide

I'm trying determine why slide is happened. I have created 2 nodes, and one of them is moving using SKAction. But when one of them above on other, it does not moving with node under them.
It's a little bit hard to explain, so I have created the video:
https://www.youtube.com/watch?v=F4OuTBVd5sM&feature=youtube_gdata_player
Thanks for your replies!
Parts of code:
CGPoint positionPoint = [valuePoint CGPointValue];
SKSpriteNode *slab = [SKSpriteNode spriteNodeWithTexture:plitaTexture];
slab.name = #"plita";
slab.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:plitaTexture.size];
slab.position = positionPoint;
slab.physicsBody.dynamic = NO;
CGFloat duration = 3.0;
CGFloat firstDuration = duration/(self.size.width/slab.position.x);
SKAction *moveLeftFirstTime = [SKAction moveTo:CGPointMake(0.0 + plitaTexture.size.width/2, positionPoint.y) duration:firstDuration];
SKAction *moveLeft = [SKAction moveTo:CGPointMake(0.0 + plitaTexture.size.width/2, positionPoint.y) duration:5];
SKAction *moveRight = [SKAction moveTo:CGPointMake(self.size.width - plitaTexture.size.width/2, positionPoint.y) duration:5];
SKAction *movingRightLeft = [SKAction sequence:#[moveRight, moveLeft]];
SKAction *moving = [SKAction sequence:#[moveLeftFirstTime,[SKAction repeatActionForever:movingRightLeft]]];
[slab runAction:moving];
[self addChild:slab];
Rabbit init code:
-(instancetype)init {
if (self = [super initWithImageNamed:#"eating-rabbit1"]) {
self.physicsBody = [SKPhysicsBody bodyWithTexture:self.texture size:self.texture.size];
self.physicsBody.allowsRotation = NO;
self.physicsBody.dynamic = YES;
self.physicsBody.friction = 1;
}
return self;
}
The main character is sliding because you are moving the slab by changing its position over time. Imagine you are standing on top of a train that is moving, friction will cause you to move along in the direction of the train. However, if the train magically moved from point A to point B by disappearing and reappearing 1 cm at a time, you will remain at point A. That is what's happening to your main character.
To correct this issue, you will need to move the slabs using physics. Here is an example of how to do that:
slab.physicsBody.affectedByGravity = NO;
slab.physicsBody.dynamic = YES;
// These will help prevent the slab from twisting due to the weight of the main character
slab.physicsBody.angularDamping = 1.0;
slab.physicsBody.mass = 1e6;
// Air resistance will slow slab's movement, set the damping to 0
slab.physicsBody.linearDamping = 0;
Define a set of SKActions that will move the slabs left and right. Adjust the speed and duration variables to achieve the desired motion.
SKAction *moveLeft = [SKAction customActionWithDuration:0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
node.physicsBody.velocity = CGVectorMake(-speed, 0);
}];
SKAction *moveRight = [SKAction customActionWithDuration:0 actionBlock:^(SKNode *node, CGFloat elapsedTime){
node.physicsBody.velocity = CGVectorMake(speed, 0);
}];
SKAction *wait = [SKAction waitForDuration:duration];
SKAction *action = [SKAction sequence:#[moveRight, wait, moveLeft, wait]];
[slab runAction:[SKAction repeatActionForever:action]];
You need to set the slab's physicsBody to be dynamic.
slab.physicsBody.dynamic = YES;
slab.physicsBody.affectedByGravity = NO;
A non-dynamic physicsBody is rendered static and ignores any forces and impulses applied on it. Look up the property in the documentation here.

Position on Node After Rotation

Okay so I have a node that's node.size is a (5,5) rectangle. Its position is (100,100), and its anchor point is the default (0.5, 0.5).
I tried to find the point at the farthest right, but still centre Y. Naturally this returned the point (102.5, 100). This is perfect. I found the point by simply using CGPointMake(node.position + node.size.width/2, node.position);
This works great, but the thing is I want to rotate the node while still knowing what that point is. I was wondering if their was some kind of math I could perform using the nodes zRotation in order to find the new point (the same point on the nodes frame) after the rotation.
To make it clear I want to know where the point that is originally 102.5, 100, is after the node rotates.
Thank you all very much in advance for all of your answers
Let r be the distance from the anchor point of the sprite to the point of interest. In your case,
r = sqrt(dx*dx+dy*dy) = 2.5;
where dx = 2.5 and dy = 0.
Also, let theta be the rotation angle, which is initially zero. We now have a point in polar coordinates, (r, theta), that represents the point (2.5, 0). We can now rotate the point about the sprite's anchor point by changing theta. After rotating the sprite, we convert (r, theta) to cartesian coordinates by
x = r * cos(theta);
y = r * sin(theta);
and add the sprite's position to convert the point to scene coordinates.
x = x + sprite.position.x;
y = y + sprite.position.y;
EDIT: I think this is what you are trying to implement.
#interface MyScene()
#property SKSpriteNode *object1;
#property SKSpriteNode *object2;
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if(self = [super initWithSize:size]) {
_object1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(10,5)];
_object1.position = CGPointMake(100, 100);
[self addChild:_object1];
SKAction *wait = [SKAction waitForDuration: 2.5f];
SKAction *rotate = [SKAction rotateByAngle:3*M_PI/4 duration:2.0f];
SKAction *addObject = [SKAction runBlock:^{
_object2 = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(10,5)];
_object2.zRotation = _object1.zRotation;
CGFloat dx = _object1.size.width / 2;
CGFloat dy = 0;
CGFloat r = sqrtf(dx*dx + dy*dy);
CGFloat x = r*cosf(_object1.zRotation);
CGFloat y = r*sinf(_object1.zRotation);
_object2.position = CGPointMake(_object1.position.x + 2*x, _object1.position.y + 2*y);
[self addChild:_object2];
}];
SKAction *seq = [SKAction sequence:#[wait, rotate, addObject]];
[_object1 runAction:seq];
}
return self;
}
Okay, I had to post code so I had to make this an answer.
So I tried your method 0x141E, and it worked perfectly. The only problem I am having is that whenever the angle is not a multiple of 0.5PI it seems to calculate improperly. Maybe its just the way that I coded it though. The goal of my code was to place one object right beside another by measuring the distance between a left most, middle point of one object with the other objects right most, middle point. This way they would be touching left side with right side... Side-by-Side basically. I placed one object first and added the second using your given algorithm, but the result as I stated above got a little funky in between angles divisible by 0.5 PI. Here's what I've done, it should work if you import the code straight into XCode if you want to see what I am talking about (I'm sorry but I don't have enough rep to attach images or I would give a screenshot):
//This is the MyScene implementation file
#interface MyScene()
{
SKSpriteNode *_object1;
SKSpriteNode *_object2;
}
#end
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if(self = [super initWithSize:size]
{
object1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(10,5)];
[self addChild:object1];
//The wait is just to make it feel smoother and not so sudden when starting up the simulator
SKAction *wait = [SKAction waitForDuration: 2.5f];
SKAction *rotate = [SKAction rotateByAngle:0.25*M_PI duration:2.0f];
SKAction *addObject = [SKAction runBlock:^{
_object2 = [spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(10,5)];
//I set the rotation of the second object to the rotation of the first so they will always be touching side-by-side
_object2.zRotation = _object1.zRotation;
//I used the full width and heigth rather than half to save time. The two ojects are of equal width and height so instead of finding both distances and adding them together I just doubled the distance
CGFloat r = sqrtf(_object2.frame.size.width*_object2.frame.size.width+_object2.frame.size.height*_object2.frame.size.height);
CGFloat x = r*cosf(_object2.zRotation);
CGFloat y = r*sinf(_object2.zRotation);
object2.position = CGPointMake(_object2.position.x + x, _object2.position + y);
[self addChild:_object2];
}
SKAction *seq = [SKAction sequence:#[wait, rotate, addObject]];
[self runAction:seq];
}
}
#end
Thank you again for all of your help thus far, and thank you even more for all of your potential future help.

Moving SKNodes and SKSpriteNodes

I have the following setup in my game:
An SKNode with the name _backgroundLayer
A repeating texture which I have added to the _backgroundLayer 9 times to make a larger background
An SKSprite with the name levelButton, which is added to the _backgroundLayer ([_backgroundLayer addChild:levelButton];).
I use levelButton.anchorPoint = CGPointMake(0.5, 0.5); to make the levelButton have an anchor point in the middle.
Now, when I make levelButton.position = CGPointMake(0, 0); and _backgroundLayer.position = CGPointMake(0, 0); the levelButton's middle is correctly situated in (0, 0) with its middle being the in lower left corner of the screen. So that's fine.
However, if I move the levelButton to be levelButton.position = CGPointMake(100, 0); and _backgroundLayer.position = CGPointMake(-100, 0);, as I show below, the levelButton should still have its middle in (0,0) aka the lower left corner of the screen. However, that is not the case, and the levelButton is more to the right, its something like 50 pixels to the right. But it shouldn't be, since I am moving the _backgroundLayer 100 to the left (-100) and _levelButton 100 to the right (100). It should have stayed in place.
These are basic stuff which I do not understand why they are not working as they should. I am probably doing something wrong but I can not find it even though I've read through the iOS Games by Tutorials and a bunch of tutorials and tips.
Please help.
Now, my code is the following:
#implementation LevelSelectScene
{
SKNode *_backgroundLayer;
}
-(id)initWithSize:(CGSize)size {
/* Setup your scene here */
_backgroundLayer = [SKNode node];
_backgroundLayer.name = #"backgroundLayer";
SKTexture *backgroundTexture = [SKTexture textureWithImageNamed:#"levelSelect"];
int textureID = 0;
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
SKSpriteNode *background = [SKSpriteNode spriteNodeWithTexture:backgroundTexture];
background.anchorPoint = CGPointZero;
background.position = CGPointMake((background.size.width)*i,
(background.size.height)*j);
background.zPosition = 0;
background.name = [NSString stringWithFormat:#"background%d", textureID];
textureID++;
[_backgroundLayer addChild:background];
}
}
[self addChild:_backgroundLayer];
SKSpriteNode * levelButton = [SKSpriteNode spriteNodeWithImageNamed:#"lock"];
levelButton.anchorPoint = CGPointMake(0.5, 0.5);
levelButton.position = CGPointMake(100, 0); //IMPORTANT
levelButton.zPosition = 150;
levelButton.name = #"test";
[_backgroundLayer addChild:levelButton];
_backgroundLayer.position = CGPointMake(-100, 0); //IMPORTANT
}
return self;
}
Found it!
I was changing the scale of the _backgroundLayer SKnode so the coordinates were different.

Erasing Cocoa Drawing done by NSRectFill?

I have an NSBox, inside of which I am drawing small rectangles, with NSRectFill(). My code for this looks like this:
for (int i = 0; i <= 100; i++){
int x = (rand() % 640) + 20;
int y = (rand() % 315) + 196;
array[i] = NSMakeRect(x, y, 4, 4);
NSRectFill(array[i]);
}
This for loop creates 100 randomly placed rectangles within the grid. What I have been trying to do is create a sort of animation, created by this code running over and over, creating an animation of randomly appearing rectangles, with this code:
for (int i = 0; i <= 10; i++) {
[self performSelector:#selector(executeFrame) withObject:nil afterDelay:(.05*i)];
}
The first for loop is the only thing inside the executeFrame function, by the way. So, what I need to do is to erase all the rectangles between frames, so the number of them stays the same and they look like they are moving. I tried doing this by just drawing the background again, by calling [myNsBox display]; before calling executeFrame, but that made it seem as though no rectangles were being drawn. Calling it after did the same thing, so did switching in setNeedsDisplay instead of display. I cannot figure this one out, any help would be appreciated.
By the way, an additional thing is that when I try to run my code for executing the frames, without trying to erase the rectangles in between, all that happens is that 100 more rectangles are drawn. Even if I have requested that 1000 be drawn, or 10,000. Then though, if I leave the window and come back to it (immediately, time is not a factor here), the page updates and the rectangles are there. I attempted to overcome that by with [box setNeedsDisplayInRect:array[i]]; which worked in a strange way, causing it to update every frame, but erasing portions of the rectangles. Any help in this would also be appreciated.
It sounds like you're drawing outside drawRect: . If that's the case, move your drawing code into a view's (the box's or some subview's) drawRect: method. Otherwise your drawing will get stomped on by the Cocoa drawing system like you're seeing. You'll also want to use timers or animations rather than loops to do the repeated drawing.
I recently wrote an example program for someone trying to do something similar with circles. The approach I took was to create an array of circle specifications and to draw them in drawRect. It works pretty well. Maybe it will help. If you want the whole project, you can download it from here
#implementation CircleView
#synthesize maxCircles, circleSize;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
maxCircles = 1000;
circles = [[NSMutableArray alloc] initWithCapacity:maxCircles];
}
return self;
}
- (void)dealloc {
[circles release];
[super dealloc];
}
- (void)drawRect:(NSRect)dirtyRect {
NSArray *myCircles;
#synchronized(circles) {
myCircles = [circles copy];
}
NSRect bounds = [self bounds];
NSRect circleBounds;
for (NSDictionary *circleSpecs in myCircles) {
NSColor *color = [circleSpecs objectForKey:colorKey];
float size = [[circleSpecs objectForKey:sizeKey] floatValue];
NSPoint origin = NSPointFromString([circleSpecs objectForKey:originKey]);
circleBounds.size.width = size * bounds.size.width;
circleBounds.size.height = size * bounds.size.height;
circleBounds.origin.x = origin.x * bounds.size.width - (circleBounds.size.width / 2);
circleBounds.origin.y = origin.y * bounds.size.height - (circleBounds.size.height / 2);
NSBezierPath *drawingPath = [NSBezierPath bezierPath];
[color set];
[drawingPath appendBezierPathWithOvalInRect:circleBounds];
[drawingPath fill];
}
[myCircles release];
}
#pragma mark Public Methods
-(void)makeMoreCircles:(BOOL)flag {
if (flag) {
circleTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(makeACircle:) userInfo:nil repeats:YES];
}
else {
[circleTimer invalidate];
}
}
-(void)makeACircle:(NSTimer*)theTimer {
// Calculate a random color
NSColor *color;
color = [NSColor colorWithCalibratedRed:(arc4random() % 255) / 255.0
green:(arc4random() % 255) / 255.0
blue:(arc4random() % 255) / 255.0
alpha:(arc4random() % 255) / 255.0];
//Calculate a random origin from 0 to 1
NSPoint origin;
origin.x = (double)arc4random() / (double)0xFFFFFFFF;
origin.y = (double)arc4random() / (double)0xFFFFFFFF;
NSDictionary *circleSpecs = [NSDictionary dictionaryWithObjectsAndKeys:color, colorKey,
[NSNumber numberWithFloat:circleSize], sizeKey,
NSStringFromPoint(origin), originKey,
nil];
#synchronized(circles) {
[circles addObject:circleSpecs];
if ([circles count] > maxCircles) {
[circles removeObjectsInRange:NSMakeRange(0, [circles count] - maxCircles)];
}
}
[self setNeedsDisplay:YES];
}
#end