sometimes sprites will not bounce off the physics boundary - rotation

I'm beginning a new game and have a weird problem right off the bat. In my didMoveToView function I have the following to put a bounds on my sprites within the frame(whole screen)
self.physicsBody=SKPhysicsBody(edgeLoopFromRect: self.frame)
The following code adds an SKSpriteNode at the touch point and adds a rotatebyangle action with a repeat forever
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(balloonWorld)
let nodeAtPoint = self.nodeAtPoint(location)
if(nodeAtPoint.name == nil) {
let location = touch.locationInNode(self)
let randNum:Int = Int.random(min: 0, max: 7)
var stringColor:String = (balloonColors.objectAtIndex(randNum) as String)
stringColor = stringColor.stringByReplacingOccurrencesOfString("face",
withString :"")
let sprite = Balloon(theColor:stringColor)
//let spriteFileName:String = balloonColors.objectAtIndex(randNum) as String
//let sprite = SKSpriteNode(imageNamed:spriteFileName)
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = location
sprite.zPosition = SceneLevel.hero.rawValue
balloonWorld!.addChild(sprite)
let action = SKAction.rotateByAngle(CGFloat(-M_PI), duration:1)
sprite.runAction(SKAction.repeatActionForever(action))
} else {
nodeAtPoint.removeFromParent()
println(nodeAtPoint.name)
}
}
}
I've setup balloonWorld as follows:
balloonWorld = SKNode()
self.addChild(balloonWorld!)
My problem is that sometimes the balloon sprites will not bounce off the edge, but just keep going thru the edge never to be seen again.
Any suggestions?
Thanks,
Ken
per request here's the code for setting up the physics bodies
balloon:
class Balloon: SKNode {
// properties
var myColor:String?
var objectSprite:SKSpriteNode?
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(theColor:String){
super.init()
self.myColor=theColor
let imageFileName = "\(theColor)face"
let objectSprite:SKSpriteNode = SKSpriteNode(imageNamed:imageFileName)
objectSprite.physicsBody = SKPhysicsBody(circleOfRadius: objectSprite.size.height / 2.0)
objectSprite.physicsBody?.affectedByGravity = true
objectSprite.name = theColor + " balloon"
addChild(objectSprite)
}
}
I didn't set up a physics body on the boundary as I though all I needed was the line self.physicsBody = SKPhysicsBody(edgeLoopFrom Rect : self:frame) since the boundary has the same frame.
I tried adding the following, but that did not change the behavior:
let borderShape=SKShapeNode(rect: CGRectMake(self.frame.origin.x+2, self.frame.origin.y+2, self.frame.size.width-4, self.frame.size.height-4))
borderShape.fillColor=SKColor.clearColor()
borderShape.strokeColor=SKColor.blackColor()
borderShape.lineWidth=1
borderShape.physicsBody?.categoryBitMask=BodyType.boundary.rawValue
borderShape.zPosition=SceneLevel.border.rawValue
borderShape.physicsBody=SKPhysicsBody(edgeLoopFromRect: borderShape.frame)
balloonWorld!.addChild(borderShape)

Create an enum to differentiate the two types of sprites:
enum ColliderType {
case sprite1 = 1
case sprite2 = 2
}
You need to add a contact and collision bit mask like so:
borderShape.physicsBody?.contactTestBitMask = ColliderType.sprite2.rawValue
Also add a collisionBitMask:
borderShape.physicsBody?.collisionBitMask = ColliderType.sprite2.rawValue
Do the same for sprite2 and set it's collisionBitMask and it's contactTestBitMask to be the rawValue of sprite1. Make sure you set this class to be the contactDelegate so you receive a warning though a function that will notify you when two objects have collided:
self.physicsWorld?.contactDelegate = self
func didBeginContact(contact: SKPhysicsContact) {
[handle the contact however you would like to here]
}
I hope this helps!

LearnCocos2D(Steffen Itterheim) pointed me in the right direction in his comment. I was using moveTo/moveBy and the physics engine would not properly handle that. Once I simply made the moves based on impulses everything worked fine.
Thanks Steffen

Related

Would someone please share a current SpriteKit texture atlas sample to pull apart

