Play a sound loop while at least one sprite of its class is on stage - swift

I'm working on a game with some monsters of different kinds (one kind = one subclass of SKSpriteNode) crossing the scene.
To each kind is given a specific sound.
One example is I can have at a specific point in time:
5 monsters A
2 monsters B
0 monster C
What I would like at any time, is to loop a sound for each class which is part of the scene (A, B) (and not for each sprite !) , and to stop playing a sound for each class absent from the scene(C).
My first idea was to store a specific sound into each class of monster and to add a SKAction to each sprite that would play loop the sound of its class.
But this would play loop as many sounds as sprites on scene, and this doesn't match with my expectations.
Do you have an idea on the best approach to adopt for this ?
Is there a possibility to implement a kind of observer that would be able to notify when an instance of class is on the scene and when it is not ?
Many thanks for your help !

As #Knight0fDragon said, "SKAction is not designed to play looped music"
Maybe you can do: (I diden't test it, now I'm on windows machine)
Class MonsterA : SKSpriteNode {
static var monsterAInScene = Set<Monster>()
static let audioAction: SKAction = SKAction.playSoundFileNamed("MonsterA-audio") //Change to AVAudio for loop
static let audioNode: SKNode()
init (scene:SKScene) {
//Your init
if let MonsterA.monsterAInScene.count == 0 {
//play audio
scene.addChild(MonsterA.audioNode)
MonsterA.playAudio()
}
MonsterA.monsterAInScene.insert(self)
}
static func playAudio() {
//Play audio code
if MonsterA.audioNode.actionFroKey("audio") != nil {return}
MonsterA.audioNode.runaction(MonsterA.audioAction, forKey "audio")
}
override removeFromParent() {
MonsterA.monsterAInScene.remove(self)
if let MonsterA.monsterAInScene.count == 0 {
//stop audio code
MonsterA.audioNode.removeActionWithKey("audio")
MonsterA.audioNode.removeFromParent()
}
super.removeFromParent()
}
}
#Knight0fDragon and #Alessandro Ornano are more expert than me, they can suggest if is the best way to do.

I have finally adopted SKAudioNode as advised bu KnightOfDragon.
At the beginning of the GameScene class, I have added an observer to check the actual number of monster of each kind (Here with kind A):
var monsterASound = SKAudioNode()
var currentNumberOfMonsterA = 0 {
didSet {
if currentNumberOfMonsterA > 0 {
monsterASound.runAction(SKAction.play())
} else{
monsterASound.runAction(SKAction.stop())
}
}
}
currentNumberOfMonsterA is updated each time I add/remove a monster
//Add a monster A
self.currentNumberOfMonsterA += 1
//Removing a monster A
self.currentNumberOfMonsterA -= 1
//Destroying a monster A
self.currentNumberOfMonsterA -= 1
Then in the didMoveToView, I have added the SKAudioNode to the scene
self.monsterASound = SKAudioNode(URL: NSBundle.mainBundle().URLForResource("monsterA", withExtension: "wav")!)
self.monsterASound.runAction(SKAction.stop())
addChild(helicoSound)

Related

Filtering Fast User Touch Input

I am coding an app in sprite-kit and swift where when you touch the screen a sprite(the player) throws a projectile at another sprite moving towards it. If the player hits the other sprite then the projectile and the sprite disappear. A problem with the game is that if the player rapidly touches the screen he can easily run up his score in the game. How can I make the code only recognize that the screen is being touched every let's say .3 seconds?
In SpriteKit/GameplayKit games, most of your code is running inside a game loop where you are constantly being passed the current time. That's what this function in an SKScene is:
override public func update(_ currentTime: TimeInterval) {
}
In here it's common to keep track of time and enable/disable things. To keep it simple:
Add the following vars
var firingEnabled = true
var enableFiringAtTime: TimeInterval = 0
var currentTime: TimeInterval = 0
When they fire, add this code
if firingEnabled {
firingEnabled = false
enableFiringAtTime = self.currentTime + 0.3
// your fire code here
}
And in the update override
self.currentTime = currentTime
if currentTime > enableFiringAtTime {
firingEnabled = true
}

How to remove a SKSpriteNode correctly

