UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Day 79 Project23 Challenge

Forums > 100 Days of Swift

I'm having trouble with challenge 2 shown on this page.

It's a Fruit Ninja clone type of game that we are creating and the challenge is...

Create a new, fast-moving type of enemy that awards the player bonus points if they hit it.

I was able to complete most of the challenge. I used the red penguin sprite from the Whack-a-Penguin project we did before, so that the player can tell the difference between the bonus penguins and the normal ones. I made the bonus penguins appear randomly, and even less frequently than the regular penguins or even bombs. I made it award 5 points instead of 1 when a player slices it. And I have made sure that the player won't lose a life if they miss one. (They are supposed to be a bonus afterall.) But I can't seem to figure out how to make them move faster than the regular penguins.

I know that it probably has something to do with this function where we create all of the sliceable enemies...

func createEnemy(forceBomb: ForceBomb = .random) {
        let enemy: SKSpriteNode

        var enemyType = Int.random(in: 0...6)

        if forceBomb == .never {
            enemyType = 1
        } else if forceBomb == .always {
            enemyType = 0
        }

        if enemyType == 0 {
            enemy = SKSpriteNode()
            enemy.zPosition = 1
            enemy.name = "bombContainer"

            let bombImage = SKSpriteNode(imageNamed: "sliceBomb")
            bombImage.name = "bomb"
            enemy.addChild(bombImage)

            if bombSoundEffect != nil {
                bombSoundEffect?.stop()
                bombSoundEffect = nil
            }

            if let path = Bundle.main.url(forResource: "sliceBombFuse", withExtension: "caf") {
                if let sound = try?  AVAudioPlayer(contentsOf: path) {
                    bombSoundEffect = sound
                    sound.play()
                }
            }

            if let emitter = SKEmitterNode(fileNamed: "sliceFuse") {
                emitter.position = CGPoint(x: bombFuseXPosition, y: bombFuseYPosition)
                enemy.addChild(emitter)
            }
        } else if enemyType == 6 {
            enemy = SKSpriteNode(imageNamed: "bonusPenguin")
            run(SKAction.playSoundFileNamed("launch.caf", waitForCompletion: false))
            enemy.name = "bonus"
        } else {
            enemy = SKSpriteNode(imageNamed: "penguin")
            run(SKAction.playSoundFileNamed("launch.caf", waitForCompletion: false))
            enemy.name = "enemy"
        }

        let randomPosition = CGPoint(x: Int.random(in: minXStartPosition...maxXStartPosition), y: yStartPosition)
        enemy.position = randomPosition

        let randomAngularVelocity = CGFloat.random(in: minAngularVelocity...maxAngularVelocity )
        let randomXVelocity: Int

        if randomPosition.x < firstQuarterOfScreen {
            randomXVelocity = Int.random(in: minOuterXVelocity...maxOuterXVelocity)
        } else if randomPosition.x < secondQuarterOfScreen {
            randomXVelocity = Int.random(in: minInnerXVelocity...maxInnerXVelocity)
        } else if randomPosition.x < thirdQuarterOfScreen {
            randomXVelocity = -Int.random(in: minInnerXVelocity...maxInnerXVelocity)
        } else {
            randomXVelocity = -Int.random(in: minOuterXVelocity...maxOuterXVelocity)
        }

        let randomYVelocity = Int.random(in: minYVelocity...maxYVelocity)

        enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemyPhysicsBodyRadius)
        enemy.physicsBody?.velocity = CGVector(dx: randomXVelocity * velocityMultiplier, dy: randomYVelocity * velocityMultiplier)

        enemy.physicsBody?.angularVelocity = randomAngularVelocity
        enemy.physicsBody?.collisionBitMask = 0

        if enemy.name == "bonus" {
            //NEED HELP HERE
        }

        addChild(enemy)
        activeEnemies.append(enemy)
    }

It seems to me that there is probably just some property of enemy.physicsBody that I need to set differently for the bonus penguins to make them move faster. But I can't figure out what it is after playing around with it for a while now.

You can see near the end of that function where I have added this...

if enemy.name == "bonus" {
    //NEED HELP HERE
}

Does anybody know what I need to put in there?

3      