My background is Arduino and I have found Swift a bit opaque. There are a lot of outdated tutorials out there too. I am working on just getting an animated sprite on screen. I have a small animated .png sequence that I will use, saving it into a ball.atlas folder and copying it to Assets.xcassets.
I have found what I believe was posted by Knight0fDragon. Running it shows "worked" in the console, but I don't understand how to assign it to a player so to speak and thus show up on the simulator. If someone would be willing to share the additional code needed to do so, I could pull it apart.
Here is the code:
let textureAtlas = SKTextureAtlas(named: "example")
var textureArray = [SKTexture]()
var frames:[SKTexture] = []
for index in 1 ... 59 {
let textureName = "example_\(index)"
let texture = textureAtlas.textureNamed(textureName)
frames.append(texture)
textureArray = frames
print("worked")
I can't share a texture atlas with you but I help explain a little see below
let textureAtlas = SKTextureAtlas(named: "example")
var textureArray = [SKTexture]()
var frames:[SKTexture] = []
for index in 1 ... 59 {
let textureName = "example_\(index)"
let texture = textureAtlas.textureNamed(textureName)
frames.append(texture)
textureArray = frames
print("worked")
The line that gives "let textureName = "example_(index)" is where KoD gives you the naming convention for all the sprites in the Atlas. The atlas itself for this bit of code is called "example". All the sprites saved in the Atlas are assigned the following name "example_X" (X is an integer). example_1, example_2, example_3 etc... etc until you get to example_59. If you don't want to make 59 textures then you need to edit the for loop giving the new value.
hmmmmm OK have a look here
import SpriteKit
class GameScene: SKScene{
//MARK: PROPERTIES
var lastUpdateTime: TimeInterval = 0
var dt: TimeInterval = 0
var myAnimation = SKAction()
let myAnimaTimeInterval: TimeInterval = 0.1
let mySprite = SKSpriteNode(texture: SKTextureAtlas(named: "example").textureNamed("example_1"))
//MARK: INITIALISATION
override func sceneDidLoad() {
}
override func didMove(to view: SKView) {
addChild(mySprite)
mySprite.position = CGPoint.zero
setAnimation()
runAnimation(node: mySprite)
}
//MARK: GAME LOOP
override func update(_ currentTime: TimeInterval) {
updateGameLoopTimer(currentTime: currentTime)
}
override func didEvaluateActions() {
}
override func didFinishUpdate() {
}
//MARK: TOUCHES
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
//MARK: HELPER FUNCTIONS
private func setAnimation() {
let textureAtlas = SKTextureAtlas(named: "example")
var textureArray: [SKTexture] = []
for index in 2...59 {
let textureName = "example_\(index)"
let texture = textureAtlas.textureNamed(textureName)
textureArray.append(texture)
}
for index in (1...58).reversed() {
let textureName = "example_\(index)"
let texture = textureAtlas.textureNamed(textureName)
textureArray.append(texture)
}
let animation = SKAction.animate(with: textureArray, timePerFrame: myAnimaTimeInterval)
myAnimation = SKAction.repeatForever(animation)
}
private func runAnimation(node: SKSpriteNode) {
node.run(myAnimation, withKey: "myAnimation")
}
private func stopAnimation(node: SKSpriteNode) {
node.removeAction(forKey: "myAnimation")
}
public func updateGameLoopTimer(currentTime: TimeInterval) {
if lastUpdateTime > 0 {
dt = currentTime - lastUpdateTime
} else {
dt = 0
}
lastUpdateTime = currentTime
}
//MARK: CLEAN-UP
deinit {
}
}
I've taken a few liberties with the first sprite for the character which made me change the loop a little.
You can attach the animation to any sprite using the runAnimation method and stop the animation with the stopAnimation method. I've just added the animation to the didMoveToView call.
Try replacing your gameScene file with the above. OK now with the game loop timer ... maybe there are better ways to time the game.... but for you and me it is OK.
Try it again

SKSpriteNode as Return error - Swift 2

I am trying to swap out a basic ball for an image of a ball. I am going through a tutorial and don't know how to fix this. This code is in a file Ball.swift as a function and would like to try avoid redwiring all of the code. Thanks in advance!
Error: Cannot convert return of the type Ball.Type to return type "Ball"
static func make()-> Ball {
var Ball = SKSpriteNode()
Ball = SKSpriteNode(imageNamed: "blueball")
//let ball = Ball(circleOfRadius: 30)
Ball.physicsBody = SKPhysicsBody(circleOfRadius: Ball.frame.size.width / 2)
Ball.physicsBody!.dynamic = true
Ball.physicsBody!.allowsRotation = true //thi was false
Ball.userData = NSMutableDictionary()
// self.addChild(Ball)
return self // error
}
Ball is the name of a variable, and not a type.
The function should return an SKSpriteNode which is the class of the ball object. You can also use type inference to avoid having to declare the type. Altogether, it would look like this:
static func make() -> SKSpriteNode {
let ball = SKSpriteNode(imageNamed: "blueball")
// Set physics body ...
return ball
}
Tip: Variables are usually named with a lowercase letter, e.g. var ball = ..., and not var Ball = .... This makes it easier to differentiate between variables and types.

Swift 2 Spritekit: Issue with background music not resuming when the game is unpaused

First of all I would like to thank anyone in advance for any help I get.
I have searched far and wide across the net and cannot find a solution to my issue. My issue is with an iOS game I am building using the SpriteKit framework. I have added a background song using an SKAudioNode and it works fine initially, but when I pause and play the game within a few seconds, the music does not begin playing again. I have tried lots of things like removing the SKAudioNode when the game is paused and adding it again when the game is resumed, but nothing has worked. I have posted a snippet of my code below keeping it as relevant as possible:
class GameScene: SKScene, SKPhysicsContactDelegate {
var backgroundMusic = SKAudioNode(fileNamed: "bg.mp3")
let pauseImage = SKTexture(imageNamed: "pause.png")
var pauseButton:SKSpriteNode!
let playImage = SKTexture(imageNamed: "play2.png")
var playButton:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.addChild(backgroundMusic)
// create pause button
pauseButton = SKSpriteNode(texture: pauseImage)
pauseButton.position = CGPoint(x: self.size.width - pauseButton.size.width, y: pauseButton.size.height)
pauseButton.zPosition = 1
pauseButton.name = "pauseButton"
self.addChild(pauseButton)
// create play button
playButton = SKSpriteNode(texture: playImage)
playButton.position = CGPoint(x: self.size.width - playButton.size.width, y: -playButton.size.height)
playButton.zPosition = 1
playButton.name = "playButton"
self.addChild(playButton)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
if pauseButton.containsPoint(touch.locationInNode(self)) {
let blockOne = SKAction.runBlock({
self.pauseButton.position.y = - self.pauseButton.size.height
self.playButton.position.y = self.playButton.size.height
})
let blockTwo = SKAction.runBlock({
self.view?.paused = true
})
self.runAction(SKAction.sequence([blockOne, blockTwo]))
}
else if(playButton.containsPoint(touch.locationInNode(self))) {
self.playButton.position.y = -self.playButton.size.height
self.pauseButton.position.y = self.pauseButton.size.height
self.view?.paused = false
}
}
}
}
You'll want to look into using AVAudioPlayer and NSNotificationCenter to pass data around the game.
Start the background audio player in your actual GameViewController class.
It's better to do it this way then use SKAudioNode... That's more for sounds that are like sound effects relating to something that happened in gameplay.
By using AVAudioPlayer, the advantage is when the music is paused it's still cued up to play in it's previous spot.
This is one of the few things that will be running regardless of what's going on. So we put it in the GameViewController.
So here's an example of GameViewController code we'd need to start
import AVFoundation
var bgMusicPlayer:AVAudioPlayer?
Then in the GameViewController we make these functions as such
override func viewDidLoad() {
super.viewDidLoad()
// PlayBackgroundSound , PauseBackgroundSound will be your code to send from other "scenes" to use the audio player //
// NSNotificationCenter to pass data throughout the game //
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.playBackgroundSound(_:)), name: "PlayBackgroundSound", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.pauseBackgroundSound), name: "PauseBackgroundSound", object: nil)
}
func playBackgroundSound(notification: NSNotification) {
let name = notification.userInfo!["fileToPlay"] as! String
if (bgSoundPlayer != nil){
bgSoundPlayer!.stop()
bgSoundPlayer = nil
}
if (name != ""){
let fileURL:NSURL = NSBundle.mainBundle().URLForResource(name, withExtension: "mp3")!
do {
bgSoundPlayer = try AVAudioPlayer(contentsOfURL: fileURL)
} catch _{
bgSoundPlayer = nil
}
bgSoundPlayer!.volume = 1
bgSoundPlayer!.numberOfLoops = -1
// -1 will loop it forever //
bgSoundPlayer!.prepareToPlay()
bgSoundPlayer!.play()
}
}
func pauseBackgroundSound() {
if (bgSoundPlayer != nil){
bgSoundPlayer!.pause()
}
}
Then when you want to use the audio player in your pause or resume button functions.
Remember you need to have the player used in each scene.
import AVFoundation.AVAudioSession
override func didMoveToView(view: SKView) {
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
}
Then if you want to pause or play something just use NSNotificationCenter.
// To Pause in a scene use this line of code in the function you need to pause the music
NSNotificationCenter.defaultCenter().postNotificationName("PauseBackgroundSound", object: self)
/// To Play initially or a new song .. use this line of code in the function you need to play the music
NSNotificationCenter.defaultCenter().postNotificationName("PlayBackgroundSound", object: self, userInfo: "FILE NAME OF BACKGROUND MUSIC")

