Love2D | Шаблон игры

В предыдущей статье мы создали простую заготовку для нашего будущего приложения на движке Love2D. Никаких полезных действий она ещё не умеет делать, просто запускает бесконечный цыкл и ждёт закрытия окна. В этой статье мы рассмотрим обработку ввода с клавиатуры и мыши. Было принято решение повременить с мобильными устройствами, так что приложение будет работать пока только на Desktop платформах.

Для удобства было решено запихнуть обработчик ввода в отдельную систему, чтобы не использовать функции движка love.keypressed, love.mousepressed и другие.

Наша система ввода будет отслеживать следующие состояния:

Весь код мы будем добавлять в файл App.lua из предыдущей статьи, так как в нём находится обработка всех событий. В конце статьи как всегда будут выложены исходные коды с доработками.

Для начала нам нужно создать новый объект и добавить к нему несколько переменных. Назовём его Input.

...

-- Система ввода
Input               = {}
App.Input           = {}
App.Input._keys     = {}
App.Input._buttons  = {}
App.Input._wheel    = 0
App.Input._mousePos = Vector( 0, 0 )

...

В массив _keys будут заноситься все нажатые клавиши клавиатуры, а в массив _buttons соответственно все нажатые кнопки мыши. Переменная _wheel будет принимать значение -1 или 1 в зависимости от того, в какую сторону была осуществлена прокрутка колёсиком мыши. В конце цикла мы будем выставлять ей значение 0. В векторе _mousePos будут находиться текущие координаты курсора мыши.

Теперь нам нужно модифицировать функцию App.run(). Для начала определим текущие координаты курсора мыши и занесём их в вектор _mousePos:

...

function App.run()
  local mouseX, mouseY  = love.mouse.getPosition()
  App.Input._mousePos   = Vector( mouseX, mouseY )

  ...
end

В месте обработки событий мы будем определять какие нажаты клавиши и заносить их в соответствующие массивы. Там же будем определять однократное нажатие или удерживание клавиш. Делается это следующим кодом:

...

