Zombie doesn't rotate correctly

Zombie is following me(hero), but sometimes it just bugs and doesn’t look towards me.

PS hero is always in the center

My code:

[lua]

function enemy(event)

      if zombie.x < display.contentCenterX then
        zombie.x = zombie.x + 1
        zombie.rotation = 270
      end

      if zombie.y > display.contentCenterY then
        zombie.y = zombie.y - 1
        zombie.rotation = 180
      end

      if zombie.x > display.contentCenterX then
        zombie.x = zombie.x - 1
        zombie.rotation = 90
      end

      if zombie.y < display.contentCenterY then
        zombie.y = zombie.y + 1
        zombie.rotation = 0
      end

    end

[/lua]

Surely this is obvious?  If zombie.x > contentCenterX  and zombie.y > contentCenterY what direction should it face?

To further clarify on what SGS is saying.

You have four completely separate conditional statements. As your code is run, every condition that is met will be activated. In other words, the lower down a conditional statement is, the more important it will be in your function.

i.e. left < below < right < above.

In other words, if the zombie is to the left of the centre, then that conditional statement will be met, BUT since the zombie will also be either below or above the centre, these will overwrite the rotation given by the zombie being to the left, and so on.

Even if you were to use if…else if…elseif rather than just if…if…if you may still notice weird behaviour.

If we take this example (I’ll simplify so it’s just rotation for now):

if zombie.x \< display.contentCenterX then zombie.rotation = 270 elseif zombie.y \> display.contentCenterY then zombie.rotation = 180 elseif zombie.x \> display.contentCenterX then zombie.rotation = 90 elseif zombie.y \< display.contentCenterY then zombie.rotation = 0 end

If the zombie was 100 pixels to the left but 500 pixels below, most people would say that the zombie was predominantly below the hero rather than to its left. But in this code it would initially trigger the “zombie is to the left” part if statement, so the zombie would be facing right instead of facing up which would be more appropriate given its position relative to the hero. 

Personally I would use some trigonometry to set the angle between the player and the enemy. I haven’t tested this but I think it’s ok:

zombie.rotation = math.deg(math.atan2(zombie.x - hero.x, zombie.y - hero.y))

If you need them to always be at a 90 degree angle then you could also implement some code to snap the angle to the nearest 90 degrees.

That works only for left and right.

I tried that for up and down, but that doesn’t work:

[lua]

zombie.rotation = math.deg(math.atan2(zombie.y - hero.y, zombie.x - hero.x))

[/lua]

It should work in all cases, the zombie should be able to rotate in any 360 degree angle towards the target (though you might need to offset the whole thing by 90/180 degrees depending on how the image is laid out in the image file itself).  

Can you paste in a slightly larger piece of code where you tried using the trig example?

[lua]

local composer = require “composer”
local joystickPlugin = require “plugin.joystick”
local physics = require “physics”
local scene = composer.newScene();