I'm doing a small game in Swift 3 and SpriteKit. I want to do a collision with my character and a special object that increases my score in 1, but for some reason, when I detect the collision, the score increases in 2 or 3.
I'm removing from parent my SpriteKitNode but it seems that it doesn't work.
Here's my code:
func checkCollisionsObject(){
enumerateChildNodes(withName: "objeto") {node, _ in
let objeto = node as! SKSpriteNode
if objeto.frame.intersects(self.personaje.frame){
objeto.removeFromParent()
self.actualizarPoints()
//self.labelNivel.text = "Level: \(self.nivel)"
}
}
}
func actualizarPoints() {
self.pointsCounter += 1
points.text = "Points: \(pointsCounter)"
}
The problem is that the collision detection is happening at 60fps (pretty fast). So in that time multiple collision detections are occurring. You are just handling the first one.
I usually like to have a property on the object that I can trigger so that I know whether or not the object has collided, and set it so that it doesn't detect anymore collisions.
In your case the object is just a SKSpriteNode so you would have to set the property in userData or make the object a custom object and have the property in the custom object class
func checkCollisionsObject(){
enumerateChildNodes(withName: "objeto") {node, _ in
let objeto = node as! CustomObject
if objeto.frame.intersects(self.personaje.frame) && objeto.hasCollided == false {
objeto.hasCollided = true
objeto.removeFromParent()
self.actualizarPoints()
}
}
}
func actualizarPoints() {
self.pointsCounter += 1
points.text = "Points: \(pointsCounter)"
}

Swift Sprite Kit Add the amount of sprites per level

I don't have any code, but I was just wondering how you would add sprites to your scene based on what level the game was at. For example: Level 1 would have 1 sprite, level 2 would have 2, level 3 would have 3....
I don't want to have to write out every single level as the game idea could go up to 100's of levels.
I have a very simple idea in mind, but I can't seem to figure it out.
I'm sorry if this is too vague, but I don't have any real code to include. Any help would be greatly appreciated.
If each level is essentially the same, except the number sprites, this is what I would recommend:
1. Create a subclass of SKScene that holds logic common to all your levels. For example:
class Level : SKScene {
var numberOfSprites: Int = 0
private func createSprites() {
for _ in 0..<numberOfSprites {
let mySprite = SKSpriteNode(imageNamed: "YourSprite")
// Setup anything else with the sprite, like the position.
self.addChild(mySprite)
}
}
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
createSprites()
}
private func levelIsComplete() {
// Cell this when the level is complete to go to the next level.
if let view = self.view {
let nextLevel = Level(size: self.size)
nextLevel.numberOfSprites = self.numberOfSprites + 1
view.presentScene(nextLevel)
}
}
}
2. When creating your first level set the number of sprites to 1 and let it handle everything else.
let myLevel = Level(size: view.bounds.size)
myLevel.numberOfSprites = 1
Here's it in action
In my version I gave the sprites random positions and called levelIsComplete in touchesBegan.

How to end a game when hitting an object from below?

