iOS SKSpriteNode - Leave screen - sprite-kit

Is there any way to remove from Parent a SKSpriteNode that has left area bounds?
for instance:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
firstNode = (SKSpriteNode *)contact.bodyA.node;
if (firstNode.position.y<0) {
[firstNode removeFromParent];
}
}
Just point me in the right direction. Is it the update method enumerate through checking their rects or is their an action you can apply. I have gone through the documentation can't seem to find it but I would have thought it would be an easy implement since it saves memory

Update method is where you can do it alright:
- (void)update:(NSTimeInterval)currentTime {
//remove any nodes named "yourNode" that make it off screen
[self enumerateChildNodesWithName:#"yourNode" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x < 0){
[node removeFromParent];
}
}];
}
Though note that removing nodes doesn't guarantee freeing up memory!!

Here's how to test if the node has left any edge of the screen. In your update loop, iterate through all children object of your layer. Test if the object is of a specific type. Then test if the node's frame is outside each edge of the screen. If so call removeFromParent. Note that since the node's position is from its center you want to take that into account.
-(void)update:(CFTimeInterval)currentTime
{
[_gameLayer.children enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
if ([obj isKindOfClass:[MyNode class]])
{
MyNode *myNode = (MyNode *)obj;
if (myNode.position.x + myNode.size.width/2 < 0 ||
myNode.position.x - myNode.size.width/2 > self.size.width ||
myNode.position.y + myNode.size.height/2 < 0 ||
myNode.position.y - myNode.size.height/2 > self.size.height)
{
[myNode removeFromParent];
}
}
}];
}

You can also detect contact with the background SKSpriteNode and set a category for it.
And then, implement didEndContact: method to remove the object
- (void)didEndContact:(SKPhysicsContact *)contact
{
//Find out your object (Remember it could be bodyA or bodyB) and remove it here
}

You can use this function.
This function will be called every frame.(ps:My English is poor)
-(void)didSimulatePhysics

I prefer contact handling. Contact testing is done in each frame just like the processing of update:. But the contact detection by comparing bits (SKPhysicsBody.categoryBitMask) is very fast and leightweight.
I need to detect and remove balls that leave the screen.
So I setup an invisible border as the scenes physicsBody that is exactly large enough to detect balls the have left the screen completely.
If physicsBody.contactTestBitMask of node#1 == physicsBody.categoryBitMask of node #2 then didBeginContact: is being called when both of them get in touch.
-(void)didMoveToView:(SKView *)view {
// bitmasks:
static uint32_t const categoryBitMaskBall = 0x1<<1;
static uint32_t const categoryBitMaskBorder = 0x1<<6;
CGFloat ballDiameter = [BallSprite radius] *2;
// floorShape is my scenes background
CGRect largeFloorFrame = floorShape.frame;
largeFloorFrame.origin.x -= ballDiameter;
largeFloorFrame.origin.y -= ballDiameter;
largeFloorFrame.size.width += ballDiameter *2;
largeFloorFrame.size.height += ballDiameter *2;
CGPathRef pathMainView = CGPathCreateWithRect(largeFloorFrame, nil);
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromPath:pathMainView];
self.physicsBody.categoryBitMask = categoryBitMaskBorder;
}
- (BallSprite*)addBall {
// initialize ball with additional configuration...
BallSprite *ball = [BallSprite ballAtPoint:(CGPoint)p];
ball.categoryBitMask = categoryBitMaskBall;
ball.contactTestBitMask = categoryBitMaskBorder;
[self addChild:ball];
}
- (void)didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
/*!
Check on outside border contact
*/
if ((firstBody.categoryBitMask & categoryBitMaskBall) != 0 &&
(secondBody.categoryBitMask & categoryBitMaskBorder) != 0) {
[firstBody.node removeFromParent];
}
if ((firstBody.categoryBitMask & categoryBitMaskBorder) != 0 &&
(secondBody.categoryBitMask & categoryBitMaskBall) != 0) {
[secondBody.node removeFromParent];
}
}