SKCameraNode Lags Behind When Player Moves

I am currently trying to have my Camera follow my player "orb" whenever the player is moved. I've set the Camera's position to move to the orb's x position whenever the orb is moved, however for some reason, as the orb is moved quickly, the SKCameraNode glitches and stutters as if it is trying to catch up to the node's position. I've read that it's because the physics runs after the actual update(), but I have no idea on how to resolve the issue. Is there any way to fix this problem?
NOTE: I don't want the camera to always center on the "orb", but rather delay a little as it follows the orb, to give it a free-moving feel to it. (if that makes sense)
import SpriteKit
class GameScene: SKScene {
var orb = SKSpriteNode()
var Touch: UITouch?
let theCamera: SKCameraNode = SKCameraNode()
override func didMoveToView(view: SKView) {
let orbTexture1 = SKTexture(imageNamed:"orb.png")
orb = SKSpriteNode(texture: orbTexture1)
orb.name = "Orb"
orb.position = CGPointMake(300, 95)
self.addChild(orb)
theCamera.position = CGPointMake(300, 0)
theCamera.name = "Camera"
self.addChild(theCamera)
self.camera = theCamera
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
if nodeAtPoint(touch.locationInNode(self)).name == "Orb" {
Touch = touch as UITouch
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
if Touch != nil {
if touch as UITouch == Touch! {
let location = touch.locationInNode(self)
orb.position = location
}
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let action = SKAction.moveToX(orb.position.x, duration: 0.5)
theCamera.runAction(action)
}
}
You can solve this by using the didSimulatePhysics method not the update method .
Replace the update method with this :
> override func didSimulatePhysics() {
> let action = SKAction.moveToX(orb.position.x, duration: 0.5)
> theCamera.runAction(action)
> }
You are getting this affect because on every update, you are attaching a new SKAction to your camera. This means in 1 second, your camera is being told 60 times to move to the orb position. This means you have 60 actions controlling how your sprite should be moving, and they are all battling to get priority.
What you need to do is remove the previous move action, or let the previous action finish.
To remove the action, you can call removeAllActions() or removeAction(forKey:)
This of course means you need to first assign a key to your action, theCamera.runAction(action, withKey:"cameraMove")
Now a shorthand way to replace an action is to just call theCamera.runAction(action, withKey:"cameraMove"), because the new action will replace the existing action, there by removing it.
Your other option is to check if the action exists with action(forKey:) Then determine if you need to process further action.

how to add a SKnode on a loop that can be toggled on and off

I am creating a game and i keep getting this error for my bullet spawn method linked with a joystick. I want to repetitively spawn the bullet node while the joystick is active. Here is how i am creating a firing method
class GameScene: SKScene, SKPhysicsContactDelegate {
let bullet1 = SKSpriteNode(imageNamed: "bullet.png")
override func didMoveToView(view: SKView) {
if fireWeapon == true {
NSTimer.scheduledTimerWithTimeInterval(0.25, target: self,
selector: Selector ("spawnBullet1"), userInfo: nil, repeats: true)
}
}
func spawnBullet1(){
self.addChild(bullet1)
bullet1.position = CGPoint (x: hero.position.x , y:hero.position.y)
bullet1.xScale = 0.5
bullet1.yScale = 0.5
bullet1.physicsBody = SKPhysicsBody(rectangleOfSize: bullet1.size)
bullet1.physicsBody?.categoryBitMask = PhysicsCategory.bullet1
bullet1.physicsBody?.contactTestBitMask = PhysicsCategory.enemy1
bullet1.physicsBody?.affectedByGravity = false
bullet1.physicsBody?.dynamic = false
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (CGRectContainsPoint(joystick.frame, location)) {
stickActive = true
if stickActive == true {
fireWeapon = true
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event:
UIEvent?) {
fireWeapon = false
}
the first bullet launches as planned and works great however, every time the second bullet launches the app crashes and i get this error "Attemped to add a SKNode which already has a parent". can someone tell me an alternative method
Your problem is exactly as the error says, you need to first remove the bullet from its parent before adding it again, or Make bullet1 a local property inside the spawnBullet function, so that each time you call the function a new bullet gets created and added as child to the scene instead of trying to re-add the same one.

Resources