I have a game with balloons. I am adding the balloons from the bottom and they fly up while able to fly offscreen. I gave the balloons physicsBodys so they collide which each other but I want to make the balloons bouce of the left and right edge of the screen. I found a solution where the sprite object bounce of all screens on Rays site. But I need a different solution.
This is my balloon code:
MBDBallon *randomBallon = [[MBDBallon alloc] initWithImageName:balloonImageName
andBallonSize:ballonSize
withBallonImageName:[self.balloonColorImageNamesArray objectAtIndex:balloonWithColorIndex]
withStartingPosition:CGPointMake(self.viewSize.width/2, - self.viewSize.height )];
//randomBallon.name = [self returnRandomImageNameFromArray:self.ballonSpriteImageNamesArray];
randomBallon.physicsBody.usesPreciseCollisionDetection = YES;
randomBallon.physicsBody.categoryBitMask = balloonCategory;
randomBallon.physicsBody.collisionBitMask = balloonCategory;
randomBallon.physicsBody.contactTestBitMask = balloonCategory;
and this is my left wall code:
SKSpriteNode *leftWall = [[SKSpriteNode alloc] initWithColor:[UIColor redColor] size:CGSizeMake( self.viewSize.width / 15, self.viewSize.height * 3 )];
[leftWall setAnchorPoint:CGPointMake(0.0f, 0.0f)];
leftWall.position = CGPointMake( self.viewSize.width / 15 , 0.0f );
leftWall.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake( self.viewSize.width / 15, self.viewSize.height * 3 )];
leftWall.physicsBody.categoryBitMask = balloonCategory;
leftWall.physicsBody.collisionBitMask = balloonCategory;
leftWall.physicsBody.contactTestBitMask = balloonCategory;
leftWall.physicsBody.usesPreciseCollisionDetection = YES;
leftWall.physicsBody.allowsRotation = NO;
leftWall.physicsBody.dynamic = NO;
leftWall.physicsBody.friction = 0.0f;
[self addChild:leftWall];
However the walls is moving after some few balloon collisions. I really don't know wheres the catch or property to stop such behavior. The :
leftWall.physicsBody.dynamic = NO;
Should stop all movement but it doesn't. Please help, thanks :).
You have not properly set up your categoryBitMasks.
randomBallon.physicsBody.categoryBitMask = balloonCategory;
randomBallon.physicsBody.collisionBitMask = wallCategory|balloonCategory;
leftWall.physicsBody.collisionBitMask = wallCategory;
leftWall.physicsBody.contactTestBitMask = balloonCategory;
CategoryBitMask is the category you want to assign to your sprite. CollisionBitMask is the category you want your sprite to collide with. Balloons in this case should be set to collide with walls and other balloons. Walls should be set to only collide with balloons.
Related
I have rectangle shape paddle node, my question is - is it possible to "cut" node in half so when, for example ball, hit right side of node ball will go back with bigger angle, that it came, and same thing on other side. Im bad with words so, here are image of what I want to do :
For paddle node i use this code:
paddle = [[SKSpriteNode alloc] initWithImageNamed: #"board.png"];
paddle.name = paddleCategoryName;
paddle.position = CGPointMake(self.frame.origin.x + 50, CGRectGetMidY(self.frame));
[self addChild:paddle];
paddle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:paddle.frame.size];
paddle.physicsBody.restitution = 0.1f;
paddle.physicsBody.friction = 0.4f;
paddle.physicsBody.dynamic = NO;
and for ball :
ball = [SKSpriteNode spriteNodeWithImageNamed: #"ball.png"];
[self addChild:ball];
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width];
ball.physicsBody.friction = 0.0f;
ball.physicsBody.restitution = 1.0f;
ball.physicsBody.linearDamping = 0.0f;
ball.physicsBody.allowsRotation = NO;
I have two body
SKShapeNode *hero = [SKShapeNode shapeNodeWithCircleOfRadius:HERO_SIZE];
hero.lineWidth = 1;
hero.fillColor = [SKColor orangeColor];
hero.name = #"Hero";
hero.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:HERO_SIZE];
hero.physicsBody.categoryBitMask = hero;
hero.physicsBody.collisionBitMask = friendlyCategory | enemyCategory;
hero.physicsBody.contactTestBitMask = friendlyCategory | enemyCategory;
hero.position = [self getStartPosition];
SKShapeNode *circle = [SKShapeNode shapeNodeWithCircleOfRadius:BALL_SIZE];
circle.position = [self randomPossition];
circle.lineWidth = 2;
circle.strokeColor = [SKColor blueColor];
circle.fillColor = color;
circle.name = #"Circle";
circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:BALL_SIZE];
circle.physicsBody.categoryBitMask = friendlyCategory;
circle.physicsBody.collisionBitMask = hero;
circle.physicsBody.contactTestBitMask = hero;
then,
- (void)didEndContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (friendlyCategory | hero ))
{
SKShapeNode* node = (SKShapeNode*)contact.bodyA.node;
if ([node.name isEqualToString:#"Hero"])
{
self.activeFriendlyBall = (SKShapeNode*)contact.bodyB.node;
}
else
{
self.activeFriendlyBall = (SKShapeNode*)contact.bodyA.node;
}
self.hero.position = self.activeFriendlyBall.position;
}
}
Hero must be added above activeFriendlyBall, for him center position. But it added near him. I think it because physic body added. But I need to use physic body for other logic.
http://cl.ly/image/0g1S0d3h1h0w
must be like, how in top screen.
I find solution.
self.heroBall.physicsBody.velocity = CGVectorMake(0, 0);
[self.heroBall runAction: [SKAction moveTo:self.activeFriendlyBall.position duration:0.0]];
I believe it is because you have collision detection enabled for both, which means they can't be put on top of one another. If you are just trying to determine if one sprite comes into contact with another all you need is the contact detection. Removing either hero or friendlyball from the collision detection bit mask will allow them to be placed on top of each other/go through each other etc.
If you don't care about collisions at all for one of them (the sprite doesn't 'bounce' off anything) I'd recommend just setting collision detection to 0 for that sprite.
Best
I can't seem to get a unmovable fixed floor / wall in Sprite Kit.
I have a scene which has an SKNode as a "floor" and when the user presses the screen, a block drops from the touched position onto the "floor". That works well, but when the user adds another lets say 20 blocks or so, suddenly the floor rotates.
This is the code for my Floor:
SKSpriteNode *floorNode = [[SKSpriteNode alloc] initWithColor:[SKColor clearColor] size:CGSizeMake(self.size.width*2, 10)];
floorNode.position = CGPointMake(CGRectGetMidX(self.frame), -5);
floorNode.name = #"floor";
floorNode.zPosition = 1;
floorNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:floorNode.size];
floorNode.physicsBody.affectedByGravity = NO;
floorNode.physicsBody.dynamic = NO;
floorNode.physicsBody.usesPreciseCollisionDetection = YES;
return floorNode;
Sorry i'm quite new to Sprite Kit. Does anyone know what's wrong with my code?
EDIT: I've added some images to make the problem a bit more clear. Since it happens in this screen also. Those black spheres you see are flying from left to right like a canonball. I increased the amount of spheres to show the problem occurring. This is the code:
- (SKNode *)newEditButton {
SKLabelNode *editButtonNode = [SKLabelNode labelNodeWithFontNamed:#"chalkduster"];
editButtonNode.text = #"EDIT CASTLE";
editButtonNode.fontSize = 19;
editButtonNode.fontColor = [SKColor darkGrayColor];
editButtonNode.position = CGPointMake(CGRectGetMidX(self.frame) + 110, CGRectGetMidY(self.frame) + 30);
editButtonNode.name = #"edit_button";
editButtonNode.zPosition = 2;
editButtonNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(140, 40)];
editButtonNode.physicsBody.usesPreciseCollisionDetection = YES;
editButtonNode.physicsBody.dynamic = NO;
editButtonNode.physicsBody.affectedByGravity = NO;
SKAction *hover = [SKAction sequence:#[[SKAction moveByX:0 y:6.0 duration:0.9],
[SKAction moveByX:0 y:-6.0 duration:0.9]]];
SKAction *hoverForever = [SKAction repeatActionForever:hover];
[editButtonNode runAction:hoverForever];
return editButtonNode;
}
- (SKNode *)newPlayButton {
SKLabelNode *playButtonNode = [SKLabelNode labelNodeWithFontNamed:#"chalkduster"];
playButtonNode.text = #"SLAUGHTER";
playButtonNode.fontSize = 19;
playButtonNode.fontColor = [SKColor darkGrayColor];
playButtonNode.position = CGPointMake(CGRectGetMidX(self.frame) - 110, CGRectGetMidY(self.frame) + 30);
playButtonNode.name = #"play_button";
playButtonNode.zPosition = 2;
playButtonNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(140, 40)];
playButtonNode.physicsBody.usesPreciseCollisionDetection = YES;
playButtonNode.physicsBody.dynamic = NO;
playButtonNode.physicsBody.affectedByGravity = NO;
SKAction *hover = [SKAction sequence:#[[SKAction moveByX:0 y:6.0 duration:0.9],
[SKAction moveByX:0 y:-6.0 duration:0.9]]];
SKAction *hoverForever = [SKAction repeatActionForever:hover];
[playButtonNode runAction:hoverForever];
return playButtonNode;
}
After a while 5-10 seconds, the "buttons" called "slaughter" and "edit castle" start to rotate everytime a sphere collides with it. But it doesn't seem like it has to do something with physics, because, if it rotates to the left, it's rotation direction will always be left. And if it starts rotating right, it's rotation direction will always be right.
It looks funny though! But not what i want ;-)
You don't know it, but you're telling SpriteKit to apply collisions from the cannonballs to the labels, and from the blocks to the floor!
That's because you're leaving SKPhysicsBody.collisionBitMask to its default value, which is to observe collisions for everything.
From the documentation:
Discussion
When two physics bodies contact each other, a collision may occur. This body’s collision mask is compared to the other body’s category mask by performing a logical AND operation. If the result is a nonzero value, this body is affected by the collision. Each body independently chooses whether it wants to be affected by the other body. For example, you might use this to avoid collision calculations that would make negligible changes to a body’s velocity.
The default value is 0xFFFFFFFF (all bits set).
So, do:
floorNode.physicsBody.collisionBitMask = 0
I've seen the Trailer of "Third Eye Crime".
How can you realise the blue field of view cone so that it's shape changes depending on obstacles?
My attempt was to cast rays till an obstacle occurs and then I take the end points of the rays to draw the cone shape.
The problem with my method is that the precision of the cone depends on the number of rays. Besides the more rays are casted the worse the performance.
Here you can see the rays:
Here you can see the cone shape drawn with the end points of the rays:
Are there better solutions?
Your question has been bugging me all day but I can't find a fully working answer. My work notes are too much for me to write it as a comment so I am adding it as an answer.
My first issue was to test for a contact between a field of view line and any obstruction object. Unfortunately SpriteKit only has the contactBitMask which can do this job AND provide contact coordinates.
I looked at SKNode's intersectsNode: but its return value is only a BOOL and we need coordinates.
I also looked at Apple's Sprite Kit Programming Guide's section Searching for Physics Bodies. This deals with line of sight, obstacles, etc... The command used here is bodyAlongRayStart:end:. The return is the first physics body it intersects with but without providing the coordinates for the actual contact point.
The code I ended up with first draws the complete line of sight cone. Next any lines found contacting an object got the contact point using contact.contactPoint and then deleted the offending line. So far so good. But I ran into trouble trying to draw the deleted line to the new end point (contact point). For some inexplicable reason only some of the deleted lines are being redrawn and not all.
Keep in mind this is rough code so slap it, pull it and throw it against the wall a couple of times. You don't really need the array for example. I hope this will steer you in the right direction or you can spot something I am too blind to see.
Side note: I ran this in simulator iPhone (4 inch) landscape.
#import "MyScene.h"
typedef NS_OPTIONS(uint32_t, CNPhysicsCategory)
{
Category1 = 1 << 0,
Category2 = 1 << 1,
Category3 = 1 << 2,
};
#interface MyScene()<SKPhysicsContactDelegate>
#end
#implementation MyScene
{
SKSpriteNode *player;
SKSpriteNode *obstacle1;
SKSpriteNode *obstacle2;
SKSpriteNode *obstacle3;
SKSpriteNode *obstacle4;
NSMutableArray *beamArray;
int beamCounter;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.physicsWorld.contactDelegate = self;
beamArray = [[NSMutableArray alloc] init];
beamCounter = 0;
player = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(20, 20)];
player.position = CGPointMake(100, 150);
[self addChild:player];
obstacle1 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(200, 20)];
obstacle1.name = #"obstacle1";
obstacle1.position = CGPointMake(250, 100);
obstacle1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle1.size];
obstacle1.physicsBody.affectedByGravity = NO;
obstacle1.physicsBody.categoryBitMask = Category2;
obstacle1.physicsBody.collisionBitMask = 0x00000000;
[self addChild:obstacle1];
obstacle2 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(40, 40)];
obstacle2.name = #"obstacle2";
obstacle2.position = CGPointMake(400, 200);
obstacle2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle2.size];
obstacle2.physicsBody.affectedByGravity = NO;
obstacle2.physicsBody.categoryBitMask = Category2;
obstacle2.physicsBody.collisionBitMask = 0x00000000;
[self addChild:obstacle2];
obstacle3 = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(50, 20)];
obstacle3.name = #"obstacle3";
obstacle3.position = CGPointMake(530, 130);
obstacle3.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle3.size];
obstacle3.physicsBody.affectedByGravity = NO;
obstacle3.physicsBody.categoryBitMask = Category2;
obstacle3.physicsBody.collisionBitMask = 0x00000000;
[self addChild:obstacle3];
for (int y = 0; y <= 320; y++)
{
SKShapeNode *beam1 = [SKShapeNode node];
beam1.name = [NSString stringWithFormat:#"%i",beamCounter++];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, player.position.x, player.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, 600, y);
beam1.path = pathToDraw;
[beam1 setStrokeColor:[UIColor yellowColor]];
[beam1 setLineWidth:2.0];
beam1.physicsBody = [SKPhysicsBody bodyWithEdgeFromPoint:CGPointMake(player.position.x, player.position.y) toPoint:CGPointMake(600, y)];
beam1.physicsBody.affectedByGravity = NO;
beam1.physicsBody.categoryBitMask = Category1;
beam1.physicsBody.contactTestBitMask = Category2;
[self addChild:beam1];
[beamArray addObject:beam1];
}
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//
}
-(void)update:(CFTimeInterval)currentTime
{
//
}
- (void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"bodyA:%# bodyB:%#",contact.bodyA.node.name, contact.bodyB.node.name);
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (Category1 | Category2))
{
CGPoint newEndPoint = contact.contactPoint;
[beamArray removeObject:contact.bodyA.node.name];
[contact.bodyA.node removeFromParent];
SKShapeNode *beam1 = [SKShapeNode node];
beam1.name = [NSString stringWithFormat:#"%i",beamCounter++];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, player.position.x, player.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, newEndPoint.x, newEndPoint.y);
beam1.path = pathToDraw;
[beam1 setStrokeColor:[UIColor greenColor]];
[beam1 setLineWidth:2.0];
[self addChild:beam1];
}
}
#end
Removing the touching lines:
Removing the touching lines and replacing the removed lines:
How I would do it is send out a ray at the edges of view and make note of the points that they hit. Then find points that are important(corners of objects for example), form rays with those points and use those rays to find collisions past those points and finally fill triangles with the points and the "source" of the light.
I might get a chance to make pictures to help demonstrate my algorithm later but this is what I have now.
I think the efficient way to do this is to use Bresenham's Algorithm. There are lots of references and example code in the Wikipedia references section.
http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
Here I've found several answers:
https://code.google.com/p/straightedge/
http://www.redblobgames.com/articles/visibility/
I'm not sure if I'm correct in wording it "modal sprite-kit scene" but what I'm trying to do is have a smaller sized scene appear over a scene when the game is complete. Attached is a screenshot to show what I mean:
The screenshot is from Flappy Bird where when the player dies, the small game-over scene pops up almost like a modal effect, and displays the user's final results. I was wondering how to go about creating this.
I tried calling this when the game was done:
[self.player runAction:death completion:^{
[self removeAllActions];
GameOverNode *gameOverNode = [[GameOverNode alloc] initWithScore:self.size];
gameOverNode.gameScene = self;
gameOverNode.position = CGPointMake(self.scene.size.width/2, -150);
[self addChild:gameOverNode];
[gameOverNode runAction:[SKAction moveToY:self.scene.size.height/2 duration:0.6]];
And this was the code for the gameOverNode in the gameOverScene.m's file:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.userInteractionEnabled = YES;
self.zPosition = 20;
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithColor:[UIColor blackColor] size:CGSizeMake(280*DoubleIfIpad, 300*DoubleIfIpad)];
bg.alpha = 0.55;
But the node only shows up in the bottom left hand corner of the screen, as opposed to in the middle like I want it too.
What am I doing wrong? How can I make the small gameover node pop up and sit in the middle of the scene.
You simply can't. According to Apple Docs and multiple StackOverflow posts you cannot be running two SKScene's at the same time. To recreate the effect you want, you can create an SKShapeNode with border and line color similar to those you want, and place objects like score inside, or you can create an image for the screen that pops up and just add SKLabelNodes to the scene that show the high score and current score. I'm attaching a picture of how I created an end "view" for my app, Average Guy.
I made the popup screen using an SKShapeNode like this:
SKShapeNode *optionsMenu = [SKShapeNode node];
CGRect tempRect = CGRectMake(SELF_WIDTH/2, SELF_HEIGHT/2, SELF_WIDTH-20, (finalScoreLabel.position.y+(finalScoreLabel.frame.size.height/2)) - (mainMenuButton.position.y-(mainMenuButton.frame.size.height/2)) + 20);
CGPathRef tempPath = CGPathCreateWithRoundedRect(CGRectMake(tempRect.origin.x - tempRect.size.width/2, tempRect.origin.y - tempRect.size.height/2, tempRect.size.width, tempRect.size.height), 5, 5, Nil);
optionsMenu.path = tempPath;
optionsMenu.fillColor = [SKColor grayColor];
optionsMenu.strokeColor = [SKColor blueColor];
optionsMenu.zPosition = 30;
optionsMenu.glowWidth = 2.0f;
CGPathRelease(tempPath);
Of course, don't mind the hectic CGRect implementation, I wanted to make everything relative to fit all of the screen sizes so I didn't hardcode any of the positions. As for adding the score and the buttons, I did this:
SKLabelNode *restartButton = [SKLabelNode labelNodeWithFontNamed:#"00 Starmap Truetype"];
restartButton.text = #"Play Again";
restartButton.zPosition = 31;
restartButton.fontColor = [SKColor blueColor];
restartButton.fontSize = 30;
restartButton.position = CGPointMake(HALF_WIDTH, HALF_HEIGHT-50);
restartButton.name = #"restart_button";
SKLabelNode *restartButtonShadow = restartButton.copy;
restartButtonShadow.position = CGPointMake(restartButton.position.x+1, restartButton.position.y-1);
restartButtonShadow.color = [SKColor colorWithRed:0 green:0 blue:.5 alpha:.1];
SKLabelNode *mainMenuButton = [SKLabelNode labelNodeWithFontNamed:#"00 Starmap Truetype"];
mainMenuButton.text = #"Main Menu";
mainMenuButton.zPosition = 31;
mainMenuButton.fontColor = [SKColor blueColor];
mainMenuButton.fontSize = 30;
mainMenuButton.position = CGPointMake(HALF_WIDTH, HALF_HEIGHT - 85);
mainMenuButton.name = #"main_menu_button";