Осваиваем ECS evolved в Defold | Часть 3 | WASD-управление и движение игрока

В сегодняшнем уроки мы создадим WASD — управление персонажа с помощью ECS evovled:
movement

Затронутые темы:
Что такое delta-time (dt) и зачем он нужен?
Векторы. Движение игрового объекта
Векторы. Движение к цели. Вычисление длины вектора vmath.length(). Нормализация вектора vmath.normalize()

Подготовка к написанию кода:
  1. Создаём и добавляем необходимые файлы в структуру проекта:

  2. Структура игрового объекта:
    image

  3. Создаём игровой объект с фабрикой игровых объектов в main.collection:

  4. Настраиваем привязки ввода в game.input_binding:

Вне функций:

Развернуть:

Подключаем evolved модуль в скрипт:

local evolved = require("modules.evolved")

Создаём константы для удобства

-- Получаем центральные координаты экрана игры, используя размеры из конфига `game.project`
local WIDTH_CENTER = tonumber(sys.get_config("display.width")) / 2
local HEIGHT_CENTER = tonumber(sys.get_config("display.height")) / 2

-- для оптимизации производительности и удобства работы с системой ввода 
local KEY_LEFT = hash("left")
local KEY_RIGHT = hash("right")
local KEY_UP = hash("up")
local KEY_DOWN = hash("down")

-- Константа для скорости
local MAX_SPEED = 150

В init():

Развернуть:

Создаём таблицу, которая будет хранить состояния клавиш(нажатие, отпускание):

self.key = {
	left = false,
	right = false,
	up = false,
	down = false
}

Создаём фрагменты:

self.velocity = evolved.id()
self.position = evolved.id()
self.go_url = evolved.id()
self.steer = evolved.id()

Собираем сущность из фрагментов:

self.player_e = evolved.builder()
:set(self.velocity, vmath.vector3(0, 0, 0))
:set(self.position, vmath.vector3(WIDTH_CENTER, HEIGHT_CENTER, 0))
:set(self.go_url, factory.create("#factory"))
:set(self.steer, vmath.vector3(0, 0, 0))
:spawn()

Создаём запрос для системы движения:

self.movement_query = evolved.builder()
:include(self.velocity, self.position, self.go_url)
:build()

Создаём систему движения:

self.movement_system = evolved.builder()
:query(self.movement_query)
:execute(function(chunk, entities, count)
local positions = chunk:components(self.position)
local velocities = chunk:components(self.velocity)
local go_urls = chunk:components(self.go_url)

local dt = self.dt
	for i=1, count do
		positions[i] = positions[i] + velocities[i] * dt
		go.set_position(positions[i], go_urls[i])
	end		
end)
:build()

Создаём запрос для системы обработки ввода:

self.steer_query = evolved.builder()
:include(self.steer, self.velocity)
:build()

Создаём систему “руления”:

self.steer_system = evolved.builder()
:query(self.steer_query)
:execute(function(chunk, entities, count)
	local steers = chunk:components(self.steer)
	local vels = chunk:components(self.velocity)

	for i=1, count do
		local steer_dir = steers[i]
		if vmath.length(steer_dir) > 0 then
			vels[i] = vmath.normalize(steer_dir) * MAX_SPEED
		else
			vels[i] = vmath.vector3(0, 0, 0)
		end	
	end
end)
:build()

Запрашиваем фокус ввода:

msg.post(".", "acquire_input_focus")

В update():

Развернуть:

Сохраняем delta-time для использования в системы движения, запускаем системы:

function update(self, dt)
	self.dt = dt
	evolved.process(self.steer_system)
	evolved.process(self.movement_system)
end

В on_input():

Развернуть:
function on_input(self, action_id, action)
	if action_id == KEY_LEFT then self.key.left = action.pressed or (self.key.left and not action.released) end
	if action_id == KEY_RIGHT then self.key.right = action.pressed or (self.key.right and not action.released) end
	if action_id == KEY_DOWN then self.key.down = action.pressed or (self.key.down and not action.released) end
	if action_id == KEY_UP then self.key.up = action.pressed or (self.key.up and not action.released) end

	local x = (self.key.right and 1 or 0) - (self.key.left and 1 or 0)
	local y = (self.key.up and 1 or 0) - (self.key.down and 1 or 0)

	evolved.set(self.player_e, self.steer, vmath.vector3(x, y, 0))
end

Полный код:

Развернуть:
local evolved = require("modules.evolved")

local WIDTH_CENTER = tonumber(sys.get_config("display.width")) / 2
local HEIGHT_CENTER = tonumber(sys.get_config("display.height")) / 2

local KEY_LEFT = hash("left")
local KEY_RIGHT = hash("right")
local KEY_UP = hash("up")
local KEY_DOWN = hash("down")

local MAX_SPEED = 150

function init(self)

	self.key = {
		left = false,
		right = false,
		up = false,
		down = false
	}
	
	-- Создаём фрагменты
	self.velocity = evolved.id()
	self.position = evolved.id()
	self.go_url = evolved.id()
	self.steer = evolved.id()
	
-- Создаём сущность
	self.player_e = evolved.builder()
	:set(self.velocity, vmath.vector3(0, 0, 0))
	:set(self.position, vmath.vector3(WIDTH_CENTER, HEIGHT_CENTER, 0))
	:set(self.go_url, factory.create("#factory"))
	:set(self.steer, vmath.vector3(0, 0, 0))
	:spawn()

	-- Запрос для системы движения
	self.movement_query = evolved.builder()
	:include(self.velocity, self.position, self.go_url)
	:build()

	-- Система движения
	self.movement_system = evolved.builder()
	:query(self.movement_query)
	:execute(function(chunk, entities, count)
		local positions = chunk:components(self.position)
		local velocities = chunk:components(self.velocity)
		local go_urls = chunk:components(self.go_url)

		local dt = self.dt
		for i=1, count do
			positions[i] = positions[i] + velocities[i] * dt
			go.set_position(positions[i], go_urls[i])
		end		
	end)
	:build()

	--Создаём запрос обработки ввода
	self.steer_query = evolved.builder()
	:include(self.steer, self.velocity)
	:build()

	-- Система управления движения
	self.steer_system = evolved.builder()
	:query(self.steer_query)
	:execute(function(chunk, entities, count)
		local steers = chunk:components(self.steer)
		local vels = chunk:components(self.velocity)

		for i=1, count do
			local steer_dir = steers[i]
			if vmath.length(steer_dir) > 0 then
				vels[i] = vmath.normalize(steer_dir) * MAX_SPEED
			else
				vels[i] = vmath.vector3(0, 0, 0)
			end	
		end
	end)
	:build()

	msg.post(".", "acquire_input_focus")
end


function update(self, dt)
	self.dt = dt
	evolved.process(self.steer_system)
	evolved.process(self.movement_system)
end

function on_input(self, action_id, action)
	if action_id == KEY_LEFT then self.key.left = action.pressed or (self.key.left and not action.released) end
	if action_id == KEY_RIGHT then self.key.right = action.pressed or (self.key.right and not action.released) end
	if action_id == KEY_DOWN then self.key.down = action.pressed or (self.key.down and not action.released) end
	if action_id == KEY_UP then self.key.up = action.pressed or (self.key.up and not action.released) end

	local x = (self.key.right and 1 or 0) - (self.key.left and 1 or 0)
	local y = (self.key.up and 1 or 0) - (self.key.down and 1 or 0)

	evolved.set(self.player_e, self.steer, vmath.vector3(x, y, 0))
end