SKPhysicsBody doesn't work on custom class for SKSpriteNode - sprite-kit

I wont use a "custom class" on interface builder for an example "Walls".
Drag a SpriteNode and set the custom class : Wall.
On code Wall.swift :
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.color = .white // This works
let body = SKPhysicsBody(rectangleOf: frame.size)
body.isDynamic = true
body.allowsRotation = false
body.pinned = false
body.affectedByGravity = true
body.friction = 0.0
body.restitution = 0.0
body.linearDamping = 1.0
body.angularDamping = 1.0
body.mass = 1.0
body.velocity = CGVector(dx: 0, dy: 0)
body.density = 1.0
body.charge = 1.0
body.categoryBitMask = 4294967295
body.collisionBitMask = 4294967295
body.fieldBitMask = 4294967295
body.contactTestBitMask = 0
self.physicsBody = body
}
The sprite change color to white, but physicsBody doesn't work.
What im doing wrong?

Related

Adding confetti animation behind the human/Animal body to a Video

I want to add confetti animation to a video so that when any animal/human appears then confetti animation should be running behind the human/animal's body. I used following code but here confetti is running as overlay in video.
import UIKit
import QuartzCore
class ConfettiView: UIView {
var emitter: CAEmitterLayer!
var colors: [UIColor]!
var intensity: Float!
private var active : Bool = false
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
colors = [UIColor.red,
UIColor.green,
UIColor.blue
]
intensity = 0.2
active = false
}
func startConfetti() {
emitter = CAEmitterLayer()
emitter.emitterPosition = CGPoint(x: frame.size.width / 2.0, y: -20)
emitter.emitterShape = CAEmitterLayerEmitterShape.line
emitter.emitterSize = CGSize(width: frame.size.width, height: 1)
var cells = [CAEmitterCell]()
for color in colors {
cells.append(confettiWithColor(color: color))
}
emitter.emitterCells = cells
layer.addSublayer(emitter)
active = true
}
func stopConfetti() {
emitter?.birthRate = 0
active = false
}
func confettiWithColor(color: UIColor) -> CAEmitterCell {
let confetti = CAEmitterCell()
confetti.birthRate = 10.0 * intensity
confetti.lifetime = 180.0 * intensity
confetti.lifetimeRange = 0
confetti.color = color.cgColor
confetti.velocity = CGFloat(350.0 * intensity)
confetti.velocityRange = CGFloat(40.0 * intensity)
confetti.emissionLongitude = CGFloat(Double.pi)
confetti.emissionRange = 0.4
confetti.spin = CGFloat(3.5 * intensity)
confetti.spinRange = 0.0
confetti.scale = 0.1
confetti.contents = UIImage(named: "image")?.cgImage
return confetti
}
}
// then I add this confettiView as a subView of VidePlayerView
I need to show/export video with confetti like this.
Watch it. Here confetti is running behind the woman.

Is there a simple way to store two objects of different type in a container?