Swift version
self.enumerateChildNodesWithName("enemy") {
node, stop in
if (node.position.x < 0) {
node.removeFromParent()
}
}

Related

Unity camera trembling on collision

The script works perfectly, exactly as it should, except when zooming too much and the SphereCast collides with the mesh. When it hits a mesh, I want the camera to just stop, and not try to go through the mesh.
if(Input.touchCount == 2)
{
Touch first = Input.GetTouch(0);
Touch second = Input.GetTouch(1);
origin = mainCamera.transform.position;
direction = mainCamera.transform.forward;
RaycastHit hit;
if(Physics.SphereCast(origin, sphereRadius, direction, out hit, maxDistance))
{
inCollision = true;
}
else
{
inCollision = false;
}
if(first.phase == TouchPhase.Began || second.phase == TouchPhase.Began)
{
initialDistance = Vector3.Distance(first.position, second.position);
}
if(first.phase == TouchPhase.Moved || second.phase == TouchPhase.Moved)
{
movedDistance = Vector3.Distance(first.position, second.position) - initialDistance;
if(inCollision)
{
movedDistance = -Mathf.Abs(movedDistance);
}
mainCamera.transform.localPosition = new Vector3(0, 0, mainCamera.transform.localPosition.z + movedDistance * zoomSensitivity * Time.deltaTime);
initialDistance = Vector3.Distance(first.position, second.position);
}
}
I tried to:
Set movedDistance to 0 when SphereCast hits the mesh;
Clamp mainCamera.transform.localPosition.z to mainCamera.transform.localPosition.z, but only when SphereCast hits the mesh, so the z doesn't zoom more than it's current value.
As commented before you all the time move forward, collide, move backwards, don't collide anymore, move forward etc.
You could probably rather not move backwards but check if you would collide if you would move and do not move at all in this case:
if(first.phase == TouchPhase.Began || second.phase == TouchPhase.Began)
{
initialDistance = Vector3.Distance(first.position, second.position);
}
else if(first.phase == TouchPhase.Moved || second.phase == TouchPhase.Moved)
{
movedDistance = Vector3.Distance(first.position, second.position) - initialDistance;
// Only check until here if you WOULD collide
origin = mainCamera.transform.position;
var targetPosition = origin + mainCamera.transform.forward * movedDistance * Time.deltaTime * zoomSensitivity;
direction = mainCamera.transform.forward;
// only move if there WILL BE no collision
if(!Physics.SphereCast(targetPosition, sphereRadius, direction, out var hit, maxDistance))
{
mainCamera.position = targetPosition;
}
initialDistance = Vector3.Distance(first.position, second.position);
}
From the example for SphereCast you can see that you could even take the hit.distance into account in order to at least move the camera the closest possible.

Player pauses for a second before moving (on some devices)

