swiftsprite-kitsknode

I want to reduce the code in methods that are basically identical and pass in the parameters but don't know where to begin?


I have a spriteKit project where I have many characters across several scenes.

As a beginner I just built each one individually for each scene - which makes for a ton of extra code.

I know I could clean this up with a "Build character class" or something like that...

I am just not sure where to begin.

Here is code from two of the characters in one scene...but imagine 5-10 characters per scene?

Also is there a way a property list could be useful for storing these type of properties?

//BEAR
 
 func buildBear() {
        let bearAnimatedAtlas = SKTextureAtlas(named: "Bear")
        var bearFrames: [SKTexture] = []
        
        let numImages = bearAnimatedAtlas.textureNames.count
        for i in 1...numImages {
            let bearTextureName = "bear\(i)"
            
            bearFrames.append(bearAnimatedAtlas.textureNamed(bearTextureName))
        }
        animatedBear = bearFrames
        let firstFrameTexture = animatedBear[0]
        bear = SKSpriteNode(texture: firstFrameTexture)
        bear.size.height = 370
        bear.size.width = 370
        bear.position = CGPoint(x: 295, y: 25)
        bear.zPosition = 1
        bear.name = "bear"
        isUserInteractionEnabled = true
        addChild(bear)
        
    }
    
    //CAT
    func buildCat() {
        let catAnimatedAtlas = SKTextureAtlas(named: "Cat")
        var catFrames: [SKTexture] = []
        
        let numImages = catAnimatedAtlas.textureNames.count
        for i in 1...numImages {
            let catTextureName = "cat\(i)"
            
            catFrames.append(catAnimatedAtlas.textureNamed(catTextureName))
        }
        animatedCat = catFrames
        let firstFrameTexture = animatedCat[0]
        cat = SKSpriteNode(texture: firstFrameTexture)
        cat.size.height = 240
        cat.size.width = 240
        cat.position = CGPoint(x: 134, y: -38)
        cat.zPosition = 2
        cat.name = "cat"
        isUserInteractionEnabled = true
        addChild(cat)
        
    }

How could I clean up something like this - I need different position/size per scene but I imagine I could just override that per scene?

I know I know how to do this! - just not where to start?

Gimme a nudge please!


Solution

  • One of the confusing things about the existence of so many languages is that they each have their own jargon, and their own conventions. The root of your problem, however, has nothing to do with Swift or Sprite Kit. When I read your question, I see code that could use some Abstract Data Types. In Java, you would create an Interface, in C++ you would create a "pure virtual" class. Well a rose by any other name still gets the job done. I recommend creating a Protocol, perhaps called Spritable, to define the types of objects that you intend to build into sprites. It would probably be as simple as this:

    protocol Spritable {
        var species: String { get }
        var height: Int { get }
        var width: Int { get }
    }
    

    The only other thing that differs between your two functions appears to be the starting position. Since this is not inherent in the meaning of a Spritable object, I would package that data separately. A tuple should do the job. With these revisions, your two functions can be merged into one:

    func buildSprite(of creature: Spritable, at position: (x: Int, y: Int, z: Int)) {
        let spriteAnimatedAtlas = SKTextureAtlas(named: creature.species)
        var spriteFrames: [SKTexture] = []
        
        let numImages = spriteAnimatedAtlas.textureNames.count
        for i in 1...numImages {
            let spriteTextureName = "\(creature.species.lowercased())\(i)"
            spriteFrames.append(spriteAnimatedAtlas.textureNamed(spriteTextureName))
        }
        animatedSprite = spriteFrames
        let firstFrameTexture = animatedSprite[0]
        sprite = SKSpriteNode(texture: firstFrameTexture)
        sprite.size.height = creature.height
        sprite.size.width = creature.width
        sprite.position = CGPoint(x: position.x, y: position.y)
        sprite.zPosition = position.z
        sprite.name = creature.species
        isUserInteractionEnabled = true
        addChild(sprite)
    }
    

    To build a bear, aside from a workshop, you will need to define a struct that implements Spritable:

    struct Bear: Spritable {
        var species: String { return "Bear" }
        var height: Int
        var width: Int
    
        init(height: Int, width: Int) {
            self.height = height
            self.width = width
        }
    }
    

    Then here would be your function call:

    buildSprite(of: Bear(height: 370, width: 370), at: (295, 25, 1))
    

    This is a pretty simple example, and could be solved in a few simpler ways than this. However, I find that the larger a project gets, the greater the benefits of organizing code around Abstract Data Types become, so it's worth taking that approach even in a simple case like this.