Hey so I am making this project in which the player has to jump platforms all the way to the top. Some monsters spawn randomly throughout the game. So the idea is to lose the game when you hit them from below, but can go on if you jump on them. I already did the part in which the player jumps on it and you destroy the monster but I am still stuck on that part to lose the game when you hit it from below. Any ideas on how I can manage to do this? For this project I followed Ray Wenderlich's tutorial on How To Make a Game Like Mega Jump.
So on my GameScene, I have the didBeginContact method:
func didBeginContact(contact: SKPhysicsContact) {
var updateHUD = false
let whichNode = (contact.bodyA.node != player) ? contact.bodyA.node : contact.bodyB.node
let other = whichNode as GameObjectNode
updateHUD = other.collisionWithPlayer(player)
if updateHUD {
lblStars.text = String(format: "X %d", GameState.sharedInstance.stars)
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
}
Which then calls the method from the GameObjectNode Scene.
class MonsterNode: GameObjectNode {
var monsterType: MonsterType!
override func collisionWithPlayer(player: SKNode) -> Bool {
if player.physicsBody?.velocity.dy < 0 {
player.physicsBody?.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: 450.0)
if monsterType == .Normal {
self.removeFromParent()
}
}
When the player jumps on top of the monster, the monster is removed from the parent. I was trying to set that if the player's velocity is greater than 0 when colliding with the monster, then the player is removed from parent. Then when I go back to my GameScene, I could declare something in my update method so that when the player is removed from the parent call the endGame() method.
override func update(currentTime: NSTimeInterval) {
if gameOver {
return
}
if Int(player.position.y) > endLevelY {
endGame()
}
if Int(player.position.y) < maxPlayerY - 500 {
endGame()
}
}
Of course I wasn't able to make that work, and I still can't. So if anyone could help me out on how I can manage to do this, or probably some tutorial which could guide me into doing this I would really appreciate it! Thank you in advance.
First you use the didBeginContact method to establish if a contact between player and monster has been made. Next you compare the y positions of the player and monster. If the monster's y position is greater than than the player's... BANG, player dies.
The code sample assumes you have multiple monsters, each with a unique name, and have them all stored in an array.
- (void)didBeginContact:(SKPhysicsContact *)contact {
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryMonster | CategoryPlayer)) {
for(SKSpriteNode *object in monsterArray) {
if(([object.name isEqualToString:contact.bodyB.node.name]) || ([object.name isEqualToString:contact.bodyA.node.name])) {
if(object.position.y > player.position.y) {
// moster is above player
}
}
}
}
Couple of notes... If you have more than one monster active at any one time, you will need to have a unique name for each one. Reason being that you will need to know which monster contacted the player and that can only happen if you can differentiate between monsters. Hence the unique name for each one.
The if check for the y position is a simple one and only needs +1 y to be fulfilled. If it is possible for your player to make side contact with a monster and not die, you can change the if condition to be something like if(object.position.y > player.position.y+50) to make sure that the contact was actually from the bottom.
(I am not all too proficient in Swift yet so code sample is in Obj-C.)

How do I make a trail behind my character in SpriteKit?

I'm using SpriteKit (with Xcode 6 and Swift) and I have a character on the screen that I move around with on screen joysticks, and I want a little trail to follow behind him. How do I do that?
How big would my image need to be, and what would it need to look like?
Also what would I use in my code?
You should take a look at SKEmitterNode; it will "emit" particles that you can use as your trail. You can design the look and feel of your particles right in Xcode by adding a "SpriteKit Particle File" to your project:
You'd then load the particle file in to a new SKEmitterNode like so:
let emitter = SKEmitterNode(fileNamed: "CharacterParticle.sks")
Then you'll need to set the SKEmitterNode's targetNode property to your SKScene so that the particles it emits don't move with your character (i.e. they leave a trail):
emitter.targetNode = scene
Then add your emitter to your character's SKNode. Lets assume you have an SKNode for your character called character, in that case the code would simply be:
character.addChild(emitter)
Typically this sort of thing would be done in your scene's setup method (in Apple's SpriteKit template, it's usually in didMoveToView). It could also be done in your character's custom SKNode or SKSpriteNode class, if you have one. If you put it in didMoveToView, it would look something like:
override func didMoveToView(view: SKView) {
// ... any character or other node setup ...
let emitter = SKEmitterNode(fileNamed: "CharacterParticle.sks")
emitter.targetNode = self
character.addChild(emitter)
// ... any other setup ...
}
Although SKEmitterNode is a fine option. I would suggest you use a SKSpriteNode instead. The Emitters in Xcode cause a lot of lag when used frequent and in sequence.
The best way to create a trail in my opinion is by preloading a SKTexture when loading up the application. For this I would suggest creating a class like this.
class AssetsManager {
private init() {};
static let shared = AssetsManager();
func preloadAssets(with texture: SKTexture) {
texture.preload {
print("Sprites preloaded")
}
}
And than calling it as so in either your AppDelegate or MenuScene:
AssetsManager.shared.preloadAssets(with: SKTexture(imageNamed: "yourImage"))
Than for the "Creating a trail part":
Create a timer
var timer: Timer!
Start your timer
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(ballTrail), userInfo: nil, repeats: true)
Create the ballTrail function
#objc func ballTrail() {
let trail = SKSpriteNode(texture: SKTexture(imageNamed: "your Image"))
trail.size = size
trail.position = player.position
trail.zPosition = player.positon - 0.1
addChild(trail)
trail.run(SKAction.scale(to: .zero, duration: seconds))
trail.run(SKAction.fadeOut(withDuration: seconds)
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
trail.removeFromParent()
}
}
You can fiddle around with the actions and timings you would like to use. Hopefully this will help someone!