I'm trying out a spring joint in SpriteKit (i.e. SKPhysicsJointSpring) with this simple scene. Pretty much, I've got a red sprite acting as the "ceiling", and then an orange sprite acting as a mass "block" that is supposed to be suspended from it by a spring (note: I did not draw anything to connect the two squares, but just imagine there was a spring there).
With the default gravity, I would expect that the orange block would begin to bounce up and down, but in fact, it just sits there. To further my confusion, if I uncomment the application of some force at the end of the scene's -didMoveToView: method, the x direction of the vector seems to actually be affecting the orange block (it begins to act as a pendulum), but the y direction vector doesn't seem to affect anything. It's as if the spring is really acting like a rigid rod. Is that supposed to happen?
And finally, why does the pendulum-like motion eventually dampen out? It seems that the default friction is 0.0, and I have not applied any friction myself. Can someone help me better understand this SKPhysicsJointSpring?
#import "XYZMainScene.h"
#interface XYZMainScene ()
#property (nonatomic, strong) SKSpriteNode *ceiling;
#property (nonatomic, strong) SKSpriteNode *block;
#end
#implementation XYZMainScene
- (void)didMoveToView:(SKView *)view {
SKSpriteNode *ceiling = self.ceiling;
[self addChild:ceiling];
SKSpriteNode *block = self.block;
[self addChild:block];
SKPhysicsJointSpring *spring = [SKPhysicsJointSpring jointWithBodyA:ceiling.physicsBody
bodyB:block.physicsBody
anchorA:ceiling.position
anchorB:block.position];
[self.physicsWorld addJoint:spring];
// [block.physicsBody applyForce:CGVectorMake(60, -100)];
}
- (SKSpriteNode *)ceiling {
if (!_ceiling) {
_ceiling = [SKSpriteNode spriteNodeWithColor:[SKColor redColor]
size:CGSizeMake(30, 30)];
_ceiling.position = CGPointMake(self.frame.size.width/2, 400);
_ceiling.physicsBody = [SKPhysicsBody bodyWithEdgeFromPoint:_ceiling.position
toPoint:_ceiling.position];
}
return _ceiling;
}
- (SKSpriteNode *)block {
if (!_block) {
_block = [SKSpriteNode spriteNodeWithColor:[SKColor orangeColor]
size:CGSizeMake(50,50)];
_block.position = CGPointMake(self.ceiling.position.x, self.ceiling.position.y - 200);
_block.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_block.frame.size];
}
return _block;
}
#end
You need to change the frequency and damping properties of the SKPhysicsJointSpring.
SKPhysicsJointSpring *spring = [SKPhysicsJointSpring jointWithBodyA:ceiling.physicsBody
bodyB:block.physicsBody
anchorA:ceiling.position
anchorB:block.position];
spring.frequency = 1.0; //gives the spring some elasticity.
spring.damping = 0.0; //Will remove damping to create the 'pendulum'
[self.physicsWorld addJoint:spring];
Read up on the SKPhysicsJointSpring class reference here.
Related
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
We have been using enums to organize the zPositions of our sprites. As we started to add SKNodes with several subsprites to our game, the structure quickly began to break down. Some child sprites that were placed on the screen had to have negative values in order to be beneath other nodes with children. These negative values are hard to keep track of in relation to other sprites in separate enums.
Is there a better way to organize the zPosition of sprites (especially those with sub-sprites) than using enums?
Upon reading more into your issue, it looks like you are using multiple enums to organize your z order. I suggest using a single enum for the z - ordering of your game.
Use layers to organize your scene:
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.view.ignoresSiblingOrder = YES;
[self addLayers];
}
return self;
}
- (void)addLayers {
self.backgroundLayer = [SKNode node];
self.backgroundLayer.zPosition = 100;
[self addChild:self.backgroundLayer];
self.playerLayer = [SKNode node];
self.playerLayer.zPosition = 300;
[self addChild:self.playerLayer];
self.enemyLayer = [SKNode node];
self.enemyLayer.zPosition = 400;
[self addChild:self.enemyLayer];
// UI elements must be on top of all nodes on the scene
self.uiLayer = [SKNode node];
self.uiLayer.zPosition = 1000;
}
- (void)addBackgrounds {
SKSpriteNode *backgroundNode1 = [SKSpriteNode spriteNodeWithTexture:[self backgroundTexture1]];
backgroundNode1.zPosition = 10;
[self.backgroundLayer addChild:backgroundNode1];
SKSpriteNode *backgroundNode2 = [SKSpriteNode spriteNodeWithTexture:[self backgroundTexture2]];
backgroundNode2.zPosition = 20;
[self.backgroundLayer addChild:backgroundNode2];
}
.... etc
please help me sort this confusion out.
From Sprite Kit Programming Guide:
A sprite node’s anchorPoint property determines which point in the
frame is positioned at the sprite’s position.
My understanding of this is that if I change the Anchor Point, the sprite`s position should stay unchanged and only the texture rendering should be moved accordingly.
But when I set the anchor point, my sprite`s position actually changes! Take a look at this snippet:
/* debug */
if (self.currentState == self.editState) {
printf("B: relativeAnchorPoint = %.02f,%.02f ", relativeAnchorPoint.x, relativeAnchorPoint.y);
printf("position = %.02f,%.02f\n",self.position.x, self.position.y);
}
[self setAnchorPoint:relativeAnchorPoint];
/* debug */
if (self.currentState == self.editState) {
printf("A: relativeAnchorPoint = %.02f,%.02f ", relativeAnchorPoint.x, relativeAnchorPoint.y);
printf("position = %.02f,%.02f\n",self.position.x, self.position.y);
}
Output:
A: relativeAnchorPoint = 0.65,0.48 position = 1532.00,384.00
B: relativeAnchorPoint = 0.65,0.48 position = 1583.00,384.00
What am I missing?
Thanks in advance
*edit: additional info: *
it only happens when my sprite has xScale to -1 to invert image
I made a quick test to confirm your observation, and it is indeed correct.
As the xScale becomes negative the anchorPoint does actually affect the node's position.
I tend to think of this as a bug since there seems to be no correlation between the negative xScale and the increase in x position. And it can't be considered normal behavior.
Also this only happens when you change the anchorPoint after the xScale is already negative. You can set anchorPoint, then change xScale all you want and things will be fine, position will not change.
I confirmed this issue exists in both Xcode 5.1 (iOS 7) and Xcode 6 beta (iOS 8 beta).
If you run the following code in a newly created Sprite Kit project in place of its auto-created MyScene.m file you'll see that as anchorPoint changes randomly between 0.0 and 1.0 the position of the sprite always remains the same until the xScale property changes to a negative value. At that point position.x starts to increase significantly.
#import "MyScene.h"
#implementation MyScene
{
SKSpriteNode *sprite;
}
-(id) initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [SKColor colorWithRed:0 green:0 blue:0.2 alpha:1];
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
sprite.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
sprite.anchorPoint = CGPointMake(0.2, 0.7);
[self addChild:sprite];
SKAction *action = [SKAction scaleXTo:-1.0 duration:10];
[sprite runAction:[SKAction repeatActionForever:action]];
}
return self;
}
-(void) update:(CFTimeInterval)currentTime
{
sprite.anchorPoint = CGPointMake(arc4random_uniform(10000) / 10000.0,
arc4random_uniform(10000) / 10000.0);
NSLog(#"pos: {%.1f, %.1f}, xScale: %.3f, anchor: {%.2f, %.2f}",
sprite.position.x, sprite.position.y, sprite.xScale,
sprite.anchorPoint.x, sprite.anchorPoint.y);
}
#end
There is a workaround for this bug:
If xScale is already negative, invert it, then set the anchorPoint, then re-invert xScale. You may need to do the same with yScale if that too can become negative.
The following update method incorporates this workaround and I confirmed that this is working as intended:
-(void) update:(CFTimeInterval)currentTime
{
BOOL didInvert = NO;
if (sprite.xScale < 0.0)
{
didInvert = YES;
sprite.xScale *= -1.0;
}
sprite.anchorPoint = CGPointMake(arc4random_uniform(10000) / 10000.0,
arc4random_uniform(10000) / 10000.0);
if (didInvert)
{
sprite.xScale *= -1.0;
}
NSLog(#"pos: {%.1f, %.1f}, xScale: %.3f, anchor: {%.2f, %.2f}",
sprite.position.x, sprite.position.y, sprite.xScale,
sprite.anchorPoint.x, sprite.anchorPoint.y);
}
The sprite.position now remains the same throughout the entire scaleXTo action duration.
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.
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!