function App.run()
  ...

  if love.event then
    love.event.pump()

    local _keys = {}
    local _btns = {}

    for e, a, b, c, d in love.event.poll() do
      if e == "quit" then
        App.RUNNING = false
      elseif e == "keypressed" then
        App.Input._keys[a] = 1
        _keys[ #_keys + 1 ] = a
      elseif e == "keyreleased" then
        App.Input._keys[a] = 0
        _keys[ #_keys + 1 ] = a
      elseif e == "mousepressed" then
        App.Input._buttons[c] = 1
        _btns[ #_btns + 1 ] = c

        if c == "wu" then
          App.Input._wheel = 1
        elseif c == "wd" then
          App.Input._wheel = -1
        end
      elseif e == "mousereleased" then
        App.Input._buttons[c] = 0
        _btns[ #_btns + 1 ] = c
      end

      for key, value in pairs( App.Input._keys ) do
        if value == 0 then
          local keyFound = false

          for i = 1, #_keys do
            if _keys[i] == key then
              keyFound = true
              break
            end
          end

          if not keyFound then
            App.Input._keys[ key ] = -1
          end
        elseif value == 1 then
          local keyFound = false

          for i = 1, #_keys do
            if _keys[i] == key then
              keyFound = true
              break
            end
          end

          if not keyFound then
            App.Input._keys[ key ] = 2
          end
        end
      end

      for key, value in pairs( App.Input._buttons ) do
        if value == 0 then
          local keyFound = false

          for i = 1, #_btns do
            if _btns[i] == key then
              keyFound = true
              break
            end
          end

          if not keyFound then
            App.Input._buttons[ key ] = -1
          end
        elseif value == 1 then
          local keyFound = false

          for i = 1, #_btns do
            if _btns[i] == key then
              keyFound = true
              break
            end
          end

          if not keyFound then
            App.Input._buttons[ key ] = 2
          end
        end
      end

      love.handlers[e]( a, b, c, d )
    end
  end

  ...
end

Здесь думаю всё понятно и подробно расписывать не имеет смысла. Осталось добавить понятные функции, к которым можно будет обращаться для получения наших значений. Вот они:

...

function Input.reset()
  App.Input._wheel = 0
end

function Input.pressed()
  for _, v in pairs( App.Input._keys ) do
    if v > 0 then
      return true
    end
  end

  return false
end

function Input.clicked()
  for _, v in pairs( App.Input._buttons ) do
    if v > 0 then
      return true
    end
  end

  return false
end

function Input.keyDown( key )
  return App.Input._keys[ key ] == 1
end

function Input.keyUp( key )
  return App.Input._keys[ key ] == 0
end

function Input.keyHeld( key )
  return App.Input._keys[ key ] == 2
end

function Input.mouseDown( button )
  return App.Input._buttons[ button ] == 1
end

function Input.mouseUp( button )
  return App.Input._buttons[ button ] == 0
end

function Input.mouseHeld( button )
  return App.Input._buttons[ button ] == 2
end

function Input.wheelUp()
  return App.Input._wheel == 1
end

function Input.wheelDown()
  return App.Input._wheel == -1
end

function Input.mousePos()
  return App.Input._mousePos
end

...

В конечном итоге наш файл App.lua будет выглядеть следующим образом:

App = {}

App.OS      = love.system.getOS()
App.WIDTH   = love.graphics.getWidth()
App.HEIGHT  = love.graphics.getHeight()
App.RUNNING = true

-- Система ввода
Input               = {}
App.Input           = {}
App.Input._keys     = {}
App.Input._buttons  = {}
App.Input._wheel    = 0
App.Input._mousePos = Vector( 0, 0 )

function App.run()
  local mouseX, mouseY  = love.mouse.getPosition()
  App.Input._mousePos   = Vector( mouseX, mouseY )

  if love.event then
    love.event.pump()

    local _keys = {}
    local _btns = {}

    for e, a, b, c, d in love.event.poll() do
      if e == "quit" then
        App.RUNNING = false
      elseif e == "keypressed" then
        App.Input._keys[a] = 1
        _keys[ #_keys + 1 ] = a
      elseif e == "keyreleased" then
        App.Input._keys[a] = 0
        _keys[ #_keys + 1 ] = a
      elseif e == "mousepressed" then
        App.Input._buttons[c] = 1
        _btns[ #_btns + 1 ] = c

        if c == "wu" then
          App.Input._wheel = 1
        elseif c == "wd" then
          App.Input._wheel = -1
        end
      elseif e == "mousereleased" then
        App.Input._buttons[c] = 0
        _btns[ #_btns + 1 ] = c
      end

      for key, value in pairs( App.Input._keys ) do
        if value == 0 then
          local keyFound = false

          for i = 1, #_keys do
            if _keys[i] == key then
              keyFound = true
              break
            end
          end

          if not keyFound then
            App.Input._keys[ key ] = -1
          end
        elseif value == 1 then
          local keyFound = false

          for i = 1, #_keys do
            if _keys[i] == key then
              keyFound = true
              break
            end
          end

          if not keyFound then
            App.Input._keys[ key ] = 2
          end
        end
      end

      for key, value in pairs( App.Input._buttons ) do
        if value == 0 then
          local keyFound = false

          for i = 1, #_btns do
            if _btns[i] == key then
              keyFound = true
              break
            end
          end

          if not keyFound then
            App.Input._buttons[ key ] = -1
          end
        elseif value == 1 then
          local keyFound = false

          for i = 1, #_btns do
            if _btns[i] == key then
              keyFound = true
              break
            end
          end

          if not keyFound then
            App.Input._buttons[ key ] = 2
          end
        end
      end

      love.handlers[e]( a, b, c, d )
    end
  end

  return App.RUNNING
end

function App.close()
  App.RUNNING = false
end

-- >> INPUT
function Input.reset()
  App.Input._wheel = 0
end

function Input.pressed()
  for _, v in pairs( App.Input._keys ) do
    if v > 0 then
      return true
    end
  end

  return false
end

function Input.clicked()
  for _, v in pairs( App.Input._buttons ) do
    if v > 0 then
      return true
    end
  end

  return false
end

function Input.keyDown( key )
  return App.Input._keys[ key ] == 1
end

function Input.keyUp( key )
  return App.Input._keys[ key ] == 0
end

function Input.keyHeld( key )
  return App.Input._keys[ key ] == 2
end

function Input.mouseDown( button )
  return App.Input._buttons[ button ] == 1
end

function Input.mouseUp( button )
  return App.Input._buttons[ button ] == 0
end

function Input.mouseHeld( button )
  return App.Input._buttons[ button ] == 2
end

function Input.wheelUp()
  return App.Input._wheel == 1
end

function Input.wheelDown()
  return App.Input._wheel == -1
end

function Input.mousePos()
  return App.Input._mousePos
end
-- << INPUT

return App

Теперь из любого места приложения мы можем обратиться к объекту Input и получить нужные нам данные.

Для проверки работоспособности данной системы мы выведем на экран спрайт и заставим его поворачиваться в сторону курсора. По нажатию клавиши ESC приложение будет закрыто. Для этого в папке с проектом нужно создать дополнительную директорию data, в которую нужно будет положить нужный нам спрайт. Вывод спрайта будет осуществляться в файле main.lua, так что настало самое время его открыть и слегка модифицировать.

require "app"

function love.load()
  -- изображение
  mac = love.graphics.newImage("data/mac.png")

  -- размер изображения
  size = Vector( mac:getWidth(), mac:getHeight() )

  -- координаты
  pos = Vector( 400, 300 )

  -- текущий угол поворота
  angle = 0
end

function love.run()
  if love.math then
    love.math.setRandomSeed( os.time() )
  end

  if love.event then
    love.event.pump()
  end

  if love.load then
    love.load( arg )
  end

  if not (love.window and love.graphics and love.window.isCreated() and love.timer) then
    return
  end

  while App.run() do
    love.timer.step()
    local dt = love.timer.getDelta()

    -- update
    if Input.keyDown("escape") then
      App.close()
    end

    local mousePos = Input.mousePos()
    local dir = pos - mousePos

    angle = -math.atan2( dir.x, dir.y ) / (math.pi / 180)

    Input.reset()

    love.graphics.clear()
    love.graphics.origin()

    -- draw
    love.graphics.draw( mac,
                        pos.x,
                        pos.y,
                        math.rad( angle ),
                        1,
                        1,
                        size.x / 2,
                        size.y / 2 )

    love.graphics.present()

    love.timer.sleep( 0.001 )
  end
end

Скриншот результата:

Love2D | Шаблон игры

На этом создание нашей системы управления вводом окончено. Приветствуются любые комментарии и пожелания :)

Исходный код статьи: