SpriteKit - Rotate sprite towards point - objective-c

I am working on a game where a bunch of enemies spawn outside the screen and move towards the center (where there's a space station). However, I need the spaceships to face in the direction of the center. Currently, I'm using this code:
- (void)rotateNode:(SKNode *)nodeA toFaceNode:(SKNode *)nodeB {
double angle = atan2(nodeB.position.y - nodeA.position.y, nodeB.position.x - nodeA.position.x);
if (nodeA.zRotation < 0) {
nodeA.zRotation = nodeA.zRotation + M_PI * 2;
}
[nodeA runAction:[SKAction rotateToAngle:angle duration:0]];
}
but it only gives this result:
How should I get the sprites to rotate correctly?
All suggestions are appreciated!
EDIT:
I am calling the code above in the init method for the Enemy sprite
-(id)initWithLevel:(int)level andSize:(CGSize)size{
if(self = [super initWithImageNamed:#"alien01"]){
//position and init stuff
[self setPosition:self withSeed:seed andSize:size];
SKNode *center = [[SKNode alloc] init];
center.position = CGPointMake(self.size.width/2, self.size.height/2);
[self rotateNode:self toFaceNode:center];
}
return self;
}

Your problem lies here:
SKNode *center = [[SKNode alloc] init];
center.position = CGPointMake(self.size.width/2, self.size.height/2);
[self rotateNode:self toFaceNode:center];
The center node's position is based on the size of the enemy sprite.
You need to move this part to the scene class. Call the method at the point you initialise the enemy node in the scene. I am assuming the name of the enemy class as EnemySprite and that of the space station node as spaceStation.
EnemySprite *enemy = [[EnemySprite alloc] initWithLevel:1 andSize:CGSizeMake(100, 100)]; //Assuming values as well
[self addChild:enemy];
[self rotateNode:enemy toFaceNode:spaceStation];
This should make it work.

Related

Cocos2D v3 CCParallaxNode scrolling can't keep player in focus

I am very fresher to game development and I need to develop a game like NinjaJump.
I have created a CCParallaxNode to setup scrolling background and added CCPhysicsNode to setup Physics world. I have created player object as shown below.
// Add a sprite
_sprite = [CCSprite spriteWithImageNamed:#"Icon.png"];
_sprite.position = ccp(self.contentSize.width/2,100);
_sprite.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, _sprite.contentSize} cornerRadius:0.0];
_sprite.physicsBody.friction = 0.0f;
_sprite.physicsBody.collisionGroup = #"player";
_sprite.physicsBody.collisionType = #"Player";
//_sprite.physicsBody.collisionMask = 0;
//[self addChild:_sprite];
[foreground addChild:_sprite];
foreground is just a node added into CCScene to easily manage player in-focus.
// code for physics world
_physicsWorld = [CCPhysicsNode node];
_physicsWorld.gravity = ccp(0,-100);
_physicsWorld.debugDraw = YES;
//_physicsWorld.collisionDelegate = self;
[self addChild:_physicsWorld];
_foreground = [CCNode node];
//[self addChild: _foreground];
[_physicsWorld addChild: _foreground];
To make player always visible we have implemented update method as
- (void) update:(CFTimeInterval)currentTime {
// Calculate player y offset
if (_player.position.y > 200.0f) {
//_midgroundNode.position = CGPointMake(0.0f, -((_player.position.y - 200.0f)/4));
_foreground.position = CGPointMake(0.0f, -(_player.position.y - 200.0f));
}
}
I can't understand but the player scrolls off screen anyhow. The code is written in Cocos2d v3.
I have also setup a demo project to show what I implemented: https://www.dropbox.com/s/5s55d00kk80wun4/HumptyJump-Example.zip?dl=0
Any kind of help is appreciated. Thanks in advance.
I could not run your sample code but one thing, I can tell you for sure that there is no need of Physics Engine here.
You just keep your player at a particular height and just move your object right to left.
Apply moving parallax background image and objects to give a feel that your character is moving upwards or downwards.
For reference you can see the games like Swing Drop, made on the same approach as above.
I have implemented 2 fixed views changing their positions while physics body goes down ;).
See the same in action
#interface TestScene () {
CCNode *_background;
CCNode *_foreground;
NSArray *grounds; // Keeps track of each of the 2 views
CCPhysicsNode *_physicsWorld;
}
#implementation TestScene
- (id)init {
self = [super init];
if (!self) return(nil);
// Enable touch handling on scene node
self.userInteractionEnabled = YES;
// Physics world setup
_physicsWorld = [CCPhysicsNode node];
_physicsWorld.gravity = ccp(0,-100);
_physicsWorld.debugDraw = YES;
_physicsWorld.collisionDelegate = self;
[self addChild:_physicsWorld];
// Foreground node in which platforms are there and moves downwards as player goes up
_foreground = [CCNode node];
// Adding background images
CCSprite *default1 = [CCSprite spriteWithImageNamed: #"Default.png"];
[default1 setAnchorPoint: ccp(0, 0)];
CCSprite *default2 = [CCSprite spriteWithImageNamed: #"Default.png"];
[default2 setAnchorPoint: ccp(0, 0)];
[default2 setPosition: ccp(0, default1.contentSize.height)];
[_foreground addChild: default1];
[_foreground addChild: default2];
// Adding into array
grounds = #[default1, default2];
// Adding into physics world
[_physicsWorld addChild: _foreground];
// creating player
_player = [CCSprite spriteWithImageNamed: #"Assets.atlas/Player.png"];
[_player setPosition: ccp(160.0f, 160.0f)];
_player.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, _player.contentSize} cornerRadius:0]; // 1
_player.physicsBody.collisionGroup = #"playerGroup"; // 2
_player.physicsBody.collisionType = #"player";
//[_physicsWorld addChild:_player];
[_foreground addChild: _player];
// Multiple platforms can be added into body, they are static entities only
PlatformNode *platform = (PlatformNode *)[PlatformNode node];
[platform createPlatformAtPosition:CGPointMake(110, 50) ofType: PLATFORM_NORMAL];
[_foreground addChild: platform];
return self;
}
- (void) update:(CFTimeInterval)currentTime {
// Take background and physics world down, 163 was Y position of the player
_physicsWorld.position = ccp(_physicsWorld.position.x, 163 - _player.position.y);
// loop the ground
for (CCNode *ground in grounds) {
// Get the world position
CGPoint groundWorldPosition = [_physicsWorld convertToWorldSpace: ground.position];
// Get screen position
CGPoint groundScreenPosition = [self convertToNodeSpace: groundWorldPosition];
NSLog(#"Positioning ground ---> world: (%f, %f) & screen: (%f, %f)", groundWorldPosition.x, groundWorldPosition.y, groundScreenPosition.x, groundScreenPosition.y, nil);
if (groundScreenPosition.y <= -ground.contentSize.height) {
ground.position = ccp(ground.position.x, ground.position.y + 2 * ground.contentSize.height);
break;
}
}
}
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
// Take the player upside
[_player.physicsBody applyImpulse: ccp(0, 215)];
}
#end
This is how I coded the background :).

