Пойман титан:
Ценность этого титана: управление камерой в 3D.
Проект показывает как можно:
- Следить за целью (персонажем или объектом)
- Позволяет вращать вид мышью/сенсором
- Позволяет приближать/отдалять (зум колёсиком)
- Может упираться в стены (физика)
Первый взгляд на титана
Запустите проект для того, чтобы увидеть мощь титана в действии по клавише ctrl + B.
Это не колоссальный титан, но представляет для нас ценность в работе с 3D.
Перейдём в main.collection для того, чтобы посмотреть на игровой мир коллекции в 3D.
Должно получиться примерно так:
Зажмите ctrl + ЛКМ для того, чтобы можно было использовать поворот в пространстве этой коллекции:
(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
Зажмите atl + ЛКМ для того, чтобы можно было перемещаться в пространстве коллекции:
(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
Прокрутите колёсико мыши, для того, чтобы изменить зум просмотра игровой коллекции:
(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
В панели Outline редактора можно просмотреть структуру открытой коллекции:
Нажмите на какой-либо из квадратиков, чтобы открыть структуру игрового объекта:
Нажми на игровой объект floor и переместите их в пространстве:
(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
Ты можешь вращать игровой объект:
(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
Ты можешь масштабировать игровой объект:
(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
Попробуй проделать вышеупомянутые действия с игровым объектом go2:
Изучаем анатомию этого титана
Игровые объекты могут содержать разные компоненты.
В нашей случае они ограничиваются такими компонентами как: игровой скрипт, камера,
модель, объект столкновения.
Затронем каждый компонент по отдельности:
Скрипт — это “треугольник на спине титана”. Отвечает за логику игрового объекта, если в скрипте написать: “изменяй позицию”, то игровой объект изменит позицию во время выполнения скрипта в игре.
Давайте перейдём в main.script и посмотрим на функцию go.animate():
go.animate("/cube", "position.x", go.PLAYBACK_LOOP_PINGPONG, -2, go.EASING_INOUTQUAD, 5)
Измените четвертый параметр (to) функции на 10. Что вы увидите?
В чём разница от минусового значения от плюсового?
Поиграйте с easing-функциями.
(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
Модель — сделайте двойной щелчок по какой-нибудь модели (значок пирамидки).
Можете посмотреть или изменить свойства этой модели.
Например, можете изменить свойство
text0 (текстуру модели):Камера — это “глаз”, с помощью которого мы видим игровые объекты в игре.
В этом проекте мы видим игровые объекты в перспективе, так как галочка Orthographic Projection снята:
Поставьте галочку и вы увидите:
Объект столкновения — отвечает за симуляцию физики в игре. Физику в игре можно “писать” самому с помощью типа объекта столкновения (Kinematic), или использовать встроенную физику Defold.
Измените в игровом объекте floor тип компонента collisionobject на Dynamic и измените массу на 1.0:
Запустите проект (
ctrl + B).(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
Сердце камеры — как я понимаю, это рендер скрипт.
В нашем случае — это orbit.script в папке orbit.
Давайте попробуем поиграться с ним.
Настройка различных свойств для камеры:
go.property("target", hash("")) -- игровой объект, к которому прикрепляется камера
go.property("distance", 10) -- начальная значение дистанции от камеры
go.property("distance_min", 2) -- минимальное значение приближения камеры
go.property("distance_max", 20) -- максимальное значение отдаления камеры
go.property("angle_x", 0)
go.property("angle_y", 0)
go.property("angle_min", -1.5)
go.property("angle_max", 0.5)
go.property("collisions", false) -- столкновения с коллайдерами
Функция для установки камеры в определенную позицию.
local function set_camera(self)
self.center = self.target ~= hash("") and go.get_world_position(self.target) or vmath.vector3(0)
local rot = vmath.quat_rotation_y(self.angle_y) * vmath.quat_rotation_x(self.angle_x)
local pos = vmath.rotate(rot, vmath.vector3(0, 0, self.distance)) + self.center
go.set_rotation(rot)
if self.collisions then
local result = physics.raycast(self.center, pos, {hash("default")})
if result then
pos = (pos - self.center) * result.fraction + self.center
end
end
go.set_position(pos)
end
Различные команды инициализации компонентов:
function init(self)
msg.post(".", "acquire_input_focus")
msg.post("@render:", "use_camera_projection")
msg.post("camera", "acquire_camera_focus")
self.touch_down = false
set_camera(self)
end
Обновление камеры в каждом кадре:
function update(self, dt)
if self.target ~= hash("") and self.center ~= go.get_world_position(self.target) then
set_camera(self)
end
end
Обработка ввода:
function on_input(self, action_id, action)
if action_id == hash("touch") then
self.touch_down = true
if action.released then
self.touch_down = false
end
end
if self.touch_down and action_id == nil then
self.angle_x = self.angle_x + action.dy * 0.01
self.angle_y = self.angle_y - action.dx * 0.01
self.angle_x = math.min(self.angle_x, self.angle_max)
self.angle_x = math.max(self.angle_x, self.angle_min)
set_camera(self)
end
if action_id == hash("wheel_down") then
self.distance = self.distance + 0.2
self.distance = math.min(self.distance, self.distance_max)
set_camera(self)
elseif action_id == hash("wheel_up") then
self.distance = self.distance - 0.2
self.distance = math.max(self.distance, self.distance_min)
set_camera(self)
end
end
На данный момент, для того, чтобы поворачивать камеру, на требуется зажать ЛКМ.
Давайте уберём эту настройку.
Закомментируем эти строки:
-- в init()
--self.touch_down = false
-- в on_input()
-- if action_id == hash("touch") then
-- self.touch_down = true
-- if action.released then
-- self.touch_down = false
-- end
-- end
А self.touch_down = true вынесем за блок кода, отвечающий за обработку действия "touch":
function on_input(self, action_id, action)
-- if action_id == hash("touch") then
-- self.touch_down = true
-- if action.released then
-- self.touch_down = false
-- end
-- end
self.touch_down = true
if self.touch_down and action_id == nil then
self.angle_x = self.angle_x + action.dy * 0.01
self.angle_y = self.angle_y - action.dx * 0.01
self.angle_x = math.min(self.angle_x, self.angle_max)
self.angle_x = math.max(self.angle_x, self.angle_min)
set_camera(self)`Текст «как есть» (без применения форматирования)`
end
-- остальной код
(Здесь должна быть GIF-анимация, которая показывает вышеупомянутый процесс).
Давайте изменим единицу зума в обработке ввода “wheel_down” и “wheel_up”:
self.distance = self.distance + 1
self.distance = self.distance - 1
-- измените на
self.distance = self.distance + 5
self.distance = self.distance - 5
Намного быстрее происходит приближение и отдаление.
Давайте увеличим максимальные значения для отдаления.
За это отвечает пользовательское свойство self.distance_max:
go.property("distance_max", 20)
-- измените на
go.property("distance_max", 2000)
Мы можем препятствовать камере заходить за игровые объекты, имеющие объекты столкновения:
Для этого нужно установить галочку в панели
Properties:Пока я не понял почему, но из скрипта значение этого свойства не меняется:
go.property("collisions", false)
Поэтому настраиваем в панели редактора Properties.
Давайте также через панель Properties изменим целевой объект, к которому прикрепляется камера. Измените значение свойства target на /go2.
Запустите проект:
В Defold существуют как локальные, так и мировые координаты.
Локальные координаты — показывают относительность внутри родителя. Какую позицию игровой объект или компонент занимает, в иерархии родитель-ребенок.
Мировые координаты — показывают положение внутри всей игровой сцены, в нашем случае, коллекция main.collection.
self.center — это не позиция камеры, а позиция точки, вокруг которой камера вращается (центр вращения).
self.center = self.target ~= hash("") and go.get_world_position(self.target) or vmath.vector3(0)
Центр вращения равен или мировой позиции целевого объекта, или нулевому вектору.
Создание угла поворота:
local rot = vmath.quat_rotation_y(self.angle_y) * vmath.quat_rotation_x(self.angle_x)
local pos = vmath.rotate(rot, vmath.vector3(0, 0, self.distance)) + self.center
rot — это угол вращения (кватернион — специальный способ записать повороты в 3D).
self.angle_y— вращение вверх-вниз (как головой кивать)self.angle_x— вращение влево-вправо (как головой мотать)
pos — это позиция камеры:
vmath.vector3(0, 0, self.distance)— камера в начале координат, но далеко назад по оси Z (расстояние self.distance)vmath.rotate(rot, ...)— поворачиваем эту позицию на угол rot+ self.center— сдвигаем на центр цели
Аналогия:
- Представь, что камера стоит в 5 метрах сзади от персонажа.
- Ты берёшь эту камеру и крутишь её вокруг персонажа (как на стержне).
- Вот где она окажется — это новая позиция.
Попробуй поиграться с значениями:
local pos = vmath.rotate(rot, vmath.vector3(0, 0, self.distance)) + self.center
local pos = vmath.rotate(rot, vmath.vector3(0, 1, self.distance)) + self.center
local pos = vmath.rotate(rot, vmath.vector3(1, 0, self.distance)) + self.center
local pos = vmath.rotate(rot, vmath.vector3(0, 0, 0)) + self.center
Устанавливаем вращение камеры на угол rot:
go.set_rotation(rot)
Проверка на столкновения с игровыми объектами:
if self.collisions then
local result = physics.raycast(self.center, pos, {hash("default")})
if result then
pos = (pos - self.center) * result.fraction + self.center
end
end
Что здесь:
physics.raycast— это луч от персонажа (self.center) до камеры (pos).- Если луч попал в стену (result) — камера “придвигается” к персонажу ближе.
result.fraction— на сколько процентов луч прошёл до препятствия (0.5 = луч пошёл на половину).
Аналогия:
- Если камера “врезается в стену”, она автоматически подтягивается поближе к персонажу, чтобы не быть под стеной.
Устанавливаем финальную позицию камеры:
go.set_position(pos)
Визуально:
Персонаж (self.center)
|
| <- расстояние self.distance
|
[Камера] <- вращается вокруг персонажа под углами angle_x и angle_y
Говорим рендер-скрипту: “Используй мою камеру для показа”:
msg.post("@render:", "use_camera_projection")
Обработка сообщений находится в main.render_script.
В game.project установлен именно этот рендер:
Сообщение объекту с ID “camera”: “Ты теперь активная камера.”
## `msg.post("camera", "acquire_camera_focus")`
“Камера вращается вокруг персонажа на расстоянии, смотря на него. Если камера упирается в стену, она подтягивается поближе. Камера всегда повёрнута так, чтобы ‘смотреть’ на персонажа.”
Выражаю благодарность abadonna · GitHub за то, что делится проектами на GitHub.
И astrochili (Roman Silin) · GitHub за то, что собрал список проектов по Defold.
Где найти титанов:





















