I've got a character in a game, and it's supposed to shoot a bullet. I've set everything up for the character, and have setup the path for the bullet to travel through. Here's the code I'm using:
//The destination of the bullet
int x = myCharacter.position.x - 1000 * sin(myCharacter.zRotation);
int y = myCharacter.position.y + 1000 * cos(myCharacter.zRotation);
//The line to test the path
SKShapeNode* beam1 = [SKShapeNode node];
//The path
CGMutablePathRef pathToDraw = CGPathCreateMutable();
//The starting position for the path (i.e. the bullet)
//The NozzleLocation is the location of the nozzle on my character Sprite
CGPoint nozzleLoc=[self convertPoint:myCharacter.nozzleLocation fromNode:myCharacter];
CGPathMoveToPoint(pathToDraw, NULL, nozzleLoc.x, nozzleLoc.y);
CGPathAddLineToPoint(pathToDraw, NULL, x, y);
//The bullet
SKSpriteNode *bullet = [SKSpriteNode spriteNodeWithTexture:bulletTexture size:CGSizeMake(6.f, 6.f)];
bullet.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:3 center:bullet.position ];
[bullet.physicsBody setAffectedByGravity:NO];
[bullet.physicsBody setAllowsRotation:YES];
[bullet.physicsBody setDynamic:YES];
bullet.physicsBody.categoryBitMask = bulletCategory;
bullet.physicsBody.contactTestBitMask = boundsCategory;
//These log the correct locations for the character
//and the nozzle Location
NSLog(#"myposition: %#",NSStringFromCGPoint(myCharacter.position));
NSLog(#"nozloc: %#",NSStringFromCGPoint(nozzleLoc));
bullet.position = [bullet convertPoint:nozzleLoc fromNode:self];
[self addChild:bullet];
NSLog(#"Bullet Position: %#",NSStringFromCGPoint(bullet.position));
[bullet runAction:[SKAction followPath:pathToDraw duration:6.f]];
//I'm using this to test the path
beam1.path = pathToDraw;
[beam1 setStrokeColor:[UIColor redColor]];
[beam1 setName:#"RayBeam"];
[self addChild:beam1];
This is what I get from the NSLogs I'm using above:
myposition: {122.58448028564453, 109.20420074462891}
nozloc: {145.24272155761719, 77.654090881347656}
Bullet Position: {145.24272155761719, 77.654090881347656}
So everything should work, right? But the issue that I'm having is that the bullets are shot from a slightly different location. You can see from the image below:
I aligned the character, so that the bullets start from that little square in the middle. This way you can see the distance from where the bullet are supposed to start (in front of the gun my character is holding), and the square in the middle of the screen.
The bullets travel correctly on a straight line, and the angle of the line is the same as the angel of the path ( the path and the line bullets form are parallel as you can see from the picture). When I move my line, the bullets also move in the same way. I think the issue is the point conversion between nodes, but I've tried both
[self convertPoint:myCharacter.nozzleLocation fromNode:myCharacter]
[bullet convertPoint:nozzleLoc fromNode: self]
[self convertPoint:nozzleLoc toNode:bullet]
However, they all result in the exact same starting point for the bullet. Do you know why I'm having this issue? Is it because I'm scaling down my character sprite using setScale (I'm setting it to 0.3)?
Thanks a lot in advance for your help.
This isn't your issue but nozzleLoc is already in the scene's coordinate space so it should be:
bullet.position = nozzleLoc;
This will save a quick second conversion from having to be calculated.
followPath:duration: is the same as followPath:asOffset:orientToPath:duration: with asOffset: YES -- it's using your current position as the origin of the path. See the documentation here.
To fix it you'll want the asOffset to be NO (requires the full method call above) or you can leave it as is and take out the line of code setting the bullet's position.
Related
I currently have two SKSpriteNodes on top of each other like so (The white part is one and the brown round circle part is the other one):
The white part is positioned 1/3 the way down on top of the round brown sprite node. In the picture, the brown round part has a SKPhysicsBody applied to it already as seen by the light blue outline around it. When I add a SKPhysicsBody around the top ovalish white part it pushes it up and not in the position I wanted it.
How can I have a SKPhysics body coving both bodies of sprites but not have the physics bodies push on one another which makes the white part move upwards? I would like the white part to stay in the position it was in the first image.
Thanks for anyone help!
Here's the code I used for the SKPhysicsBody's:
// create, position, scale & add the round body
roundBody = SKSpriteNode( imageNamed: "roundBody" )
roundBody.position = CGPoint( x: 207, y: 70 )
roundBody.zPosition = 1
roundBody.xScale = 0.3
roundBody.yScale = 0.3
// add sprite node to view
self.addChild( roundBody )
// create, position, scale & add the head
theHead!.position = CGPoint( x: 207, y: roundBody.frame.maxY / 1.15 )
theHead!.zPosition = 2
theHead!.xScale = 0.3
theHead!.yScale = 0.3
// setting up a SKPhysicsBody for the round body
roundBody.physicsBody = SKPhysicsBody( circleOfRadius: roundBody.size.width / 4 )
roundBody.physicsBody!.dynamic = true
roundBody.physicsBody!.affectedByGravity = true
roundBody.physicsBody!.allowsRotation = false
roundBody.physicsBody!.pinned = false
// setting up a SKPhysicsBody for the head
theHead!.physicsBody = SKPhysicsBody(circleOfRadius: theHead!.size.width / 2 )
theHead!.physicsBody!.dynamic = true
theHead!.physicsBody!.affectedByGravity = false
theHead!.physicsBody!.allowsRotation = false
theHead!.physicsBody!.pinned = false
I was able to figure out that if you use SKPhysicsJointPin it does the exact thing I needed! (Which was to basically pin a sprite head on it's body and share a physics body)
let joinTogether = SKPhysicsJointPin.jointWithBodyA(
roundBody.physicsBody!,
bodyB:theHead!.physicsBody!,
anchor: GPointMake(CGRectGetMidX(roundBody.frame),
CGRectGetMinY(theHead!.frame)))
scene!.physicsWorld.addJoint(joint)
Hope this helps someone in the future!
If you never want it to move, set it's .dynamic property to false. Then other objects may or may not bounce/collide with it (depending upon their collisionBitMask) but it won't move in response to those collisions.
Your own answer is correct and a better solution but just to explain further.
The reason of the bodies colliding is that by default a physics body's collision bit mask is set to all categories which means it will collide with everything. In your code you are not calling
roundBody.physicsBody?.collisionBitMask = ...
which is why its using the default values.
To change that you could give your body and head a different collisionBitMask.
Im sure you will deal with this sooner or later when you handle collisions
Also as a tip it's a better idea to not force unwrap the physics bodies unless you have too, even though you know they exist. So you should replace your ! with ? whenever possible.
Thanks in advance. I currently have a sprite controlled by the tilting of the device and would like every so often(NSTimer) for another sprite to appear behind it following it with the same physics. I have it set up, but i need the position to update every second so it follow behind it. How would I go about this? This is what I've tried/done.
let snakeBodyY = snakeHead.position.y - 5
let snakeBodyX = snakeHead.position.x - 5
snakeHead = SKSpriteNode(imageNamed: "snakeHead")
snakeHead.size = CGSize(width: 60, height: 60)
snakeHead.position = CGPoint(x: self.frame.midX, y: self.frame.midY - 50)
self.addChild(snakeHead)
snakeBody = SKSpriteNode(imageNamed: "snakeBody")
snakeBody.size = CGSize(width: 50, height: 50)
snakeBody.position.x = snakeBodyX
snakeBody.position.y = snakeBodyY
self.addChild(snakeBody)
Depends on how advance you need it,
if this is a free range 2D world you could assign the each new body to the tail so that the positions are relative to the snake, and always try to make the body parts move to the center of the previous body part up to the head.
Set up your sprite like this:
let snake = SKNode...
let head = SKSpriteNode...
let tail = SKSpriteNode...
//make sure you set up your positions for each body part, they should be relative to snake, not the screen
//setup collision so that tail and head can collide
head.physicsBody = SKPhysicsBody...
head.categoryBitMask = 1
head.collisionBitMask = 1
tail.physicsBody = SKPhysicsBody...
tail.categoryBitMask = 1
tail.collisionBitMask = 1
//setup restitution so that they do not bounce when colliding
head.restitution = 0
tail.restitution = 0
//add the parts to snake
snake.addChild(head)
snake.addChild(tail)
//Now you have a snake with a head and tail create an action to move the snake tail to the center of head
let moveToCenter = SKAction.moveTo(head.position, duration:0)
tail.runAction(moveToCenter)
//this will move the talk to try to get to the center of head, but collision physics will stop it from overlapping.
//Now everytime you want to add a new body
let body = SKSpriteNode...
body.physicsBody = SKPhysicsBody...
body.categoryBitMask = 1
body.collisionBitMask = 1
body.restitution = 0
snake.addChild(body)
let moveToCenter = SKAction.moveTo(tail.position, duration:0)
body.runAction(moveToCenter)
tail = body //lets make the tail the new piece to add
//Then on every update loop. you want to go through the children and do something like this
let previousChild = head
for child in snake.children
{
if child == head
{
continue;
}
child.removeAllActions()
let moveToCenter = SKAction.moveTo(previousChild.position, duration:0)
child.runAction(moveToCenter)
previousChild = child
}
Now if you find that this code may end up running slow for you, and don't mind a more stiff approach, you could also look up SKAction.followPath. Now I am not going to get into the details here, but basically what you need to do is record how your snake moves, then you generate a path with it, and use SKAction.followPath to make all the body parts move along the path.
If you are working in a 2d tiled world that only allows vertical and horizontal movement, you create an array of your sprites, and every time your snake makes a movement, you go down the snake and change its position to the previous body parts position
I have a very strange behaviour when rendering SKSpriteNodes using an atlas via imageNamed.
sprite = SKSpriteNode(imageNamed: triangle.triangleType1.spriteName)
sprite.size.height = TileHeight
sprite.size.width = TileWidth
sprite.zRotation = DegreesToRadians(triangle.rotation)
sprite.position = pointForColumn(triangle.column, row:triangle.row, columnOffset: TileWidth, rowOffset: TileHeight, tw: TileWidth,th: TileHeight)
triangleLayer.addChild(sprite)
triangle.sprite1 = sprite
addedTriangles++
print(sprite)
Some of those Sprites are not rendered adding them to the layer (another SKSpriteNode) for first time - printing the "sprite" shows that they're size 0,0 and the image shows "TrViolet" instead of "TrViolet#2x.png".
Other ones are rendered as they supposed to be.
Removing all the nodes and executing the same code for a second time DOES work though.
When I put the image in xcassets it works like a charm - even for the first time.
Is there any way to debug this kind of behavior or you have any idea on what might be happening here?
I have a square barrier that has edges defined at run time based on where the user puts the position and rotation of my barrier.
b2BodyDef barrierBodyDef;
barrierBodyDef.type = b2_staticBody;
barrierBodyDef.position.Set(curBarrier
.position.x/PTM_RATIO, curBarrier.position.y/PTM_RATIO);
barrierBodyDef.userData = curBarrier;
b2Body *barrierBody;
barrierBody = _world->CreateBody(&barrierBodyDef);
b2EdgeShape barrierEdge;
b2FixtureDef barrierShapeDef;
barrierShapeDef.shape = &barrierEdge;
barrierShapeDef.friction = 1.0f;
barrierEdge.Set(b2Vec2((x1)/PTM_RATIO, (y1)/PTM_RATIO),
b2Vec2((x2)/PTM_RATIO, (y2)/PTM_RATIO));
barrierBody->CreateFixture(&barrierShapeDef);
barrierEdge.Set(b2Vec2((x2)/PTM_RATIO, (y2)/PTM_RATIO),
b2Vec2((x3)/PTM_RATIO, (y3)/PTM_RATIO));
barrierBody->CreateFixture(&barrierShapeDef);
barrierEdge.Set(b2Vec2((x3)/PTM_RATIO, (y3)/PTM_RATIO),
b2Vec2((x4)/PTM_RATIO, (y4)/PTM_RATIO));
barrierBody->CreateFixture(&barrierShapeDef);
barrierEdge.Set(b2Vec2((x4)/PTM_RATIO, (y4)/PTM_RATIO),
b2Vec2((x1)/PTM_RATIO, (y1)/PTM_RATIO));
barrierBody->CreateFixture(&barrierShapeDef);
I now want to delete these edges, so that the user can re-position the barrier.
How do I go about removing the edges between point x1,y1, -> x4,y4, so that they are immune to collisions.
I am a bit new to Box2D.
Keep the fixture when creating it (local var here for example, you should use an ivar):
b2Fixture* barrierFixture = barrierBody->CreateFixture(&barrierShapeDef);
And later destroy the fixture:
barrierBody->DestroyFixture(barrierFixture);
barrierFixture = nil;
You can also use the body's GetFixtureList() to iterate over fixtures.
What you can not do is to add or remove shapes from a fixture, or modify the shape's vertices. To remove a point from a body's shape, you'll have to destroy the fixture and replace it with a new one.
It is not necessary to recreate the entire body, in fact that can be problematic since you'll probably want to preserve the body's current state (not just position but also linear and angular velocities and perhaps other things too).
I'm new here so i hope you can help me.
I am following a tutorial on making simple cocos2d game
Ray Wenderlich's Tutorial
I implemented it on another game a jumping one like doodle jump.
in the said tutorial the monsters/targets are moving freely coming from the right to the left side of the screen. when i implement it on my app the monsters are like flying from left to right. What if i want the monsters to stand on the platforms just like the one on doodle jump? what particular things will i do?
PS:i tried some other things on google but none works
Here is the code of the monsters/targets:
- (void)initPlatforms {
// NSLog(#"initPlatforms");
currentPlatformTag = kPlatformsStartTag;
while(currentPlatformTag < kPlatformsStartTag + kNumPlatforms) {
[self initPlatform];
currentPlatformTag++;
}
[self resetPlatforms];
}
- (void)initPlatform {
CGRect rect;
switch(random()%2) {
case 0: rect = CGRectMake(608,64,102,36); break;
case 1: rect = CGRectMake(608,128,90,32); break;
}
AtlasSpriteManager *spriteManager = (AtlasSpriteManager*)[self getChildByTag:kSpriteManager];
AtlasSprite *platform = [AtlasSprite spriteWithRect:rect spriteManager:spriteManager];
[spriteManager addChild:platform z:3 tag:currentPlatformTag];
}
-(void)addTarget {
Sprite *target = [Sprite spriteWithFile:#"komodo.png"];
target.position = ccp(300,200);
[self addChild:target];
CGSize winSize = [[Director sharedDirector]winSize];
int minX = target.contentSize.height/2;
int maxX = winSize.height -target.contentSize.height/2;
int rangeX = maxX - minX;
int actualX = (arc4random() % rangeX) +minX;
int minDuration = 2.0;
int maxDuration = 4.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
id actionMove = [MoveTo actionWithDuration:actualDuration position:ccp(-target.contentSize.width,actualX)];
id actionMoveDone = [CallFuncN actionWithTarget:self selector:#selector(spriteMoveFinished:)];
[target runAction:[Sequence actions:actionMove, actionMoveDone,nil]];
target.tag = 1;
[_targets addObject:target];
}
Thanks to those who will help... you are so nice.
This is a pretty broad question unfortunately, which makes it difficult to answer in any definitive terms. If you are hoping to create actual platforms that can be bounced on (in a doodle jump manner) you are going to need to implement collision detection between the Monsters and the Platform ccNodes. There are numerous tutorials online for cocos2d collision detection, both simple implementation and the more advanced box 2d/chipmunk based solutions.
If you are looking to clone doodle jump fairly closely, there is an open source version of a clone available on github here - though I've not actually looked at the code.
Finally, if you mean that you simply want to restrict the movement of the monsters to a particular area of the screen (so they don't keep running off the edge) you just need to position the target to an area on the screen and alter theccAction so that the ccMoveTo uses the left most point of the 'platform' as the furthest point left it can move to and the right most point as the furthest right. (I'll confess I've not played Doodle Jump so have no idea what the enemies actually do).
If the enemies run back and forth across the platform you should look into using ccRepeatForever on your movement sequence and have two destination positions in the CCSequence : one that moves the monster to the left of the platform, the other to move it to the right.
Additional Info
Ok, I see what you are trying to do. This should get you started:
Platforms are created in initPlatforms. This calls initPlatform a number of times. This grabs an image from the AtlasSprite for the platform, creates a ccSprite for each platform and assigns it a unique tag.
Then, in - (void)step:(ccTime)dt it loops through all the platforms and moves them to their correct location based on how far the bird has moved:
for(t; t < kPlatformsStartTag + kNumPlatforms; t++) {
AtlasSprite *platform = (AtlasSprite*)[spriteManager getChildByTag:t];
//etc...
So, the bit you are waiting for:
If you want to add a monster to these platforms, you will have to follow a similar pattern. To get started try something like this (You will want to have a cleaner design than this though but it should put you on the right track)
in initPlatform add the following to the end of the function
// add a monster sprite
AtlasSprite *monster = [AtlasSprite spriteWithRect:CGRectMake(608,128,64,64) spriteManager:spriteManager];
[spriteManager addChild:monster z:3 tag:currentPlatformTag + 1000];
(I've just grabbed an image from the existing Atlas. You could replace the above with your actual 'Monster' sprite object. Notice I add 1000 to thecurrentPlatformTag. This is just for testing; you should have a monsterTag implementation eventually.
So now every platform has a 'monster' (Again, you will only want to target random platforms)
so we need to update the positions for the monsters.
In - (void)step:(ccTime)dt directly after you get the current platform
AtlasSprite *platform = (AtlasSprite*)[spriteManager getChildByTag:t];
You now also need to get the current monster (remembering to use the updated tag value we created for 'monsters':
AtlasSprite *monster = (AtlasSprite*)[spriteManager getChildByTag:t + 1000];
Then, a few lines below where we reposition the platform we will need to reposition the monster
platform.position = pos;
// We update the monster and set it a 32 pixels above the platform:
monster.position = ccp(pos.x, pos.y + 32);
So now each platform has a monster on it whose y position moves with the the platforms :-)
Hope this helps