Anti-gravity physics in SceneKit - ios

I am trying to make a simple app where if you tap the screen a box is switched from floating to affected by gravity. I can't seem to find a way to make the box float in the air though.
This code here takes care of half the problem:
boxNode.physicsBody = [SCNPhysicsBody dynamicBody];
This causes the box to drop out of the air and hit a floor I created. Is there anything in SCNPhysicsBody that would do the opposite of this? Say, perhaps, cause objects to float or just sail off toward a ceiling?
Also, I've written this code:
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
if (myBool == false) {
myBool = true;
NSLog(#"true");
} else {
myBool = false;
NSLog(#"false");
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// touch recognizer
UITapGestureRecognizer *screenTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[self.view addGestureRecognizer:screenTap];
// create box
SCNBox *myBox = [SCNBox boxWithWidth:1.0 height:1.0 length:1.0 chamferRadius:0.1];
SCNNode *boxNode = [SCNNode nodeWithGeometry:myBox];
boxNode.position = SCNVector3Make(0.0, 0.0, 4.0);
[myScene.rootNode addChildNode:boxNode];
while (myBool == true) {
boxNode.physicsBody = [SCNPhysicsBody dynamicBody];
}
}
I'm not sure why the while loop doesn't work though. I was thinking it would detect that myBool has been changed and alter the physics of the boxNode, but it doesn't.

The viewDidLoad method is only called once when the view is loaded. If your app is initialised with myBool = false, then the while loop will never be run. However in this case if myBool was true the while loop would never stop executing, preventing the view from loading, and therefore preventing the user from tapping the view to trigger your gesture recogniser.
I haven't tested the below, but it should at least give you a starting point. The scene is created in the viewDidLoad as per your code, importantly the scene's physicsWorld has its gravity set to zero (this is -9.8 by default). Later on when the user taps the view we reset the gravity to its default value, this should cause the box to fall.
The header file GameViewController.h
#import <UIKit/UIKit.h>
#import <SceneKit/SceneKit.h>
#interface GameViewController : UIViewController {
SCNScene *myScene;
}
#end
and GameViewController.m
#import "GameViewController.h"
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// create a new scene
myScene = [SCNScene scene];
// create box
SCNBox *myBox = [SCNBox boxWithWidth:1.0 height:1.0 length:1.0 chamferRadius:0.1];
SCNNode *boxNode = [SCNNode nodeWithGeometry:myBox];
boxNode.position = SCNVector3Make(0.0, 0.0, 4.0);
[myScene.rootNode addChildNode:boxNode];
boxNode.physicsBody = [SCNPhysicsBody dynamicBody];
//'disable' scene gravity
myScene.physicsWorld.gravity = SCNVector3Make(0, 0, 0);
SCNView *scnView = (SCNView *)self.view;
scnView.scene = myScene;
scnView.allowsCameraControl = YES;
scnView.autoenablesDefaultLighting = YES;
scnView.backgroundColor = [UIColor blackColor];
// add a tap gesture recognizer
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
NSMutableArray *gestureRecognizers = [NSMutableArray array];
[gestureRecognizers addObject:tapGesture];
[gestureRecognizers addObjectsFromArray:scnView.gestureRecognizers];
scnView.gestureRecognizers = gestureRecognizers;
}
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
myScene.physicsWorld.gravity = SCNVector3Make(0, -9.8, 0);
}
#end

Related

SceneKit Crash when move finger change scncamrea's position quickly

iPhoen8 Plus,iOS 11(15A372), Xcode9(9A235)
I load a scn file to show the 3D object.This is my code.This modelView will
as subView on a UIView, the UIView is the subView on UIScrollView.When I touch and move my finger, scnview's object will do some rotation. If I move quickly, it crashed.
// ModelView.m : SCNView
+ (instancetype)modelWithxxx {
ModelView *model = [[ModelView alloc] initWithFrame:MODELVIEWRECT];
model.scene = [SCNScene sceneNamed:#"xxx.scn"];
return model;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
self.allowsCameraControl = YES;
self.autoenablesDefaultLighting = YES;
}
return self;
}
It looks like ScnView's bug, I don't know what happen, I just load the .scn file and use cameracontrol.

