How to calculate an SKPhysicsBody on the fly while animating a sprite? - ios

In my original build, my sprites were static, so to make the collision physics as accurate as I could rather than use rectangleOfSize for SKPhysicsBody passing in the node, I used this tool to define a path SpriteKit's SKPhysicsBody with polygon helper tool.
However now I'm animating the sprites so that they move back and forth during gameplay (obviously my physics path remains static given the above) so my physicsbody no longer matches what the player sees on screen.
The helper tool seemed like a bit of hack that Apple would eventually fix in the API, has there been anything recent in SpriteKit that would help me out here so that I can pass in the node and define a precise physicsbody rather than the hard-coded approach? If not, any other alternatives?

Not sure how performant this is but you can pre-generate the physics bodies for each texture then animate the sprite along with its physicsBody using a custom action.
Here is an example:
func testAnimation() {
var frameTextures = [SKTexture]()
var physicsBodies = [SKPhysicsBody]()
for index in 1...8 {
// The animation has 8 frames
let texture = SKTexture(imageNamed: "guy\(index)")
frameTextures.append(texture)
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.affectedByGravity = false
physicsBodies.append(physicsBody)
}
let sprite = SKSpriteNode(texture: frameTextures[0])
let framesPerSecond = 16.0
let animation = SKAction.customAction(withDuration: 1.0, actionBlock: { node, time in
let index = Int((framesPerSecond * Double(time))) % frameTextures.count
if let spriteNode = node as? SKSpriteNode {
spriteNode.texture = frameTextures[index]
spriteNode.physicsBody = physicsBodies[index]
}
})
sprite.run(SKAction.repeatForever(animation));
sprite.position = CGPoint(x: 0, y: 0)
addChild(sprite)
}

Related

Physics bodies keep falling away from the sprite node

