Here is my question:
How can I convert an SKSpriteNodearray into an array of GKObstacles so that an agent can appropriately avoid these obstacles by using the goalToAvoidObstacles:(nonnull NSArray<GKObstacle *> *) maxPredictionTime:(NSTimeInterval)/?
It seems that the way in which I am creating the GKObstacle array is not allowing the GKGoal to identify these as obstacles correctly, no matter what weight I give to the goal.
I have currently several SKNodes that are added to an SKScene chapterScene. Each of these nodes are added to an array that I am storing called obstaclesArray. I have set up a test in the following way that works quite well:
WORKING OBSTACLES
- (void)didMoveToView:(nonnull SKView *)view {
[super didMoveToView:view];
// Add three obstacles in a triangle formation around the center of the scene.
NSArray<GKObstacle *> *obstacles = #[
[self addObstacleAtPoint:CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame) + 150)],
[self addObstacleAtPoint:CGPointMake(CGRectGetMidX(self.frame) - 200,
CGRectGetMidY(self.frame) - 150)],
[self addObstacleAtPoint:CGPointMake(CGRectGetMidX(self.frame) + 200,
CGRectGetMidY(self.frame) - 150)],
];
// The player agent follows the tracking agent.
self.player = [[OPlayer alloc] initWithScene:self
radius:50
position:CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame))];
self.player.agent.behavior = [[GKBehavior alloc] init];
[self.agentSystem addComponent:self.player.agent];
// Create the seek goal, but add it to the behavior only in -setSeeking:.
self.seekGoal = [GKGoal goalToSeekAgent:self.character];
// Add an avoid-obstacles goal with a high weight to keep the agent from overlapping the obstacles.
[self.player.agent.behavior setWeight:100 forGoal:[GKGoal goalToAvoidObstacles:obstacles maxPredictionTime:1]];
}
- (GKObstacle *)addObstacleAtPoint:(CGPoint)point {
SKShapeNode *circleShape = [SKShapeNode shapeNodeWithCircleOfRadius:50];
circleShape.lineWidth = 2.5;
circleShape.fillColor = [SKColor grayColor];
circleShape.strokeColor = [SKColor redColor];
circleShape.zPosition = 1;
circleShape.position = point;
[self addChild:circleShape];
GKCircleObstacle *obstacle = [GKCircleObstacle obstacleWithRadius:50];
obstacle.position = (vector_float2){point.x, point.y};
return obstacle;
}
While this works just fine, the issue I am having is that I can not get the behavior to identify the SKNode bodies array that I have as obstacles. I can only get these manually created GKCircleObstacle items to register as valid obstacles that the agent avoids. The reason this is an issue is because I am relying on many obstacles that are not in fact simple circles, but polygon structures some complex, some more straightforward. Nevertheless, I was attempting the following but my agent did not seem to avoid any of the obstacles that are of SKNode this way:
NOT WORKING OBSTACLES
NSArray *obstacles = [SKNode obstaclesFromNodePhysicsBodies:obstaclesArray];
/*
The other code seen above
*/
// Add an avoid-obstacles goal with a high weight to keep the agent from overlapping the obstacles.
[self.player.agent.behavior setWeight:100 forGoal:[GKGoal goalToAvoidObstacles:obstacles maxPredictionTime:1]];
Unfortunately, this does not seem to work as no matter how high I set the weight to, the agent never responds to avoiding the obstacles. Here is a log of the array I am passing to it:
2016-07-24 22:32:24.907 <GAME>[1516:475581] obstacles: (
"<GKPolygonObstacle: 0x14d20d70>",
"<GKPolygonObstacle: 0x14d20e10>",
"<GKPolygonObstacle: 0x14d20ba0>",
"<GKPolygonObstacle: 0x14d20bb0>",
"<GKPolygonObstacle: 0x14d20bc0>",
"<GKPolygonObstacle: 0x14d20a60>",
"<GKPolygonObstacle: 0x14d208b0>",
"<GKPolygonObstacle: 0x14d207d0>",
"<GKPolygonObstacle: 0x14d20a70>"
)
Each of which have all been clearly and appropriately initialized and added to the scene. Each of these elements are in fact responsive with the SpriteKit didBeginContact:(SKPhysicsContact *)contact method, so it seems that the nodes are properly handing the boundaries that they should be having.
If someone knows what I am doing wrong or a better way to turn each of these 'obstacle nodes' into GKObstacle's I would be much appreciative. Thanks in advance!
UPDATE: Here is a node with a physics body that I am testing:
SKSpriteNode *innerMap1 = [SKSpriteNode spriteNodeWithImageNamed:#"test"];
innerMap1.physicsBody = [SKPhysicsBody bodyWithTexture:[SKTexture textureWithImageNamed:#"test"] size:CGSizeMake(innerMap1.frame.size.width*0.92, innerMap1.frame.size.height*0.92)];
innerMap1.position = CGPointMake(-220, -140);
innerMap1.physicsBody.dynamic = NO;
[chapterSKSpriteNodes addObject:innerMap1];
Here is what the body looks like with skView.showsPhysics = YES;:
So I know that the physics body is what I need it to be. When I attempt to create the GKObstacle from this body however, it does not seem to register as an obstacle when being assigned in the avoidObstacles goal.
This sounds like a bug. You should check the return array from obstaclesFromNodePhysicsBodies and make sure you are actually getting back the obstacles you expect. I suspect you are not.
You can check the relevant vertices on your obstacles as such:
https://developer.apple.com/library/ios/documentation/GameplayKit/Reference/GKPolygonObstacle_Class/index.html#//apple_ref/occ/cl/GKPolygonObstacle
Make sure they are what you expect.
Related
Let's say I have two circles respectively at (0,0) and (0,1).
I have a SKPhysicsJoint between them and it is working good, now I want to separate them with a distance of 2 on runtime, meaning while physics are working in-game. How can I achieve this?
I've tried setting anchor points to (0,0) and (0,2) but something is bugged, although I see the joint it doesn't have any affect.
I want the circles smoothly push each other, as if the length of the spring has increased.
Everything works if I make a circle 'teleport' to a distance of 2 and then anchor the spring to it, but making a physics object teleport cause other bugs as you can guess.
Before adding the joint I 'teleported' the second object to the desired position, then added the joint and then 'teleported' back to the original position.
Here is the code piece:
SKSpriteNode* node1 = [_bodies objectAtIndex:loop];
SKSpriteNode* node2 = [_bodies objectAtIndex:loop-1];
CGPoint prev1 = node1.position;
CGPoint prev2 = node2.position;
node1.position = [((NSValue*)[positions objectAtIndex:loop]) CGPointValue];
node2.position = [((NSValue*)[positions objectAtIndex:loop-1]) CGPointValue];
[self AttachPoint:node1 secondPoint:node2 pos1:node1.position pos2:node2.posiiton] ;
node1.position = prev1;
node2.position = prev2;
it is working as it is, but I'm not sure how efficient this is. I wish SKPhysicsJointSpring had a 'length' parameter that can be changed over time.
I am using SpriteKit's built in Physics Engine to build a game for iOS. Basically it involves a bouncing ball which moves via me manually setting it's initial velocity and bounces via resetting the velocity within the contact event with the floor.
The issue is, the actual maths for this environment do not add up. Using 'SUVAT' equations it's easy to determine how far the ball's x-displacement should be when it reaches the floor after being thrown with a certain velocity, however (with gravity set to -9.81), it barely moves a couple of pixels.
I simplified the problem to just trying to shoot a ball a certain distance upwards (in the y-direction) and the same thing happened, it moves a couple of points up and then just falls to the floor, at least a 20th of how far it should move.
This is how I have set the physics environment up:
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0.0, -9.81);
And then this is my function for generating this ball (shooting upwards) example. Mathematically it should reach the height of the tower:
-(void)generateTestBall {
self.ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball"];
SKSpriteNode * tower = [SKSpriteNode spriteNodeWithImageNamed:#"player"];
self.ball.position = CGPointMake(self.scene.size.width/2,self.scene.size.height/2);
self.ball.size = CGSizeMake(20,20);
self.ball.color = [SKColor redColor];
self.ball.colorBlendFactor = 1;
tower.position = CGPointMake(self.scene.size.width/2 + 20,self.scene.size.height/2+100);
tower.size = CGSizeMake(20,200);
tower.color = [SKColor blueColor];
tower.colorBlendFactor = 1;
[self addChild:tower];
[self addChild:self.ball];
self.ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:10];
self.ball.physicsBody.affectedByGravity = YES;
self.ball.physicsBody.linearDamping = NO;
self.ball.physicsBody.dynamic = YES;
CGFloat ballVel = sqrt(2*9.81*tower.size.height);
NSLog(#"%f",ballVel);
self.ball.physicsBody.velocity = CGVectorMake(0.0f, ballVel);
}
Please can someone explain what I am doing wrong? I've double checked my maths (I'm a maths student so fingers crossed that's not the issue)!
Thanks!
Steve
So I FINALLY managed to figure it out. Just incase anyone else is experiencing the same issue I'll post the answer here:
The issue was that, although gravity is (apparently) in ms^-2 and velocity m2^-1 (to replicate earth), any distances in Objective C are measured in POINTS rather than the required form of METRES. Therefore any calculation done with x,y position / size values taken from SKSpriteNodes etc will be a certain factor out.
After running a few tests I found the factor to roughly be 157. This means that you must multiply any sizes / distances in POINTS by 157 to get the relative 'METRE' value which will work with SUVAT.
The actual numbers themselves seem a bit ridiculous as they're all very big (velocity, distance etc) but that doesn't actually pose a problem anyway as they all now work relative to each other!
Hope this helps anyone!
Steve
I am trying to develop a basic game for iOS involving a rag doll-like entity. Basically (as you can tell below), I have a head, a body, and a right arm (all three are basic sprite nodes) connected via pin joints, simulating joins in the human body (roughly).
The head and the body work perfectly in that, when a force is applied, the body rotates around the head perfectly and eventually comes to a rest under the head, vertically (see picture).
The arm's base is pinned with a pin joint to the body and is supposed to rotate around its base (kind of like a shoulder) and it is set with an initial rotation of 45 degrees so it looks like an arm before the physics engine takes over.
My question is: why doesn't the arm come to rest in a vertical position (like the body) due to gravity? Shouldn't gravity cause the arm to rotate about its base until the tip of the arm rests directly below the top of the arm (shoulder)? Furthermore, when a force is applied to the body (shown in the example code below), the body rotates about the neck joint, exactly as it should, but the arm does not move from its current orientation (and this is not desirable).
If this is not the case, how would I achieve this effect?
Thank you for your time and I'd be happy to provide any additional information if desired
Picture of physics simulation at rest:
Relevant code which demonstrates the problem:
//make the head node
SKSpriteNode *head = [SKSpriteNode spriteNodeWithImageNamed:#"head"];
head.size = CGSizeMake(20 * [CFLConstants universalWidthScaleFactor], 20 * [CFLConstants universalWidthScaleFactor]);
head.position = position;
head.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:head.size.width/2];
head.physicsBody.categoryBitMask = CFLPhysicsCategoriesHead;
head.physicsBody.collisionBitMask = 0;
head.physicsBody.dynamic = NO;
[self.ragdollLayer addChild:head];
//make the body node
SKSpriteNode *body = [SKSpriteNode spriteNodeWithImageNamed:#"body"];
body.size = CGSizeMake(head.size.width, head.size.width * 3);
body.position = CGPointMake(head.position.x, head.position.y - head.size.height/2 - body.size.height/2);
body.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:body.size];
body.physicsBody.categoryBitMask = CFLPhysicsCategoriesBody;
body.physicsBody.collisionBitMask = 0;
[self.ragdollLayer addChild:body];
//attach the head and the body (via a neck joint)
SKPhysicsJointPin *neckJoint = [SKPhysicsJointPin jointWithBodyA:head.physicsBody bodyB:body.physicsBody anchor:CGPointMake(head.position.x, head.position.y - head.size.height/2)];
[self.physicsWorld addJoint:neckJoint];
//make the right arm
SKSpriteNode *rightArm = [SKSpriteNode spriteNodeWithImageNamed:#"arm"];
rightArm.size = CGSizeMake(head.size.width/5, head.size.width/5 * 10);
rightArm.anchorPoint = CGPointZero;
CGPoint rightArmPosition = CGPointMake(body.position.x + body.size.width * 1/5, body.position.y + body.size.height * 1/5);
rightArm.position = rightArmPosition;
rightArm.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rightArm.size];
rightArm.physicsBody.categoryBitMask = CFLPhysicsCategoriesRightArm;
rightArm.physicsBody.collisionBitMask = 0;
rightArm.zRotation = -M_PI_4;
//force which makes the arm problem even more noticeable
[body.physicsBody applyImpulse:CGVectorMake(100, 0)];
[self.ragdollLayer addChild:rightArm];
//make the joint which holds the right arm to the body, but should allow the arm to rotate about this point (and doesn't)
SKPhysicsJointPin *rightShoulderJoint = [SKPhysicsJointPin jointWithBodyA:body.physicsBody bodyB:rightArm.physicsBody anchor:rightArmPosition];
[self.physicsWorld addJoint:rightShoulderJoint];
In my experience, this is because changing the anchor point on the sprite, doesn't change the anchor point for the physics body. Though I swear sometimes you don't have to, so maybe it's an order of operations thing. But anyways, offset the center of the physics body to account for the sprite anchor point. Something like:
spriteRightArm.anchorPoint = CGPointMake(0, 1);
spriteRightArm.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteRightArm.size center:CGPointMake(spriteRightArm.size.width/2, -spriteRightArm.size.height/2)];
I am actually learning SpriteBuilder which is really a great tool for me, but I am facing a trouble concerning including a CCNode inside my scene (programmatically way).
So, I have a scene "Gameplay" where my characters are implemented from other CCB files.
Concerning the scenery, at first I put my map and some wall (for scene limit) within my Gameplay.ccb (withing a physic node).
Then, I wanted to add that scenery from external file (because I would like to be able to switch between different scenery within the same scene). So I created a CCSprite, I inserted my map inside and then my wall (this new file is map.ccbi).
When I implement the map.ccbi inside my scene, the map is displayed, but the wall seems to be away (there is no collision between the character and the wall).
The map is implemented within the physic node of my Gameplay scene.
Here's the part of the code where I am implementing the map:
- (void)didLoadFromCCB {
self.userInteractionEnabled = TRUE;
// Set the class as delegate for collision
_physicWorld.collisionDelegate = self;
_hero.physicsBody.collisionType = #"hero";
// Set up the map
CCNode *map = [CCBReader load:#"Map/TestIsland"];
// position the map
map.position = ccpAdd(_physicWorld.position, ccp(0.5, 0.5));
// add the map to the world
[_physicWorld addChild:map z:-2];
}
My map is implemented via a Class:
#implementation TestIsland
- (id)init {
self = [super init];
if (self) {
CCLOG(#"Map loaded");
}
return self;
}
#end
The keyword for this trouble should probably be sub-folder (or "tree").
Sprite builder is actually not supporting joint (sub-folder to a CCNode) while importing a node.
So one of the solution while facing such problem is to change the way you made your CCNode.
In this case, it have been solved by changing this:
(LAYER1 >>> LAYER2 >>> LAYER3 >>> ........)
CCNode >>> CCSprite + CCPhysicNode >>> CCNode(Wall) + CCNode(Obstacle) + ....
To this:
CCNode >>> CCSprite + CCNode(wall) + CCNode(Obstacle) + ....
Thanks to #Tibor for warning me that the implementation was good (it induced me that the trouble should be in the interface of SB) and for giving me the good tips for debugging my work.
If you want to attach a wheel to a car, and you want the wheel be affected by gravity when the car is flying. How do you make a physics body for the wheel which sais: "The wheel is attached to the car with a spring and affected by gravity."?
I tried this:
SKPhysicsBody *phys = [SKPhysicsBody bodyWithCircleOfRadius:20];
But then the wheel just falls off the car. It is not "attached" to the car.
To put it other way: If you grab a toy car the wheels go down a little bit because gravity pulls them. But they dont fall down to the floor. They are attached to the car.
Or think of a spring with a weight on it. When you shake it, the weight bounces around. But it is attached to the spring and doesnt fall down.
What is the proper way to simulate things like this in Sprite Kit? I found there is something called SKPhysicsJointSpring. I will post an answer when I figure out.
OK, so you need SKPhysicsJointSpring. But the anchor is problematic because it must be defined in world coordinates.
SKPhysicsBody *p = [SKPhysicsBody bodyWithCircleOfRadius:20];
p.affectedByGravity = NO;
p.allowsRotation = NO;
self.car.physicsBody = p;
p = [SKPhysicsBody bodyWithCircleOfRadius:30];
p.affectedByGravity = NO;
p.allowsRotation = NO;
self.wheel.physicsBody = p;
SKPhysicsJointSpring *spring = [SKPhysicsJointSpring jointWithBodyA:_car.physicsBody bodyB:_wheel.physicsBody anchorA:_car.position anchorB:_wheel.position];
spring.damping = 0.05;
spring.frequency = 0.8;
[self.rootNode.scene.physicsWorld addJoint:spring];
This looks like it works but only as long as I dont run actions on the car or wheel. If I do then the car and wheel jitter around a lot.