Page 1 of 1

Moving the player also moves other actors

Posted: Mon Dec 30, 2024 8:36 am
by Bunabyte
In my game, part of the game state table is a table of actors. These actors have numerous properties, including a position table with x and y components.
However, I'm having this issue where calling `Actor:move(x, y)` on the `player` actor also moves the other actor, `fakePlayer`, when it is not supposed to.
This is the code for my project so far:

Code: Select all

local TILE_SIZE = 16

local Actor = {
  name = "Actor",
  position = {
    x = 0,
    y = 0
  },
  moveTimer = 0
}

function Actor:new(name)
  local o = {}
  setmetatable(o, self)
  self.__index = self

  o.name = name or "Actor"

  return o
end

function Actor:move(x, y)
  if x == 0 and y == 0 then return end
  if self.moveTimer < 0.2 then return end

  self.position.x = self.position.x + (x * TILE_SIZE)
  self.position.y = self.position.y + (y * TILE_SIZE)

  self.moveTimer = 0
end

local Player = Actor:new("Player")
Player.sprite = {
  imgSrc = "res/sprites/player.png",
  img = nil,
  frames = {
    idle = {
      x = 0,
      y = 0,
      w = 24,
      h = 24
    }
  },
  pivot = {
    x = 0.5,
    y = 1
  },
  currentFrame = "idle"
}

local Camera = Actor:new("Camera")

local Level = {
  name = "Level"
}

function Level:new()
  local o = {}
  setmetatable(o, self)
  self.__index = self

  return o
end

local testLevel = Level:new()
testLevel.name = "Test Level"

local gameState = {
  actors = {},
  level = {}
}

function loadLevel(src)
  gameState.level = src:new()
end

function love.load()
  love.window.setTitle("Ghetto Earth")
  love.window.setMode(320, 240, {
    resizable = true,
    minwidth = 320,
    minheight = 240
  })

  gameState.actors = { 
    player = Player:new(),
    cam = Camera:new(),
    fakePlayer = Actor:new()
  }

  gameState.actors.fakePlayer.sprite = {
    imgSrc = "res/sprites/player.png",
    img = nil,
    frames = {
      idle = {
        x = 0,
        y = 0,
        w = 24,
        h = 24
      }
    },
    pivot = {
      x = 0,
      y = 0
    },
    currentFrame = "idle"
  }

  loadLevel(testLevel)
end

function love.update(dt)
  for id, actor in pairs(gameState.actors) do
    actor.moveTimer = actor.moveTimer + dt
  end

  local direction = {}

  if love.keyboard.isDown("d") then
    direction.x = 1
  elseif love.keyboard.isDown("a") then
    direction.x = -1
  else
    direction.x = 0
  end

  if love.keyboard.isDown("s") then
    direction.y = 1
  elseif love.keyboard.isDown("w") then
    direction.y = -1
  else
    direction.y = 0
  end

  local player = gameState.actors.player
  player:move(direction.x, direction.y)

  local cam = gameState.actors.cam
  local viewWidth, viewHeight = love.graphics.getDimensions()
  cam.position = {
    x = player.position.x + (viewWidth / 2),
    y = player.position.y + (viewHeight / 2)
  }

  print(player.position.x)
  print(gameState.actors.fakePlayer.position.x)
end

function love.draw()
  local i = 0
  for id, actor in pairs(gameState.actors) do
    --love.graphics.print(actor.name .. " (" .. id .. ")", 4, 4 + (i * 16))

    if actor.sprite then
      local sprite = actor.sprite
      local frame = sprite.frames[sprite.currentFrame]

      if sprite.img == nil then
        sprite.img = love.graphics.newImage(sprite.imgSrc)
      end

      local cam = gameState.actors.cam
      love.graphics.draw(sprite.img, cam.position.x - actor.position.x - (frame.w * sprite.pivot.x), cam.position.y - actor.position.y - (frame.h * sprite.pivot.y))
    end

    i = i + 1
  end

  i = i + 1
  --love.graphics.print("Current level: " .. gameState.level.name, 4, 4 + (i * 16))
end
I don't really understand what I did wrong here. How can I make it so that `player:move(direction.x, direction.y)` only moves `self`/`player` and not every single actor in the world?

Re: Moving the player also moves other actors

Posted: Tue Dec 31, 2024 8:30 am
by pgimeno
Hello, welcome to the forums.

What's happening here is that you have the `position` table only in your class, but you don't create a new `position` table for each instance. You can see for yourself that both objects share the same table with:

Code: Select all

  print(player.position, gameState.actors.fakePlayer.position)
In the class, apart from the methods, you should only have data that is shared by all instances, if any. Moving the data to the constructor will be enough:

Code: Select all

local Actor = {}

function Actor:new(name)
  local o = {
    position = {
      x = 0,
      y = 0
    },
    moveTimer = 0
  }
  setmetatable(o, self)
  self.__index = self

  o.name = name or "Actor"

  return o
end
Edit: Oops, pasted the wrong constructor. Fixed.