Знакомство c API ортогональной камеры от britzl

Всем привет :waving_hand:

В этой теме мы попробуем немного познакомиться с API ортографической камеры для игрового движка Defold от britzl. Потому что именно этот API камеры используется во многих примерах Defold Public Examples в качестве библиотечной зависимости. Мы не будем разбирать код из примера, оставим это на другой раз. В этом посте, мы просто ознакомимся с некоторыми функциями “пользовательской” камерой от britzl, для того, чтобы в дальнейшем нам было бы легче разбираться в других проектах.

Orthographic Camera API

Orthographic Camera API от britzl — API ортографической камеры позволяет с легкостью преобразовывать экранные координаты в мировые, плавно следовать за игровым объектом и создавать эффект дрожания экрана.

Ссылка на Orthographic Camera API.

Зависимость —это всё, что тебе нужно подключить извне, установить или правильно собрать, чтобы твой проект работал.

Для того, чтобы воспользоваться предоставленными возможностями ортогональной камеры от britzl, нам необходимо добавить этот проект в свой проект (если он не добавлен). Откройте файл game.project и в поле dependencies под проектом добавьте:

https://github.com/britzl/defold-orthographic/archive/master.zip

Или укажите ZIP-файл определенного релиза .

Нажимаем на плюсик и вставляем вышеприведенную ссылку на архив в качестве зависимости в наш проект в поле Dependencies:

image

После добавления зависимости:

  1. Сохраняем файл game.project (Ctrl+S).
  2. Теперь выбираем Project ▸ Fetch Libraries, чтобы обновления вступили в силу

В качестве основы под практику возьмём проект publicexamples/examples/top_down_spaceship at master · britzl/publicexamples · GitHub.
Как я понял, встроенную камеру в игровой движок в какой-то версии поменяли. Теперь её нужно настраивать по другому, поэтому пример не является исправным. Но, сейчас мы постараемся исправить этот пример с применением камеры от britzl.
Пример в действии: Top Down Spaceship 0.0

Добавление камеры в проект:

Во-первых, добавим в этот проект библиотечную зависимость камеры от britzl, как было сказано выше.
Во-вторых, переходим в game.projectBootstrap и меняем файл скрипт рендера на тот, что был добавлен вместе с включенной библиотекой orthographic. Он позволит работать нам с “новой” камерой.


В-третьих, переходим в top_down_spaceship.collection и добавляем игровой объект в коллекцию из зависимости ortographic.


image

В-четвертых, удаляем эту строку:

go.set_position(pos + vmath.vector3(-568, - 320, 0), "camera")

В-пятых, добавляем строчку кода в function init(self):

camera.follow(CAMERA_ID, "/player")

Теперь запускаем проект(CTRL + B или f5).

Для того, чтобы можно было воспользоваться функциями API в нашем скрипте, подключим Lua-модуль camera.lua.
С помощью переменной cameraи точечной нотации мы сможем использовать функции подключенного модуля.

local camera = require "orthographic.camera"

Создаём хеш из строк "/camera" — это идентификатор игрового объекта камеры, которую мы добавляли ранее:

local CAMERA_ID = hash("/camera")

Слежение/прекращение слежения за игровым объектом:

С помощью этой функции мы заставили следовать камеру за игровым объектом:

camera.follow(CAMERA_ID, "/player")

В функцию init(self) вставьте создадим переменную-флаг, который будет отвечать за текущее состояние камеры — следует/не следует за игровым объектом:

self.isFollow = true

В функцию on_input добавьте такой блок кода:

-- устанавливаем/убираем слежение камерой за spaceship
	if action_id == hash("follow") and action.released then
		if self.isFollow then
			camera.unfollow(CAMERA_ID)
			self.isFollow = false
		else
			camera.follow(CAMERA_ID, "/player")
			self.isFollow = true
		end
	end

В папку game.input_binding добавляем новую привязку ввода:
image

Запустите проект и нажмите клавишу F на клавиатуре. Камера должна прекращать/начинать следовать за космическим кораблём.

Таким образом, camera.follow(...) и camera.unfollow(...) отвечают за слежение/прекращение слежения за игровым объектом: ссылка на детальное ознакомление с этой функцией.

Добавление зума в проект:

Зум — это уровень масштабирования камеры.

Добавим возможность изменять масштаб камеры по нажатию клавиши Z и X клавиатуры, также не забудьте про добавления привязок ввода:

	-- увеличиваем/уменьшаем масштабирование камеры
	if action_id == hash("zoom_inc") and action.released then
		local zoom = camera.get_zoom(CAMERA_ID)
		camera.set_zoom(CAMERA_ID, zoom + 0.1)
		print("Zoom increased to:", camera.get_zoom(CAMERA_ID))
	elseif action_id == hash("zoom_dec") and action.released then
		local zoom = camera.get_zoom(CAMERA_ID)
		camera.set_zoom(CAMERA_ID, zoom - 0.1)
		print("Zoom increased to:", camera.get_zoom(CAMERA_ID))
	end
