Make CCSprite move towards other CCSprite - objective-c

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?

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 :).

SpriteKit - Rotate sprite towards point

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.

How can I have the same sprite in multiple locations dynamically?

How can I have the same sprite in multiple locations dynamically? I have already seen the other question, but, you can only do that with three sprites. I want to have a dynamic number of sprites. My objective is that I am trying to make, instead of shooting only one bullet, I want it to shoot three or more. I have all of the math done, but, I need to draw the three sprites in a for-loop. Here is what I have so far.
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint pointOne = [touch locationInView:[touch view]];
CGSize size = [[CCDirector sharedDirector] winSize];
CGPoint position = turret.position;
CGFloat degrees = angleBetweenLinesInDegrees(position, pointOne);
turret.rotation = degrees;
pointOne.y = size.height-pointOne.y;
CCSprite *projectile = [CCSprite spriteWithFile:#"projectile.png"];
projectile.position = turret.position;
// Determine offset of location to projectile
int angle = angleBetweenLinesInDegrees(position, pointOne);
int startAngle = angle-15;
int shots = 3;
NSMutableArray *projectiles = [[NSMutableArray alloc] initWithCapacity:shots];
// Ok to add now - we've double checked position
for(int i = 0;i<shots;i++) {
[self addChild:projectile z:1];
int angleToShoot = angle;
int x = size.width;
int y = x*tan(angle);
CGPoint realDest = ccp(x,y);
projectile.tag = 2;
if (paused==0 ) {
[_projectiles addObject:projectile];
// Move projectile to actual endpoint
[projectile runAction:
[CCSequence actions:
[CCMoveTo actionWithDuration:1 position:realDest],
[CCCallBlockN actionWithBlock:^(CCNode *node) {
[_projectiles removeObject:node];
[node removeFromParentAndCleanup:YES];
}],
nil]];
}
}
}
This gives me the error: 'NSInternalInconsistencyException', reason: 'child already added. It can't be added again'
you need to create 3 different sprite and add all 3 of them as a child.
usually for doing stuff like this is better to use a CCBatchNode (take a look to the cocos doc).
With a batchnode you get all the childs be drawn in 1 draw call with the only constrain that all the childs of the batchnode needs to have the texture on the same spriteSheet (or in your case if they have the same "filename")
for just 3 projectiles you wont have performance problems but its the correct way to design it, if you will need to have dozens of projectiles on screen without using a batchnode the game wont run smooth.
to recap:
create a ccbatchnode,
add the batchnode as a child of self (i suppose its ur layer or main node)
create 3 sprites and add them as a child of the batchnode

Cocos2d, moving a sprite to a touch location

