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.
Related
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
I'm building a simple video editor for macOS: A movie file is loaded as an AVAsset, transformed by a series of CIFilters in a AVVideoComposition, and played by an AVPlayer. I present UI controls for some of the parameters of the CIFilters.
When video is playing everything is working great, I slide sliders and effects change! But when the video is paused the AVPlayerView doesn't redraw after the controls in the UI are changed.
How can I encourage the AVPlayerView to redraw the contents of the videoComposition of it's current item when it's paused?
class ViewController: NSViewController {
#objc #IBAction func openDocument(_ file: Any) { ... }
#IBOutlet weak var moviePlayerView: AVPlayerView!
var ciContext:CIContext? = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!)
var sliderValue: Double = 0.0
#IBAction func sliderMoved(_ sender: NSSlider) {
self.sliderValue = sender.doubleValue
// need to update the view here when paused
self.moviePlayerView.setNeedsDisplay(self.moviePlayerView.bounds)
// setNeedsDisplay has no effect.
}
func loadMovie(file: URL) {
let avMovie = AVMovie(url: file)
let avPlayerItem = AVPlayerItem(asset: avMovie)
avPlayerItem.videoComposition = AVVideoComposition(asset: avMovie) { request in
let output = request.sourceImage.applyingGaussianBlur(sigma: self.sliderValue)
request.finish(with: output, context: self.ciContext)
}
self.moviePlayerView.player = AVPlayer(playerItem: avPlayerItem)
}
}
It turns out re-setting the AVPlayerItem's videoComposition property will trigger the item to redraw. This works even if you set the property to it's current value: item.videoComposition = item.videoComposition. The property setter appears to have undocumented side effects.
To fix the sample code above, do this:
#IBAction func sliderMoved(_ sender: NSSlider) {
self.sliderValue = sender.doubleValue
// update the view when paused
if self.moviePlayerView.player?.rate == 0.0 {
self.moviePlayerView.player?.currentItem?.videoComposition = self.moviePlayerView.player?.currentItem?.videoComposition
}
}
Hopefully someone finds this useful!
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")
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.
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