I am trying to create nodes, that are put into an array, and then are added to the scene with their physics bodies.
Here is the code for creating the initial sprites:
let name = createTarget()
let targetNode = SKSpriteNode(imageNamed: name)
targetNode.name = name
chickenNodes.append(targetNode)
targetNode.position = generateRandomLocation()
let range = SKRange(lowerLimit: targetNode.position.y, upperLimit: targetNode.position.y)
let lockToCenter = SKConstraint.positionY(range)
targetNode.constraints = [lockToCenter]
if movingItems { animateTargets(targetNode) }
Once all of these nodes are in the array, I add them in didMove to a background node, fgNode, in the scene, like this:
for chicken in chickenNodes {
let texture = SKTexture(imageNamed: chicken.name!)
chicken.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
chicken.physicsBody?.isDynamic = true
chicken.physicsBody?.affectedByGravity = true
chicken.physicsBody?.allowsRotation = false
chicken.physicsBody?.linearDamping = 0.0
chicken.physicsBody?.restitution = 1.0
chicken.physicsBody?.friction = 0.0
fgNode.addChild(chicken)
}
When I view the physics through the scene, the physics bodies keep falling away from the sprite (as though they're responding to the gravity in the scene); and the sprite is just locked where it is. How do I ensure that the physicsBody sticks to the sprite?

Projectile not originating from the correct location

I am trying to make a SKSpriteNode come from a SKShapeNode. When the below code runs, the projectiles are appearing, but they will originate from a different point on the screen, not the player location.
Here is my shoot function that is in my Player Class.
func shoot() {
let newProjectile = Projectile()
newProjectile.position = self.position
self.addChild(newProjectile)
let action = SKAction.moveTo(CGPointMake(
600 * -cos(newProjectile.zRotation - 1.57079633) + newProjectile.position.x,
600 * -sin(newProjectile.zRotation - 1.57079633) + newProjectile.position.y
), duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
newProjectile.runAction(SKAction.sequence([action, actionMoveDone]))
}
Here is my Projectile Class :
class Projectile : SKSpriteNode {
let Texture = SKTexture(imageNamed: "image.png")
static var counter : Int = 0
init(){
//super.init()
super.init(texture: Texture, color: UIColor.whiteColor(), size: CGSize(width: radius * 2, height: radius * 2))
self.name = "projectile-" + NSUUID().UUIDString
self.physicsBody = SKPhysicsBody(circleOfRadius: radius)
self.physicsBody?.categoryBitMask = GlobalConstants.Category.projectile
self.physicsBody?.collisionBitMask = GlobalConstants.Category.projectile
self.physicsBody?.contactTestBitMask = GlobalConstants.Category.projectile | GlobalConstants.Category.wall
self.zPosition = GlobalConstants.ZPosition.projectile
}
}
you probably need to add your projectiles to the scene instead of the player itself. you're adding the projectiles to your player.
you should add the projectile to the parents scene. something like this.
self.scene!.addChild(newProjectile)
If you want the projectiles to be children of the player, then the projectile's position must be expressed in terms of the player i.e. A position of (0, 0) will place the projectile at the player's anchor point.
If you want the projectiles to be children of the scene, then the projectile's position will be expressed in terms of the scene i.e. a position equal to the player's position will be needed to put the projectile on top of the player.
You combined the two, causing the projectile to be placed with the (x,y) position of the player in the scene, but relatIve to the player, which means that wherever the player is compared to the scene's origin, then that's where the projectile will appear relative to the player.

Node with Two Physics Bodies

I know that this question has been "asked" here but it was not helpful, nor did it cover what I am asking.
That being said, I want to know how to achieve the following:
I need a node that has 2 physics bodies: 1) a smaller body (200x200) that represents the node's frame, and 2) a larger body (500x500) that represents an outer perimeter of an area that will act as a "detection" boundary. The outer body will not physically impact colliding objects, it just needs to register that an object has encountered that boundary line. Below is a simple idea of what I am describing:
My initial approach was to use SKPhysicsBody bodyWithBodies:<NSArray> but that would have just created the overlap body as the smaller rect.
So I figured I would have to create an SKSpriteNode for the smaller body (the black square), and another SKSpriteNode for the larger body (the blue square), and add it as a child to the smaller square (or vice versa).
My problem is that I can't seem to get the outer boundary to allow another object to pass through while registering that interaction. The closest I have come is getting a movable node to hit the boundary, then continually get pushed back and forth along the line of the 500x500 boundary as long as I attempt to pass through the boundary. I have been able to make the moving node UNABLE to pass through the 500x500, but this is quite the opposite of what I need.
Here is the code that I am using, perhaps someone can see something I am doing wrong:
/// TESTING
// Detection body - 500x500
SKSpriteNode *dbody = [SKSpriteNode spriteNodeWithImageNamed:#"object500"];
dbody.position = CGPointMake(500, 100);
dbody.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:dbody.frame];
dbody.physicsBody.categoryBitMask = detection;
dbody.physicsBody.collisionBitMask = 0;
dbody.physicsBody.contactTestBitMask = player;
// Node body - 200x200
SKSpriteNode *testBoundary = [SKSpriteNode spriteNodeWithImageNamed:#"object"];
testBoundary.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:testBoundary.frame];
testBoundary.physicsBody.categoryBitMask = object;
testBoundary.physicsBody.contactTestBitMask = player;
testBoundary.position = dbody.position;
// Add boundary as child to main node
[testBoundary addChild:dbody];
// Add to scene
[chapterScene addChild:dbody];
All of this is inside LevelScene.m, an SKScene set with <SKPhysicsContactDelegate> in the header. Also, I have defined chapterScene as such:
// Set up main chapter scene
self.anchorPoint = CGPointMake(0.5, 0.5); //0,0 to 1,1
chapterScene = [SKNode node];
[self addChild:chapterScene];
, with chapterScene defined at the top of LevelScene.
#interface LevelScene () {
#pragma 1 Scene objects
SKNode *chapterScene;
}
If someone can help me figure out what I am doing wrong, or perhaps an alternative approach that would give me what I am looking for I would appreciate it.
NOTE: My backup solution would be seemingly expensive in terms of CPU and memory, but I believe it would work nonetheless. I would have a BOOL isDetected to represent if the moving player node IS within the 500x500 area, then in update I would monitor the distance to a smaller 200x200 rect boundary of the center of the 500x500 node. But I would much rather use the 2 SKPhysicsBody's as I am sure it can be done and would be much easier to implement.
UPDATE:
here is my moving character (a separate SKNode class called Character.m)
- (void)createCharacterWithName:(NSString *)charName {
character = [SKSpriteNode spriteNodeWithImageNamed:charName];
[self addChild:character];
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:character.frame.size.width/2.0];
self.physicsBody.dynamic = YES;
self.physicsBody.allowsRotation = YES;
// Define physics body relationships
self.physicsBody.categoryBitMask = player;
self.physicsBody.collisionBitMask = player;
self.physicsBody.contactTestBitMask = detection;
}
Here is an image of my simulator (it is a little different, in terms of sizes, as my description. My description was for ease of understanding not the exact scaling of the nodes)
this is working for me. sorry i didnt write it on obj c. just a lot faster for me in swift
import SpriteKit
let CategoryOuter:UInt32 = 1 << 0
let CategoryInner:UInt32 = 1 << 1
let CategoryTester:UInt32 = 1 << 2
class GameScene: SKScene, SKPhysicsContactDelegate {
let outerSprite = SKSpriteNode(color: SKColor.redColor(), size: CGSizeMake(100, 100))
let innerSprite = SKSpriteNode(color: SKColor.blueColor(), size: CGSizeMake(40, 40))
let tester = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(10, 10))
override init(size: CGSize) {
super.init(size: size)
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVectorMake(0, 0)
outerSprite.position = CGPointMake(size.width/2, size.height/2)
outerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: outerSprite.size)
outerSprite.physicsBody!.dynamic = false
outerSprite.physicsBody!.categoryBitMask = CategoryOuter
outerSprite.physicsBody!.collisionBitMask = 0
outerSprite.physicsBody!.contactTestBitMask = CategoryTester
innerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: innerSprite.size)
innerSprite.physicsBody!.dynamic = false
innerSprite.physicsBody!.categoryBitMask = CategoryInner
innerSprite.physicsBody!.collisionBitMask = CategoryTester
innerSprite.physicsBody!.contactTestBitMask = CategoryTester
outerSprite.addChild(innerSprite)
addChild(outerSprite)
tester.physicsBody = SKPhysicsBody(rectangleOfSize: tester.size)
tester.physicsBody!.categoryBitMask = CategoryTester
tester.physicsBody!.collisionBitMask = CategoryInner
addChild(tester)
}
func didBeginContact(contact: SKPhysicsContact) {
let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
if collision == (CategoryOuter | CategoryTester) {
print("outer collision")
}
else if collision == (CategoryInner | CategoryTester) {
print("inner collision")
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let location = touch.locationInNode(self)
tester.position = location
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Custom Particle System for iOS

I want to create a particle system on iOS using sprite kit where I define the colour of each individual particle. As far as I can tell this isn't possible with the existing SKEmitterNode.
It seems that best I can do is specify general behaviour. Is there any way I can specify the starting colour and position of each particle?
This can give you a basic idea what I was meant in my comments. But keep in mind that it is untested and I am not sure how it will behave if frame rate drops occur.
This example creates 5 particles per second, add them sequentially (in counterclockwise direction) along the perimeter of a given circle. Each particle will have different predefined color. You can play with Settings struct properties to change the particle spawning speed or to increase or decrease number of particles to emit.
Pretty much everything is commented, so I guess you will be fine:
Swift 2
import SpriteKit
struct Settings {
static var numberOfParticles = 30
static var particleBirthRate:CGFloat = 5 //Means 5 particles per second, 0.2 means one particle in 5 seconds etc.
}
class GameScene: SKScene {
var positions = [CGPoint]()
var colors = [SKColor]()
var emitterNode:SKEmitterNode?
var currentPosition = 0
override func didMoveToView(view: SKView) {
backgroundColor = .blackColor()
emitterNode = SKEmitterNode(fileNamed: "rain.sks")
if let emitter = emitterNode {
emitter.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
emitter.particleBirthRate = Settings.particleBirthRate
addChild(emitter)
let radius = 50.0
let center = CGPointZero
for var i = 0; i <= Settings.numberOfParticles; i++ {
//Randomize color
colors.append(SKColor(red: 0.78, green: CGFloat(i*8)/255.0, blue: 0.38, alpha: 1))
//Create some points on a perimeter of a given circle (radius = 40)
let angle = Double(i) * 2.0 * M_PI / Double(Settings.numberOfParticles)
let x = radius * cos(angle)
let y = radius * sin(angle)
let currentParticlePosition = CGPointMake(CGFloat(x) + center.x, CGFloat(y) + center.y)
positions.append(currentParticlePosition)
if i == 1 {
/*
Set start position for the first particle.
particlePosition is starting position for each particle in the emitter's coordinate space. Defaults to (0.0, 0,0).
*/
emitter.particlePosition = positions[0]
emitter.particleColor = colors[0]
self.currentPosition++
}
}
// Added just for debugging purposes to show positions for every particle.
for particlePosition in positions {
let sprite = SKSpriteNode(color: SKColor.orangeColor(), size: CGSize(width: 1, height: 1))
sprite.position = convertPoint(particlePosition, fromNode:emitter)
sprite.zPosition = 2
addChild(sprite)
}
let block = SKAction.runBlock({
// Prevent strong reference cycles.
[unowned self] in
if self.currentPosition < self.positions.count {
// Set color for the next particle
emitter.particleColor = self.colors[self.currentPosition]
// Set position for the next particle. Keep in mind that particlePosition is a point in the emitter's coordinate space.
emitter.particlePosition = self.positions[self.currentPosition++]
}else {
//Stop the action
self.removeActionForKey("emitting")
emitter.particleBirthRate = 0
}
})
// particleBirthRate is a rate at which new particles are generated, in particles per second. Defaults to 0.0.
let rate = NSTimeInterval(CGFloat(1.0) / Settings.particleBirthRate)
let sequence = SKAction.sequence([SKAction.waitForDuration(rate), block])
let repeatAction = SKAction.repeatActionForever(sequence)
runAction(repeatAction, withKey: "emitting")
}
}
}
Swift 3.1
import SpriteKit
struct Settings {
static var numberOfParticles = 30
static var particleBirthRate:CGFloat = 5 //Means 5 particles per second, 0.2 means one particle in 5 seconds etc.
}
class GameScene: SKScene {
var positions = [CGPoint]()
var colors = [SKColor]()
var emitterNode: SKEmitterNode?
var currentPosition = 0
override func didMove(to view: SKView) {
backgroundColor = SKColor.black
emitterNode = SKEmitterNode(fileNamed: "rain.sks")
if let emitter = emitterNode {
emitter.position = CGPoint(x: frame.midX, y: frame.midY)
emitter.particleBirthRate = Settings.particleBirthRate
addChild(emitter)
let radius = 50.0
let center = CGPoint.zero
for var i in 0...Settings.numberOfParticles {
//Randomize color
colors.append(SKColor(red: 0.78, green: CGFloat(i * 8) / 255.0, blue: 0.38, alpha: 1))
//Create some points on a perimeter of a given circle (radius = 40)
let angle = Double(i) * 2.0 * Double.pi / Double(Settings.numberOfParticles)
let x = radius * cos(angle)
let y = radius * sin(angle)
let currentParticlePosition = CGPoint.init(x: CGFloat(x) + center.x, y: CGFloat(y) + center.y)
positions.append(currentParticlePosition)
if i == 1 {
/*
Set start position for the first particle.
particlePosition is starting position for each particle in the emitter's coordinate space. Defaults to (0.0, 0,0).
*/
emitter.particlePosition = positions[0]
emitter.particleColor = colors[0]
self.currentPosition += 1
}
}
// Added just for debugging purposes to show positions for every particle.
for particlePosition in positions {
let sprite = SKSpriteNode(color: SKColor.orange, size: CGSize(width: 1, height: 1))
sprite.position = convert(particlePosition, from: emitter)
sprite.zPosition = 2
addChild(sprite)
}
let block = SKAction.run({
// Prevent strong reference cycles.
[unowned self] in
if self.currentPosition < self.positions.count {
// Set color for the next particle
emitter.particleColor = self.colors[self.currentPosition]
// Set position for the next particle. Keep in mind that particlePosition is a point in the emitter's coordinate space.
emitter.particlePosition = self.positions[self.currentPosition]
self.currentPosition += 1
} else {
//Stop the action
self.removeAction(forKey: "emitting")
emitter.particleBirthRate = 0
}
})
// particleBirthRate is a rate at which new particles are generated, in particles per second. Defaults to 0.0.
let rate = TimeInterval(CGFloat(1.0) / Settings.particleBirthRate)
let sequence = SKAction.sequence([SKAction.wait(forDuration: rate), block])
let repeatAction = SKAction.repeatForever(sequence)
run(repeatAction, withKey: "emitting")
}
}
}
Orange dots are added just for debugging purposes and you can remove that part if you like.
Personally I would say that you are overthinking this, but I might be wrong because there is no clear description of what you are trying to make and how to use it. Keep in mind that SpriteKit can render a bunch of sprites in a single draw call in very performant way. Same goes with SKEmitterNode if used sparingly. Also, don't underestimate SKEmitterNode... It is very configurable actually.
Here is the setup of Particle Emitter Editor:
Anyways, here is the final result:
Note that nodes count comes from an orange SKSpriteNodes used for debugging. If you remove them, you will see that there is only one node added to the scene (emitter node).
What you want is completely possible, probably even in real time. Unfortunately to do such a thing the way you describe with moving particles as being a particle for each pixel would be best done with a pixel shader. I don't know of a clean method that would allow you to draw on top of the scene with a pixel shader otherwise all you would need is a pixel shader that takes the pixels and moves them out from the center. I personally wouldn't try to do this unless I built the game with my own custom game engine in place of spritekit.
That being said I'm not sure a pixel per pixel diffusion is the best thing in most cases. Expecially if you have cartoony art. Many popular games will actually make sprites for fragments of the object they expect to shader. So like if it's an airplane you might have a sprite for the wings with perhaps even wires hanging out of this. Then when it is time to shatter the plane, remove it from the scene and replace the area with the pieces in the same shape of the plane... Sorta like a puzzle. This will likely take some tweaking. Then you can add skphysicsbodies to all of these pieces and have a force push them out in all directions. Also this doesn't mean that each pixel gets a node. I would suggest creatively breaking it into under 10 pieces.
And as whirlwind said you could all ways get things looking "like" it actually disintegrated by using an emitter node. Just make the spawn area bigger and try to emulate the color as much as possible. To make the ship dissappear you could do a fade perhaps? Or Mabye an explosion sprite over it? Often with real time special effects and physics, or with vfx it is more about making it look like reality then actually simulating reality. Sometimes you have to use trickery to get things to look good and run real-time.
If you want to see how this might look I would recommend looking at games like jetpac joyride.
Good luck!

Nodes Spawning off of screen in sprite kit swift

I am building a ios game with swift and I have run into a bit of a problem. I am trying to spawn balls from the top of the screen and have them come down towards the ground. They are supposed to have random x values and go down at random rates but instead of spawning on the screen the nodes spawn on an x value which is not encompassed by the screen. Please help me as I think I have done everything right.
Here is the code for my addball function...
func addBall(){
//create ball sprite
var ball = SKSpriteNode(imageNamed: "ball.png")
//create physics for ball
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size) // 1
ball.physicsBody?.dynamic = true // 2
ball.physicsBody?.categoryBitMask = PhysicsCategory.Ball // 3
ball.physicsBody?.contactTestBitMask = PhysicsCategory.Person & PhysicsCategory.Ground
ball.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
//generate random postion along x axis for ball to spawn
let actualX = random(min:ball.frame.size.width/2+1, max: self.frame.size.width - ball.frame.size.width/2-1)
println(actualX)
//set balls positon
ball.position = CGPoint(x: actualX, y: size.height - ball.size.width/2)
//add ball to scene
addChild(ball)
//determine speed of ball
let actualDuration = random(min: CGFloat(3.0), max: CGFloat(5.0))
//create movement actions
let actionMove = SKAction.moveTo(CGPoint(x:actualX, y: -ball.size.width/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
ball.runAction(SKAction.sequence([actionMove, actionMoveDone]), withKey: "action")
}
here is the code for my random functions
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
The problem here is that your SKScene likely takes up much more space than the screen of your device. Thus, when you calculate a random value using the whole scene, some of the time the ball will spawn in the area of the scene not visible to you.
The two main properties that control the scene's size are its size and scaleMode properties. The scaleMode property relates to how the scene is mapped. Unless you initialized and presented this scene yourself, you can check the scaleMode in your view controller. It will likely be set to aspectFill, which according to Apple means:
The scaling factor of each dimension is calculated and the larger of the two is chosen. Each axis of the scene is scaled by the same scaling factor. This guarantees that the entire area of the view is filled but may cause parts of the scene to be cropped.
If you don't like this, there are other scaleModes. However, in most cases this mode would actually be preferable since SpriteKit's internal scaling is able to make universal apps. If this is fine for you, then the easiest thing to do is set hardcoded values for something like the spawn locations for your ball node.

Resources