iOS SpriteKit - Creating physics for a bobblehead? - ios

I am trying to create a realistic bobblehead app, and want to use physics for the bobble animation. I have seen other apps out there that do it, and from what I gather I need to use SpriteKit.
I have created a SKScene and used a SKPhysicsJointPin to pin the head to the body, but it doesnt move around at all. Any ideas or suggestions?
UPDATED CODE (5/28):
Now using 2 spring joints on the head and it moves left to right, but not up and down. Also, tapping quickly causes the head to eventually go far enough right to "fall of" and out of view. Weird.
Have any ideas on what the proper setting would be to allow it to bobble up, down, left, and right whil staying remotely centered on it's starting position and staying within a specified region so it doesnt come off the body and look all funny?
BobbleheadView is a subclassed SKView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor =[UIColor clearColor];
self.showsFPS = YES;
self.showsNodeCount = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(animateBobble)];
[self addGestureRecognizer:tap];
SKScene *bobbleheadScene = [SKScene sceneWithSize:self.bounds.size];
[self presentScene:bobbleheadScene];
// 1. Body
self.body = [SKSpriteNode spriteNodeWithImageNamed:#"Bobble-Body"];
self.body.position = CGPointMake(self.frame.size.width/2, self.body.frame.size.height/2);
self.body.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[bobbleheadScene addChild:self.body];
// 2. Head
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"Bobble-Head"];
self.head.position = CGPointMake(self.center.x, self.body.frame.size.height);
//self.head.physicsBody.affectedByGravity = YES;
// self.head.physicsBody.dynamic = NO;
//This bobbles head great, but head falls off body and out of view
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size center:self.head.position];
//End
//self.head.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.head.frame];
//self.head.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.head.size.height/2];
[bobbleheadScene addChild:self.head];
// 3. Ceiling
self.ceiling = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor] size:CGSizeMake(32, 32)];
self.ceiling.position = CGPointMake(self.frame.origin.x+self.frame.size.width/2, self.frame.size.height);
self.ceiling.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[bobbleheadScene addChild:self.ceiling];
//Spring Joint for Ceiling to Head
SKPhysicsJointSpring *spring1 = [SKPhysicsJointSpring jointWithBodyA:self.ceiling.physicsBody bodyB:self.head.physicsBody anchorA:self.ceiling.position anchorB:CGPointMake(self.head.frame.origin.x+self.head.frame.size.width/2, 0)];
spring1.frequency = 20.0; //gives the spring some elasticity.
spring1.damping = 5.0; //Will remove damping to create the 'pendulum'
[bobbleheadScene.physicsWorld addJoint:spring1];
//Spring Joint for Head to Body
SKPhysicsJointSpring *spring = [SKPhysicsJointSpring jointWithBodyA:self.body.physicsBody bodyB:self.head.physicsBody anchorA:CGPointMake(self.body.position.x+self.body.size.width/2, self.body.position.y) anchorB:CGPointMake(self.head.position.x+self.head.size.width/2, self.body.position.y-self.body.size.height/2)];
spring.frequency = 10.0; //gives the spring some elasticity.
spring.damping = 1.0;
[bobbleheadScene.physicsWorld addJoint:spring];
}
return self;
}
-(void)animateBobble{
NSLog(#"Did Tap Bobblehead!");
[self.head.physicsBody applyImpulse:CGVectorMake(100, -200)];
//[self.body.physicsBody applyImpulse:CGVectorMake(20, 10)];
}

The only way you'll get this to work is to make a "sled".
So: make an app where, on screen there are (say) six sliders.
TBC I mean, when the app is actually running, you'll see six sliders.
TBC, this is only a sled, it's only for you as a developer, it's not for the consumer app.
(I believe the term "sled" comes from the automotive industry; when they make the first version of a chassis/engine to test it out.)
Make it so that some sliders control spring power/damp (or whatever factors you have available in cocos) and that other sliders nudge the position of the connections/spring lengths.
it's the only way to get a result! Be sure to show the values on screen, or at least print them out on the console as they change.
This is the everyday thing in game development. Typically the sled is more work than the actual consumer scene or feature. You won't be able to achieve it without a "sled". I hope it helps!

Related

Change Physical body frame in spritekit

I develop an iOS game using SpriteKit (such a helpful framework to quickly make a game). I add texture and configure a physical body for a main character as image
The green rectangle is the frame of the physical body. I'm using the following code to create it
#interface MainCharacter : SKSpriteNode
#end
#implementation MainCharacter
+ (instancetype)mainCharacterAtPosition:(CGPoint)pos {
MainCharacter* mainChar = [[MainCharacter alloc] initWithTexture:[SKTexture textureWithImageNamed:#"stand_up"]];
mainChar.position = pos;
mainChar.xScale = 0.5f;
mainChar.yScale = 0.5f;
return mainChar;
}
- (instancetype)initWithTexture:(SKTexture *)texture {
if (self = [super initWithTexture:texture]) {
self.name = kCharacterName;
self.anchorPoint = CGPointMake(0.5f, 0.0f);
[self standup];
CGSize spriteSize = self.size;
CGPoint center = CGPointMake(spriteSize.width*(self.anchorPoint.x-0.5f), spriteSize.height*(0.5f-self.anchorPoint.y));
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteSize center:center];
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = kCharacterCategory;
self.physicsBody.contactTestBitMask = 0x0;
self.physicsBody.collisionBitMask = 0x0;
}
return self;
}
- (void)standup {
SKAction* standupAction = [SKAction setTexture:self.standupTexture resize:YES];
[self runAction:standupAction];
}
- (void)standdown {
SKAction* standownAction = [SKAction setTexture:self.standdownTexture resize:YES];
[self runAction:standownAction completion:^{
}];
[self performSelector:#selector(standup) withObject:nil afterDelay:1.0f];
}
MainCharacter is a class that inherits from SKSPriteNode, just an convienient class to manage a main character. Stand Up is a first state of the character. I have another state, temporarily called stand down (demonstrate as following image)
I add a swipe down gesture to make character stand down.
The green rectangle also the physical body but it's too large for the character. I want to make a physical body frame as the red rectangle.
Can anyone help me how to make the physical body smaller when my character stand down and enlarge the physical body after it stands up
You can destroy the current physics body self.physicsBody = nil; and then simply create a new one with the new size requirements.
I solve this problem by using 2 nodes for 2 states (as a suggestion): stand up state and stand down state. I named it
standupNode and standdownNode
First, add the standupNode to the game scene. If swipe donw gesture recognize, I remove the standupNode from game scene and add the standdownNode instead. On contrary, removing the standdownNode from the game scene then add the standupNode if character stands up

Check if SKShapeNode which is a Line contains Point

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.

Image doesn't fit SKSpriteNode frame

I'm trying to create a player node on my scene. But the image "player.png" doesn't fit the physics body. They are both separate.
PhysicsBody is in the ground and the image is floating on the air. What's wrong with my code? How do I make them both together?
#import "Player.h"
#implementation Player
- (instancetype)init {
self = [super initWithImageNamed:#"player.png"];
self.name = #"player";
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(50, 50)];
self.physicsBody.dynamic = YES;
self.physicsBody.allowsRotation = NO;
self.physicsBody.affectedByGravity = YES;
self.zPosition = 100;
self.anchorPoint = CGPointMake(0.5, 0);
return self;
}
#end
// myScene.h
-(void)createSceneContents {
self.currentBackground = [Background generateBackground];
[self addChild: self.currentBackground];
self.physicsWorld.gravity = CGVectorMake(0, _gravity);
self.physicsWorld.contactDelegate = self;
Player *player = [[Player alloc]init];
player.position = CGPointMake([UIScreen mainScreen].applicationFrame.size.width/2, 50);
[self addChild:player];
}
Anchor point is the center of your physics body also here.
Your point 0.5, 0.0 means center x and zero y.
So, the center of your physics body is at the bottom edge of your sprite node.
Likely the bottom center of the image.
But the physics body extends downward from there.
This is because of the method you used to create the physics body.
Anchor points are confusing.
They play dual roles sometimes.
They include a lot of poorly documented implicit behaviors.
Unless you have logic relying on the anchor point, it's best to shy away from changing them.
With a physics body, what matters is where the body is in the physics world.
Keep your model of the sprite as simple as possible.
Refine constantly towards the simplest model to get the job done. It will simplify your game logic.

Tilemap floor NOT display before background picture

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

Why won't this SKPhysicsJointPin keep these 2 sprites together?

I clearly don't understand the SKPhysicsJoint very well, but there is so little info on the web yet, other than the Apple docs of course. What is wrong with the following code, which I would think should keep the head and neck permanently joined - my intention is that they act like 2 pieces of paper with a pin, so that they can rotate a bit, but not just totally come apart. When I run this code, they fall to the bottom of the SKScene they're in, hit the ground, then the head falls off the body.
Maybe the joint is not moving WITH them or something, it's just staying in place while they move??
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size];
self.head.physicsBody.mass = 0.05;
self.head.physicsBody.dynamic = YES;
self.chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck"];
self.chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.chest.size];
self.chest.physicsBody.mass = 0.05;
self.chest.physicsBody.dynamic = YES;
self.leftLeg = [SKSpriteNode spriteNodeWithImageNamed:#"left_leg"];
self.leftLeg.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.leftLeg.size];
self.leftLeg.physicsBody.mass = 10;
self.leftLeg.physicsBody.dynamic = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.head.position = CGPointMake(282, 220);
self.chest.position = CGPointMake(282, 130);
self.leftLeg.position = CGPointMake(282, 10);
} else {
self.head.position = CGPointMake(512, 380);
self.chest.position = CGPointMake(512, 290);
self.leftLeg.position = CGPointMake(512, 10);
}
[self addChild:self.head];
[self addChild:self.chest];
[self addChild:self.leftLeg];
self.chestJointPinAnchor = CGPointMake(self.chest.position.x, self.chest.position.y+39);
self.chestJointPin = [SKPhysicsJointPin jointWithBodyA:self.head.physicsBody bodyB:self.chest.physicsBody anchor:self.chestJointPinAnchor];
[self.physicsWorld addJoint:self.chestJointPin];
This is because you set sprite's position after you set up its physicsBody property.
I haven't discovered any mention of that in documentation, but I broke my head last weekend trying to figure out why my manually created rope works, but recursively one doesn't.
SpriteNodes' position MUST be set before physicsBody.
So, just reorder your code somehow like that:
self.head = [SKSpriteNode spriteNodeWithImageNamed:#"head.png"];
self.chest = [SKSpriteNode spriteNodeWithImageNamed:#"chest_neck"];
self.leftLeg = [SKSpriteNode spriteNodeWithImageNamed:#"left_leg"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.head.position = CGPointMake(282, 220);
self.chest.position = CGPointMake(282, 130);
self.leftLeg.position = CGPointMake(282, 10);
} else {
self.head.position = CGPointMake(512, 380);
self.chest.position = CGPointMake(512, 290);
self.leftLeg.position = CGPointMake(512, 10);
self.head.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.head.size];
self.head.physicsBody.mass = 0.05;
self.head.physicsBody.dynamic = YES;
self.chest.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.chest.size];
self.chest.physicsBody.mass = 0.05;
self.chest.physicsBody.dynamic = YES;
self.leftLeg.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.leftLeg.size];
self.leftLeg.physicsBody.mass = 10;
self.leftLeg.physicsBody.dynamic = YES;
}
Oh, I've noticed, you've already found an answer by yourself... Could you please mark your question as answered then.
That seems about right.
A pin joint allows both bodies to rotate around the joint's anchor point. A real world example is an analog clock. Or a bicycle's pedals. Or a car wheel's axle.
One thing you have to know is that bodies connected through a joint will not collide with each other. You can use the pin joint limits though to prevent the head from doing a full 360 spin around the pin joint anchor.
Okay, so I found out that this is an actual bug in Sprite Kit. The fix is to set the sprite's position before setting its physicsBody. I did that and it worked perfectly, as expected.
Additionally, not invalidating what was said above, but apparently the physics engine assumes the scene's anchorPoint is left at (0, 0). If you change it to something else, the physics engine will still treat it like (0, 0).

Resources