Why does this code not properly center scene when doing UIPanGestureRecognizer?

I am a beginner developing a program for the AppStore using Xcode's sprite kit. I made a 'test' program so I can try out new things before adding it to my game. Right now I am fiddling around with swiping a scene - I have an SKNode (called "background") where I am adding several children as SKSpriteNodes. One sprite node is visible on the initial scene (the center, position 160,240), and two more that are not visible: to the left of the scene (position -160,240), and to the right of the scene (position 480,240).
I would like my game to be able to swipe left or right, and when it swipes left or right, the view will auto-center itself (with animation) to one of the three SKSpriteNodes. My code using the UIPanGestureRecognizer to move the background node works properly, and my code for auto-centering the view works MOSTLY (background position set to 0,0 or -320,0 or +320,0), but sometimes it has a strange offset and doesn't completely center itself (for example, the background position will be 7,0 or -34,0 when I pan right or left). What am I doing wrong?
P.S: I am using code from RayWenderlich's "iOS Games" for the SKTMoveEffect. I also want to note that if I make the function f(t)=t, there is no problem (at least in my several tests), but f(t)=t^2 or anything else seems to have an issue; if it helps to see the code for this I can post it too
#implementation LTMyScene
{
SKNode *background;
SKSpriteNode *spaceship1, *spaceship2;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
background=[SKNode node];
[self addChild:background];
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
myLabel.text = #"Hello, World!";
myLabel.fontSize = 30;
myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[background addChild:myLabel];
spaceship1=[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship1.position=CGPointMake(-self.size.width/2, self.size.height/2);
spaceship1.anchorPoint=CGPointMake(0.5, 0.5);
[background addChild:spaceship1];
spaceship2=[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship2.position=CGPointMake(self.size.width*3/2, self.size.height/2);
spaceship2.anchorPoint=CGPointMake(0.5, 0.5);
[background addChild:spaceship2];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
UIPanGestureRecognizer *swipe = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dragPlayer:)];
[[self view] addGestureRecognizer:swipe];
}
-(void)dragPlayer: (UIPanGestureRecognizer *)gesture {
[[[self view] layer] removeAllAnimations];
CGPoint trans = [gesture translationInView:self.view];
SKAction *moveAction = [SKAction moveByX:trans.x y:0 duration:0];
[background runAction:moveAction];
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
if([gesture state] == UIGestureRecognizerStateEnded) {
CGFloat finalX=0;
if (abs(background.position.x)<self.size.width/2) {
finalX=0;
} else if (abs(background.position.x)<self.size.width*3/2) {
finalX=self.size.width*background.position.x/abs(background.position.x);
}
NSLog(#"%f",finalX);
SKTMoveEffect *upEffect =
[SKTMoveEffect effectWithNode:background duration:0.5
startPosition:background.position
endPosition:CGPointMake(finalX, 0)];
upEffect.timingFunction = ^(float t) {
// return powf(2.0f, -3.0f * t) * fabsf(cosf(t * M_PI * 1.0f)) //has bounce
// return (-1.0f*t*t+1) //no bounce ... for parabola this is only solution with (1,0) and (0,1) as intercepts and vertex at (1,0)
return (t*t)
;};
SKAction *upAction = [SKAction actionWithEffect:upEffect];
[background runAction:upAction];
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
NSLog(#"%f,%f",background.position.x,background.position.y);
}
#end
You have to remember that you are moving the larger background node with other nodes as its children. Keeping that in mind, the code you need to center on a specific node is:
_worldNode.position = CGPointMake(-(myNode.position.x-(self.size.width/2)), -(myNode.position.y-(self.size.height/2)));
The above assumes that your main background "canvas" is called _worldNode and your target node is a child of _worldNode

Detecting collision between objects in array in sprite kit

i'm trying to make a game where obstacles fall from the top of the screen and the player has to try to avoid them, but the didBeginContact method is not getting called and its driving me crazy. here are the parts of my code relevant to the node collisions...
//myScene.h
#interface MyScene : SKScene <SKPhysicsContactDelegate>
//myScene.m
#implementation MyScene
static const uint32_t playerCategory = 0x1 << 0;
static const uint32_t obstacleCategory = 0x1 << 1;
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
//setup scene
self.backgroundColor = [SKColor whiteColor];
self.physicsWorld.gravity = CGVectorMake(0, 0);
self.physicsWorld.contactDelegate = self;
obstacles = [NSMutableArray array];
//add player node
player = [SKNode node];
SKSpriteNode *spritePlayer = [SKSpriteNode spriteNodeWithImageNamed:#"black.png"];
spritePlayer.size = CGSizeMake(50, 50);
spritePlayer.position = CGPointMake(self.frame.size.width/2, 100);
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.frame.size];
player.physicsBody.dynamic = NO;
player.physicsBody.mass = 0.02;
[player addChild:spritePlayer];
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.contactTestBitMask = obstacleCategory;
player.physicsBody.collisionBitMask = 0;
player.physicsBody.usesPreciseCollisionDetection = YES;
[self addChild:player];
[self spawnNewObstacles];
}
return self;
}
- (void)spawnNewObstacles
{
//spawn obstacles
obstacle = [SKSpriteNode spriteNodeWithImageNamed:#"black.png"];
obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle.frame.size];
obstacle.physicsBody.dynamic = YES;
obstacle.physicsBody.categoryBitMask = obstacleCategory;
obstacle.physicsBody.contactTestBitMask = playerCategory;
obstacle.physicsBody.collisionBitMask = 0;
obstacle.physicsBody.usesPreciseCollisionDetection = YES;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"didBeginContact Called!");
}
the didBeginContact method is not getting called and i can't find what wrong with my code
all help is appreciated...
sknode size isn't set correctly because there is no image reference, and if you log its size if should be inf, and when I tested your code I returned an exception.
to fix this error change the following line
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.frame.size]; change that to
to the following and it should work
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spritePlayer.size];
as I said below just ditch the player sknode, it is simply redundant unless there is a strong reason to use it.
///////previous answer
I couldn't help but to notice that you didn't add the obstacle node to any parent node, namely the scene, like what you did with the player node using this line
[self addChild:player];
you need to do the same thing with the obstacle node
[self addChild:obstacle];
, I am surprised that you didn't mention not seeing those nodes on the screen, unless you added them in a different section of your code that you did not include, then at this point I suggest you log the positions of both nodes to give an idea of how things are located on the screen.
as for the array you won't need it all you have to do is give a name for every obstacle node like #"obstacle", then you can use the following method to access all the nodes that has that name
obstacle.name = #"obstacle";
/////later when you want to check for node removal
[self enumerateChildNodesWithName:#"obstacle" usingBlock:^(SKNode *node, BOOL *stop) {
SKSpriteNode *child = (SKSpriteNode*)node;
if (child.position.y <0){ //or any arbitrary condition
[child removeFromParent];
}
}];
Lastly, I can't see the point from using the player as a sknode and then adding an skspritenode to it, unless I am missing something !!! :) . You can simply ditch the player sknode all together and simply use the spritePlayer node instead.
Hope this helps.

