1

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!

3 Answers3

1

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.

TallChuck
  • 1,725
  • 11
  • 28
  • Good answer. It does however throw errors due to width/height/zposition expecting CGFloat instead of Int, which would be fixed by changing the Ints to CGFloats. As OP has different scenes, is the expectation that the func needs to be added to each scene, or is the thinking it should sit not in any of the scenes but in a different file and only just called in the game scenes? – JohnL Jun 12 '21 at 11:01
  • This is a great answer - I was thinking protocol - but still learning how to really use them. I started trying to implement this - but I do get lost as to where the various parts should live - I created a new file for the protocol and buildSprite method - but where would I put the structs for each creature? Could I put them all in one file and then just call each build with he unique parameters in each scene? Thats what my hope is. One file with all this, conform each scene to the protocol and call the builds? that seem right? Kind of in line with what @JohnL is asking too! THANKS! – Sir Piedmont Pants Jun 15 '21 at 01:32
  • I'm not super experienced in Swift, so I haven't figured out "the best" way to organize files, it sounds like you are on the right track. Try lots of things and over time you'll figure out which way is best. – TallChuck Jun 15 '21 at 04:57
1

I ended up not using a protocol on this.

I simply built a method to construct the sprites - similar to what @TallChuck suggested.

func buildCharacter(name:String, height: CGFloat, width: CGFloat, position: CGPoint, zPosition: CGFloat) {
    let animatedAtlas = SKTextureAtlas(named: name)
    var animationFrames: [SKTexture] = []
    
    let numImages = animatedAtlas.textureNames.count
    for i in 1...numImages {
        let textureName = "\(name)\(i)"
        
        animationFrames.append(animatedAtlas.textureNamed(textureName))
    }
    
    animatedCharacter = animationFrames
    let firstFrameTexture = animatedCharacter[0]
    builtCharacter = SKSpriteNode(texture: firstFrameTexture)
    builtCharacter.size.height = height
    builtCharacter.size.width = width
    builtCharacter.position = position
    builtCharacter.zPosition = zPosition
    builtCharacter.name = name
    
    isUserInteractionEnabled = true
    addChild(builtCharacter)
    
   }

It works perfect for building and adding to the scenes - had some issues accessing the nodes names for touch detection but got it sorted. Now trying to figure out how to call actions on the nodes - its all different than the normal way. So I will prolly ask that question next. But overall reduced a ton of repeated code! Thanks for the help.

0

None of this has to be done in code. With Sprite Kit, you create your bear and your cat via the sprite kit editor, and then you load in the sks file by using the constructor that loads by filename.

This is a similar behavior to how game objects work in unity.

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44