I implemented #mfessenden suggestion regarding using a custom SKAction to match the SKPhysicsBody to follow the animation using alphaThreshold. ( https://stackoverflow.com/a/54096147 ) But it does not appear to be functioning for me. Where have I gone wrong? Can you help #mfessenden?
var rockFrames: [SKTexture] = []
let rockAnimatedAtlas = SKTextureAtlas(named: "funny")
let numImages = rockAnimatedAtlas.textureNames.count
for i in 1...numImages {
let rockTextureName = "\(i)"
rockFrames.append(rockAnimatedAtlas.textureNamed(rockTextureName))
}
let firstFrameTexture = rockFrames[0]
rock = SKSpriteNode(texture: firstFrameTexture)
rock.position = CGPoint(x: self.frame.midX, y: self.frame.midY + 100)
rock.size = CGSize(width: 480 * wR, height: 270 * hR)
self.addChild(rock)
let rockaction = SKAction.animate(with: rockFrames,
timePerFrame: (1/24),
resize: false,
restore: true)
rock.run(SKAction.repeatForever(rockaction))
typealias Frame = (texture: SKTexture, duration: TimeInterval)
let timePerFrame: TimeInterval = 1/24
let dogFrames: [Frame] = rockFrames.map {
return ($0, timePerFrame)
}
rock = SKSpriteNode(texture: rockFrames.first)
let dogAnimationAction = animateTexturesWithPhysics(dogFrames)
rock.run(dogAnimationAction)
public func animateTexturesWithPhysics(_ frames: [(texture: SKTexture, duration: TimeInterval)], repeatForever: Bool=true) -> SKAction {
var actions: [SKAction] = []
for frame in frames {
// define a custom action for each frame
let customAction = SKAction.customAction(withDuration: frame.duration) { node, _ in
// if the action target is a sprite node, apply the texture & physics
if node is SKSpriteNode {
let setTextureGroup = SKAction.group([
SKAction.setTexture(frame.texture, resize: false),
SKAction.wait(forDuration: frame.duration),
SKAction.run {
node.physicsBody = SKPhysicsBody(texture: frame.texture, alphaThreshold: 0.5, size: frame.texture.size())
node.physicsBody?.isDynamic = false
node.physicsBody?.categoryBitMask = self.arrowCategory
// add physics attributes here
}
])
node.run(setTextureGroup)
}
}
actions.append(customAction)
}
// add the repeating action
if (repeatForever == true) {
return SKAction.repeatForever(SKAction.sequence(actions))
}
return SKAction.sequence(actions)
}
Related
I am trying to detect collision between 2 objects but it works only some times. Most of the time it doesn't work. Maybe you can help me out understanding what i am doing wrong.
static let soldier: UInt32 = 0x1 << 4
static let enemy: UInt32 = 0x1 << 5
function setting the physicsBody for the ground, the soldier and the enemy:
func addPhysicsBody(to sprite: SKSpriteNode, with name: String) {
let centerPoint = CGPoint(x:sprite.size.width / 2 - (sprite.size.width * sprite.anchorPoint.x), y:sprite.size.height / 2 - (sprite.size.height * sprite.anchorPoint.y))
sprite.physicsBody = SKPhysicsBody(rectangleOf: sprite.size, center: centerPoint)
sprite.physicsBody?.allowsRotation = false
sprite.physicsBody?.affectedByGravity = true
sprite.physicsBody?.restitution = 0.0
sprite.physicsBody?.friction = 0
if name.contains(GameConstants.StringConstants.soldierName) {
sprite.physicsBody?.categoryBitMask = GameConstants.PhysicsCategories.soldier
sprite.physicsBody?.contactTestBitMask = GameConstants.PhysicsCategories.enemy
sprite.physicsBody?.collisionBitMask = GameConstants.PhysicsCategories.ground
sprite.physicsBody?.isDynamic = true
} else if name.contains(GameConstants.StringConstants.enemyName) {
sprite.physicsBody?.categoryBitMask = GameConstants.PhysicsCategories.enemy
sprite.physicsBody?.contactTestBitMask = GameConstants.PhysicsCategories.soldier
sprite.physicsBody?.collisionBitMask = GameConstants.PhysicsCategories.ground
sprite.physicsBody?.isDynamic = true
} else if name == GameConstants.StringConstants.groundName {
sprite.physicsBody?.affectedByGravity = false
sprite.physicsBody?.categoryBitMask = GameConstants.PhysicsCategories.ground
sprite.physicsBody?.isDynamic = false
}
}
generating the soldier and the enemy and moving them:
func generateASoldier() {
let soldier = SKSpriteNode(texture: SKTexture(image: UIImage(named: "0\(GameConstants.StringConstants.idlePrefixKey)0")!), color: UIColor.darkGray, size: self.frame.size)
soldier.size = CGSize(width: castle.frame.size.height/4, height: castle.frame.size.height/4)
soldier.name = "\(GameConstants.StringConstants.soldierName)\(soldierCounter)"
soldierCounter+=1
addPhysicsBody(to: soldier, with: soldier.name!)
soldier.position = CGPoint(x: frame.minX-soldier.size.width*2, y: frame.midY/1.1)
soldier.zPosition = GameConstants.ZPositions.playerZ
addChild(soldier)
isMovingSoldier.append(true)
moveSoldier(soldier: soldier, index: isMovingSoldier.count-1)
}
func moveSoldier(soldier: SKSpriteNode, index: Int) {
soldier.physicsBody?.applyImpulse(CGVector(dx: charactersSpeed, dy: 0))
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
if self.isMovingSoldier[index] {
self.moveSoldier(soldier: soldier, index: index)
}
})
}
func generateAnEnemy() {
let enemy = SKSpriteNode(texture: SKTexture(image: UIImage(named: "1\(GameConstants.StringConstants.idlePrefixKey)0")!), color: UIColor.darkGray, size: self.frame.size)
enemy.size = CGSize(width: -castle.frame.size.height/4, height: castle.frame.size.height/4)
enemy.name = "\(GameConstants.StringConstants.enemyName)\(enemyCounter)"
enemyCounter+=1
addPhysicsBody(to: enemy, with: enemy.name!)
enemy.position = CGPoint(x: frame.maxX-enemy.size.width*2, y: frame.midY/1.1)
enemy.zPosition = GameConstants.ZPositions.playerZ
addChild(enemy)
isMovingEnemy.append(true)
moveEnemy(enemy: enemy, index: isMovingEnemy.count-1)
}
func moveEnemy(enemy: SKSpriteNode, index: Int) {
enemy.physicsBody?.applyImpulse(CGVector(dx: -charactersSpeed, dy: 0))
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
if self.isMovingEnemy[index] {
self.moveEnemy(enemy: enemy, index: index)
}
})
}
generating the ground:
func generateGround() {
ground = SKSpriteNode(color: .white, size: CGSize(width: frame.maxX*3, height: UIScreen.main.bounds.size.height/2.435))
ground.name = GameConstants.StringConstants.groundName
addPhysicsBody(to: ground, with: ground.name!)
ground.zPosition = GameConstants.ZPositions.playerZ
ground.position = CGPoint(x: frame.midX, y: frame.minY)
self.addChild(ground)
}
Using this code func didBegin(_ contact: SKPhysicsContact) is called a random amount of times and i am trying to make sure it will be called every time the soldier and the enemy collide
Edit:
I found out that the problem is detecting the collision using the applyImpulse function. Using gravity it detects the collision
This is my GameScene code.
class GameScene: SKScene, SKPhysicsContactDelegate {
let orcWidth = UIScreen.main.bounds.width / 5
var orcCategory:UInt32 = 0x1 << 0
var knightCategory:UInt32 = 0x1 << 1
private var orc = SKSpriteNode()
private var knight = SKSpriteNode()
private var orcWalkingFrames: [SKTexture] = []
private var knightIdleFrames: [SKTexture] = []
private var knightAttackFrame: [SKTexture] = []
var background = SKSpriteNode(imageNamed: "game_background1")
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
setupbackground()
startGame()
}
func setupbackground() {
background.zPosition = 0
background.size = self.frame.size
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
addChild(background)
}
func startGame() {
buildRandomOrcs()
buildKnight()
}
func stopGame() {
}
func buildOrc(yposition: CGFloat) {
var orcWalkFrames: [SKTexture] = []
let orcAnimatedAtlas = SKTextureAtlas(named: "OrcWalking")
let numImages = orcAnimatedAtlas.textureNames.count
for i in 0...numImages - 1 {
let orcTextureName = "0_Orc_Walking_\(i)"
orcWalkFrames.append(orcAnimatedAtlas.textureNamed(orcTextureName))
}
self.orcWalkingFrames = orcWalkFrames
let firstFrameTexture = orcWalkingFrames[0]
orc = SKSpriteNode(texture: firstFrameTexture)
orc.name = "orc"
orc.position = CGPoint(x: frame.minX-orcWidth/2, y: yposition)
self.orc.zPosition = CGFloat(self.children.count)
orc.scale(to: CGSize(width: orcWidth, height: orcWidth))
orc.physicsBody = SKPhysicsBody(rectangleOf: orc.size, center: orc.position)
orc.physicsBody?.affectedByGravity = false
orc.physicsBody?.isDynamic = true
orc.physicsBody?.categoryBitMask = orcCategory
orc.physicsBody?.contactTestBitMask = knightCategory
orc.physicsBody?.collisionBitMask = knightCategory
addChild(orc)
walkOrc()
moveOrcForward()
}
func buildKnight() {
var knightIdleFrames: [SKTexture] = []
let knightIdleAtlas = SKTextureAtlas(named: "KnightIdle")
let numImages = knightIdleAtlas.textureNames.count
for i in 0...numImages - 1 {
let orcTextureName = "_IDLE_00\(i)"
knightIdleFrames.append(knightIdleAtlas.textureNamed(orcTextureName))
}
self.knightIdleFrames = knightIdleFrames
let firstFrameTexture = knightIdleFrames[0]
knight = SKSpriteNode(texture: firstFrameTexture)
knight.name = "knight"
knight.position = CGPoint(x: frame.maxX-orcWidth/2, y: frame.midY)
self.knight.zPosition = 1
knight.scale(to: CGSize(width: -orcWidth, height: orcWidth))
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size, center: knight.position)
knight.physicsBody?.affectedByGravity = false
knight.physicsBody?.isDynamic = false
knight.physicsBody?.categoryBitMask = knightCategory
knight.physicsBody?.contactTestBitMask = orcCategory
knight.physicsBody?.collisionBitMask = orcCategory
addChild(knight)
idleKnight()
}
func idleKnight() {
knight.run(SKAction.repeatForever(SKAction.animate(with: knightIdleFrames, timePerFrame: 0.1)))
}
func walkOrc() {
orc.run(SKAction.repeatForever(SKAction.animate(with: orcWalkingFrames,timePerFrame: 0.025)))
}
func moveOrcForward() {
orc.run(SKAction.repeatForever(SKAction.moveBy(x: 55, y: 0, duration: 0.25)))
}
func buildRandomOrcs () {
let wait = SKAction.wait(forDuration: TimeInterval(makeRandomNumberBetween(min: 0, max: 0)))
let spawn = SKAction.run {
self.buildOrc(yposition: self.makeRandomCGFloatNumber())
}
let spawning = SKAction.sequence([spawn,wait])
self.run(SKAction.repeat(spawning, count: 10))
}
func makeRandomCGFloatNumber() -> CGFloat {
let randomNumber = arc4random_uniform(UInt32((frame.maxY-orcWidth/2) - (frame.minY+orcWidth/2))) + UInt32(frame.minY+orcWidth/2)
return CGFloat(randomNumber)
}
func makeRandomNumberBetween (min: Int, max: Int) -> Int{
let randomNumber = arc4random_uniform(UInt32(max - min)) + UInt32(min)
return Int(randomNumber)
}
func didBegin(_ contact: SKPhysicsContact) {
let collision:UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == orcCategory | knightCategory {
self.scene?.view?.isPaused = true
print("COLLIDED")
}
}
}
The problem is that the scene pauses almost 2-3 seconds after the collision.
I changed the position of knight and delay time changed.
For example, if I set position to frame.minX+orcWidth/2 there is no delay.
What is wrong with my code?
Your problem isn't things are being delayed, your problem is your bounding box is not where you think it is
use view.showPhysics = true to determine where your boxes are
once you realize they are in the wrong spots, go to this line
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size, center: knight.position)
and fix it
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size)
Do so for the rest of your bodies
My guess is that manipulations of SKView properties must happen on main thread, i.e.
DispatchQueue.main.async { [unowned self] in
self.scene?.view?.isPaused = true
print("COLLIDED")
}
I'm trying to get my player to play the jump animation when the touch begins. The player is in his own swift file and I have the GameScene.swift call his file so everything that deals with his movement is in player.swift file. Any help would be awesome!
import SpriteKit
struct ColliderType {
static let PLAYER: UInt32 = 0
static let GROUND: UInt32 = 1
static let ROCKET_AND_COLLECTABLES: UInt32 = 2
}
class Player: SKSpriteNode {
private var textureAtlas = SKTextureAtlas()
private var playerAnimation = [SKTexture]()
private var animatePlayerAction = SKAction()
let jumpAnimation1 = [SKTexture]()
let jumpAnimation2 = [SKTexture]()
var jumpAnimation = SKAction ()
func initializePlayer() {
name = "Player"
for i in 1...7 {
let name = "Player \(i)"
playerAnimation.append(SKTexture(imageNamed: name))
}
animatePlayerAction = SKAction.animate(with: playerAnimation,
timePerFrame: 0.08, resize: true, restore: false)
//Animate the player forever.
self.run(SKAction.repeatForever(animatePlayerAction))
physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:
self.size.width-60, height: self.size.height-80))
physicsBody?.affectedByGravity = true
physicsBody?.allowsRotation = false
//Restitution is Boucniess
physicsBody?.restitution = 0
physicsBody?.categoryBitMask = ColliderType.PLAYER
physicsBody?.collisionBitMask = ColliderType.GROUND
physicsBody?.contactTestBitMask =
ColliderType.ROCKET_AND_COLLECTABLES
}
func move (){
self.position.x += 10
}
func reversePlayer() {
physicsBody?.velocity = CGVector(dx: 0, dy: 0)
physicsBody?.applyImpulse(CGVector(dx: 0, dy: 300))
playerNode?.removeAction(forKey:"animate") playerNode?.run(attackAnimation,completion:{
self.playerNode?.run(self.animation,withKey: "animate")
}
}
Replace the part where you set textures, create and run the action with the following code snippet. It works perfectly for me and is the simplest way to do it.
let hero = SKSpriteNode(imageNamed: "hero")
hero.size = CGSize(width: 45, height: 125)
let f1 = SKTexture.init(imageNamed: "h1")
let f2 = SKTexture.init(imageNamed: "h2")
let f3 = SKTexture.init(imageNamed: "h3")
let frames: [SKTexture] = [f1, f2, f3]
let hero = SKSpriteNode(imageNamed: "h1")
let animation = SKAction.animate(with: frames, timePerFrame: 0.1)
hero.run(SKAction.repeatForever(animation))
Also, it is possible that your animation action cannot be accessed as it is in another file. Declare the player and animations globally i.e. outside of any class or function. Hope it works for you!
I am trying to detect the collision between the circle called player and the balls called enemyCircle but nothing happen
func didBeginContact(contact: SKPhysicsContact) {
if let nodeA = contact.bodyA.node as? SKShapeNode, let nodeB = contact.bodyB.node as? SKShapeNode {
if nodeA.fillColor != nodeB.fillColor {
print("not same color")
}else {
print("same color")
}
}
}
private func drawPlayer(radius: CGFloat) {
player = SKShapeNode(circleOfRadius: radius)
player.physicsBody = SKPhysicsBody(circleOfRadius: radius)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = false
player.physicsBody?.pinned = true
player.physicsBody?.categoryBitMask = bodyType.player.rawValue
player.physicsBody?.collisionBitMask = bodyType.enemy.rawValue
player.physicsBody?.contactTestBitMask = bodyType.enemy.rawValue
player.strokeColor = SKColor.purple
player.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
player.glowWidth = 2.0
world.addChild(player)
}
private func drawEnemy(radius: CGFloat, color: SKColor) {
let enemyCircle = SKShapeNode(circleOfRadius: radius)
enemyCircle.fillColor = color
enemyCircle.glowWidth = 0.5
enemyCircle.strokeColor = color
enemyCircle.physicsBody = SKPhysicsBody(circleOfRadius: radius)
enemyCircle.physicsBody?.affectedByGravity = false
enemyCircle.physicsBody?.isDynamic = false
enemyCircle.physicsBody?.collisionBitMask = bodyType.player.rawValue
enemyCircle.physicsBody?.categoryBitMask = bodyType.enemy.rawValue
let nextEnemyPosition = determineNextEnemyPosition()
let enemyPosition = spawnAtRandomPosition(edge: nextEnemyPosition)
enemyCircle.position = enemyPosition
world.addChild(enemyCircle)
numberOfEnemies += 1
enemyCircle.name = String(numberOfEnemies)
enemy.append(enemyCircle)
runToCenter(enemy: enemyCircle)
}
enum bodyType: UInt32 {
case enemy = 1
case player = 2
}
and this
class GameScene: SKScene, SKPhysicsContactDelegate
and i put this in didMove func
self.physicsWorld.contactDelegate = self
can someone explain me the issues here
Note:: I made both not dynamic as I don't want anyone to move the other or change it, just be notified when it touches so I stop the game
I am trying to use the SKPhysicsContactDelegate function in SpriteKit and it will not seem to work. I want one sprite to perform an action when it hits the other. I have set up breakpoints at the didBeginContact function and for some reason my application never calls this function. All help appreciated. Code posted below.
struct PhysicsCatagory {
static let Enemy :UInt32 = 0x1 << 0
static let Slider :UInt32 = 0x1 << 1
static let Circle :UInt32 = 0x1 << 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var EnemyTimer = NSTimer()
var Circle = SKSpriteNode()
var Slider = SKSpriteNode()
var FastButton = SKNode()
var Title = SKSpriteNode()
var Text = SKSpriteNode()
var Path = UIBezierPath()
var gameStarted = Bool()
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.backgroundColor = UIColor.whiteColor()
Circle = SKSpriteNode(imageNamed:"blueCircle")
Circle.size = CGSize(width: 140, height: 140)
Circle.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
Circle.zPosition = 1.0
self.addChild(Circle)
Slider = SKSpriteNode(imageNamed: "blocker1")
Slider.size = CGSize(width: 15, height: 50)
Slider.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2 + 80)
addChild(Slider)
Slider.zPosition = 1.0
moveClockWise()
}
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.node != nil && contact.bodyB.node != nil{
let firstBody = contact.bodyA.node as! SKSpriteNode
let secondBody = contact.bodyB.node as! SKSpriteNode
if ((firstBody.name == "Enemy") && (secondBody.name == "Slider")){
collisionBall(firstBody, Slider: secondBody)
}
else if ((firstBody.name == "Slider") && (secondBody.name == "Enemy")) {
collisionBall(secondBody, Slider: firstBody)
}
}
}
func collisionBall(Enemy : SKSpriteNode, Slider : SKSpriteNode){
Enemy.physicsBody?.dynamic = true
Enemy.physicsBody?.affectedByGravity = true
Enemy.physicsBody?.mass = 4.0
Slider.physicsBody?.mass = 4.0
Enemy.removeAllActions()
Enemy.physicsBody?.contactTestBitMask = 0
Enemy.physicsBody?.collisionBitMask = 0
Enemy.name = nil
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Slider.hidden = false
FastButton.hidden = false
Title.hidden = true
Text.hidden = true
EnemyTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(GameScene.Enemies), userInfo: nil, repeats: true)
//Physics
Slider.physicsBody?.categoryBitMask = PhysicsCatagory.Slider
Slider.physicsBody?.collisionBitMask = PhysicsCatagory.Enemy
Slider.physicsBody?.contactTestBitMask = PhysicsCatagory.Enemy
Slider.name = "Slider"
Slider.physicsBody?.dynamic = true
Slider.physicsBody?.affectedByGravity = true
if gameStarted == false{
gameStarted = true
}
else if gameStarted == true{
}
}
func moveClockWise(){
let dx = Slider.position.x / 2
let dy = Slider.position.y / 2
let rad = atan2(dy, dx)
let Path = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2, y: self.frame.height / 2), radius: 90, startAngle: rad, endAngle: rad + CGFloat(M_PI * 4), clockwise: true)
let follow = SKAction.followPath(Path.CGPath, asOffset: false, orientToPath: true, speed: 150)
//let rotate = SKAction.rotateByAngle(75, duration: 100)
Slider.runAction(SKAction.repeatActionForever(follow).reversedAction())
//Slider.runAction(SKAction.repeatActionForever(rotate).reversedAction())
}
func Enemies(){
let Enemy = SKSpriteNode(imageNamed: "darkRedDot")
Enemy.size = CGSize(width: 20, height: 20)
//Physics
Enemy.physicsBody = SKPhysicsBody(circleOfRadius: Enemy.size.width / 2)
Enemy.physicsBody?.categoryBitMask = PhysicsCatagory.Enemy
Enemy.physicsBody?.contactTestBitMask = PhysicsCatagory.Slider //| PhysicsCatagory.Circle
Enemy.physicsBody?.collisionBitMask = PhysicsCatagory.Slider //| PhysicsCatagory.Circle
Enemy.physicsBody?.dynamic = true
Enemy.physicsBody?.affectedByGravity = false
Enemy.name = "Enemy"
The contact is not beginning because your slider has no physics body, making it impossible to recognize contact. Unless, it's not in this code, your slider has no physics body, but your enemy does. Try declaring Slider.phisicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "blocker1"), size: Slider.size)
Also, you should read about standard naming conventions in Swift. It is recommended that your variables always start with lowercase letters (eg. enemyTimer, circle, slider, fastButton, etc...).