I'm trying to position an SKCameraNode at the center of multiple objects in my scene. I'm thinking of placing all objects of interest into some sort of container, and then calculating the centroid every update(). I have two classes, Ball and Car, that inherit from PhysicalObject, which inherits from SKSpriteNode. What I'd like to do is something like
var cameraObjects: Array<Ball, Car>!
...
var ball1 = Ball()
var car1 = Car()
cameraObjects.append(ball1)
cameraObjects.append(car1)
...
// Loop through objects and calculate centroid
But this crashes at the first append().
Note that I'd rather not create a dummy protocol for each class, as mentioned here. Is there a simple way to do this?
PhysicalObject.swift
import SpriteKit
class PhysicalObject: SKSpriteNode {
var objectMass: CGFloat!
override init(texture: SKTexture!, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.name = "physicalObject"
//Physics setup
physicsBody?.usesPreciseCollisionDetection = true
physicsBody?.affectedByGravity = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Ball.swift
import SpriteKit
class Ball: PhysicalObject {
let MAXBALLSPEED :CGFloat = 0.08
init(spawnPosition: CGPoint) {
/* Temporary initialization.. will have further customization for different classes */
let ballTexture = SKTexture(imageNamed: "ball")
let ballRadius = CGFloat(50)
let ballSize = CGSize(width: ballRadius, height: ballRadius)
super.init(texture: ballTexture, color: UIColor.clear, size: ballSize)
self.position = spawnPosition
self.name = "ball"
//Physics Setup
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(origin: spawnPosition, size: carSize) )
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.height/2)
self.objectMass = CGFloat(0.005)
physicsBody?.isDynamic = true // Default is true
physicsBody?.restitution = 1.0
self.physicsBody?.categoryBitMask = PhysicsCategory.Ball
self.physicsBody?.contactTestBitMask = PhysicsCategory.Boards | PhysicsCategory.Car
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Car.swift
import SpriteKit
class Car: PhysicalObject {
let MAXCARSPEED: CGFloat = 500
var steering: Bool
var diagonalLength: CGFloat
init(spawnPosition: CGPoint) {
/* Temporary initialization.. will have further customization for different classes of cars */
let carTexture = SKTexture(imageNamed: "BasicCar")
let carWidth = CGFloat(screenSize.width/20)
let carScale = CGFloat(183.0/140.0)
let carSize = CGSize(width: (carWidth*carScale), height: carWidth)
// Initialize movement variables
self.steering = false
self.diagonalLength = hypot(carSize.width, carSize.height)
//self.steerRight = false
//self.steerLeft = false
super.init(texture: carTexture, color: UIColor.clear, size: carSize)
self.speed = CGFloat(MAXCARSPEED)
self.position = spawnPosition
self.name = "car"
//Physics Setup
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(origin: spawnPosition, size: carSize) )
let carPhysicsSize = CGSize(width: self.size.width, height: self.size.height)
self.physicsBody = SKPhysicsBody(rectangleOf: carPhysicsSize)
self.objectMass = CGFloat(2000)
self.physicsBody?.friction = 0.5
self.physicsBody?.angularDamping = 1.0
self.physicsBody?.linearDamping = 1.0
self.physicsBody?.restitution = 1.0
physicsBody?.isDynamic = true // Default is true
self.physicsBody?.categoryBitMask = PhysicsCategory.Car
self.physicsBody?.contactTestBitMask = PhysicsCategory.Car | PhysicsCategory.Ball
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can make an Array<PhysicalObject> (a.k.a. [PhysicalObject]), which can store both Ball objects, and Car objects.
You may find that the enumerateChildNodes method of SKNode is a better way to enumerate over all the objects with a certain name. That way, you don't need to manage the array yourself.
Take a look at Apple's documentation.
All you need to do is place all the objects you want be involved with the camera into a new parent SKNode, and use calculateAccumulatedFrame to get the frame around all the nodes, then just use the center of the parent's frame.

How to reference SKSpriteNode without full instantiation in Swift3?

Previously, I had been using var progressBar = ProgressBar?() right above init within one of my SKSpriteNode subclasses, to allow various methods to reference progressBar throughout the class like so:
class Ship:SKSpriteNode{
var progressBar = ProgressBar?()
static var shipState = "norm"
var powerupOn = false
var moveSpeed:CGFloat
var lives:Int
var canShoot = false
let las = SKSpriteNode(texture:laserTexture)
var gun:GenericGun = GenericGun()
let scaleFactor:CGFloat = 0.07
init(startPosition startPos:CGPoint, controllerVector:CGVector){
self.lives = 3
self.moveSpeed = 160
super.init(texture: ship0, color: UIColor.clear, size: ship0.size())
//Set position
self.position = startPos
attachGun(gun)
self.setScale(scaleFactor)
self.physicsBody = SKPhysicsBody(texture: self.texture!, size: self.size)
self.physicsBody?.isDynamic = true
self.physicsBody?.categoryBitMask = PhysicsCategory.Ship
self.physicsBody?.collisionBitMask = 0
self.physicsBody?.contactTestBitMask = PhysicsCategory.Alien
self.physicsBody?.allowsRotation = false
self.physicsBody?.angularVelocity = CGFloat(0)
self.physicsBody?.usesPreciseCollisionDetection = true
self.physicsBody?.affectedByGravity = false //TBD
self.physicsBody?.velocity.dx = controllerVector.dx * moveSpeed
self.physicsBody?.velocity.dy = controllerVector.dy * moveSpeed
self.name = "ship"
//Call animations
self.animateShip1()
}
func updateVelocity(_ v:CGVector){
self.physicsBody?.velocity.dx = v.dx * moveSpeed
self.physicsBody?.velocity.dy = v.dy * moveSpeed
}
func attachGun(_ gun:GenericGun){
gun.position = CGPoint(x: 0, y: 0)
self.addChild(gun)
}
func animateShip1() {
let animate = SKAction.animate(with: shipFrames, timePerFrame: 0.1)
let forever = SKAction.repeatForever(animate)
self.run(forever)
}
func updateShipProperties(shipVelocity v:CGVector,laserStartPos laserStart:CGPoint){
updateVelocity(v)
progressBar?.position = CGPoint(x: self.position.x - (self.size.width/2 + self.size.width/7.0), y: self.position.y)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In Swift3 I'm getting an error that I need to provide full arguments, is there a way to achieve this same functionality in Swift3?

Assigning an image to a SKShapeNode as a Ball - Spritekit Game

I am trying to add an image to my ball in my Swift 2 Sprite-kit game. I have a make() ball function and it there are no errors there however in the game scene, when the method is called, I keep receiving this error :
"Cannot assign SKShapeNode' to type Ball!
I am not sure how to fix and am new to Swift.
// ----- GameScene.Swift ----
class GameScene: SKScene, GameDelegate, SKPhysicsContactDelegate {
// ball part one
var ball: Ball!
let ballSpeedX = CGFloat(500)
}
override func didMoveToView(view: SKView) {
// ball
ball = Ball.make() // error!!!
ball.position.x = CGRectGetMidX(self.frame)
ball.position.y = CGRectGetMidY(self.frame)
ball.physicsBody!.categoryBitMask = CollideType.Ball.toMask()
ball.physicsBody!.collisionBitMask = CollideType.toMask([.Scene, .Ceil, .Floor]) | boards.usedCollideMasks
ball.physicsBody!.contactTestBitMask = CollideType.toMask([.Scene, .Ceil, .Floor]) | boards.usedCollideMasks
ball.hidden = true
self.addChild(ball)
}
And this is Ball.swift where the make() is:
//Ball. Swift
//imports...
class Ball: SKShapeNode {
var speedXFirst = CGFloat(0)
var speedXSecond = CGFloat(0)
var lastFloor = -1
var xSpeed: CGFloat {
get {
return speedXFirst + speedXSecond
}
}
var lastBoardNumber = 0
private override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
static func make()-> SKShapeNode {
let textureBall : SKTexture! = SKTexture(imageNamed:"colorBall.png")
let ballSize: CGSize = textureBall.size()
var ball = SKShapeNode.init(circleOfRadius: ballSize.width/2)
ball.fillTexture = textureBall
ball.strokeColor = UIColor.clearColor()
ball.name = "ball"
ball.zPosition = 1
ball.fillColor = UIColor.redColor() // use the color you want
return ball
}
func freezeX() {
self.speedXFirst = 0
self.speedXSecond = 0
}
}
You have this issue because you try to assign a regular initialized and customized SKShapeNode (built by your make() method) to a type Ball that isn't the same SKShapeNode type (subclassed with a specific init).
You could customized your Ball class like this:
class Ball: SKShapeNode {
var speedXFirst = CGFloat(0)
var speedXSecond = CGFloat(0)
var lastFloor = -1
private var radius: CGFloat!
var xSpeed: CGFloat {
get {
return speedXFirst + speedXSecond
}
}
var lastBoardNumber = 0
init(imageNamed: String){
super.init()
let textureBall : SKTexture! = SKTexture.init(imageNamed:imageNamed)
let ballSize: CGSize = textureBall.size()
self.radius = ballSize.width/2
self.path = CGPathCreateWithEllipseInRect(CGRect(origin: CGPointZero, size: CGSize(width: radius*2, height: radius*2)), nil)
self.strokeColor = UIColor.clearColor()
self.name = "ball"
self.zPosition = 1
self.fillTexture = textureBall
self.fillColor = UIColor.redColor() // use the color you want
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func freezeX() {
self.speedXFirst = 0
self.speedXSecond = 0
}
}
So when you are in your scene you can call it like this:
var ball: Ball!
ball = Ball(imageNamed:"colorBall.png")

spritekit collision detection inconsistency (failing to get position of both nodes)

Within didBeginContact function I'm successfully capturing the collision between a horizontal grid line and a circle.
At the collision I can successfully get the position of the circle(head).
if let headBody = contact.bodyB.node as? SnakeBodyUnit {
print("head position ", headBody.position)
}
This successfully prints out:
head position (87.536979675293, 267.116882324219)
Now if I try to examine the horizontal line:
if let tripRow = contact.bodyA.node as? HeadTripper {
print("row position ", tripRow.position)
}
I get the following error:
po tripRow
error: :1:1: error: use of unresolved identifier 'tripRow'
tripRow
Here is the condition where this detection fires:
if (contact.bodyA.categoryBitMask == ColliderType.TripRow && contact.bodyB.categoryBitMask == ColliderType.Head) {
//contact successfully fires
}
The HeadTripper class and the SnakeBodyUnit class are both SKNode classes.
Here is the SnakeBodyUnit class:
import SpriteKit
class SnakeBodyUnit : SKNode {
var bodyDir: Direction?
var id = -1
var staticIDref = -1
var partX:CGFloat = -1
var partY:CGFloat = -1
var bodyT = -1
var staticTurn: TurnCrumb?
var turnRequested = 0
var requestedTurn: Direction?
var unitHolder: SKSpriteNode?
init(size: CGSize, bodyType: Int) {
super.init()
bodyT = bodyType
let reducedSize = size.width
unitHolder = SKSpriteNode()
unitHolder!.size = CGSize(width: (size.width), height: (size.height))
if (bodyT == 0) {
//head
unitHolder!.color = UIColor.clearColor()
//unitHolder!.position = CGPoint(x:xPos, y:yPos);
let shape = SKShapeNode(circleOfRadius: reducedSize/2)
shape.fillColor = UIColor.whiteColor()
shape.strokeColor = UIColor.whiteColor()
shape.position = CGPoint(x: reducedSize/2, y: reducedSize/2)
unitHolder!.addChild(shape)
self.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(reducedSize, reducedSize), center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Head
self.physicsBody?.dynamic = false
self.physicsBody?.contactTestBitMask = ColliderType.TripRow | ColliderType.TripColumn | ColliderType.Food | ColliderType.Body | ColliderType.WallLeft | ColliderType.WallRight | ColliderType.WallBottom | ColliderType.WallTop | ColliderType.Canvas
} else if (bodyT == 1) {
//body part
self.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(reducedSize, reducedSize), center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Body
self.physicsBody!.collisionBitMask = 0
self.physicsBody?.dynamic = true
self.physicsBody?.contactTestBitMask = ColliderType.WallLeft | ColliderType.WallRight | ColliderType.WallBottom | ColliderType.WallTop | ColliderType.BodyStatic
} else if (bodyT == 2) {
//static corner
self.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(reducedSize/2, reducedSize/2), center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.BodyStatic
self.physicsBody!.collisionBitMask = 0
self.physicsBody?.dynamic = true
}
unitHolder!.anchorPoint = CGPoint(x: 0, y: 0)
self.addChild(unitHolder!)
}
func updateColor(color: UIColor) {
unitHolder!.color = color
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//fatalError("init coder not implemented")
}
}
Here is the HeadTripper class:
import SpriteKit
class HeadTripper : SKNode {
var id = -1
var rowTripLine: SKSpriteNode?
var colTripLine: SKSpriteNode?
init(size: CGSize, rowOrCol: Int) {
//init(size: CGSize, xPos: Double, yPos: Double, rowOrCol: Int) {
super.init()
//row 0
//col 1
if (rowOrCol == 0) {
//row
rowTripLine = SKSpriteNode()
rowTripLine!.color = UIColor.redColor()
//rowTripLine!.position = CGPoint(x: 0, y: yPos);
rowTripLine!.size = CGSize(width: Int(size.width), height: 1)
//borderLeft!.physicsBody = SKPhysicsBody(rectangleOfSize: size, center: CGPointMake(reducedSize/2, reducedSize/2))
rowTripLine!.physicsBody = SKPhysicsBody(rectangleOfSize: rowTripLine!.size, center: CGPointMake(rowTripLine!.size.width/2, rowTripLine!.size.height/2))
rowTripLine!.physicsBody?.affectedByGravity = false
rowTripLine!.physicsBody?.categoryBitMask = ColliderType.TripRow
rowTripLine!.physicsBody!.collisionBitMask = 0
rowTripLine!.physicsBody?.dynamic = true
rowTripLine!.anchorPoint = CGPoint(x: 0, y: 0)
self.addChild(rowTripLine!)
} else {
//col
colTripLine = SKSpriteNode()
colTripLine!.color = UIColor.redColor()
//colTripLine!.position = CGPoint(x: xPos, y: 0);
colTripLine!.size = CGSize(width: 1, height: Int(size.height))
//borderLeft!.physicsBody = SKPhysicsBody(rectangleOfSize: size, center: CGPointMake(reducedSize/2, reducedSize/2))
colTripLine!.physicsBody = SKPhysicsBody(rectangleOfSize: colTripLine!.size, center: CGPointMake(colTripLine!.size.width/2, colTripLine!.size.height/2))
colTripLine!.physicsBody?.affectedByGravity = false
colTripLine!.physicsBody?.categoryBitMask = ColliderType.TripColumn
colTripLine!.physicsBody!.collisionBitMask = 0
colTripLine!.physicsBody?.dynamic = true
colTripLine!.anchorPoint = CGPoint(x: 0, y: 0)
self.addChild(colTripLine!)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init coder not implemented")
}
}
I generate the HeadTripper lines like this in the main scene:
for var i = 0; i < rowCount; i++ {
let rowTrip = HeadTripper(size: size, rowOrCol: 0)
rowTrip.id = i
rowTrip.position.x = 0
rowTrip.position.y = CGFloat(yVal)
self.addChild(rowTrip)
rowLines.append(CGFloat(rowTrip.position.y))
CGPathMoveToPoint(linePath, nil, 0, CGFloat(yVal))
CGPathAddLineToPoint(linePath, nil, CGFloat(theGrid.screenWidth), CGFloat(yVal))
yVal += (theGrid.snakeHigh + yValPad)
}
Here is how the collider types are setup:
struct ColliderType {
static let Head: UInt32 = 0
static let Food: UInt32 = 0b1
static let Body: UInt32 = 0b10
static let BodyStatic: UInt32 = 0b100
static let WallLeft: UInt32 = 0b1000
static let WallBottom: UInt32 = 0b10000
static let WallRight: UInt32 = 0b100000
static let WallTop: UInt32 = 0b1000000
static let None: UInt32 = 0b10000000
static let Canvas: UInt32 = 0b100000000
static let TripColumn: UInt32 = 0b1000000000
static let TripRow: UInt32 = 0b10000000000
}
The tripRow let setup happens on line 310. On line 309 I can see the contact.bodyA:
0x000000012758db20
{
NSObject = {
isa = 0x000000012758db20
}
_representedObject = 0x00000001275765a0
_field = 0x0000000000000000
_dynamicType = 2
_world = 0x0000000127761b90
_joints = 0x0000000127576270 "0 values"
_inUse = true
_shapeType = 2
_radius = 0
_edgeRadius = 0.0010000000474974513
_mask = 0x0000000000000000
_isPinned = false
_allowsRotation = true
_postStepBlock = 0x000000019e666c70
}
I can also see the contact.bodyA.node:
0x00000001275765a0
{
UIKit.UIResponder = {...}
}
Line 311 never gets called because its in the if condition that isn't passing.
At line 313 I attempt to look at tripRow and get this:
(lldb) po tripRow
error: <EXPR>:1:1: error: use of unresolved identifier 'tripRow'
tripRow
It sounds like issue is related to the children within HeadTripper. I have named the children "row" and "col" respectively.
print("body A name ", contact.bodyA.node?.name)
let tripRow = contact.bodyA.node as? SKSpriteNode
This gives a name of "row". Also tripRow now prints out as:
(lldb) po tripRow
0x0000000155e737c0
{
SpriteKit.SKNode = {...}
}
I think this is very close. The problem still exists that the position is not accurate. The y value should be a number like 54 and it is zero:
(lldb) po tripRow?.position
(x = 0, y = 0)
This was resolved by having just 1 SKSpriteNode referenced in the HeadTripper SKNode class instead of 2.