Make CCSprite move towards other CCSprite

I know this has been asked a lot before, but I can't find the solution. I have a CCSprite on the screen, the player, that is steered with the accelerometer. Then on top of the screen other CCSprites are spawned every 2 seconds, the enemies. I want all the enemies to follow the player, if the player moves the player the enemies should change direction and go towards that CCSprite. This is my code this far:
- (void)spawnEnemies
{
monsterTxt = [[CCTexture2D alloc] initWithCGImage:[UIImage imageNamed:#"obj.png"].CGImage resolutionType:kCCResolutionUnknown];
monster = [CCSprite spriteWithTexture:monsterTxt];
...
//random spawn position etc.
CCMoveTo *movemonster = [CCMoveTo actionWithDuration:7.0 position:ccp(_rocket.boundingBox.origin.x, _rocket.boundingBox.origin.y)];
[monster runAction:[CCSequence actions:movemonster, nil]];
[_monsters addObject:monster]; //adds the sprite to a mutable array
}
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
...
//determines new position and move sprite there
[monster stopAllActions];
CCMoveTo *movemonster = [CCMoveTo actionWithDuration:7.0 position:ccp(_rocket.boundingBox.origin.x, _rocket.boundingBox.origin.y)];
[monster runAction:[CCSequence actions:movemonster, nil]];
}
Now when I start the game the sprites are going towards the player, but when the player moves the enemies doesn't update their destination, they just continue down and stops at the y-coordinate of the player. And after a while the app crashes. What am I doing wrong?
My guess is that the problem may be in that section of code that you did not post.
In v2.1 of Cocos2d, to receive accelerometer measurements in your layer:
Do this in your init or onEnterTransitionDidFinish call:
self.accelerometerEnabled = YES;
And overload the is method:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
CCLOG(#"Acceleration (%f,%f,%f)",
acceleration.x,
acceleration.y,
acceleration.z);
// Do something meaningful...you probably want to send a notification to
// some other scene/layer/logic. It should cache that the acceleration value
// and then do something on an update(...) call with it. Then reset it so that
// it does not get used the next update cycle untila new value comes in (here).
}
It seems you have this...and you have indicated you are receiving them...so I'm a bit confused.
SO (SANITY CHECK TIME)...
I created an example project (here) which has a single player sprite being chased by multiple monster sprites. When the user touches the screen (I did not use the accelerometer), the player sprite changes location and the sprites "chase" him. I added some random offset to their target positions so they would not all cluster on top of the player.
Here is the code for (the most important parts of) the file, which is a modified version of the HellowWorldLayer that comes with a cocos2d v2.1 project (I created it from the template):
-(void)createPlayer
{
playerMoved = NO;
if(self.player != nil)
{
[self removeChild:player];
self.player = nil;
}
CGSize scrSize = [[CCDirector sharedDirector] winSize];
CCSprite* playerSprite = [CCSprite spriteWithFile:#"Icon.png"];
playerSprite.scale = 2.0;
playerSprite.position = ccp(scrSize.width/2,scrSize.height/2);
[self addChild:playerSprite];
self.player = playerSprite;
}
-(void)createMonsters
{
const int MAX_MONSTERS = 4;
if(self.monsters.count > 0)
{ // Get rid of them
for(CCSprite* sprite in monsters)
{
[self removeChild:sprite];
}
[self.monsters removeAllObjects];
}
CGSize scrSize = [[CCDirector sharedDirector] winSize];
for(int idx = 0; idx < MAX_MONSTERS; idx++)
{
CCSprite* sprite = [CCSprite spriteWithFile:#"Icon.png"];
float randomX = (arc4random()%100)/100.0;
float randomY = (arc4random()%100)/100.0;
sprite.scale = 1.0;
sprite.position = ccp(scrSize.width*randomX,scrSize.height*randomY);
[self addChild:sprite];
[monsters addObject:sprite];
}
}
-(void)update:(ccTime)delta
{
if(playerMoved)
{ // Modify all the actions on all the monsters.
CGPoint playerPos = player.position;
for(CCSprite* sprite in monsters)
{
float randomX = (1.0)*(arc4random()%50);
float randomY = (1.0)*(arc4random()%50);
CGPoint position = ccp(playerPos.x+randomX,playerPos.y+randomY);
[sprite stopAllActions];
[sprite runAction:[CCMoveTo actionWithDuration:3.0 position:position]];
}
playerMoved = NO;
}
}
I have a retained array of CCSprite* objects (monsters) and a CCSprite* for the player. I have a flag to tell me if the player has changed position (player moved). In the update loop, if the player has moved, stop all the actions on the monsters and update them.
On the screen it looks like this:
And then when I move the main sprite...
They follow it...
Was this helpful?

Background position alternating position every time I build in Xcode 5

I am just beginning programming games in Xcode 5 using cocos2D and found this pretty strange. I'm starting out fresh on a menu scene and was importing a background and a button. The following code positions my background just fine sometimes, but then other times it's adjusted upwards about 50 pixels (my simulator is on it's side, or it's length is lying horizontal, so technically it's shifting about -50 pixels in the "width" direction, although to the simulator it shifts upwards).
Note I found that every time I run my program, it alternates between being properly aligned and shifted. Why would this be happening ugh! Below is the code I'm using.
Note 2 I'm using Kobold2D and the framework I'm using has a config.lua that's a little beyond my scope for me to understand everything. The config.lua code is located here http://snipt.org/BEt6
-(id) init
{
if ((self = [super init]))
{
CCSprite *sprite = [CCSprite spriteWithFile:#"background.png"];
sprite.anchorPoint = CGPointZero;
[self addChild:sprite z:-1];
sprite = [CCSprite spriteWithFile:#"button.png"];
sprite.anchorPoint = CGPointZero;
sprite.position = CGPointMake(200,200);
[self addChild:sprite z:0];
}
return self;
}
The only problem with your code is that you are initializing sprite twice, which is NOT very good.
I would think it is causing your problem.
Try this code:
-(id) init
{
if ((self = [super init]))
{
CCSprite *sprite = [CCSprite spriteWithFile:#"background.png"];
sprite.anchorPoint = CGPointZero;
[self addChild:sprite z:-1];
CCSprite *sprite2 = [CCSprite spriteWithFile:#"button.png"];
sprite2.anchorPoint = CGPointZero;
sprite2.position = CGPointMake(200,200);
[self addChild:sprite2 z:0];
}
return self;
}