function scene:create(event)
  physics.start()
  physics.setGravity(0,0)
  local sceneGroup = self.view;
  system.activate(“multitouch”)
  local function zombieCollision(self, event)
    if event.phase == “began” then
      print(“zombie”)
    end
  end
  local spaceBackground = display.newImageRect(“Images/SpaceBackground.png”,
  display.actualContentWidth, display.actualContentHeight)
  spaceBackground.x = display.contentCenterX
  spaceBackground.y = display.contentCenterY
  local loading = display.newText(“Loading…”, display.contentCenterX - 230, 330,
  “Lobster-Regular”, 40)
  local map = display.newImage(“Images/survivalModeMap.png”)
  map.isVisible = false
  map.x = display.contentCenterX
  map.y = display.contentCenterY
  local hero = display.newImage(“Images/mehaHat.png”)
  hero.isVisible = false
  hero.x = display.contentCenterX
  hero.y = display.contentCenterY
  physics.addBody(hero,“static”)
  hero.direction = “up”
  hero.hp = 100
  local zombie = display.newImage(“Images/zombie.jpg”)
  zombie.isVisible = false
  zombie.x = display.contentCenterX
  physics.addBody(zombie,“dynamic”)
  zombie.collision = zombieCollision
  zombie:addEventListener(“collision”, zombie)
  zombie.y = -100
  zombie.hp = 10
  local AttackButton = display.newImage(“Images/mehaAttackButton.png”)
  AttackButton.isVisible = false
  AttackButton.x = 480
  AttackButton.y = 270
  local AttackAnimationSheetData = {
    width = 400,
    height = 400,
    numFrames = 10,
    sheetContentWidth = 800,
    sheetContentHeight = 2000
  }
  local AttackAnimationSheet = graphics.newImageSheet(“Images/mehaAttackAnimation.png”, AttackAnimationSheetData)
  local sequenceData = {
    { name = “Attack”, start = 1, count = 10, time = 500, loopCount = 1}
  }
  local props = {
    x = 10,
    y = 270,
    backgroundRadius = 50,
    movedStickRadius = 25,
  }
  function loadingEnd()
    spaceBackground.isVisible = false
    loading.isVisible = false
    map.isVisible = true
    hero.isVisible = true
    zombie.isVisible = true
    AttackButton.isVisible = true

    function enemy(event)
      if zombie.x < display.contentCenterX then
        zombie.x = zombie.x + 1
        --zombie.rotation = 270
        zombie.rotation = math.deg(math.atan2(zombie.x - hero.x, zombie.y - hero.y))
      end
      if zombie.y > display.contentCenterY then
        zombie.y = zombie.y - 1
        --zombie.rotation = 180
        zombie.rotation = math.deg(math.atan2(zombie.y - hero.y, zombie.x - hero.x))
      end
      if zombie.x > display.contentCenterX then
        zombie.x = zombie.x - 1
        --zombie.rotation = 90
        zombie.rotation = math.deg(math.atan2(zombie.x - hero.x, zombie.y - hero.y))
      end
      if zombie.y < display.contentCenterY then
        zombie.y = zombie.y + 1
        --zombie.rotation = 0
        zombie.rotation = math.deg(math.atan2(zombie.y - hero.y, zombie.x - hero.x))
      end
    end

    local Joystick = joystickPlugin.newJoystick(props)
    Joystick.background:setFillColor(0.5)
    Joystick.movedStick:setFillColor(0.7)
    local function enterFrame(event)
      if Joystick.isActivated() then
        local vector = Joystick.getVector()
        if vector.x < -0.5 then
          map.x = map.x + 2
          zombie.x = zombie.x + 2
          hero.rotation = 270
          hero.direction = “left”
        elseif vector.x > 0.5 then
          map.x = map.x - 2
          zombie.x = zombie.x -2
          hero.rotation = 90
          hero.direction = “right”
        end
        if vector.y < -0.5 then
          map.y = map.y + 2
          zombie.y = zombie.y + 2
          hero.rotation = 0
          hero.direction = “up”
        elseif vector.y > 0.5 then
          map.y = map.y - 2
          zombie.y = zombie.y-2
          hero.rotation = 180
          hero.direction = “down”
        end
      end
    end
    local AttackCoolDown = 0
    local function Attack(event)
      if(event.phase == “began”) then
        if AttackCoolDown == 0 then
          hero.isVisible = false
          local mehaAttackAnimation = display.newSprite( AttackAnimationSheet, sequenceData )
          mehaAttackAnimation.x = display.contentCenterX
          mehaAttackAnimation.y = display.contentCenterY
          if hero.direction == “up” then
            mehaAttackAnimation.rotation = 0
          elseif hero.direction == “right” then
            mehaAttackAnimation.rotation = 90
          elseif hero.direction == “down” then
            mehaAttackAnimation.rotation = 180
          elseif hero.direction == “left” then
            mehaAttackAnimation.rotation = 270
          end
          mehaAttackAnimation:play()
          AttackCoolDown = 1
          timer.performWithDelay( 500, function()
          hero.isVisible = true
            mehaAttackAnimation:removeSelf()
            AttackCoolDown = 0
          end)
        end
      end
    end
    Runtime:addEventListener(“enterFrame”, enemy)
    Runtime:addEventListener(“enterFrame”, enterFrame)
    AttackButton:addEventListener(“touch”, Attack)
  end
  timer.performWithDelay( 2000, loadingEnd )
end
scene:addEventListener(“create”, scene);
return scene;

[/lua]

A bit more than I expected, but that’s fine  :slight_smile:

I wasn’t clear enough in my first message. Take the trig rotation code out of the if statements, and put it by itself (but still in the enter frame function).

That way it always rotates exactly towards the player regardless of whether the zombie is “more to the left” than it is “more up above the player”:

function enemy(event) if zombie.x \< display.contentCenterX then zombie.x = zombie.x + 1 end if zombie.y \> display.contentCenterY then zombie.y = zombie.y - 1 end if zombie.x \> display.contentCenterX then zombie.x = zombie.x - 1 end if zombie.y \< display.contentCenterY then zombie.y = zombie.y + 1 end zombie.rotation = math.deg(math.atan2(zombie.x - hero.x, zombie.y - hero.y)) end

Zombie turns right and left, but when it need to turn up it turns down and when it need turn down it turns up.

Oops, I had the x and y arguments the wrong way round. Try this instead:

zombie.rotation = math.deg(math.atan2(zombie.y - hero.y, zombie.x - hero.x))

And depending on the orientation of the zombie in the image file itself, you might need to offset the final result as mentioned before. E.g:  

zombie.rotation = 90 + math.deg(math.atan2(zombie.y - hero.y, zombie.x - hero.x))

[lua]

zombie.rotation = 90 + math.deg(math.atan2(zombie.y - hero.y, zombie.x - hero.x))

[/lua]

works perfectly!

Thanks for help! :slight_smile:

I’m glad that worked.   

If I were you, I would consider spending some time getting a better understanding of why your original method of trying if…if…if…if… didn’t work - XeduR’s original reply explained it quite well.