I know you can detect contact collisions using SKPhysicsContactDelegate, but can you check if a physicsbody is currently touching another physicsbody?
I need this for checking which area in the scene is still available to put an item (eg. pick a random spot, and if there's something in the way, pick another random spot).
There's this function:
/* Returns an array of all SKPhysicsBodies currently in contact with this one */
- (NSArray *)allContactedBodies;
But it doesn't appear to return anything useful until after the next update of creating the node.
You can write a function to iterate manually through all the nodes and check if the two circles intersect a point.
Since you said that the radius of the circles will differ each time, you have to keep track of it. One method is to use the user data of the node.
[node.userData setObject:[NSNumber numberWithFloat:10.0] forKey:#"radius"];
Then you can find if there are intersecting circles in the following way.
-(BOOL)checkPointForNode:(CGPoint)point withRadius:(CGFloat)nodeRadius
{
for (SKNode* child in [self children])
{
NSNumber *childRadius = child.userData[#"radius"];
if (childRadius != nil)
{
CGFloat diffX = point.x - child.position.x;
CGFloat diffY = point.y - child.position.y;
CGFloat distance = sqrtf(diffX * diffX + diffY * diffY);
CGFloat sumRadius = nodeRadius + childRadius.floatValue;
if (distance <= sumRadius)
{
return YES;
}
}
}
return NO;
}
The function returns YES if there is a circle within the boundary of the circle you are going to add. This means you cannot add a new node without touching another node. Otherwise it returns NO. This means you can add a new node without touching any other nodes.
Related
Is it possible to get 2 positions from the same SKSpriteNode in the -(void)update:(CFTimeInterval)currentTime method?
I am making a demo. A ball is shot from a random point outside of the screen. I want to get the ball's direction and an array of positions so that I can do something based on it. But when I use the update: method, it seems that it fills the array in each update. How do I add only one SKSpriteNode to the array for each update?
NSMutableArray *nodePos;
int returnPosCount;
-(void)update:(CFTimeInterval)currentTime {
if ([self childNodeWithName:#"//ball"]!=nil){
returnPosCount++;
if (returnPosCount == 15) {
SKNode *node = [self childNodeWithName:#"//ball"];
[nodePos addObject:node];
if ([nodePos count]>4) {
[nodePos removeObjectAtIndex:0];
NSLog(#"%#",nodePos);
}
returnPosCount =0;
}
NSLOG:
<SKSpriteNode> name:'ball' (64 x 64)] position:{-127.63968658447266, 135.19813537597656}
<SKSpriteNode> name:'ball' (64 x 64)] position:{-127.63968658447266, 135.19813537597656}
<SKSpriteNode> name:'ball' (64 x 64)] position:{-127.63968658447266, 135.19813537597656}
You are not filling the array in each each update. You are seeing the array full, because you are only printing the data when it is full. Just move your NSLog outside the [nodePos count] condition.
Now the reason why you have the same value is because you are adding the node, instead of its position at each update. Since the node is a reference, you will have an array full of references to the same object, thus obtaining the same position. You should build an array of CGPoint instead of an array of SKNode.
To do so, try changing:
[nodePos addObject:node];
To
[nodePos addObject:node.position];
Like many that post about this topic, I too am busy trying to write myself an accurate simulator for the movement of objects in a 2D gravitation field.
I decided early on that I would settle on Velocity Verlet Integration, as I want my objects to maintain stable orbits and conserve energy even if the timestep is rather large. So, what might the problem be?
Well, so far, everything seems to behave correctly, except for one component. When I try to calculate the correct velocity for a stable orbit at a certain distance, the resulting velocity sends them into odd elliptical orbits that quickly increase in magnitude each time.
So, to begin, here are the following methods that determine an objects next position, velocity, and acceleration in scene: (Objective C)
Acceleration:
-(CGVector)determineAccelerationFor:(SKObject *)object
{ // Ok, let's find Acceleration!
CGVector forceVector = (CGVector){0,0}; // Blank vector that we will add forces to
for (SKObject *i in self.sceneObjects)
{
if (![i isEqual:object]) // Just make sure we're not counting ourselves here
{
CGPoint distance = [self getDistanceBetween:i.position And:object.position];
float hypotenuse = sqrtf(powf(distance.x, 2)+ powf(distance.y, 2));
float force = ((self.gravity * object.mass * i.mass)/powf(hypotenuse, 3));
float xMagnitude = (force * distance.x);
float yMagnitude = (force * distance.y);
forceVector.dx += xMagnitude;
forceVector.dy += yMagnitude;
}
}
CGVector acceleration = (CGVector){forceVector.dx/object.mass, forceVector.dy/object.mass};
return acceleration;
}
Cool, so basically, I just take an object, add all the other forces that each other object imposes on it together then divide the X & Y factor by the mass of the current object to get the acceleration!
Next up is Velocity. Here I use the following equation:
The method for it is pretty straightforward too:
-(CGVector)determineVelocityWithCurrentVelocity:(CGVector)v OldAcceleration:(CGVector)ao NewAcceleration:(CGVector)a
{
float xVelocity = (v.dx + ((ao.dx + a.dx)/2) * self.timeStep);
float yVelocity = (v.dy + ((ao.dy + a.dy)/2) * self.timeStep);
CGVector velocity = (CGVector){xVelocity,yVelocity};
return velocity;
}
And finally, position! The equation for this is:
And it is determined with the following method!
-(CGPoint)determinePositionWithCurrentPosition:(CGPoint)x CurrentVelocity:(CGVector)v OldAcceleration:(CGVector)ao
{
float xPosition = (x.x + v.dx * self.timeStep + ((ao.dx * powf(self.timeStep, 2))/2));
float yPosition = (x.y + v.dy * self.timeStep + ((ao.dy * powf(self.timeStep, 2))/2));
CGPoint position = (CGPoint){xPosition,yPosition};
return position;
}
This is all called from the below method!!
-(void)refreshPhysics:(SKObject *)object
{
CGPoint position = [self determinePositionWithCurrentPosition:object.position CurrentVelocity:object.velocity OldAcceleration:object.acceleration]; // Determine new Position
SKAction *moveTo = [SKAction moveTo:position duration:0.0];
[object runAction:moveTo]; // Move to new position
CGVector acceleration = [self determineAccelerationFor:object]; // Determine acceleration off new position
CGVector velocity = [self determineVelocityWithCurrentVelocity:object.velocity OldAcceleration:object.acceleration NewAcceleration:acceleration];
NSLog(#"%# Old Velocity: %f, %f",object.name,object.velocity.dx,object.velocity.dy);
NSLog(#"%# New Velocity: %f, %f\n\n",object.name,velocity.dx,velocity.dy);
[object setAcceleration:acceleration];
[object setVelocity:velocity];
}
Okay, so those methods above dictate how objects are moved in scene. Now onto the initial issue, the ever present problem of achieving a stable orbit!
In order to determine what velocity an object should have to maintain an orbit, I use the following equation:
And I implement that as follows:
-(void)setObject:(SKObject *)object ToOrbit:(SKObject *)parent
{
float defaultSeparation = 200;
// Move Object to Position at right of parent
CGPoint defaultOrbitPosition = (CGPoint){parent.position.x + (parent.size.width/2)+ defaultSeparation,parent.position.y};
[object setPosition:defaultOrbitPosition];
// Determine Orbital Velocity
float velocity = sqrtf((self.gravity * parent.mass)/(parent.size.width/2+defaultSeparation));
CGVector vector = (CGVector){0,velocity};
[object setVelocity:vector];
}
And for some reason, despite this, I get abysmal results. Here is some of the output:
Information:
Gravity(constant) = 1000 (For test purposes)
Mass(Parent) = 5000 units
Mass(Satellite) = 1 units
Separation = 224 pixels
It determines that in order for the Satellite to Orbit the Parent, a velocity of:
149.403580 pixels/timeStep
is required. And that checks out on my calculator.
So this has left me a little confused as to what could be going wrong. I log all the output concerning new velocities and positions, and it does use the velocity I set it to, but that just doesn't seem to make a difference. If anyone could possible help spot what's going wrong here I would be immensely grateful.
If anyone believes I have left something out, tell me and I will edit this right away. Thanks!
I'm building a game using Apple's SpriteKit and SKPhysics that use squares that move around on the screen based on user input. I'm having an issue with collisions in that the squares will move out of place if they collide. For example, if all the blocks move to the right, any blocks that are on the same "row" need stack next to each other and not overlap or move position vertically. As of now, they will change their vertical direction. Here is my code:
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.size];
self.physicsBody.dynamic = YES;
self.physicsBody.allowsRotation = NO;
self.physicsBody.affectedByGravity = NO;
Are there any other settings that I'm missing?
The issue could be coming from your collisionBitMask category. In order to solve that, you need to first create categories for the blocks' physics bodies as follows:
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let block : UInt32 = 0b1
}
then set the blocks' settings to the following.
block.physicsBody?.categoryBitMask = PhysicsCategory.block
block.physicsBody?.collisionBitMask = PhysicsCategory.None
This should prevent the collision calculations from being automatically carried out by spritekit.
If you're moving your sprites via user inputs(i.g. SKAction's moveTo), then you're most likely not using physics to move your sprite. In this case, you should make the velocity of the physicsbody to 0- this will make the sprite completely rigid when it comes in contact with another object.
Try:
self.physicsBody.velocity = CGVectorMake(0, 0);
You should put this code inside your update loop.
I want to move an object from a random point just outside of the view in a Sprite Kit game.
The logical way of doing this would be to create a rectangle 100px (example) bigger than the view, and pick a random point on it's perimeter. Unfortunately, I don't know an easy way to do this.
How can I easily create a random point on the perimeter of a rectangle (which is slightly bigger than my view)?
Update
This should do what you want:
- (CGPoint)randomPointOutsideRect:(CGRect)rect withOffset:(CGFloat)offset {
NSUInteger random = arc4random_uniform(4);
UIRectEdge edge = 1 << random; // UIRectEdge enum values are defined with bit shifting
CGPoint randomPoint = CGPointZero;
if (edge == UIRectEdgeTop || edge == UIRectEdgeBottom) {
randomPoint.x = arc4random_uniform(CGRectGetWidth(rect)) + CGRectGetMinX(rect);
if (edge == UIRectEdgeTop) {
randomPoint.y = CGRectGetMinY(rect) - offset;
}
else {
randomPoint.y = CGRectGetMaxY(rect) + offset;
}
}
else if (edge == UIRectEdgeLeft || edge == UIRectEdgeRight) {
randomPoint.y = arc4random_uniform(CGRectGetHeight(rect)) + CGRectGetMinY(rect);
if (edge == UIRectEdgeLeft) {
randomPoint.x = CGRectGetMinX(rect) - offset;
}
else {
randomPoint.x = CGRectGetMaxX(rect) + offset;
}
}
return randomPoint;
}
This should be fairly straightforward, let me know if there's something unclear.
Basically, we pick one edge at random, then "fix" one axis and pick a random value on the other (within the width/height boundaries).
arc4random_uniform gives us only integers, but that's fine because floating point values in frames are bad when displaying stuff on screen.
There is probably a shorter way to write this; feel free to edit to improve, everyone.
Original answer
How can I easily create a point 100 pixels away from the edge of my view?
With CGRectOffset().
Assuming you want a CGPoint 100pt "higher" (ie. with a lower y) than your view, do:
CGRect viewFrame = // lets say for this example that your frame is at {{20, 40}, {300, 600}}
CGRect offsetFrame = CGRectOffset(viewFrame, 0, -100);
CGPoint offsetPoint = offsetFrame.origin
// offsetPoint = {20, -60}
I'm pretty new to iOS and cocos2d and I'm having a problem trying to create what I want the code to do. Let me give you the rundown first then i'll show what I've got.
What I got so far is a giant sprite in the middle and when that is touched, I want to have say 2000 of a different sprite generate from the center position and like a particle system, shoot off in all directions.
First off, I tried coding implementing the velocity code (written in Objective-c) over to Cocos2d and that didn't work. -code-
-(void)ccTouchBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(CGRectContainsPoint([[self getChildByTag:1] boundingBox], location))
{
for( int i = 0; i < 100; i++)
{
CCSprite *ballGuySprite = [CCSprite spriteWithFile:#"ball.png"];
[self addChild:ballGuySprite z:7];
ballGuySprite.position = ccp(((s.width + i *10) /2), (s.height + i *10) /2);
}
}
}
What that does is when I touch the first sprite, 100 of the other sprites are on top of each other leading to the top right corner.
The velocity code that I used when as followed and when I try to apply it to the sprite nothing happens. - Velocity code -
-(void) checkCollisionWithScreenEdges
{
if(ballGuysRect.origin.x <= 0)
{
ballVelocity.x = abs(ballVelocity.x);
}
if(ballGuysRect.origin.x >= VIEW_WIDTH - GUY_SIZE)
{
ballVelocity.x = -1 * abs(ballVelocity.x);
}
if(ballGuysRect.origin.y <= 0)
{
ballVelocity.y = abs(ballVelocity.y);
}
if(ballGuysRect.origin.y >= VIEW_HEIGHT - GUY_SIZE)
{
ballVelocity.y = -1 * abs(ballVelocity.y);
}
}
-(void) updateModelWithTime:(CFTimeInterval)timestamp
{
if(lastTime == 0.0)
{
lastTime = timestamp;
}
else
{
timeDelta = timestamp - lastTime;
lastTime = timestamp;
ballGuysRect.origin.x += ballVelocity.x * timeDelta;
ballGuysRect.origin.y += ballVelocity.y * timeDelta;
[self checkCollisionWithScreenEdges];
}
}
When I attach that code to the sprite, nothing happen.
I also tried adding a CCParticleExplosion which did do what I wanted but I still want to add a touch function to each individual sprite that's generated and they tend to just fade away.
So again, I'm still fairly new to this and if anyone could give any advice that would be great.
Thanks for your patients and time to read this.
Your code looks good to me, but you never seem to update the position of your sprites. Somewhere in updateModelWithTime I would expect you to set ballGuySprite.position = ballGuysRect.origin plus half of its height or width, respectively.
Also, I don't see how updateModelWithTime can control 100 different sprites. I see only one instance of ballGuysRect here. You will need a ballGuysRect for each sprite, e.g. an array.
Finally, I'd say that you don't really need ballGuysRect, ballVelocity, and the sprite. Ball could be a subclass of CCSprite, including a velocity vector. Then all you need to do is keep an array of Balls and manage those.
I am not sure what version of cocos2d you are using but a few things look a bit odd.
Your first problem appears to be that you are just using the same sprite over and over again.
Since you want so many different sprites shooting away, I would recommend that you use a CCSpriteBatchNode, as this should simplify things and speed things up.
The following code should help you get that set up and move them offscreen with CCMoveTo:
//in your header file:
CCSpriteBatchNode *batch;
//in your init method
batch = [CCSpriteBatchNode batchNodeWithFile:#"ball.png"];
//Then in your ccTouches method
for( int i = 0; i < 100; i++)
{
CCSprite *ballGuySprite = [CCSprite spriteWithFile:#"ball.png"];
[batch addChild:ballGuySprite z:7 tag:0];
ballGuySprite.position = ccp(where-ever the center image is located);
id actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(random off screen location)];
[ballGuySprite runAction:actionMove];
}
Also usually your update method looks something like the following:
-(void)update:(ccTime)delta{
//check for sprites that have moved off screen and disable them.
}
Hope this helps.