I didn't include this before, but Challenge 1 of this project was to remove all the magic numbers from this function, and create constants for them instead. So, these are the constants I created.

    let numEnemyTypes = 6
    let bombFuseXPosition = 76
    let bombFuseYPosition = 64
    let minXStartPosition = 64
    let maxXStartPosition = 960
    let yStartPosition = -128
    let firstQuarterOfScreen: CGFloat = 256
    let secondQuarterOfScreen: CGFloat = 512
    let thirdQuarterOfScreen: CGFloat = 768
    let minAngularVelocity: CGFloat = -3
    let maxAngularVelocity: CGFloat = 3
    let minOuterXVelocity = 8
    let maxOuterXVelocity = 15
    let minInnerXVelocity = 3
    let maxInnerXVelocity = 5
    let minYVelocity = 24
    let maxYVelocity = 32
    let enemyPhysicsBodyRadius: CGFloat = 64
    let velocityMultiplier = 40

But I also have another questions about this same project.

  1. Does anybody know the puropse of velocityMultipier here? Paul basically just tells us to write the line with no explanation. So, we calculate the randomXVelocity and randomYVelocity but then we multiply each of them by 40 for some unexplained reason.
    enemy.physicsBody?.velocity = CGVector(dx: randomXVelocity * velocityMultiplier, dy: randomYVelocity * velocityMultiplier)

    Is this just to make the random number generator more efficient? So it only has to pick a random number between 3-5, 8-15, or 24-32, rather than picking a random number between 120-200, 320-600, or 960-1280 each time?

3      

Ok... actually writing out those numbers helped me understand what the velocity vectors are actually doing better, and I was able to figure out an answer to my original question.

Basically, the dx and dy parameters in the velocity function indicate it the final point that would be reached if there were no gravity in effect. So, to make some of the enemies move faster, you have to set the dx value to a place far off the screen. That way, it has to move faster to get to its finishing location in time. I didn't change the dy values, because we are working in landscape format, so if I change that too much the enemies will just fly off the top of the screen too quickly.

But I ended up creating 2 more constants...

    let bonusOuterXVelocity = 30
    let bonusInnerXVelocity = 20

I know those don't seem to be far off the screen, but remember that we are multiplying each one by 40 per Paul's instruction.

These are comparable to these four constants that are used for the velocities of our regular enemies

let minOuterXVelocity = 8
    let minOuterXVelocity = 8
    let maxOuterXVelocity = 15
    let minInnerXVelocity = 3
    let maxInnerXVelocity = 5

Basically I tried to just double the maxInnerX and maxOuterX Velocities to test with, but I found that the ones with an inner starting point were still moving too slow, so I ended up quadrupling it instead. So, that 30 and 20 don't have any real significance other than they seemed to test well.

There are only two variables this time because I just decided not to make the bonus enemies' speeds vary based on a random number generator. They don't need a min and a max. But I still wanted the ones that start near the center of the screen to move a little bit slower since they only have to move across about half of the screen before they are gone, while the ones that start near the edges have to move across the full screen, giving the player more time to react to them.

Then I just had to modify my code shown above where the velocity values were set like this...

        let randomXVelocity: Int

        if randomPosition.x < firstQuarterOfScreen {
            if enemy.name == "bonus" {
                randomXVelocity = bonusOuterXVelocity
            } else {
                randomXVelocity = Int.random(in: minOuterXVelocity...maxOuterXVelocity)
            }
        } else if randomPosition.x < secondQuarterOfScreen {
            if enemy.name == "bonus" {
                randomXVelocity = bonusInnerXVelocity
            } else {
                randomXVelocity = Int.random(in: minInnerXVelocity...maxInnerXVelocity)
            }
        } else if randomPosition.x < thirdQuarterOfScreen {
            if enemy.name == "bonus" {
                randomXVelocity = -bonusInnerXVelocity
            } else {
                randomXVelocity = -Int.random(in: minInnerXVelocity...maxInnerXVelocity)
            }
        } else {
            if enemy.name == "bonus" {
                randomXVelocity = -bonusOuterXVelocity
            } else {
                randomXVelocity = -Int.random(in: minOuterXVelocity...maxOuterXVelocity)
            }
        }

There might be a cleaner way to write that, but it gets the job done for now.

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.