myself and my friend have been told Stackoverflow is the place to ask questions regarding code. :)
We're pretty new to Objective-C, Xcode and Cocos2d and are trying to complete simple tasks to further our knowledge.
Looking at our code below, we have made an Ant Object and placed him on screen. We now want to use a touch location to move the ant to to location the user touches.
We're unsure what the correct way to go about it is. At the moment we're staying away from creating classes so have all the code in one place but we can't figure out how to get the ant that's on screen to go to the touch location.
Can anyone help?
//
// HelloWorldLayer.m
// Timer
//
// Created by Lion User on 06/06/2012.
// Copyright __MyCompanyName__ 2012. All rights reserved.
//
// Import the interfaces
#import "HelloWorldLayer.h"
// HelloWorldLayer implementation
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
// on "init" you need to initialize your instance
-(id) init
{
if( (self=[super initWithColor:ccc4(225, 225, 225, 255)])) {
// enable touches
self.isTouchEnabled=YES;
// ask director the the window size
CGSize size = [[CCDirector sharedDirector] winSize];
// set time to zero
myTime = currentTime;
timeLabel = [CCLabelTTF labelWithString:#"00:00" fontName:#"Arial" fontSize:48];
timeLabel.position = CGPointMake(size.width / 2, size.height);
// set label color
timeLabel.color = ccBLACK;
// Adjust the label's anchorPoint's y position to make it align with the top.
timeLabel.anchorPoint = CGPointMake(0.5f, 1.0f);
// Add the time label
[self addChild:timeLabel];
// run create ant method
[self createAnt];
//update
[self schedule:#selector(update:)];
}
return self;
}
-(void)update:(ccTime)dt{
totalTime += dt;
currentTime = (int)totalTime;
if (myTime < currentTime)
{
myTime = currentTime;
[timeLabel setString:[NSString stringWithFormat:#"%02d:%02d", myTime/60, myTime%60]];
}
}
////* method for creating an ant and moving it*////
-(void)createAnt{
////*requirements for animation setup*////
// create cache object to store spritesheet in
CCSpriteFrameCache *cache=[CCSpriteFrameCache sharedSpriteFrameCache];
// add the sprite list to the cache object
[cache addSpriteFramesWithFile:#"antatlas.plist"];
// create frame array to store the frames in
NSMutableArray *framesArray=[NSMutableArray array];
//loop through each frame
for (int i=1; i<3; i++){
// increment the name to include all frames in sprite sheet
NSString *frameName=[NSString stringWithFormat:#"ant%d.png", i];
// create frame object set it to the cache object and add the frameNames to the cache
id frameObject=[cache spriteFrameByName:frameName];
// add the frame object into the array
[framesArray addObject:frameObject];
}
////* setup the actions for running the animation*////
// create animation object and pass the list of frames to it (it expects this as an array)
id animObject=[CCAnimation animationWithFrames:framesArray delay:0.05];
// setup action to run the animation do not return to frame 1
id animationAction=[CCAnimate actionWithAnimation:animObject restoreOriginalFrame:NO];
//loop the animation indefinitely
animationAction = [CCRepeatForever actionWithAction:animationAction];
// move ant action
id moveAnt=[CCMoveTo actionWithDuration:3 position:ccp(60, 160)];
// create sprite, set location and add to layer (starts with the name of the first frame in the animation
CCSprite *ant=[CCSprite spriteWithSpriteFrameName:#"ant1.png"];
ant.position=ccp(240, 160);
[self addChild:ant];
//run animation action
[ant runAction: animationAction];
// run move ant action
[ant runAction:moveAnt];
}
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[touches anyObject];
CGPoint loc=[touch locationInView:[touch view]];
loc=[[CCDirector sharedDirector]convertToGL:loc];
NSLog(#"touch (%g,%g)",loc.x,loc.y);
// Move ant to point that was pressed
[ant runAction:[CCSequence actions:
[CCMoveTo actionWithDuration:realMoveDuration position:loc],
[CCCallFuncN actionWithTarget:self selector:#selector(spriteMoveFinished:)],
nil]];
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
[super dealloc];
}
#end
Seems like your ant is local to your createAnt method. So in the touchesBegan statement, it doesn't "see" the ant. Go over to your header file, and in the #interface declare the ant...
CCSprite *ant;
Then back in your createAnt statement, you can just write...
ant=[CCSprite spriteWithSpriteFrameName:#"ant1.png"];
Now any other method in the implementation ( .m ) file will know what you mean when you write "ant"
Hope it helps!

strange problem playing cocos2d animation from another class?

strange thing. if someone could please,please help me. its 3 days, and i think i am going to be fired :(
i have cocos2d class with animation function,and another xcode class.
if i call from the cocos2d class init function to animation function, the animation is being played when app is starts.
if i call from another class to the cocos2d class-animation, so it does the init function and enter the animation,but i cant see it playing.
so the animation is working only if called from within the cocos class only.
WHY ?
this is how i call the animation :
ran=[[HelloWorld alloc] init];
[ran animation];
this is the animation:
-(void)animation
{
//[self removeChild:background cleanup:YES];
//[b_pic.parent removeChild:b_pic cleanup:YES];
//animation
CCSpriteBatchNode *danceSheet = [ CCSpriteBatchNode batchNodeWithFile:#"head.png"];
[self addChild:danceSheet];
CCSprite *danceSprite = [CCSprite spriteWithTexture:danceSheet.texture rect:CGRectMake(0, 0, 480, 320)];
[danceSheet addChild:danceSprite];
//danceSprite.anchorPoint=CGPointMake(0, 0);
CGSize s = [[CCDirector sharedDirector] winSize];
danceSprite.position = ccp(s.width/2,s.height/2);
CCAnimation *danceAnimation = [CCAnimation animation];
[danceAnimation setDelay:0.1f];
int frameCount = 0;
for (int y = 0; y < 4; y++)
{
NSLog(#"%#",animation);
for (int x = 0; x < 5; x++)
{
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:danceSheet.texture rect:CGRectMake(x*320,y*440,320,440)];
[danceAnimation addFrame:frame];
frameCount++;
if (frameCount == 25)
break;
}
}
Can you call the animation function twice within that same class...Try
[self animation];
and
[[CCScheduler sharedScheduler] scheduleSelector:#selector(animation) forTarget:self
interval:10 paused:NO];
and see if the animation gets called the second time.
If it doesn't which at this point I don't think it will, you should define all that you can in the init and then only run the pieces you need in the animation method (i.e. add the batchNode and Sprite in the init as ivars and reuse them in the animation).
solved , and i dont know why.
but if you want to call a cocos2d function from the outside, you have to do :
[(HelloWorld*)[[[CCDirector sharedDirector] runningScene] getChildByTag:42] HardwareEvent:DollPart];
and tag your layer like :
layer.tag=42;
on the scene method !
thats the only way it works great.