I'm developing a Sprite Kit Game, which I have a node named hero who dodges approaching villains. I have an issue with applyImpulse for a jump action and, in this case, the hero can jump repeatedly and fly instead of dodging the villains. I'm used a boolean variable to change the status with a timer value to jump only once in 3 seconds but that is not working. Here is my code
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if isGameOver == true {
self.restart()
}
if isstarted {
self.runAction(SKAction.playSoundFileNamed("jump.wav", waitForCompletion: false))
for touch: AnyObject in touches{
if jumpon == false { //hero has not jumped
let location = touch.locationInNode(self)
heroAction()
jumpon = true //hero jumped
}
}
} else {
isstarted = true
hero.stop()
hero.armMove()
hero.rightLegMove()
hero.leftLegMove()
treeMove()
villanMove()
let clickToStartLable = childNodeWithName("clickTostartLable")
clickToStartLable?.removeFromParent()
addGrass()
// star.generateStarWithSpawnTime()
}
}
func changeJump(){
jumpon = false // hero has landed
}
and function update is called in every second and then changeJump must be called
override func update(currentTime: CFTimeInterval) {
if isstarted == true {
let pointsLabel = childNodeWithName("pointsLabel") as MLpoints
pointsLabel.increment()
}
if jumpon == true { // checked everyframe that hero jumped
jumpingTimer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: "changeJump", userInfo: nil, repeats: true) // changing jump status
}
}
How should I update my code to make the hero jump only once in three seconds. Thanks in advance.
Some thoughts...
I suggest you use an SKAction or two instead of an NSTimer in Sprite Kit games. SKActions pause/resume appropriately when you pause/resume the scene and/or view
The update method is called ~60 times a second not every second
Checking the status of a jump in update is not need
Alternatively, you can check if the hero is on the ground before starting another jump sequence instead of using a timer. The game may frustrate the user if the hero is on the ground (and ready to jump) but the timer is still counting down
and some code...
if (!jumping) {
jumping = true
let wait = SKAction.waitForDuration(3)
let leap = SKAction.runBlock({
// Play sound and apply impulse to hero
self.jump()
})
let action = SKAction.group([leap, wait])
runAction(action) {
// This runs only after the action has completed
self.jumping = false
}
}
Related
I basically running a timer in the didMove function of the GameScene class in SpriteKit. The function basically just makes the player shoot bullets at 0.5-second intervals. However, the problem I am getting is that once the player dies, and I use removeFromParent() on the player, the bullets are still firing, as if they are just appearing in empty space.
Here is my code:
var timer1 = Timer()
self.timer1 = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in self.bullet1.fireBullet1(player: self.player1, scene: self)})
And here is the fireBullet1 function:
func fireBullet2(player: Player2, scene: SKScene) {
let bulletNode = Bullet()
bulletNode.texture = self.textureAtlas.textureNamed("bullet2")
bulletNode.position = player.position
bulletNode.position.y += -sin(player.zRotation)*10
bulletNode.position.x += -cos(player.zRotation)*10
bulletNode.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width/500)
bulletNode.physicsBody?.mass = 0.005
bulletNode.physicsBody?.affectedByGravity = false
bulletNode.zRotation = player.zRotation
bulletNode.physicsBody?.categoryBitMask = PhysicsCategory.bullet2.rawValue
bulletNode.physicsBody?.contactTestBitMask =
PhysicsCategory.boundary.rawValue |
PhysicsCategory.bullet1.rawValue |
PhysicsCategory.player1.rawValue
scene.addChild(bulletNode)
bulletNode.physicsBody?.applyImpulse(CGVector(dx: -cos(player.zRotation)*1, dy: -sin(player.zRotation)*1))
}
Can someone please tell me what I need to do in order to make the timer stop calling the function after the player dies.
I want to dim the phone screen when my app is running and if there are no touch events during a certain period of time (say 10 sec) and then make the screen brighter as soon anywhere on the screen is touched again.
After searching SO, It seems like I need to create a custom UIApplication in order to process all the touches. Below is my code so far:
import UIKit
#objc(MyApplication)
class MyApplication: UIApplication {
override func sendEvent(_ event: UIEvent) {
var screenUnTouchedTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.makeScreenDim), userInfo: nil, repeats: true);
// Ignore .Motion and .RemoteControl event simply everything else then .Touches
if event.type != .touches {
super.sendEvent(event)
return
}
// .Touches only
var restartTimer = true
if let touches = event.allTouches {
// At least one touch in progress? Do not restart timer, just invalidate it
self.makeScreenBright()
for touch in touches.enumerated() {
if touch.element.phase != .cancelled && touch.element.phase != .ended {
restartTimer = false
break
}
}
}
if restartTimer {
// Touches ended || cancelled, restart timer
print("Touches ended. Restart timer")
} else {
// Touches in progress - !ended, !cancelled, just invalidate it
print("Touches in progress. Invalidate timer")
}
super.sendEvent(event)
}
func makeScreenDim() {
UIScreen.main.brightness = CGFloat(0.1)
print("makeScreenDim")
}
func makeScreenBright() {
UIScreen.main.brightness = CGFloat(0.5)
print("makeScreenBright")
}
}
The print out looks something like this:
makeScreenBright
Touches in progress. Invalidate timer
makeScreenBright
Touches ended. Restart timer
makeScreenDim
makeScreenDim
makeScreenDim
makeScreenDim
makeScreenDim
...
As you can see above there is big issue with the code, it seems like I am creating a new Timer for every touch event. I don't know how to create a static (only one) Timer in the UIApplication.
How should I be implementing only one timer in the correct way?
(I am using an Iphone7, latest version of swift and xcode)
You have to invalidate the previously created timer somewhere, otherwise you will get your described behaviour.
Store it in a property on each call to sendEvent, so you can access it the next time the method is called.
class MyApplication: UIApplication {
var screenUnTouchedTimer : Timer?
override func sendEvent(_ event: UIEvent) {
screenUnTouchedTimer?.invalidate()
screenUnTouchedTimer = Timer ......
I've never done any coding before until a few weeks ago, and whilst I've made reasonable progress on my own, I'm struggling with something that I hope someone wouldn't mind helping with.
I'm trying to make a simple app for my daughter in Swift (xCode Version 7.3 - 7D175). It's a series of animations where characters pop up and down inside a moon crater.
What I want to be able to do, is call a function when one of the animated UIImages is pressed. Ideally I'd like it to play a sound when the animation is pressed (I know how to call sounds, just not in this way)
I've created a Class where I wrote the code for the animations
func alienAnimate() {
UIView.animateWithDuration(0.3, delay: 0, options: [.CurveLinear, .AllowUserInteraction], animations: {
self.center.y -= 135
}, completion: nil)
UIView.animateWithDuration(0.3, delay: 3, options: [.CurveLinear, .AllowUserInteraction], animations: {
self.center.y += 135
}, completion: nil)
}
If someone could point me in the right direction, I'd greatly appreciated. I guess there's probably better ways of animating, but this is all I know at the moment.
EDIT - 2nd May
OK so I eventually got it working...well, nearly :) It still doesn't work exactly as I'd like, but I'm working on it.
The animations essentially move vertically along a path (so the aliens pop up like they're coming out of a hole). The below code works, but still allows you to click on the image at the start of it's path when it's effectively down a hole.
I still need to work out how to click where it actually is now, and not be able to press its starting point.
View of App
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.locationInView(self.view)
if self.imgAlien.layer.presentationLayer()!.hitTest(touchLocation) != nil {
sfxPop.play()
} else if self.imgAlien.layer.presentationLayer()!.hitTest(touchLocation) != nil {
sfxPop.play()
} else if self.imgAlien2.layer.presentationLayer()!.hitTest(touchLocation) != nil {
sfxPop.play()
} else if self.imgAlien3.layer.presentationLayer()!.hitTest(touchLocation) != nil {
sfxPop.play()
} else if self.imgAlien4.layer.presentationLayer()!.hitTest(touchLocation) != nil {
sfxPop.play()
} else if self.imgAlien5.layer.presentationLayer()!.hitTest(touchLocation) != nil {
sfxPop.play()
} else if self.imgUFO.layer.presentationLayer()!.hitTest(touchLocation) != nil {
sfxPop.play()
}
}
Supposing your have your planet and animation working up and down as you wish, what do you do is to detecting touch:
This is just an example:
var image : UIImage = UIImage(named:"alien.png")!
var image2 : UIImage = UIImage(named:"alienBoss.png")!
var alien1 = UIImageView(image: image)
var alien2 = UIImageView(image: image)
var alienBoss = UIImageView(image: image2)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
if(touch.view == alien1){
// User touch alien1, do whatever you want
} else
if(touch.view == alien2){
// User touch alien2, do whatever you want
} else
if(touch.view == alienBoss){
// User touch alienBoss, do whatever you want
}
}
Then, you want to enable sound so you can use AVAudioPlayer library:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let audioPath = NSBundle.mainBundle().pathForResource("alienSound", ofType: "mp3")
var error:NSError? = nil
}
You can use the code below to play a sound, stop, set the volume:
do {
player = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath!))
// to play the mp3
player.play()
// to stop it
player.stop()
// to pause it
player.pause()
// to set the volume
player.volume = 0.5 // from 0.0 to 1.0
...
}
catch {
print("Something bad happened.")
}
So I've got an NSTimer running a function every 0.1 seconds.
No matter how many conditions & caveats I put in place, this sound (and others on the timer) play twice. The same sound triggered by a touch plays only once.
myTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("flip:"), userInfo: nil, repeats: true)
Here's the function it triggers.
func flip(timer: NSTimer) {
if current_timer == -3.0 && rang_3 == false {
if rang_3 == false {
rang_3 = true
dialed_3()
NSLog("DIALED 3")
}
}
}
And here's the sound function I need triggered only ONCE.
func dialed_3() {
if sound_mute == false {
do {
dial_3 = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("3", ofType: "mp3")!))
dial_3.volume = 2
dial_3.numberOfLoops = 0
dial_3.prepareToPlay()
dial_3.play()
} catch {
print("Error")
}
}
}
I don't know about your other code, but with what you show, it runs 1 time.
A small suggestion for you: move initialization to global without run it everytime timer run.
Alright! So I moved the !rang_3 check to the fixed !sound_mute check & it worked. As a failsafe I replicated the sounds that are used during countdown & muted them after 1 play. So if they play twice, the user won't notice.
if !sound_mute && !rang_3 {
do {
//soundstuff
}
}
I have been pulling my hair out over this seemingly simple feature I'm trying to build in to a game I am working on in SpriteKit and Swift. I have searched and can't figure why this isn't working.
What I want to do:
When the player comes in contact with an enemy or an obstacle, I want to load and play through an explosion animation createExplosion() (The animation completes in 0.5 seconds) and then progress to the gameOverAction(). The problem is that only the first frame of the animation is shown and then it progresses to the gameOverAction().
I tried to make an SKAction.sequence but Xcode did not like me calling functions inside the sequence, so I found SKAction.waitForDuration. Problem is, it is having no effect on the execution of the code.
How should this be done properly?
func collisionWithPlayer(playerObject: SKSpriteNode) {
runAction(SKAction.playSoundFileNamed("spaceExplosion.mp3", waitForCompletion: false))
createExplosion(playerObject)
playerObject.removeFromParent()
runAction(SKAction.waitForDuration(NSTimeInterval(0.5)))
gameOverAction()
}
And here is the code for the gameOverAction():
func gameOverAction() {
backgroundMusicPlayer.stop()
globalHighScore = NSUserDefaults.standardUserDefaults().objectForKey("GameHighScore") as? Int
localHighScore = self.monstersDestroyed
if globalHighScore == nil {
globalHighScore = localHighScore
}
globalHighScore = self.recordHighScore(localHighScore, highScore: globalHighScore!)
println(globalHighScore!)
let reveal = SKTransition.fadeWithDuration(1.0)
let gameOverScene = GameOverScene(size: self.size, won: false)
self.view?.presentScene(gameOverScene, transition: reveal)
}
func createExplosion(object: SKSpriteNode) -> SKSpriteNode {
var explosionArray = Array<SKTexture>()
var explosionSprite = SKSpriteNode()
explosionArray.append(SKTexture(imageNamed: "explosionSprite01.png"))
explosionArray.append(SKTexture(imageNamed: "explosionSprite02.png"))
explosionArray.append(SKTexture(imageNamed: "explosionSprite03.png"))
explosionArray.append(SKTexture(imageNamed: "explosionSprite04.png"))
explosionArray.append(SKTexture(imageNamed: "explosionSprite05.png"))
explosionArray.append(SKTexture(imageNamed: "explosionSprite06.png"))
explosionArray.append(SKTexture(imageNamed: "explosionSprite07.png"))
explosionArray.append(SKTexture(imageNamed: "explosionSprite08.png"))
explosionArray.append(SKTexture(imageNamed: "explosionSprite09.png"))
let animateAction = SKAction.animateWithTextures(explosionArray, timePerFrame: 0.05)
explosionSprite = SKSpriteNode(texture:explosionArray[0])
explosionSprite.position = CGPoint(x: object.position.x + 10.0, y: object.position.y)
addChild(explosionSprite)
explosionSprite.runAction(SKAction.sequence([animateAction, SKAction.removeFromParent()]))
return(explosionSprite)
}
The waitForDuration SKAction can be used to introduce a delay into an action sequence, but it doesn't block the thread calling runAction - so your waitForDuration action will be set to run on your node, but then gameOverAction() will be called immediately.
You can use the completion block of runAction:completion to perform gameOverAction() after the actions are complete -
func collisionWithPlayer(playerObject: SKSpriteNode) {
var actions = Array<SKAction>();
actions.append(SKAction.playSoundFileNamed("spaceExplosion.mp3", waitForCompletion: false))
actions.append(SKAction.waitForDuration(NSTimeInterval(0.5)))
actions.append(SKAction.removeFromParent())
createExplosion(playerObject)
let sequence = SKAction.sequence(actions);
playerObject.runAction(sequence,completion: { () -> Void in
self.gameOverAction()
})
}
Depending on what createExplosion does it may be possible to incorporate it into the sequence too and remove the need for the waitForDuration