i have a 2d platformer with a player that moves. It works perfect in unity and i packaged it and downloaded the apk on my galaxy s7 which also works perfect.
I then tested it in my local supermarkets tablet display and on both tablets i tried, if i touched left, it would pause for a second, then move left.
The animated enemies, spinning coins and physics was working fine so there wasnt that type of lag, just a pause when moving which makes me think the code i have to move is wrong. The tablets looked fairly high spec too.
Here is how i move the player, i call this in the update function:
List<FingerTouch> currentTouches = new List<FingerTouch>();
public class FingerTouch
{
public int id;
public float beganXPos;
public FingerTouch (int fingerId, float fingerBegan)
{
this.id = fingerId;
this.beganXPos = fingerBegan;
}
}
if (!disableMovment)
{
for (int i = 0; i < Input.touchCount; i++)
{
if (Input.GetTouch(i).phase == TouchPhase.Began && !EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId))
{
float xPos = Input.GetTouch(i).position.x;
if(xPos < Screen.width / 5)
{
playerLeft = true;
} else if (xPos > ((Screen.width / 5) * 4))
{
playerRight = true;
} else
{
playerUp = true;
}
int fingerId = Input.GetTouch(i).fingerId;
currentTouches.Add(new FingerTouch(fingerId, xPos));
} else if (Input.GetTouch(i).phase == TouchPhase.Ended)
{
if (currentTouches.Count != 0)
{
for (int j = 0; j < currentTouches.Count; j++)
{
FingerTouch currentTouch = currentTouches[j];
if (Input.GetTouch(i).fingerId == currentTouch.id || currentTouches.Count == 1)
{
if (currentTouches[j].beganXPos < Screen.width / 5)
{
playerLeft = false;
}
else if (currentTouches[j].beganXPos > ((Screen.width / 5) * 4))
{
playerRight = false;
}
else
{
playerUp = false;
}
currentTouches.RemoveAt(j);
break;
}
}
} else {
playerUp = false;
playerRight = false;
playerLeft = false;
}
}
}
}
It looks a little over the top but it basicily divides the screen into 3 sections 20/60/20 which move left right jump. When the user touches, it stores that touch and when its released, its removed.
This allows multi touch (jumping and moving) while also preventing jumping, sliding ur finger to the right and removing it and being in an unlimited jumping state as you didnt remove your finger in the middle.
Then after that it moves the player like this:
if (hitLeftWall && playerLeft)
{
playerBody.velocity = new Vector2(0, playerBody.velocity.y);
}
Is there anything here that would cause the player to pause before moving? Should this be in fixed update as the player has a rigidbody? Im new to unity so im not sure on the proper way to do things yet.

play a sound when two nodes collide

