I'm just trying to set up a basic scene in landscape, with gravity, and having the scene in an edge loop.
I set up the scene's physics body and the mainCharacter sprite physics body, here is my code:
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [SKColor redColor];
[self setPhysicsBody:[SKPhysicsBody bodyWithEdgeLoopFromRect:[self frame]]];
}
return self;
}
-(void)setupMain
{
if (!self.mainCharacter)
{
self.mainCharacter = [[SKSpriteNode alloc] initWithImageNamed:#"spriteDefault"];
[self.mainCharacter setPosition:CGPointMake(CGRectGetMidX([self frame]), CGRectGetMidY([self frame]))];
[self addChild:self.mainCharacter];
self.mainCharacter.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.mainCharacter.frame.size];
self.mainCharacter.physicsBody.dynamic = YES;
self.mainCharacter.physicsBody.affectedByGravity = YES;
self.mainCharacter.physicsBody.mass = 0.02;
}
}
So, in portrait mode, everything works perfectly, however, in landscape, things get really screwy.
I figured it has something to do with
[self setPhysicsBody:[SKPhysicsBody bodyWithEdgeLoopFromRect:[self frame]]];
Oddly enough, the edge loop for the x axis for landscape (the y axis in portrait mode) works fine, but I just fall through the y axis (x for portrait).
My guess is that the frame is returning the position on the y axis somewhere not within the bounds of the screen in landscape mode.... meaning its somewhere above or below the screen.
...Maybe... Not really sure.
However, I have tried several different options, including manually setting the rectangle myself by using
CGRectMake()
I wasn't able to get anything to work properly.
Any advice would be greatly appreciated!!!
Okay, this is a super Janky fix...
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0, 200, 320, 200)];
I'm not sure why this works... and it's obviously going to have some problems on smaller screen sizes... better fixes and explanations would be much appreciated!!
Thanks :D
You will need to setup the edge loop in the viewWillLayoutSubviews, since the scene size is only known at then:
-(void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
if (!skView.scene) {
skView.showsFPS = YES;
skView.showsNodeCount = YES;
SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene];
}
}
You need to set the collisionBitMask of your mainCharacter equals to the sceneCategory (need to create). Your nodes will only be affected if you tell them.
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
I've developed a game in SpriteKit for a 5s. All my sprites are positioned just where I want them when I run the Xcode simulator on a 4s, 5 or 5s:
http://tinypic.com/r/f0yanl/8
But when I run it on a 6 my sprites are not positioned correctly, and some are even placed off the entire frame/screen:
http://tinypic.com/r/akdac7/8
Notice my HUD is showing up top out of the frame, and the shadow and positioning of my "Machine" at the bottom is off, etc.
Is there a quick, simple fix to ensure the sprites maintain their position relative to my background image, when running on a 6? For now, I'm OK with the entire screen being collapsed a bit when running on a 6 (with empty borders on all four sides), but I would like to make sure everything at least looks just as clean and in order as it does on a 5.
I've used anchor points for most of my nodes, for e.g.:
+ (instancetype) machineAtPosition:(CGPoint)position {
GameMachineNode *machine = [self spriteNodeWithImageNamed:#"machine_1"];
machine.position = position;
machine.name = #"Machine";
machine.anchorPoint = CGPointMake(0.5, 0);
....
}
My "GameViewController.m" includes a resize to fill mode:
SKScene * scene = [GameTitleScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeResizeFill;
Update: figured it out. Was as simple as adding this single line of code to the initWithSize method:
background.size = self.frame.size;
Full code block for reference:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.lastUpdateTimeInterval = 0; //initializing this property
self.timeSinceEnemyAdded = 0; //initializing this property
self.addEnemyTimeInterval = 1.25;
self.totalGameTime = 0;
self.minSpeed = GameAstroDogMinSpeed;
self.restart = NO;
self.gameOver = NO;
self.gameOverDisplayed = NO;
// Setup your scene here....
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:#"background_1"];
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
background.size = self.frame.size;
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!
I have a SpriteKit game which I want to support all orientations. Right now when I change the orientation, the node doesn't keep its position. I use the SKSceneScaleModeResizeFill for scaling, because it will keep the right sprite size.
When I start the game, the game player is positioned in the mid screen like this:
Then when I rotate the device, the position becomes like this:
Here is my view controller code:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
if (!skView.scene) {
// Create and configure the scene.
SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeResizeFill;
// Present the scene.
[skView presentScene:scene];
}
}
And my scene code:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
//Add spaceship in the center of the view
SKSpriteNode *spaceship = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship.position = CGPointMake(size.width/2, size.height/2);
[spaceship setScale:.3];
[self addChild:spaceship];
}
return self;
}
Your sprite does keep its position after the scene resizes — you can see from your screenshots that it keeps the same horizontal and vertical distance from the lower left corner of the scene. The catch is that after the scene has resized, that absolute offset represents a different relative position in your scene.
Sprite Kit can resize a scene automatically, but the relative positioning of nodes after a scene resize isn't something it can do for you. There's no "right answer" to how a scene's content should be rearranged at a different size, because the arrangement of scene content is something your app defines.
Implement didChangeSize: in your SKScene subclass, and put whatever logic you want there for moving your nodes.
For example, you could make it so nodes keep their positions as a relative proportion of the scene size using something like this:
- (void)didChangeSize:(CGSize)oldSize {
for (SKNode *node in self.children) {
CGPoint newPosition;
newPosition.x = node.position.x / oldSize.width * self.frame.size.width;
newPosition.y = node.position.y / oldSize.height * self.frame.size.height;
node.position = newPosition;
}
}
Depending on what's in your scene and you you've arranged it, you probably don't want that, though. For example, if you have HUD elements in each corner of your game scene, you might want them at a fixed offset from the corners, not a proportion of the scene size.
I add I similar issue and found this question. I solved differently, using the viewWillTransitionToSize:withTransitionCoordinator:, as stated by #GOR here.
I added the following code in my view controller (that manage the SKView and its SKScene)
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
//skView is my SKView object, with scaleMode SKSceneScaleModeResizeFill
skView.frame = CGRectMake(0, 0, size.width, size.height);
//currentScene is my SKScene object
currentScene.size = skView.frame.size;
//Then, as all the objects in my scene are children of a unique SKNode* background, I only relocate it
currentScene.background.position = CGPointMake(CGRectGetMidX(currentScene.frame), CGRectGetMidY(currentScene.frame));
}
and it works like a charm!
For Swift 3,
override func didChangeSize(_ oldSize: CGSize) {
for node in self.children{
let newPosition = CGPoint(x:node.position.x / oldSize.width * self.frame.size.width,y:node.position.y / oldSize.height * self.frame.size.height)
node.position = newPosition
}
}
Thus we are able to use a constant and initialise it in one line. Then in node.position = newPosition we can set the new position.
Also we are able to make use of the enhanced for loop leading to a much more elegant solution.