Change SCNScene's sceneNamed programmatically

I am trying to change a current sceneNamed with code but it seems my method has some problems. First, the new scene will be changed but I have to touch or rate the object to changing process happens.Second, it seems childNodeWithName doesn't change at all ! Here is my code :
- (void)load3DObjectName:(NSString*)name nodeName:(NSString*)nodeName zPhone:(CGFloat)positioniPhone zPad:(CGFloat)positioniPad{
SCNScene * scene = [SCNScene sceneNamed:name];
// retrieve the ship node
SCNNode *trex = [scene.rootNode childNodeWithName:nodeName recursively:YES];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
trex.position = SCNVector3Make(0, 0, positioniPhone);
} else {
trex.position = SCNVector3Make(0, 0, positioniPad);
}
_the3DScence.scene = scene;
_the3DScence.autoenablesDefaultLighting = YES;
_the3DScence.allowsCameraControl = YES;
_the3DScence.backgroundColor = [UIColor colorWithRed:0.92 green:0.92 blue:0.92 alpha:1.00];
}
// Load default object :
- (void)viewDidLoad {
[self load3DObjectName:#"cube.dae" nodeName:#"cube1" zPhone:-40 zPad:-30];
}
//Trying to change the 3D object with button:
- (IBAction)nextObject:(id)sender {
[self load3DObjectName:#"redCube.dae" nodeName:#"cube2" zPhone:-40 zPad:-30];
}
- (IBAction)changeIt:(id)sender {
[self load3DObjectName:#"dayere.dae" nodeName:#"Sphere" zPhone:-40 zPad:-40];
}
Here is a source code :
https://www.dropbox.com/s/rrvbxmrb9wcrnoj/3D%20Objects%20Change.zip?dl=0
The code in the Dropbox version is not what I posted above. Here is the Dropbox version:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self load3DObjectName:#"cube.dae" nodeName:#"Cube" zPhone:-40 zPad:-40];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)load3DObjectName:(NSString*)name nodeName:(NSString*)nodeName zPhone:(CGFloat)positioniPhone zPad:(CGFloat)positioniPad{
SCNScene * scene = [SCNScene sceneNamed:name];
// retrieve the ship node
SCNNode *node = [scene.rootNode childNodeWithName:nodeName recursively:YES];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
node.position = SCNVector3Make(0, 0, positioniPhone);
}
else {
node.position = SCNVector3Make(0, 0, positioniPad);
}
_the3DView.scene = scene;
_the3DView.autoenablesDefaultLighting = YES;
_the3DView.allowsCameraControl = YES;
_the3DView.backgroundColor = [UIColor colorWithRed:0.92 green:0.92 blue:0.92 alpha:1.00];
}
- (IBAction)changeIt:(id)sender {
[self load3DObjectName:#"dayere.dae" nodeName:#"Sphere" zPhone:-40 zPad:-40];
}
So I have take a look at your project. here is the issue: you don't have camera in you scenes. So I put camera for your each scenes manually at the same distance, and moved the nodes as desired. here is what it looks like now:
- (void)load3DObjectName:(NSString*)name nodeName:(NSString*)nodeName zPhone:(CGFloat)positioniPhone zPad:(CGFloat)positioniPad
{
SCNScene * scene = [SCNScene sceneNamed:name];
SCNNode *node = [scene.rootNode childNodeWithName:nodeName recursively:YES];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
{
node.position = SCNVector3Make(0, 0, positioniPhone);
} else {
node.position = SCNVector3Make(0, 0, positioniPad);
}
SCNCamera *cam = [[SCNCamera alloc] init];
cam.xFov = 35;
cam.yFov = 35;
cam.zFar = 5000;
cam.zNear = 0;
SCNNode *camNode = [[SCNNode alloc] init];
camNode.camera = cam;
camNode.position = SCNVector3Make(0, 0, 400);
[scene.rootNode addChildNode:camNode];
_the3DView.pointOfView = camNode;
_the3DView.scene = scene;
_the3DView.autoenablesDefaultLighting = YES;
_the3DView.allowsCameraControl = YES;
_the3DView.backgroundColor = [UIColor colorWithRed:0.92 green:0.92 blue:0.92 alpha:1.00];
}
and I also changed the your object positions for testing purposes:
- (IBAction)changeIt:(id)sender
{
[self load3DObjectName:#"dayere.dae" nodeName:#"Sphere" zPhone:-340 zPad:-340];
}
and this one as well:
- (void)viewDidLoad
{
[super viewDidLoad];
[self load3DObjectName:#"cube.dae" nodeName:#"Cube" zPhone:-40 zPad:-40];
}
and last but not least, the screen shots:
It looks to me like your second round of code works fine. I notice that you're loading different scenes/nodes in the Dropbox sample than you are in the code you posted first. So that makes me think there's something in your DAE file that isn't what you expect it to be. Check for node names that are missing or misspelled. Add NSAssert calls after your childNodeWithName: and sceneNamed: calls, to make sure that you really did successfully load the scene and find the child node.
SCNScene * scene = [SCNScene sceneNamed:name];
NSAssert(scene, #"failed to load scene named %#", name);
// retrieve the ship node
SCNNode *node = [scene.rootNode childNodeWithName:nodeName recursively:YES];
NSAssert(node, #"couldn't fine child node named %#", nodeName);
If the NSAsserts fail, use your 3D editing tool to fix your Collada file.
Note also that when you replace the3DView.scene (aside: please don't bang instance variables directly, use accessor methods instead!), you're replacing the camera and lighting too. So you should expect a jerk in the camera positioning. Maybe you want to animate this? Or remove/replace nodes for the objects, without messing with camera and lighting?
When I run the code you posted to Dropbox, I see a screen full of red. When I zoom away, and rotate the camera, it looks like this:
After I tap the "Button" button, I see this:

Objective-C. How is your first SKScene initialised?

This method sets up the initial scene and is called as soon as the application opens:
#import "GameScene.h"
#import "WarScene.h"
#import "Math.h"
#implementation GameScene
{
SKNode *map;
float oldY;
float oldX;
BOOL buttonClicked;
int thisButton;
float oldMapPosition;
float midScreenX;
float midScreenY;
int separation;
float sendY;
BOOL interacting;
CGVector speed;
float oldPosition;
float newPosition;
BOOL isReleased;
int iterations;
float diff;
BOOL comeToStop;
BOOL actualStop;
BOOL clicked;
}
int numberOfLevels = 20;
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
map = [SKNode node];
separation = [UIScreen mainScreen].bounds.size.height;
if (UIDeviceOrientationIsLandscape((UIDeviceOrientation)[[UIApplication sharedApplication] statusBarOrientation]))
{
//landscape mode
midScreenX = ([[UIScreen mainScreen]bounds].size.width>[[UIScreen mainScreen]bounds].size.height?[[UIScreen mainScreen]bounds].size.width:[[UIScreen mainScreen]bounds].size.height)/2;
midScreenY = ([[UIScreen mainScreen]bounds].size.width<[[UIScreen mainScreen]bounds].size.height?[[UIScreen mainScreen]bounds].size.width:[[UIScreen mainScreen]bounds].size.height)/2;
}
else
{
//portrait mode
midScreenX = [[UIScreen mainScreen]bounds].size.width/2;
midScreenY = [[UIScreen mainScreen]bounds].size.height/2;
}
NSLog(#"Initial Width: %f Height: %f", midScreenX*2, midScreenY*2);
map.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: CGSizeMake([UIScreen mainScreen].bounds.size.width, separation*(numberOfLevels+1))];
map.physicsBody.affectedByGravity = NO;
map.physicsBody.allowsRotation = NO;
clicked = NO;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handleTap:)];
tap.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tap];
self.backgroundColor = [SKColor blackColor];
for (int i = 0; i<numberOfLevels; i += 1) {
SKLabelNode *title;
{
title = [SKLabelNode labelNodeWithFontNamed:#"Cochin"];
title.text = [NSString stringWithFormat: #"Level %d", 1+i];
title.fontSize = 60;
title.position = CGPointMake(CGRectGetMidX(self.frame),
((i+1)*separation));
title.fontColor = [UIColor whiteColor];
title.name = [NSString stringWithFormat: #"Level %d", 1+i];
[map addChild:title];
}
}
[self addChild:map];
}
This works exactly as I intended it to, however, when I call this from another class:
-(void)continueMethod:(UIButton*)button{
NSLog(#"InitwithSize to GameScene Width: %f Height: %f", midScreenX*2, midScreenY*2);
GameScene *map = [[GameScene alloc] initWithSize: CGSizeMake(midScreenX*2 ,midScreenY*2)];
SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:1.0];
SKView * skView = (SKView *)self.view;
UIView *viewToRemove = [self.view viewWithTag:3];
[viewToRemove removeFromSuperview];
[skView presentScene:map transition:reveal];
}
The scene which should be set up doesn't appear as I intended it to, or indeed how it used to look the last time that it was initialised. The separation variable appears bigger, the text appears bigger and everything is wider. I've verified that the UIScreen that I have initialised is exactly the same throughout.
This led me to question how the initial scene of a SKSprite application comes to be and whether it is different to "initWithSize:" however, looking through all the code that comes in the game template I can't ever see my first scene get called. The closest that I can find to initWithSize: is the following from the GameViewController-
GameScene *scene = [GameScene unarchiveFromFile:#"GameScene"];
scene.scaleMode = SKSceneScaleModeAspectFill;
Firstly, is this what is initialising my initial scene? Secondly is it in anyway different to initWithSize: ? And finally if the answer to the previous tow questions is yes, should it be used instead of initWithsize: ?
Yes, this is the initialization of the initial scene! What you are seeing in the View Controller is a slightly different initialization. You are using the sks file, first of all, and initWithCoder: is being called when the view controller unarchives it. However, this isn't very different from initWithSize if you specify all of the same dimensions and properties.
That being said, the scene's scaleMode in your initialization and the view controller's scaleMode is different. As you can see, the view controller specifies:
scene.scaleMode = SKSceneScaleModeAspectFill;
The AspectFill scale mode according to Apple:
The scaling factor of each dimension is calculated and the smaller of the two is chosen. Each axis of the scene is scaled by the same scaling factor. This guarantees that the entire scene is visible but may require letterboxing in the view.
When you initialize your scene, on the other hand, you leave the default scaleMode, which happens to be SKSceneScaleModeFill. This maps each axis of the scene to the view, so you get a distorted scene. What you need to do is simply state:
map.scaleMode = SKSceneScaleModeAspectFill;
This will use the same scale mode as in the original scene and the distortion that you see now will be gone.
Hope this helps, let me know if you have questions.

Bonbons falling continuously from random positions

I have got the following problem in SpriteKit: My aim is to let bonbons fall continuously from the top of the screen. These bonbons can be collected by the player. Every 0.8 seconds or so, another bonbon should fall down and when the player collides with one of the bonbons, it should disappear. This is my code:
-(void)populate {
for (int i = 0; i < 2; i++) {
[self generate];
}
}
-(void)generate {
Y = (arc4random() % 280) - 140;
bonbon = [SKSpriteNode spriteNodeWithImageNamed:#"Bonbon.png"];
bonbon.size = CGSizeMake(20, 20);
bonbon.name = #"bonbon";
bonbon.physicsBody.categoryBitMask = bonbonCategory;
bonbon.position = CGPointMake(Y, 500);
bonbon.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bonbon.size.width/2];
bonbon.physicsBody.dynamic = YES;
[bonbon addChild:bonbon];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if ([contact.bodyA.node.name isEqualToString: #"wallLeft"] || [contact.bodyB.node.name isEqualToString: #"wallLeft"])
{
[hero.physicsBody applyImpulse: CGVectorMake(100, 60)];
self.jumpDirection = YES;
}
else if ([contact.bodyA.node.name isEqualToString: #"bonbon"] || [contact.bodyB.node.name isEqualToString: #"bonbon"])
{
[world enumerateChildNodesWithName:#"bonbon" usingBlock:^(SKNode *node, BOOL *stop) {
PointsLabel *pointsLabel = (PointsLabel *)[self childNodeWithName:#"pointsLabel"];
[pointsLabel increment];
NSLog(#"didContactBonbon");
[bonbon removeFromParent];
NSLog(#"removedFromParent");
}];
}
else {
[hero.physicsBody applyImpulse: CGVectorMake(-100, 60)];
self.jumpDirection = NO;
}
}
I hope you understand my problem. Currently, no bonbons are falling at all. If you need more information, please do not hesitate to ask.
Greets
edit: I hope the formatting is better now. I am quite a newbie, i'm sorry for foolish mistakes :) This is in my MyScene.m, when is call the populate in the initWithSize method with [self populate]; I receive the message signal SIGABRT. What did I do wrong?
I think the problem is that you are adding your SKSpriteNode named bonbon to itself with :
- (void)generate {
...
bonbon = [SKSpriteNode spriteNodeWithImageNamed:#"Bonbon.png"];
...
[bonbon addChild:bonbon];
}
Try to add bonbon to your SKScene instance.
To use an SKView instance you have to create a UIViewController and give its view the SKView class. You can do that in your storyboard.
Then, in your viewController :
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
// Configure the scene
SKScene *scene = [SKScene sceneWithSize:self.view.bounds.size];
// You can add objects to your scene
[scene addChild:bonbon];
// Present the scene.
[skView presentScene:scene];
}
#end
This is a very simple example but if you want more information, I suggest you read this tutorial: Sprite Kit Tutorial for Beginners.

CCNode Hud, not acting as Hud but "scrolling with scene"

Attempting to simply have a scene where I have the gameplay, and the hud. Since I have it positioning the scene (which contains both layers), I assume this is why my Menu button is moving even though it's in the Hud Layer.
To fix it I would think that I would maybe have to change this line
[self setCenterOfScreen: mainChar.position];
to something like this:
[gameLayer setCenterOfScreen: mainChar.position];
But then I get this:
No visible #interface for CCNode declares the selector
'setCenterOfScreen:'
Here is the
GameScene (commented where I think issue is):
+ (GameScene *)scene
{
return [[self alloc] init];
}
// -----------------------------------------------------------------------
- (id)init
{
self = [super init];
if (!self) return(nil);
CGSize winSize = [CCDirector sharedDirector].viewSize;
self.userInteractionEnabled = YES;
self.theMap = [CCTiledMap tiledMapWithFile:#"AftermathRpg.tmx"];
self.contentSize = theMap.contentSize;
CCNode *gameLayer = [CCNode node];
gameLayer.userInteractionEnabled = YES;
gameLayer.contentSize = self.contentSize;
[self addChild:gameLayer];
[gameLayer addChild:theMap z:-1];
self.mainChar = [CCSprite spriteWithImageNamed:#"mainChar.png"];
mainChar.position = ccp(200,300);
[gameLayer addChild:mainChar];
// POSSIBLY WHY BOTH LAYERS ARE SHIFTING, RATHER THEN HUD STANDING STILL,
// I essentially want the gameLayer to be centered on screen, not both.
// I tried [gameLayer setCenterOfScreen: mainChar.position] but got error posted above
[self setCenterOfScreen: mainChar.position];
CCNode *hudLayer = [CCNode node];
hudLayer.userInteractionEnabled = YES;
hudLayer.position = ccp(winSize.width * 0.9, winSize.height * 0.9);
[self addChild:hudLayer];
CCButton *backButton = [CCButton buttonWithTitle:#"[ Menu ]" fontName:#"Verdana-Bold" fontSize:18.0f];
backButton.positionType = CCPositionTypeNormalized;
backButton.position = ccp(0.85f, 0.95f); // Top Right of screen
[backButton setTarget:self selector:#selector(onBackClicked:)];
[hudLayer addChild:backButton];
return self;
}
but then I just get:
Just confused on how I would set up a scene, to initially have 2 layers - one that keeps position in say the top right as a HUD, and the other that scrolls with scene for gameplay when telling it to.
This is related to the other question you asked here (Adding a Hud Layer to Scene Cocos2d-3) which I posted the answer to. In the future it is better to modify your original OP in your first question rather than create a new question that is almost the same.
Hope this helped.

Resources