I have an app with three sprite nodes, ball1, ball2, ball3. they move around the scene by tipping the device up, down, right, left. Everything is working fine.
I would like to add an effect in which when any two balls touch (collide) a sound plays. I am using SpriteKit and have each ball with a physicalBody and they have a ball shape property, I have precisionCollision Yes, categoryBitMask set to ballCategory1, 2, and 3 for each ball. (or can I just use one category)
I have tried various tutorials on how to do this but nothing seems to be working.
I have tried using Ray Wenderlich's example with a tweek for mine. but,
//here are my three categories
static const uint32_t ballCategory = 0x1 << 1;
static const uint32_t ballCategory2 = 0x1 << 2;
static const uint32_t ballCategory3 = 0x1 << 3;
//these are my three nodes
ball = [SKSpriteNode spriteNodeWithImageNamed:#"mankala piece"];
ball.scale = 1;
ball.position = CGPointMake(600, 500);
ball.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:(ball.size.width/2)];
ball.physicsBody.usesPreciseCollisionDetection = YES;
ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.collisionBitMask = ballCategory2|ballCategory|ballCategory3;
ball.physicsBody.affectedByGravity = NO;
[self addChild:ball];
ball2 = [SKSpriteNode spriteNodeWithImageNamed:#"mankala piece"];
ball2.scale = 1;
ball2.position = CGPointMake(350, 200);
ball2.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:(ball2.size.width/2)];
ball2.physicsBody.usesPreciseCollisionDetection = YES;
ball2.physicsBody.categoryBitMask = ballCategory2;
ball2.physicsBody.collisionBitMask = ballCategory2|ballCategory|ballCategory3;
ball2.physicsBody.affectedByGravity = NO;
[self addChild:ball2];
ball3 = [SKSpriteNode spriteNodeWithImageNamed:#"mankala piece"];
ball3.scale = 1;
ball3.position = CGPointMake(500, 400);
ball3.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:(ball3.size.width/2)];
ball3.physicsBody.usesPreciseCollisionDetection = YES;
ball3.physicsBody.categoryBitMask = ballCategory3;
ball3.physicsBody.collisionBitMask = ballCategory2|ballCategory|ballCategory3;
ball3.physicsBody.affectedByGravity = NO;
[self addChild:ball3];
// and here is my revised contact code
- (void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if ((firstBody.categoryBitMask & ballCategory) != 0)
{
NSLog(#"click");
}
if ((firstBody.categoryBitMask & ballCategory2) != 0)
{
NSLog(#"click2");
}
if ((firstBody.categoryBitMask & ballCategory3) != 0)
{
NSLog(#"click3");
}
}
but still nothing happens.
has anyone worked with collisions in spriteKit. Just want to have a sound play when two different sprite nodes come in contact.
First, you do not have any contactTestBitMasks set up. Currently you're telling the physics delegate which nodes are set up to collide with one another, but nothing is set up to contact. Add this to each ball physicsBody:
ball2.physicsBody.contactTestBitMask = ballCategory | ballCategory2 | ballCategory3
This way, the physics delegate will understand that each ball can make contact with the others.
Your contact if statement is still incorrectly being used. ballCategory and ballCateogry2 are variables which you've equated to integers; this means that your categoryBitMask are now set equal to those integers.
What your if statement does is check to see if the physicsBody's category equals a specific integer, or just equals an integer greater than zero (it's up to you).
In this case, you will need to adjust your if statement to test the value of those categoryBitMasks you've set up:
if ((firstBody.physicsBody.categoryBitMask == ballCategory) &&
(secondBody.physicsBody.categoryBitMask == ballCategory2))
{
NSLog(#"click");
}
What you have is incorrect, as you're not testing the value of anything. You need to use your if statement to check if the firstBody and secondBody have a categoryBitMask set equal to specific ballCategory variable values.

SpriteKit - Make a sprite move similar t another sprite

I'm making a type of a platformer ( think super mario but in a top-down view style ) game and i want my main character to move with the platform it has contact with.
So i used this:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if (firstBody.categoryBitMask == PlatformCategory || secondBody.categoryBitMask == PlayerCategory) {
_contact = YES;
}
-(void)didSimulatePhysics
{
if (_contact) {
_player.position = CGPointMake(_platform2.position.x, _platform2.position.y + 10);
}
}
}
It kinda worked, and now my player is moving with that SPECIFIC platform when it touches it, but that's not a very practical way for doing it because i will be randomly generating platforms. So how t achieve that ?
SKPhysicsBody has a property called node, which is the parent sprite it's attached to. In your collision method, test if the physics body is of collision category platform, then:
SKSpriteNode *thisCollidedPlatform = (SKSpriteNode*) nameOfPhysicsBody.node
And then use the position of thisCollidedPlatform to set the player position.

How Do I Destroy an SKPhysicsBody?

At certain points in my game I want a ton of balls to fall onto the screen. Once they hit the ground and bounce a bit, I want them to just sit there, and no longer need them to move.
Once I get up to 200 physics bodies, the game gets very slow, so I'd like to destroy the bodies. Here is what I was trying in my code:
-(void)didBeginContact:(SKPhysicsContact *)contact {
if (contact.contactPoint.y < 150) {
if (contact.bodyA.categoryBitMask == MYPhysicsCategoryBall) {
NSLog(#"body a is ball");
contact.bodyA = nil;
}
if (contact.bodyB.categoryBitMask == MYPhysicsCategoryBall) {
NSLog(#"body b is a weapon");
}
}
This doesn't work, because contact.bodyA and contact.bodyB are both readonly, so I have to fix that, but apart from that, will just setting the actual physics body to nil destroy it and make the physics simulator run faster? Or is there a better way to fix the performance hit? I want to be able to add more than 200 balls, maybe 500 or 600.
Use contact.bodyA.node.physicsBody:
-(void)didBeginContact:(SKPhysicsContact *)contact {
if (contact.contactPoint.y < 150) {
if (contact.bodyA.categoryBitMask == MYPhysicsCategoryBall) {
NSLog(#"body a is ball");
// contact.bodyA = nil;
contact.bodyA.node.physicsBody = nil;
}
if (contact.bodyB.categoryBitMask == MYPhysicsCategoryBall) {
NSLog(#"body b is a weapon");
}
}
I haven't tested it though