Saturday, January 31, 2015

Code Jam : Particle Effects




For today's post, we will see about graceful entry and exit for the spaceship, enemies trembling when a hit happens, audio player introduction, and display of explosion effect using Emitter Node. 

Fade In :
In Update, where each new enemy spawns: 
            FrameCount = 0
            /* Called before each frame is rendered */
            let sprite = SKSpriteNode(imageNamed: "Spaceship")
            sprite.xScale = 0.15
            sprite.yScale = 0.15
            sprite.position = CGPoint(x: Int(arc4random_uniform(1000)), y: Int(arc4random_uniform(700)))
            sprite.alpha = 0.1;
            var spriteActionArray = Array<SKAction>()
            spriteActionArray.append(SKAction.fadeInWithDuration(0.3))
            
            spriteActionArray.append(SKAction.moveTo(
                CGPoint(
                    x: Int(arc4random_uniform(1000)), y: Int(arc4random_uniform(1000))
                ), duration: 60))
            let seq = SKAction.sequence(spriteActionArray)
            sprite.runAction(seq)
            ...

The lines highlighted with blue color indicate fade in transition for the enemies. The alpha has to be set to 0.1 because fadeInWithDuration increases current alpha value to 1.0 in the number of seconds mentioned in the argument (0.3 seconds) in our case. 

Note: The fadeInWithDuration has to be added as first in the sequence of actions, otherwise, the spaceships move to the destination and then fade in, because the actions in the sequence are executed one by one. Which means only when the first action ends, the next action will begin. If you had imagined the spaceship gradually gaining visibility as it moves to the destination, you would be wrong as well.

Fade Out:
When enemies are hit, they have to fade out gradually so that the transition is smooth. For this purpose we use the function fadeOutWithDuration() as indicated by the blue line. Again as name indicates, this reduces the alpha value from the current value to 0.

In Touches Began:

for (i,e) in enumerate(Enemies){
                var x = CGFloat(location.x - e.position.x)
                var y  = CGFloat(location.y - e.position.y)
                var d2 = CGFloat(x*x + y*y)
                if(d2 < 500){
                    e.runAction(SKAction.fadeOutWithDuration(0.5));
                    Hits++
                }     
 }

Since we are not removing the destroyed enemy in the same frame, it has to be carried out separately in update() . Here we check the alpha value of each enemy, and if it is 0, then remove it from the scene and from the Enemies array. 

For - in enumerate syntax is used to iterate through the Array.

for(i,e) in enumerate(Enemies){
        if e.alpha == 0 {
              self.removeChildrenInArray([e])
              Enemies.removeAtIndex(i)
        }
}

Audio:

Now comes the interesting new part.  Adding audio to be played during each hit. 
I downloaded scream.aiff file from http://www.bigsoundbank.com . This is not what I had in mind, but for testing purposes, this should suit just fine. 
First we need to add the file to Resources section of the game. For this, go to Target->BuildPhases->Copy Resources subsection. 


Ignore the red colored files for now. I myself am trying to figure out a way to fix them. 
Once the file has been browsed and added, you can verify if the file was indeed added by going to the place where the .app bundle was created, and opening package contents (control + click on .app). For me the .app file was stored in /Users/<username>/Library/Developer/Xcode/DerivedData/SwiftGame-axdwdnvxnbxhlebvxgkasiyslagc/Build/Products/Debug-iphonesimulator/ . Don't worry about the bunch of letters after SwiftGame. 

Next let NSBundle find the path to the scream aiff file. NSBundle represents location in filesystem that groups code and resources together. This is what official doc says.  They help location resource files like scream.aiff, in the code, and allows loading and unloading executable dynamically and help in localization. 

Here we would be using NSBundle to get the absolute path for the resource :scream.aiff
NSBundle.mainBundle() is used to get the main application bundle object. mainBundle returns back NSBundle object that corresponds to the application from where the current executable is running. 
If unsure, you have to verify the returned Bundle object. Currently we are not verifying it for simplicity purpose. In short it returns the bundle object for the .app bundle from where the current exe file is running. It is available in OS 10.0 or later. 

Now that we have the main bundle, we have to get the absolute path to scream.aiff. This is done by using pathForResource(). All it needs is file name, and type of file (ofType parameter).

In didMoveToView:
let AudioDestruction = (NSBundle.mainBundle().pathForResource("scream", ofType: "aiff"))