your code goes here

Ссылка на информацию про зум: GitHub - britzl/defold-orthographic: Orthographic camera functionality for the Defold game engine

Причина тряски?

Добавьте в on_message, в блок кода, отвечающий за столкновение с невидимой стеной функцию:

camera.shake(CAMERA_ID, 0.05, 1, "both", function()
			print("Пилот контужен")
			camera.stop_shaking(CAMERA_ID)
		 end)

Эта функция отвечает за тряску экрана. Последний аргумент функции — коллбэк-функция, она вызывается после завершения функции shake. В нашем случае она останавливает тряску камеры и выводит сообщение о том, что пилот был контужен :slightly_smiling_face:

Ссылка на функцию: тык.

Также, я не упомянул о том, что API можно использовать двумя способами:

  1. Вызов функций camera.lua модуля
  2. Отправка сообщений вcamera.script

Мы использовали первый способ.
Второй способ заключается в том, что мы просто отправляем сообщение камере или скрипту камеры:

msg.post("camera", "shake", { intensity = 0.05, duration = 1, direction = "both"})

Но, как я понял, в таком случае можно передать три аргумента, вместо четырёх:

Если есть желание, попробуйте реализовать слежение камеры за пулей.

Итоговый код в файле spaceship.script:
local camera = require "orthographic.camera"
local CAMERA_ID = hash("/camera")

go.property("angular_velocity", 5)
go.property("linear_velocity", 900)

function init(self)
	msg.post(".", "acquire_input_focus")
	self.isFollow = true
	camera.follow(CAMERA_ID, "/player")
	self.rotate = 0
end

function final(self)
	msg.post(".", "release_input_focus")
end

function update(self, dt)
	-- rotate based on user input and angular velocity (radians per second)
	local rotation = go.get_rotation()
	rotation = rotation * vmath.quat_rotation_z(self.angular_velocity * self.rotate * dt)
	go.set_rotation(rotation)
		
	-- move in direction of rotation with linear velocity (pixels per second)
	local pos = go.get_position()
	local distance = self.linear_velocity * dt
	local direction = vmath.rotate(rotation, vmath.vector3(0, distance, 0))
	pos = pos + direction
	go.set_position(pos)
end

function on_input(self, action_id, action)

	-- set direction of rotation based on user input
	if action_id == hash("left") then
		if action.pressed then
			self.rotate = 1
		elseif action.released then
			self.rotate = 0
		end
	elseif action_id == hash("right") then
		if action.pressed then
			self.rotate = -1
		elseif action.released then
			self.rotate = 0
		end
	end

	-- увеличиваем/уменьшаем масштабирование камеры
	if action_id == hash("zoom_inc") and action.released then
		local zoom = camera.get_zoom(CAMERA_ID)
		camera.set_zoom(CAMERA_ID, zoom + 0.1)
		print("Zoom increased to:", camera.get_zoom(CAMERA_ID))
	elseif action_id == hash("zoom_dec") and action.released then
		local zoom = camera.get_zoom(CAMERA_ID)
		camera.set_zoom(CAMERA_ID, zoom - 0.1)
		print("Zoom increased to:", camera.get_zoom(CAMERA_ID))
	end

	-- устанавливаем/убираем слежение камерой за spaceship
	if action_id == hash("follow") and action.released then
		if self.isFollow then
			camera.unfollow(CAMERA_ID)
			self.isFollow = false
		else
			camera.follow(CAMERA_ID, "/player")
			self.isFollow = true
		end
	end
	-- fire bullets
	-- spawn bullet in front of the spaceship (based on rotation)
	if (action_id == hash("trigger") or action_id == hash("touch")) and action.released then
		local rotation = go.get_rotation()
		local position = go.get_position() + vmath.rotate(rotation, vmath.vector3(0, 60, 0))
		local bullet = factory.create("#bulletfactory", position, rotation)
		local to = position + vmath.rotate(rotation, vmath.vector3(0, 1000, 0))
		go.animate(bullet, "position", go.PLAYBACK_ONCE_FORWARD, to, go.EASING_LINEAR, 0.5, 0, function()
			go.delete(bullet)
		end)
	end
end

function on_message(self, message_id, message, sender)
	-- basic contact separation
	if message_id == hash("contact_point_response") and message.group == hash("wall") then
		go.set_position(go.get_position() + message.normal * message.distance)
		-- трясём камеру
		camera.shake(CAMERA_ID, 0.05, 1, "both", function()
			print("Пилот был контужен")
			camera.stop_shaking(CAMERA_ID)
		 end)
	end
end

Тоже самое, но в видеоформате.

Спасибо за внимание :light_blue_heart:

2 Likes