I'm programming a little Game. For this I need some Walls. Therefor I have used:
Wall[w] = [[SKShapeNode alloc] init];
Wall[w].path = WallPos[w];
Wall[w].lineWidth = 4;
Wall[w].strokeColor = [UIColor whiteColor];
Wall[w].zPosition = 3;
[self addChild: Wall[w]];
Wall is an Array of SKShapeNodes and is set in #interface, so I can use it in every method. WallPos contains CGMutablePathRefs.
In TouchesBegan and TouchesMoved I'm calling a method which should check if you have touched one of the walls.
I have also some SKShapeNodes which are Rectangles, and to check if they are touched, I have used
if ([SomeShape containsPoint: Position] {
//Do some stuff
}
But with a line it's not working. Sometimes I'm touching on the line and nothing happens. Then I've seen this: Detecting Touch on SKShapeNode that is a Line and I have tried to do it on that way:
for (int i = 0; i < NrWalls; i++) {
if (CGRectContainsPoint(Wall[i].frame, Position)) {
[self GameOver];
}
}
But now every Point I touch sets a "Game Over" to me!!
Has anyone an Idea, how could I check if the line is touched?
Thanks for your help!
DXXL
Not sure why you want to use SKShapeNodes for walls and rectangles. To answer your question, you can attach a physics body to your shape node and use the contact methods to check for possible contacts. However, assigning a physics body to a shape node could be a tricky undertaking due to the anchor points and getting a desired alignment.
Seeing that you are really only drawing rectangles for your walls, I suggest you use a SKSpriteNode with a physics body instead. Something like this:
SKSpriteNode *myNode = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(5, 100)];
myNode.position = CGPointMake(100, 100);
myNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:myNode.size];
myNode.physicsBody.dynamic = NO;
myNode.physicsBody.categoryBitMask = CategoryWall;
[self addObject:myNode];
If you need, you can read up on SKPhysicsBody here.
Related
I have created an SKSpriteNode called let's say Map that has an edge path that I have defined (some simple polygon shape).
What I am trying to figure out is how to add several other edge paths that would act as interior edges of the Map. As if the "map" as a whole did in fact have holes. Some sort of inner boundary shapes that could act with Map as a whole: one edge path (as shown below)
©
I understand that there is a method that allows for creating an SKPhysicsBody with bodies (some NSArray), like such
Map.physicsBody = [SKPhysicsBody bodyWithBodies:bodiesArray];
Does this method in fact generate what I have shown in the image? Assuming that the bodiesArray contains 3 SKSpriteNode's each with a defined path from using such method:
+ (SKPhysicsBody *)bodyWithEdgeChainFromPath:(CGPathRef)path
, with creating the path like such
SKSpriteNode *innerNode1 = [SKSpriteNode spriteNodeWithImageNamed:#"map"];
CGMutablePathRef innerNode1Path = CGPathCreateMutable();
CGPathMoveToPoint(mapPath, NULL, 1110, 1110);
CGPathAddLineToPoint(mapPath, NULL, <some x1>, <some y1>);
CGPathAddLineToPoint(mapPath, NULL, <some x2>, <some y2>);
CGPathAddLineToPoint(mapPath, NULL, <some x3>, <some y3>);
.
.
.
CGPathCloseSubpath(mapPath);
innerNode1.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:innerNode1Path];
[bodiesArray addObject:innerNode1];
// Repeat for other 2 nodes
I understand that an alternative would be to create 3 separate nodes with the location and shape of the intended "holes", but I am tying to avoid creating more nodes than I need. If anyone can confirm what I am trying to do is correct, or perhaps suggest an alternative that I am unaware of.
NOTE: IF what I am doing is correct but I am missing something, I would appreciate it if someone can show me the correct way to do what I am trying to do (even a simple example of a square with an inner smaller square would be great). Thanks!
EDIT 1:
Below is the code snippet that I am using as an attempt to create the "inner boundaries". This issue here, is that while both the outer and inner rect's are drawn and shown, when I add the inner rect to the Map bodyWithBodies, it takes full control of the collision detection, removing all contact control from the outer rect shell. When I remove the bodyWithBodies it goes back to normal with showing both rects, the outer has collision detection (does not allow me to pass through), while the inner one has nothing... so close
// 1 Create large outer shell Map
CGRect mapWithRect = CGRectMake(map.frame.origin.x + offsetX, map.frame.origin.y + offsetY, map.frame.size.width * shrinkage, map.frame.size.height * shrinkage);
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0);
self.physicsWorld.contactDelegate = self;
// 2 Create smaller inner boundary
CGRect innerRect = CGRectMake(100, 100, 300, 300);
SKPhysicsBody *body = [SKPhysicsBody bodyWithEdgeLoopFromRect:innerRect];
body.categoryBitMask = wallCategory;
NSArray *bodyArray = [NSArray arrayWithObject:body];
// 3 Add bodies to main Map body
myWorld.physicsBody = [SKPhysicsBody bodyWithBodies:bodyArray];
myWorld.physicsBody.categoryBitMask = wallCategory;
if ( [[levelDict objectForKey:#"DebugBorder"] boolValue] == YES) {
// This will draw the boundaries for visual reference during testing
[self debugPath:mapWithRect];
[self debugPath:innerRect];
}
EDIT 2
This approach works..by just adding a new node with the same properties as the outer rect:
SKPhysicsBody *innerRectBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:innerRect];
innerRectBody.collisionBitMask = playerCategory;
innerRectBody.categoryBitMask = wallCategory;
SKNode *innerBoundary = [SKNode node];
innerBoundary.physicsBody = innerRectBody;
[myWorld addChild: innerBoundary];
...but I would very much like a cleaner solution that does not require additional nodes..thoughts?
you are doing nothing wrong here i come with an example where i created two edge rect bodies with two physics bodies
//adding bodies after some time using gcd
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self addBodyA];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self addBodyB];
});
-(void)addBodyB
{
SKSpriteNode *node=[SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(20, 20)];
node.physicsBody=[SKPhysicsBody bodyWithRectangleOfSize:node.frame.size];
node.position=CGPointMake(550, 420);
node.physicsBody.restitution=1;
[self addChild:node];
}
-(void)addBodyA
{
SKSpriteNode *node=[SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(20, 20)];
node.physicsBody=[SKPhysicsBody bodyWithRectangleOfSize:node.frame.size];
node.position=CGPointMake(400, 420);
node.physicsBody.restitution=1;
[self addChild:node];
}
-(void)addEdgesBodies
{
SKAction *r=[SKAction rotateByAngle:1.0/60 duration:1.0/60];
SKSpriteNode *rect=[SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(300,300)];
rect.physicsBody=[SKPhysicsBody bodyWithEdgeLoopFromRect:rect.frame];
rect.position=CGPointMake(500, 400);
[self addChild:rect];
//
SKSpriteNode *rect1=[SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(100,100)];
rect1.physicsBody=[SKPhysicsBody bodyWithEdgeLoopFromRect:rect1.frame];
rect1.position=CGPointMake(550, 450);
[self addChild:rect1];
[rect1 runAction:[SKAction repeatActionForever:r]];
}
[self addEdgesBodies];
remember edge bodies comes with low cpu overhead so don't worry about performance untill your polygon don't have so many edges.
Your code for making a path then using it in a physics body looks like it would work. As well as your physics body from bodies. Unfortunately I do not know SKPhysicsBody's can really support holes because you can not flip the normals of the body. The way I read apples documentation is that it is meant to do things like take two circle shapes and make them into one body, rather then creating a complex shape like that. The problem being that having a hole inside of your bigger shape would mean ignoring collision in that area.
Here are some alternative options
One option is you could build your stages from multiple shapes. For example if you break your map into two pieces (with a line going through each shape) and make physics bodies for those. Then have them overlap and make them into one body then it might work out. I made a diagram showing this (pardon its terrible quality you should still be able to understand what it is doing (hopefully)).
Another option would be to make it with a texture, this can hurt preformance a bit but if you can manage it then it probably would work nicely.
Map.physicsBody = SKPhysicsBody(texture: Map.texture, size: Map.size)
It's possible for one node to have two physics body paths? I want to create a node that has two (circle) physics bodies on the sides of the node.
If it's not possible, is there are any workaround to achieve that? thank you
You want to use [SKPhysicsBody bodyWithBodies:...]. From the docs :
The shapes of the physics bodies passed into this method are used to
create a new physics body whose covered area is the union of the areas
of its children. These areas do not need to be contiguous. If there is
space between two parts, other bodies may be able to pass between
these parts. However, the physics body is treated as a single
connected body, meaning that a force or impulse applied to the body
affects all of the pieces as if they were held together with an
indestructible frame.
It would look something like this :
SKPhysicsBody *leftCircle = [SKPhysicsBody bodyWithCircleOfRadius:leftCircleRadius center:leftCircleCenter];
SKPhysicsBody *rightCircle = [SKPhysicsBody bodyWithCircleOfRadius:rightCircleRadius center:rightCircleCenter];
node.physicsBody = [SKPhysicsBody bodyWithBodies:#[leftCircle, rightCircle]];
Here's an example of how to connect to sprite nodes using SKPhysicsJointFixed. First, create two sprites:
SKSpriteNode *sprite1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(64, 64)];
// position must be set before creating physics body to avoid bug in iOS 7.0.x
sprite1.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
sprite1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite1.size];
sprite1.physicsBody.restitution = 1.0;
[self addChild:sprite1];
SKSpriteNode *sprite2 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(64, 64)];
sprite2.position = CGPointMake(CGRectGetMidX(self.frame)-sprite2.size.width*2,
CGRectGetMidY(self.frame));
sprite2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite2.size];
sprite2.physicsBody.restitution = 1.0;
[self addChild:sprite2];
then connect the nodes by calling this method:
[self connectNode1:sprite1 toNode2:sprite2];
This method joins two nodes at their midpoint. Note that both physic bodies must be in the scene prior to calling this method.
- (void) connectNode1:(SKSpriteNode *)node1 toNode2:(SKSpriteNode *)node2
{
CGPoint midPoint = CGPointMake((node1.position.x + node2.position.x)/2,
(node1.position.y + node2.position.y)/2);
SKPhysicsJointFixed *joint = [SKPhysicsJointFixed jointWithBodyA:node1.physicsBody
bodyB:node2.physicsBody
anchor:midPoint];
[self.physicsWorld addJoint:joint];
}
Here is an easy way to achieve the behavior you are looking for:
How to detect contact on different areas of a physicsbody
//Create SpriteNode1 & physicsBody
//Create SpriteNode2 & physicsBody
[SpriteNode1 addChild: SpriteNode2]
You can position SpriteNode2 relative to SpriteNode1. Any movement, etc. performed on SpriteNode1 will also move SpriteNode2.
Set: SpriteNode2.PhysicsBody.Dynamic=NO;
You could also create a SpriteNode that acts as the main object and add both SN1 and SN2 as children if you find that easier.
I am having a little trouble doing something that is supposed to be very simple.
I cannot get the floor of my tiles to display above a background picture. However I can get all my other game objects to show from my control pad, to my HUD to even coins and monsters set up in the same tile map. Basically everything appears in front the background like I expect the floor of my tilemap so it looks like im walking on air. I have tried many things like changing which layer i add the background picture or the tilemap floor too, or even tried setting it the same way i set my characters but same results. Tilemap floor is always at the back.
Adding my Set up code, Hope it is helpful too solving the problem.
I created this BG sprite since, I wanted my tilemap to scroll vertically or horzi. automatically. So the easiest way I found to do that was to make the tilemap the child of the "bg" and scroll the "bg" hence scrolling the tile map. However, I have tried setting the background as the child of the bg and setting the Z for both of them but that didnt seem to help.
Thanks in advance for any help in solving this
#implementation GameLevelScene
{
SKNode *_worldNode;
SKSpriteNode *bg;
SKSpriteNode *bkg;
}
Init
-(id)initWithSize:(CGSize)size level:(int)level {
if (self = [super initWithSize:size]) {
// [self showBackground];
NSDictionary *levelData = config[#"levels"][level];
//[show background];
if (levelData[#"tmxFile"]) {
[self showBackground];
_tileMap = [ JSTileMap mapNamed:levelData[#"tmxFile"]];
}
//self.backgroundColor = [SKColor colorWithRed:.4 green:.4 blue:.95 alpha:1.0];
// UIImage *bkgb =[UIImage imageNamed:#"land.jpg"];
// self.position=CGPointZero;
// self.anchorPoint=CGPointZero;
// self.backgroundColor=[UIColor colorWithPatternImage:bkgb];
//Above code shows no picture but it changes the color
[self setUpWorld];
[self createChar];
[self controlPadNode];
//[show background];
}
return self;
}
setUpWorld
- (void)setUpWorld
{
bg = [SKSpriteNode spriteNodeWithImageNamed:#"bg3"];
bg.userInteractionEnabled=NO;
bg.anchorPoint = CGPointZero;
bg.zPosition=0;
bg.position = CGPointZero;
bg.name = #"bg";
[self addChild:bg];
_worldNode = [SKNode node];
if (_tileMap) {
[bg addChild:_tileMap];
}
[bg addChild:_worldNode];
self.physicsWorld.contactDelegate = self;
}
create char
- (void)createChar
{
_Layer = [[TmxTileMapLayer alloc]
initWithTmxObjectGroup:[_tileMap
groupNamed:#"LevelOneObjects"]
tileSize:_tileMap.tileSize
gridSize:_bgLayer.gridSize];
[self addChild:_Layer];
}
Create Control
- (SKSpriteNode *)controlPadNode
//-(void)controlPad
{
SKSpriteNode *controlPadNode = [SKSpriteNode spriteNodeWithImageNamed:#"controller.png"];
controlPadNode.position = CGPointMake(100,50);
controlPadNode.name = #"controlPadNode";
controlPadNode.zPosition = 1.0;
[self addChild:controlPadNode];
}
background
-(void)showBackground
{
bkg = [SKSpriteNode spriteNodeWithImageNamed:#"desert_land.jpg"];
bkg.userInteractionEnabled=NO;
bkg.anchorPoint = CGPointZero;
bkg.position = CGPointZero;
bkg.zPosition=-1;
bkg.name = #"bkg";
// [self addChild:bkg];
//[_tileMap addChild:bkg];
// [_worldNode addChild:bkg];
[bg addChild:bkg];
}
Set your bkg.zposition to 1 and set your bg.zPosition to 2.
Also, the whole point of having a tile map is NOT to use a gigantic background picture but to instead use tiles.
** Update **
I just tested your code and ran a sample project myself. I assume you are using the Tiled app for your tiles. I ran variations on parents (self, worldNode, etc...) and zPositions. The bottom line is you cannot do it. Tiled does not have an alpha channel for its background color options. So either the background image is covered by the tile map or, in your case, the background image covers the tile map.
Your possible solutions are to either not use Tiled and place your tiles manually or to look for another tile app which has an alpha channel.
I noticed that where you define the wall properties of the tile if you say
SKSpriteNode* wall =
[SKSpriteNode spriteNodeWithColor:[SKColor greenColor]
size:CGSizeMake(w, h)];
the green color will be uptop of the background. Bit of a dirty fix than an answer
I want to make two sprites disappear when they hit each other in sprite kit. The example in apple's documentation doesn't really help me that much so if you could explain it it would be good. :)
OK, so you need to do several things.
I'm going to assume that you have two sprites...
SKSpriteNode *sprite1;
SKSpriteNode *sprite2;
These are currently flying around your scene and may or may not come into contact with each other.
So, you need to set everything up to enable hit testing and call backs.
First, add physics bodies to the sprites...
sprite1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10];
sprite2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(10, 10)];
// this is a couple of examples, you'll have to set yours up to match the sprite size, shapes
Next you need to add categories to the sprites. This gives the physics engine some information about what type of body it is...
sprite1.physicsBody.categoryBitMask = 1 << 0;
sprite2.physicsBody.categoryBitMask = 1 << 1;
// again this is an example. You probably want to put these in an NS_OPTIONS typedef.
Then you need to tell the physics engine which contact you want to be told about. In the is case you want to be told when sprite1 (1 << 0) comes into contact with sprite2 (1 << 1).
So...
sprite1.physicsBody.contactTestBitMask = 1 << 1; // it will test for contact against sprite 2
sprite2.physicsBody.contactTestBitMask = 1 << 0; // test against sprite 1.
Now in you scene you need to adopt <SKPhysicsContactDelegate> and set the scene to be the physics delegate...
self.physicsWorld.contactDelegate = self;
And then create the method...
- (void)didBeginContact:(SKPhysicsContact *)contact
{
// these two physics bodies have come into contact!
SKSpriteNode *firstContactNode = contact.bodyA.node;
SKSpriteNode *secondContactNode = contact.bodyB.node;
// now remove them...
[firstContactNode removeFromParent];
[secondContactNode removeFromParent];
}
SKAction* _vanishAction =[SKAction fadeOutWithDuration: 1];
[sprite runAction:_vanishAction completion:^(void){
[sprite removeFromParent];
}];
I'm using collision detection in Sprite Kit. It is working and preventing my sprites from crossing paths. However, I'm not getting notifications in didBeginContact: and I don't seem to have any control over how the physics engine responds when a collision occurs.
I have various cars (SKSpriteNodes) moving around following paths using the SKAction followPath:asOffset:orientToPath:duration:
Previously, if two cars crossed paths they would both just continue as normal with one driving over the other. To implement collision detection I have made the following changes...
Added this to my #interface:
<SKPhysicsContactDelegate>
Added this to my #implementation:
static const uint32_t carCategory = 0x1 << 0;
Added this in my init method:
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0.0, 0.0);
I create my blue car:
- (void)addBlueCar
{
_blueCar = [SKSpriteNode spriteNodeWithImageNamed:#"Blue Car.png"];
_blueCar.position = CGPointMake(818.0, -50.0);
_blueCar.name = #"car";
[self addChild:_blueCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
_blueCar.physicsBody.categoryBitMask = carCategory;
_blueCar.physicsBody.collisionBitMask = carCategory;
_blueCar.physicsBody.contactTestBitMask = carCategory;
}
And I also create a red car:
- (void)addRedCar
{
_redCar = [SKSpriteNode spriteNodeWithImageNamed:#"Red Car.png"];
_redCar = CGPointMake(818.0, -50.0);
_redCar = #"car";
[self addChild: _redCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_redCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
_redCar.physicsBody.categoryBitMask = carCategory;
_redCar.physicsBody.collisionBitMask = carCategory;
_redCar.physicsBody.contactTestBitMask = carCategory;
}
There are other cars as well, but I'm just using these two until I figure out what the problem is. I want to be notified of any collision between any two cars. That's why I'm just using a single category, carCategory.
The first problem is that I get no notifications. I have this yet it never logs anything to the console:
- (void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"Contact");
}
The second problem is that when two cars cross paths they start nudging each other off course. This is ok, but I don't seem to able to manage any aspects of how the collision is handled. The bizarre thing is that nothing changes when I do this to both of my car creation methods:
- (void)addBlueCar
{
_blueCar = [SKSpriteNode spriteNodeWithImageNamed:#"Blue Car.png"];
_blueCar.position = CGPointMake(818.0, -50.0);
_blueCar.name = #"car";
[self addChild:_blueCar];
CGSize contactSize = CGSizeMake(_blueCar.size.width - 5.0, _blueCar.size.height - 5.0);
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
// _blueCar.physicsBody.categoryBitMask = carCategory;
// _blueCar.physicsBody.collisionBitMask = carCategory;
// _blueCar.physicsBody.contactTestBitMask = carCategory;
}
Collision detection still works the same with those three lines commented out for both cars. This just doesn't seem right to me. Collision detection only ceases when I also comment out this line:
_blueCar.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
So my main question is: why is the contactTest not reporting anything? My second question is: why is collision detection happening when I don't assign a categoryBitMask, collisionBitMask or contactTestBitMask to any car?
collisionBitMask's default value is 0xFFFFFFFF (all bits set). Therefore the node will collide with each physicBody on the scene.
Just a small observation, perhaps you should setup your nodes and set the contact delegate in the scene's didMoveToView method.
If that doesn't solve it, any chance you can post some working code to github for debugging?
Something that helped me with this: Collisions and contacts are different things.
Collision is handled by the physics engine and setting collisionBitMask will determine which bodies collide and interact with each other in the physics world. By default, all your bodies will collide with each other, which kinda makes sense.
Contacts are events- when bodies make contact, will they create a notification that you can use in the delegate method to do more interesting things. If you have a ball you want to hit with a paddle, you want them to collide at a minimum. But if you have a ball moving through a cloud, you probably don't want them to collide in the physics world, but you might want to register the contact.