Animation trouble

Home 4 Forums Swift and Sprite Kit Animation trouble

This topic contains 7 replies, has 2 voices, and was last updated by  David Armstrong 11 months ago.

  • December 9, 2017 at 4:24 pm #177120

    Hi. I’m creating my first app. It’s a “Space Invaders” style top-down shooter. I’m using Swift 4 and Xcode 9.2. Everything is working out fine so far, but I’m having a problem animating with a simple texture atlas. Most of the tutorials I’ve seen put the animation code in the “didMove(to view: )” function in the “GameScene.swift.” However, my player is set up as a separate Swift file that passes necessary functions into the “didMove(to view: )”. Everything works fine EXCEPT the animation. All my player sprite needs to do is animate four sprites in a texture atlas. The ship is the first thing I’ve tried to animate. All the names and spelling are correct. I get NO error messages, but the code doesn’t work. I’ve stared at this thing so long that even the code I’m sure of looks weird. I know it’s pretty basic, but I need some help. Most of my code knowledge was acquired via YouTube, and tutorial sites. Depending on the date of the tutorials, there seem to be number of ways to do things. I’m looking for “simple.” What you see below is the result of trial and error. Like I said, it works very well EXCEPT for the animation.

    ONE OTHER THING TO NOTE: the image (mainShip1) used to define the constant, “ship” in line 12 is NOT part of the texture atlas. I had to place a duplicate outside the atlas in order to be recognized. If not, I just get a big “missing sprite X.” I THINK that’s part of the problem. It’s almost like the atlas isn’t being referenced.

    HERE ARE THE PERTINENT SECTIONS OF CODE:

    For the player position, physics, and sprite animation:

    class Ship: SKSpriteNode {

    var goLeft: Bool = false
    var goRight: Bool = false
    let mySpeed: CGFloat = 700
    var canShoot = true
    let fireRate = 0.5
    let bulletTexture = SKTexture(imageNamed: “testBullet”)

    public static func newInstance() -> Ship {
    let ship = Ship(imageNamed: “mainShip1”)

    ship.position = CGPoint(x: 0, y: -300)
    ship.size = CGSize(width: 128, height: 128)
    ship.anchorPoint = CGPoint(x: 0.5, y: 0.5)

    ship.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(32))
    ship.physicsBody?.isDynamic = true
    ship.physicsBody?.affectedByGravity = false
    ship.physicsBody?.categoryBitMask = shipCategory
    ship.physicsBody?.contactTestBitMask = spiderCategory
    ship.physicsBody?.collisionBitMask = shipCategory

    return ship
    }

    func shipAnim(){

    let atlas = SKTextureAtlas(named: “ship”)
    let s1 = atlas.textureNamed(“mainShip1.png”)
    let s2 = atlas.textureNamed(“mainShip2.png”)
    let s3 = atlas.textureNamed(“mainShip3.png”)
    let s4 = atlas.textureNamed(“mainShip2.png”)

    let textures = [s1, s2, s3, s4]
    let shipAnim = SKAction.animate(with: textures, timePerFrame: 0.1)

    run(SKAction.repeatForever(shipAnim))

    }

    For GameScene:
    class GameScene: SKScene, SKPhysicsContactDelegate {

    var lastFrameTime: Double = 0
    let player = Ship.newInstance()
    let base = SKSpriteNode(imageNamed: “base”)
    let fireButton = SKSpriteNode(imageNamed: “fireButton”)
    let leftArrow = SKSpriteNode(imageNamed: “leftArrow”)
    let rightArrow = SKSpriteNode(imageNamed: “rightArrow”)
    let panel = SKSpriteNode(imageNamed: “panel2”)
    let statPanel = SKSpriteNode(imageNamed: “Ceiling”)
    let floor = SKSpriteNode(imageNamed: “Floor1”)
    let mine = SKSpriteNode(imageNamed: “oreToMine”)

    let nest = spiderNest()
    let stats = statController()
    let ship = Ship()

    override func didMove(to view: SKView) {
    setupControls()
    addChild(player)
    createBulletRemoveLine()
    stats.setupScore(scene: self)
    stats.setupLives(scene: self)
    stats.setupOre(scene: self)
    ship.shipAnim()

    self.physicsWorld.contactDelegate = self

    }

    Hopefully someone can tell me what I’m doing wrong.

    Thanks!

  • December 12, 2017 at 2:18 pm #177202

    So that’s not exactly how I would do things nowadays, but it doesn’t mean this setup is wrong either. I think you’d be better off using a saved action (Animate with Textures), just because it’s SO much easier. Watch the very beginning of this lesson to see what I mean. https://cartoonsmart.com/how-to-create-a-sprite-based-swipe-line/

    In your ship subclass you’re writing….

    run(SKAction.repeatForever(shipAnim))

    I think what you want though is…

    ship.run(SKAction.repeatForever(shipAnim))

    Otherwise run is just being applied to the subclass itself, not the instance of ship.

    Other notes: I wouldn’t setup your subclass like this anyway, where you are returning this newInstance (this part….)

    public static func newInstance() -> Ship {

    Especially if the ship is your main player and there’s only once instance of it anyway. Maybe I’m missing something though. =/

    • December 12, 2017 at 7:13 pm #177207

      Thanks! When I started, I thought having a separate .swift file for all the game elements would be the way to go. I’m not too far along so changes should be fairly easy. I DO know that ship.run(SKAction.repeatForever(shipAnim)) gives me an error. It claims that “ship” is an unresolved identifier. I’m going to need to move some things around, sounds like.

      I’ll take a look at those lessons you linked. Like I said, I’m new at this. Haven’t really written any programs since I used BASIC on my Commodore 64 back in ’85. Even then it was just school projects. Lots of changes. I do like the prospect of learning Swift along the way so I’m trying do as much with actual code as possible (as opposed to graphic interfaces).

      Thanks again!

  • December 12, 2017 at 8:26 pm #177210

    Here’s what I think your Ship class should look like…

    import Foundation
    import SpriteKit
    
    class Ship: SKSpriteNode {
        
        
        
        var goLeft: Bool = false
        var goRight: Bool = false
        var mySpeed: CGFloat = 700
        var canShoot = true
        var fireRate = 0.5
        var bulletTexture = SKTexture(imageNamed: "testBullet")
        
        //make all these vars above, no reason not to
        
    
        func setUp()  {
            
            
            //self.position = CGPoint(x: 0, y: -300) // okay, but I would set in the GameScene class
            
           // self.size = CGSize(width: 128, height: 128) //not needed, size is set by the image you initialize with
           // self.anchorPoint = CGPoint(x: 0.5, y: 0.5) // not needed, this is default
            
            self.physicsBody = SKPhysicsBody(circleOfRadius: self.frame.width / 2 ) // might as well use the image's frame size
            self.physicsBody?.isDynamic = true
            self.physicsBody?.affectedByGravity = false
            self.physicsBody?.categoryBitMask = shipCategory
            self.physicsBody?.contactTestBitMask = spiderCategory
            self.physicsBody?.collisionBitMask = shipCategory
            
            
            if let animationAction:SKAction = SKAction(named: "shipAnim") {
                
                 self.run( animationAction ) // requires that you create a saved Action in your Actions.sks file named shipAnim. You can make it loop in the Actions.sks file
                
            }
            
           
        
        }
    
    }

    Then your GameScene class can look like this….

    class GameScene: SKScene {
        
        var ship:Ship = Ship()  //declare the ship
        
        override func didMove(to view: SKView) {
            
          
            
            ship = Ship(imageNamed: "mainShip1")  //initialize the same way you would any SKSpriteNode
            ship.setUp() // call setup
            ship.position = CGPoint(x: 0, y: -300) // I prefer to position in the main class
            self.addChild(ship) // add it to the scene
            
        }

    Here’s a lesson on setting up saved Actions

    • December 14, 2017 at 1:50 pm #177288

      So, these code recommendations have caused trouble with other sections. My fault. I lost track of my changes somewhere along the way. I’m going to start over from scratch using your recommendations. That should work. I’m also going to go ahead and use the graphic interfaces to do some of the editing.

  • December 14, 2017 at 2:45 pm #177289

    Check out that short Asteroids series I just did. Here’s the first lesson…

    https://cartoonsmart.com/module-2/part-1-rotation-and-tap-gestures-for-ship-controls/

    But with the code I gave you, you could probably skip to the second lesson. That gets into creating a Bullet subclass and that should squash some of the problems you’re having

    • December 16, 2017 at 3:42 pm #177379

      SHIP ANIMATION WORKING! THANK YOU!

      For some reason, however, my directional ship controls do not function. I should also say that when I started over, I pretty much just cut-and-pasted my old directional controller code (which worked) from the last iteration into the files for new version. It’s basically just a left and right arrow setup. `

      IN THE SHIP CLASS:

      `func moveLeft() {
      goRight = false
      goLeft = true
      self.physicsBody?.velocity.dx = -mySpeed
      }

      func moveRight() {
      goRight = true
      goLeft = false
      self.physicsBody?.velocity.dx = mySpeed
      }

      func stopLeft() {
      goLeft = false
      if goRight{
      moveRight()
      } else {
      self.physicsBody?.velocity.dx = 0
      }
      }

      func stopRight() {
      goRight = false
      if goLeft{
      moveLeft()
      } else {
      self.physicsBody?.velocity.dx = 0
      }
      }

      func stopBoth() {
      goRight = false
      goLeft = false
      self.physicsBody?.velocity.dx = 0;
      }

      func stayInBounds() {
      if self.position.x < -350 {

      self.position.x = -350

      }
      if self.position.x > 350 {

      self.position.x = 350

      }
      }
      `

      
      
      IN THE GAMESCENE.SWIFT:
      
         

      override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
      for touch in touches {

      let location = touch.location(in: self)
      startShip(location: location)
      }
      }

      override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
      for touch in touches {

      let location = touch.location(in: self)
      moveShip(location: location)
      }
      }

      override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
      for touch in touches {
      let location = touch.location(in: self)
      stopShip(location: location)
      }
      }

      func startShip(location: CGPoint){

      if leftArrow.contains(location) {
      ship.moveLeft()
      }

      if rightArrow.contains(location) {
      ship.moveRight()
      }

      if fireButton.contains(location) {
      ship.fire(scene: self)
      }
      }

      func moveShip(location: CGPoint){
      if leftArrow.contains(location) && !ship.goLeft{
      ship.moveLeft()
      } else if !leftArrow.contains(location) && ship.goLeft{
      ship.stopLeft()
      }

      if rightArrow.contains(location) && !ship.goRight{
      ship.moveRight()
      } else if !rightArrow.contains(location) && ship.goRight{
      ship.stopRight()
      }
      }

      func stopShip(location: CGPoint) {
      ship.stopBoth()
      }

      override func update(_ currentTime: TimeInterval) {
      ship.stayInBounds()
      if lastFrameTime != 0{
      nest.Update(scene: self, currentTime: currentTime – lastFrameTime)
      }
      lastFrameTime = currentTime
      }
      `

    • December 16, 2017 at 4:28 pm #177383

      I’ll work harder on figuring out your “code” format button next time. Sorry!

You must be logged in to reply to this topic.