Note: If name of the file is given as "scream.aiff", it returns nil object. 


 if let ad = AudioDestruction {
            GameAudioPlayer = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: ad), fileTypeHint: "aiff", error: nil)
        }

In this line: if let ad = AudioDestruction { ,  Since AudioDestruction is an optional parameter, it could be nil as well. In that case, shortest code to verify if not nil would be to set its value to a constant by using "let" keyword, and if it is not nil, then the execution enters the if block.Inside the if block, GameAudioPlayer is a AVAudioPlayer object declared inside the class like so:   ...
AVAudioPlayer is a class that provides methods to control audio playback from a file or memory. Programmer can tune volume, panning, channels etc using AVAudioPlayer's instance.
GameAudioPlayer is set to a new instance of the class AVAudioPlayer, whose initializer requires an URL of the sound file path. contentsOfURL parameter contains URL that has the path to local resource file. NSURL also can contain path to data on a remote server. When NSURL is used to indicate path of the local file, then it can be used to change file's last modification date.
So why can't we use simple strings instead of URL? First because AVAudioPlayer requires URL in its initializer parameter. Second, for Apple related code, URL seems to be preferred way of accessing local disk files. You can load contents of file from URL into NSString object using stringWithContentsOfURL method or you can load contents of file from URL into NSData using dataWithContentsOfURL.
Second parameter in AVAudioPlayer initializer is fileTypeHint, which indicates the UTI(Unique Transaction Identifier) string for file type. Swift supports many audio types including AIFF, AIFC, WAV, etc.
Third and last parameter for the initializer is Error object which gets set if there is error in loading the file. Currently it is set to nil, since we are not worried about error handling today.
When enemy is hit, play the audio :


....
These are simply made accessible inside the class.
In didMoveToView :
Then, when a ship is hit, those ships that are nearby within a certain radius need to tremble.
This is the code jam of the week starting with Jan 26 2015.Time it took to complete the mini-game project : 2


class GameScene: SKScene {
    var Hits = Int()
    var GameAudioPlayer = AVAudioPlayer()

Play the sound: 

for (i,e) in enumerate(Enemies){
            
                println(e.position.x)
                var x = CGFloat(location.x - e.position.x)
                var y  = CGFloat(location.y - e.position.y)
                var d2 = CGFloat(x*x + y*y)
                if(d2 < 500){
                    e.runAction(SKAction.fadeOutWithDuration(0.5));
                    Hits++
                   GameAudioPlayer.play()
                }
  }


Tremble:
When enemy ship is hit, few of the ships around that area, tremble, including the hit ship in order to create explosion feel. 

Here is tremble in short : move left, right, left and right back to the center, then move up, down, up and down back to center. 

class GameScene : SKScene {

...
var TrembleSequence = SKAction()
var TrembleActionArray = Array<SKAction>()


Again, find distance between each enemy and the current touch location and if it falls within certain fixed radius, then cause them to tremble a bit.

Explosion Particle Effect : 

In order to add explosion particle effect when a spaceship is hit, I am using SKEmitterNode. Sprite Kit has its own class for particle effects and it is easy to use it. You cannot access the generated sprites, but you can modify the behavior by adjusting particles created per second, alpha of the particles etc. Emitter node is used to render smoke, fire, sparks etc. 

First copy the .sks file (predefined emitter node file) that you download from internet or some other project. I used Spark.sks file from Flappy bird sample code (found in Github) for testing purposes. Use build phase -> copy file resource section to copy spark.sks file and spark.png file needed by the emitter. 

Then use the following code to load emitter, change its attributes, and to display them in a particular location. 

let FireEmitter : SKEmitterNode = SKEmitterNode(fileNamed: "Spark"); This line creates a FireEmitter instance of SKEmitterNode type using file of name : "Spark". Pleasantly this did not need an URL having full absolute path to the resource file. I guess it gets the file path inside the initializer function, under the hood. 

Next set the position of the particle effect to be that of the touch location, since a spaceship dies in that location. Set xScale and yScale depending on how large the spark appears on the screen. Set name of the emitter, create sequence and actions for the emitter to execute. First runAction takes in a sequence (which is not required because it has only one action). Second runAction, which gets executed after the fadeInWithDuration, creates a tremble effect, which shakes the particle effect by a tad bit. 

Currently the particles travel south, most likely affected by gravity. In next post lets see about how to fix or circumvent that feature to make it look more convincing. 

This is the code jam of the week starting with Jan 26 2015.




No comments:

Post a Comment