Alright, here we go. I have a cocos2d app, and there are targets that move toward the player. When the player moves, I would like for them to slowly change their destination toward the player again, so they aren't just moving into empty space. Is it possible to change the destination of a sprite mid-runAction?
edit:
This is the code in - (void)changeTargetDest
- (void)changeTargetDest {
NSMutableArray* deleteArray = [[NSMutableArray alloc] init];
for(CCSprite* s in _targets) {
float offX = s.position.x - player.position.x;
float offY = s.position.y - player.position.y;
float adjustX;
float adjustY;
float offDistance = sqrt(powf(offX, 2.0f) + powf(offY, 2.0f));
if(offDistance < 15) {
[deleteArray addObject:s];
deaths++;
[deathLabel setString:[NSString stringWithFormat:#"Deaths: %ld", deaths]];
if(deaths == 0)
[kdLabel setString:[NSString stringWithFormat:#"K/D ratio: %ld.00", score]];
else
[kdLabel setString:[NSString stringWithFormat:#"K/D ratio: %.2f", ((float)score / (float)deaths)]];
}
else {
adjustX = offX * .99;
adjustY = offY * .99;
CGPoint point = CGPointMake(player.position.x + adjustX, player.position.y + adjustY);
[s setPosition:point];
}//else
}//for
for (CCSprite *target in deleteArray) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}
}
This works well, except for one problem. Because the new position is calculated by just taking .99 of the previous offset, the closer the target gets to the player, the more slowly it moves. How can I make its speed constant?
You can stop the action and run a new action each few frames in a scheduled method.
but the better way is to compute the position of targets according to players position and use setPosition to manualy change their positions each frame in your update method.
Related
I am trying to make a platform game for the iphone, using cocos2d and Tiled (for the maps).
All of the tutorials i've seen on the net are using Layers in Tiled to do collision detection.
I want to use objects to do that...not Layers.
With objects you can create custom shapes that can give a better 'reality' into the game.
To give an example of what i mean :
I've drawn the ground as a background and created an object layer on top.
I want to detect player collision with that, instead of the background tile.
Now using the most famous tutorial out there : http://www.raywenderlich.com/15230/how-to-make-a-platform-game-like-super-mario-brothers-part-1
I am trying to rewrite checkForAndResolveCollisions method to check collisions for the objects instead.
The problem is that in Tiled the coordinates system is different than cocos2d. Tiled starts from top left corner, cocos2d from bottom left corner....and not only that...I noticed that the width and height of the object properties in Tiled (probably) dont correspond to the same in iphone devices.
The above rectangle has properties:
its w/h is 480/128 in tiled (for retina devices) which means its probably huge inside the map if i keep them like this. My guess is i have to divide this by 2.
So far i got this:
-(void)checkForAndResolveObjCollisions:(Player *)p {
CCTiledMapObjectGroup *objectGroup = [map objectGroupNamed:#"Collision"];
NSArray* tiles = [objectGroup objects];
CGFloat x, y, wobj, hobj;
for (NSDictionary *dic in tiles) {
CGRect pRect = [p collisionBoundingBox]; //3
x = [[dic valueForKey:#"x"] floatValue];
y = [[dic valueForKey:#"y"] floatValue];
wobj = [[dic valueForKey:#"width"] floatValue];
hobj = [[dic valueForKey:#"height"] floatValue];
CGPoint position = CGPointMake(x, y);
CGPoint objPos = [self tileForPosition:position];
CGRect tileRect = CGRectMake(objPos.x, objPos.y, wobj/2, hobj/2);
if (CGRectIntersectsRect(pRect, tileRect)) {
CCLOG(#"INTERSECT");
CGRect intersection = CGRectIntersection(pRect, tileRect);
NSUInteger tileIndx = [tiles indexOfAccessibilityElement:dic];
if (tileIndx == 0) {
//tile is directly below player
p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + intersection.size.height);
p.velocity = ccp(p.velocity.x, 0.0);
p.onGround = YES;
} else if (tileIndx == 1) {
//tile is directly above player
p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y - intersection.size.height);
p.velocity = ccp(p.velocity.x, 0.0);
} else if (tileIndx == 2) {
//tile is left of player
p.desiredPosition = ccp(p.desiredPosition.x + intersection.size.width, p.desiredPosition.y);
} else if (tileIndx == 3) {
//tile is right of player
p.desiredPosition = ccp(p.desiredPosition.x - intersection.size.width, p.desiredPosition.y);
} else {
if (intersection.size.width > intersection.size.height) {
//tile is diagonal, but resolving collision vertially
p.velocity = ccp(p.velocity.x, 0.0);
float resolutionHeight;
if (tileIndx > 5) {
resolutionHeight = -intersection.size.height;
p.onGround = YES;
} else {
resolutionHeight = intersection.size.height;
}
p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + resolutionHeight );
} else {
float resolutionWidth;
if (tileIndx == 6 || tileIndx == 4) {
resolutionWidth = intersection.size.width;
} else {
resolutionWidth = -intersection.size.width;
}
p.desiredPosition = ccp(p.desiredPosition.x + resolutionWidth , p.desiredPosition.y);
}
}
}
// }
}
p.position = p.desiredPosition; //8
}
- (CGPoint)tileForPosition:(CGPoint)p
{
NSInteger x = (NSInteger)(p.x / map.tileSize.width);
NSInteger y = (NSInteger)(((map.mapSize.height * map.tileSize.width) - p.y) / map.tileSize.width);
return ccp(x, y);
}
I am getting the object x,y,w,h and try to convert them to cocos2d dimensions and sizes.
The above translates to this:
Dimens: 480.000000, 128.000000
Coord: 0.000000, 40.000000
Basically its a mess. And its not working .....at all. The player just falls right through.
I am surprised noone has done collision detection based on objects before...unless i am wrong.
Does anyone know if this can be done or how it can be done ?
Kinda what he does here : https://www.youtube.com/watch?feature=player_detailpage&v=2_KB4tOTH6w#t=30
Sorry for the long post.
Thanks for any answers in advance.
i'm pretty new to Objective-C and Cocos2D so go easy, I know this is basic stuff.
I'm using an array to randomly place 4 gold coin sprites on the screen, and i'm using another sprite (a dragon) to fly around and collect the coins. Obviously, I want the coin to disappear and another to randomly appear (like collecting apples in Snake). My problem is that I don't know how to reference the individual sprites. I've added some handling to the update method but a) I don't think its appropriate and b) it doesn't do anything. Please help. I think I need to utilise:
[self removeChild:sprite cleanup:YES];
But I'm not sure how. This is how i'm populating the screen with rings::
#implementation MainScene {
CCSprite *_hero;
CCPhysicsNode *_physicsNode;
CCSprite *_goldRing;
NSMutableArray *_goldRings;
}
-(void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
_goldRing.physicsBody.collisionType = #"Level";
_goldRing.physicsBody.sensor = TRUE;
_physicsNode.collisionDelegate = self;
_hero.physicsBody.collisionType = #"hero";
CGFloat random = ((double)arc4random() / ARC4RANDOM_MAX);
_meteorites = [NSMutableArray array];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
[self spawnNewGoldRing];
}
-(void)spawnNewGoldRing {
CGFloat randomX = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat randomY = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat rangeX = 200;
CGFloat rangeY = 300;
CCNode *goldRing = [CCBReader load:#"goldRing"];
goldRing.position = ccp(randomX * rangeX, (randomY * rangeY)+100);
[_physicsNode addChild:goldRing];
[_goldRings addObject:goldRing];
}
- (void)update:(CCTime)delta {
_hero.position = ccp(_hero.position.x, _hero.position.y);
if(_hero.position.x <= _hero.contentSize.width/2 * -1.5) {
_hero.position = ccp(_ground.contentSize.width + _hero.contentSize.width/2, _hero.position.y);
NSMutableArray *collectedGoldRings = nil;
for (CCNode *goldRing in _goldRings) {
CGPoint goldRingWorldPosition = [_physicsNode convertToWorldSpace:goldRing.position];
CGPoint goldRingScreenPosition = [self convertToNodeSpace:goldRingWorldPosition];
if (goldRingScreenPosition.x < -goldRing.contentSize.width) {
if (!collectedGoldRings) {
collectedGoldRings = [NSMutableArray array];
}
[collectedGoldRings addObject:goldRing];
}
}
for (CCNode *goldRingToRemove in collectedGoldRings) {
[goldRingToRemove removeFromParent];
[_goldRings removeObject:goldRingToRemove];
// for each removed goldRing, add a new one
[self spawnNewGoldRing];
}
}
}
Would it be too much to ask how to keep count of these too to display a score?
Thanks so much for any help.
EDIT*
NSLog on the array of gold rings
"<goldRing = 0x9b756b0 | Rect = (0.00,0.00,24.00,23.50) | tag = | atlasIndex = -1>"
)
2014-05-08 10:24:32.201 dragonCollector2[10165:60b] Gold Ring Loaded
2014-05-08 10:24:32.201 dragonCollector2[10165:60b] Array:(
"<goldRing = 0x9b77e50 | Rect = (0.00,0.00,24.00,23.50) | tag = | atlasIndex = -1>"
As I see, you are using spritebuilder, so you are using the new version of Cocos2D.
In this version you can track a collision between two types of nodes. In your case, the coins and the hero.
This is as simple as:
1) Set your coin.physicsBody.collisionType = #"coin";
2) Set your hero.physicsBody.collisionType = #"hero";
3) Implement this method:
-(void)ccPhysicsCollisionPostSolve:(CCPhysicsCollisionPair *)pair hero:(CCNode *)nodeA coin:(CCNode *)nodeB{
//this method will be automatically called when the hero hits a coin.
//now call your method to remove the coin and call the method to add another coin.
}
To keep a count of the score, just make an int variable, int score;. In your didLoadFromCCB method, initialize it to 0, score=0; and inside this collision method, just do score++; and maybe do a NSLog(#"Your score:%i",score);
Hope this helps.
for things like that, you do not need to use physics. When your hero will touch a coin, just update the user score (it could be an integer) and the coin position.
in your update method do something like
for(CCSprite *gold in _goldRings){
if(CGRectIntersectsRect(_hero.boundingBox,gold.boundingBox)){
//touched a coin
//do whatever u want
//do not remove the coin from the array, just change it's position!
_userPoints += 1;
gold.position=ccp(arc4random()%200 , arc4random()%300);
break;
}
}
I've been trying to get my MKMapView to detect whether or not a tap was on a tile with alpha > 0. I'm quite new at ObjC and Xcode as well so this functionality is a bit over my head. All help will me greatly appreciated!
So far I've tried many different strategies but always come up short. We have custom classes to replace MKOverlay and MKOverlayView that implement each respectively so I've been trying to grab the tiles when they're created and save them to an array to later reference in the MKMapViewController when the map is touched.
- (NSArray *)tilesInMapRect:(MKMapRect)rect zoomScale:(MKZoomScale)scale
{
NSInteger z = zoomScaleToZoomLevel(scale);
// Number of tiles wide or high (but not wide * high)
NSInteger tilesAtZ = pow(2, z);
NSInteger minX = floor((MKMapRectGetMinX(rect) * scale) / TILE_SIZE);
NSInteger maxX = floor((MKMapRectGetMaxX(rect) * scale) / TILE_SIZE);
NSInteger minY = floor((MKMapRectGetMinY(rect) * scale) / TILE_SIZE);
NSInteger maxY = floor((MKMapRectGetMaxY(rect) * scale) / TILE_SIZE);
NSMutableArray *tiles = nil;
for (NSInteger x = minX; x <= maxX; x++) {
for (NSInteger y = minY; y <= maxY; y++) {
// As in initWithTilePath, need to flip y index to match the gdal2tiles.py convention.
NSInteger flippedY = abs(y + 1 - tilesAtZ);
NSString *tileKey = [[NSString alloc] initWithFormat:#"%d/%d/%d", z, x, flippedY];
if ([tilePaths containsObject:tileKey]) {
if (!tiles) {
tiles = [NSMutableArray array];
}
MKMapRect frame = MKMapRectMake((double)(x * TILE_SIZE) / scale,
(double)(y * TILE_SIZE) / scale,
TILE_SIZE / scale,
TILE_SIZE / scale);
NSString *path = [[NSString alloc] initWithFormat:#"%#/%#.png", tileBase, tileKey];
ImageTile *tile = [[ImageTile alloc] initWithFrame:frame path:path];
[tiles addObject:tile];
[myTiles addObject:tile];
[path release];
[tile release];
}
[tileKey release];
}
}
return tiles;
}
That's where I populate the array which is a "class variable". If I comment out the [tiles addObject:tile]; I get the background of the map drawn but no buildings so I think adding specifically those tiles is correct.
Then in the mapviewController gesture handler function I check if the touch is in the tile.frame which is is for 8 out of 32 (it can be 0 if you click far from the buildings and the total changes when you zoom around, but always gets bigger)which seems like an odd number. But pretending that that works correctly I check the alpha at that point using a modified version of this answerer's function: how to get the RGBA value of UIImage in the specific clicked point
but I don't know if that works for mapView's like it would for imageViews. I think I might need to translate the context but I've never worked with contexts before...
Sorry for so much text! Maybe this isn't even possible? I'll add more code if clarification is needed. Any input would help!
I'm trying to develop an App with an "Around Me"-like feature of a location list with small directional arrows on the side.
Bearing and offset to the different locations hadn't been a problem thanks to Stackoverflow and compensating the compass-lag did well with following tutorial:
http://www.sundh.com/blog/2011/09/stabalize-compass-of-iphone-with-gyroscope/
All the stuff works fine with only one location in that UITableView.
But when there are more than one location, the arrows won't turn smooth and it feels like my iPhone isn't fast enough for calculating the stuff and turning these multiple arrows but I don't know how to do that better.
At the moment I'm trying this (without the locations specific directional offset):
I'm saving all the UIImageViews of all the cells in an array
when getting a new yaw value I loop through the array an actualize all the Images Rotation
if(motionManager.isDeviceMotionAvailable) {
// Listen to events from the motionManager
motionHandler = ^ (CMDeviceMotion *motion, NSError *error) {
CMAttitude *currentAttitude = motion.attitude;
float yawValue = currentAttitude.yaw; // Use the yaw value
// Yaw values are in radians (-180 - 180), here we convert to degrees
float yawDegrees = CC_RADIANS_TO_DEGREES(yawValue);
currentYaw = yawDegrees;
// We add new compass value together with new yaw value
yawDegrees = newCompassTarget + (yawDegrees - offsetG);
// Degrees should always be positive
if(yawDegrees < 0) {
yawDegrees = yawDegrees + 360;
}
compassDif.text = [NSString stringWithFormat:#"Gyro: %f",yawDegrees]; // Debug
float gyroDegrees = (yawDegrees*radianConst);
// If there is a new compass value the gyro graphic animates to this position
if(updateCompass) {
[self setRotateArrow:gyroDegrees animated:YES];
[self commitAnimations];
updateCompass = 0;
} else {
[self setRotateArrow:gyroDegrees animated:NO];
[UIView commitAnimations];
}
};
and the setRotateArrow:animated method:
- (void) setRotateArrow:(float)degrees animated:(BOOL)animated{
UIImage *arrowImage = [UIImage imageNamed:#"DirectionArrow.png"];
for (int i = 0; i<arrowImageViews.count; i++) {
[(UIImageView *)[arrowImageViews objectAtIndex:i] setImage:arrowImage];
CGFloat arrowTransform = degrees;
//Rotate the Arrow
CGAffineTransform rotate = CGAffineTransformMakeRotation(arrowTransform);
[(UIImageView *)[arrowImageViews objectAtIndex:i] setTransform:rotate];
}
}
If anyone got an idea how to get the arrows rotation following smoothly the device rotation I would be very thankful.
I am working on the basis of Ray Wenderlich's tutorial on rotating turrets in Cocos 2d (see here: http://www.raywenderlich.com/25791/rotating-turrets-how-to-make-a-simple-iphone-game-with-cocos2d-2-x-part-2). I need my game to be in portrait mode so I have managed to get the position of the turret correctly:
The turret manages to shoot right, but not left. Here is my code:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (_nextProjectile != nil) return;
// Choose one of the touches to work with
UITouch *touch = [touches anyObject];
CGPoint location = [self convertTouchToNodeSpace:touch];
// Set up initial location of projectile
CGSize winSize = [[CCDirector sharedDirector] winSize];
_nextProjectile = [[CCSprite spriteWithFile:#"projectile2.png"] retain];
_nextProjectile.position = ccp(160, 20);
// Determine offset of location to projectile
CGPoint offset = ccpSub(location, _nextProjectile.position);
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return;
// Determine where you wish to shoot the projectile to
int realX = winSize.width + (_nextProjectile.contentSize.width/2);
float ratio = (float) offset.y / (float) offset.x;
int realY = (realX * ratio) + _nextProjectile.position.y;
CGPoint realDest = ccp(realX, realY);
// Determine the length of how far you're shooting
int offRealX = realX - _nextProjectile.position.x;
int offRealY = realY - _nextProjectile.position.y;
float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
float velocity = 480/1; // 480pixels/1sec
float realMoveDuration = length/velocity;
// Determine angle to face
float angleRadians = atanf((float)offRealY / (float)offRealX);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle = -1 * angleDegrees;
float rotateDegreesPerSecond = 180 / 0.5; // Would take 0.5 seconds to rotate 180 degrees, or half a circle
float degreesDiff = _player.rotation - cocosAngle;
float rotateDuration = fabs(degreesDiff / rotateDegreesPerSecond);
[_player runAction:
[CCSequence actions:
[CCRotateTo actionWithDuration:rotateDuration angle:cocosAngle],
[CCCallBlock actionWithBlock:^{
// OK to add now - rotation is finished!
[self addChild:_nextProjectile];
[_projectiles addObject:_nextProjectile];
// Release
[_nextProjectile release];
_nextProjectile = nil;
}],
nil]];
// Move projectile to actual endpoint
[_nextProjectile runAction:
[CCSequence actions:
[CCMoveTo actionWithDuration:realMoveDuration position:realDest],
[CCCallBlockN actionWithBlock:^(CCNode *node) {
[_projectiles removeObject:node];
[node removeFromParentAndCleanup:YES];
}],
nil]];
_nextProjectile.tag = 2;
}
Thanks for the help!
You are checking x axis instead of Y
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return
;
Did you actually set the application to run in portrait mode or have you just rotated the simulator and repositioned the turret?
If you didn't explicitly set the app to run in portrait your x and y coordinates will be swapped (x will run from the ios button to the top of the phone, not accross as you would expect).
If it is converted properly I have answered this question before :)
This issue here is that you've copy-pasted the math instead of editing it properly for your purposes. There are some assumptions made in Ray's code that rely on you shooting always to the right of the turret instead of up, down, or left.
Here's the math code you should be looking at:
// Determine offset of location to projectile
CGPoint offset = ccpSub(location, _nextProjectile.position);
// Bail out if you are shooting down or backwards
if (offset.x <= 0) return;
Note here that you will have an offset.x less than 0 if the tap location is to the left of the turret, so this is an assumption you took from Ray but did not revise. As gheesse said, for your purposes this should be set to offset.y as you don't want them shooting south of the projectile's original location. But this is only part of the problem here.
// Determine where you wish to shoot the projectile to
int realX = winSize.width + (_nextProjectile.contentSize.width/2);
Here's your other big issue. You did not revise Ray's math for determining where the projectile should go. In Ray's code, his projectile will always end up on a location that is off the screen to the right, so he uses the width of the screen and projectile's size to determine the real location he wants the projectile to go. This is causing your issue since you don't have the assumption that your projectile will always head right - yours will always go up (hint, code similar to this should be used for your realY)
float ratio = (float) offset.y / (float) offset.x;
int realY = (realX * ratio) + _nextProjectile.position.y;
Again, Ray makes assumptions in his math for his game and you haven't corrected it in this realY. Your code has the turret turning in ways that will effect the realX coordinate instead of the realY, which is the coordinate that Ray's always shoot right turret needed to effect.
You need to sit down and re-do the math.