Разбор проектов на Defold 12. Simple Ai (простой ИИ)

Всем привет :waving_hand:
Продолжаем серию разборов чужого кода с этой страницы: Публичный пример Defold.

Сегодня разбираем пример от britzlПростой искуственный интеллект.
Попробуйте этот проект в действии: запустите пример.
Исходная папка с проектом: ссылка на github.
Исходная папка с публичными примера Defold на github: ссылка на github.

Обращение к новичкам:

Я предполагаю, что у вас уже установлен Defold, если нет, перейдите по этой ссылке: Добро пожаловать в Defold.
Также, будет плюсом, если вы хотя бы поверхностно знакомы со строительными блоками Defold. Если нет, ознакомиться с основными концепциями Defold можно на официальном сайте — перейдите по этой ссылке.12.

Пример того, как скачать и открыть готовый проект:

Скачиваем архив с примерами проектов из github
Распаковываем скачанный ZIP архив примеров в любую папку во вашему усмотрению.
Переходим в папку examples.
Ищем проект play_animation.
Открываем game.project.

Внимание: скриншоты представлены ниже, это пример скачивания и открытия проекта, в этом примере мы рассматриваем проект с названием play_animation, потому название папок проекта будет отличаться.





В этом примере от britzl зомби появляются в случайных местах на карте, движутся к цели (либо к случайной позиции на стене, либо к игровому объекту “хитман”), а хитманы создаются по клику игрока и умирают от зомби при столкновении.

Файловая структура проекта:

Рассмотрим game.atlas и level.tilesours и level.tilemap:

В папке images у нас содержатся изображения, которые будут помещены в тайловый источник и атлас:
image
Они являются “источником”, откуда потом можно установить значения для спрайта или настроить тайловую карту:


Тайловый источник содержит и группу столкновений wall. С которыми игровой объект сможет взаимодействовать:

level.tilemap выступает в качестве изображения поверхности уровня.


Если вы посмотрите, изображение он берёт из тайлового источника:

Рассмотрим игровой объект hitman.go:


В качестве компонентов он содержит в себе: объект столкновения, скрипт, спрайт.
Тип для объекта столкновения стоит как Kinematic, что означает, что логика за столкновения будет управляться пользовательским скриптом.
Хитман имеет группу hitman.
Взаимодействовать будет с wall, zombie, detection.
Спрайт нужен для визуализации игрового объекта, изображение для спрайта берём из атласа:

Рассмотрим game.collection:

  • У нас имеется игровой объект go, который содержит в себе две фабрики: hitmanfactory и zombiedfactory, а также game.script, отвечающий за игровой процесс.
  • Игровой объект level, который содержит в себе объект столкновения, который будет препятствовать зомби выходить за тайлы стен:
    image
    А также, содержит тайловую карту уровня.
Рассмотрим hitman.script:

Этот скрипт управляет поведением объекта “хитман”.

local COLLISION_RESPONSE = hash("collision_response")
local ZOMBIE = hash("zombie")

function on_message(self, message_id, message, sender)
	if message_id == COLLISION_RESPONSE and message.group == ZOMBIE then
		go.delete()
	end
end

function on_reload(self)
    -- Add reload-handling code here
    -- Remove this function if not needed
end

COLLISION_RESPONSE = hash("collision_response") — хэш для обработки сообщений о столкновениях.
ZOMBIE = hash("zombie") — хэш для идентификации группы объектов “зомби”.

on_message(self, message_id, message, sender):

  • Обрабатывает сообщения, получаемые объектом хитмана.
  • Если сообщение — COLLISION_RESPONSE (столкновение) и группа объекта, с которым произошло столкновение, — ZOMBIE, то хитман удаляется с помощью go.delete().
Рассмотрим zombie.script:

Этот скрипт управляет поведением зомби, которые появляются на краях экрана, движутся к хитману и реагируют на столкновения.

Что делает зомби:

  • Зомби появляется на краю экрана и движется к случайной позиции на стене.
  • При столкновении с хитманом зомби начинает преследовать его, если он ближе текущей цели.
  • При контакте со стеной зомби корректирует свою позицию и выбирает новую случайную цель на стене.
  • Зомби плавно поворачивается к цели и движется с заданной скоростью.
go.property("speed", 50)

local COLLISION_RESPONSE = hash("collision_response")
local CONTACT_POINT_RESPONSE = hash("contact_point_response")
local HITMAN = hash("hitman")
local WALL = hash("wall")

go.property("speed", 50) — cкорость зомби, задаётся как свойство с начальным значением 50 единиц в секунду.
COLLISION_RESPONSE, CONTACT_POINT_RESPONSE, HITMAN, WALL: — хэши для обработки столкновений и идентификации групп объектов (“hitman” и “wall”).

local function random_position_on_wall()
	local wall = math.random(1, 4)
	if wall == 1 then
		return vmath.vector3(0, math.random(0, 640), 0)
	elseif wall == 2 then
		return vmath.vector3(1156, math.random(0, 640), 0)
	elseif wall == 3 then
		return vmath.vector3(math.random(0, 1156), 0, 0)
	else
		return vmath.vector3(math.random(0, 1156), 640, 0)
	end
end

Функция random_position_on_wall():

  • Генерирует случайную позицию на одной из четырёх стен игрового поля:
    • Стена 1: левая граница (x = 0, y случайное от 0 до 640).
    • Стена 2: правая граница (x = 1156, y случайное от 0 до 640).
    • Стена 3: нижняя граница (x случайное от 0 до 1156, y = 0).
    • Стена 4: верхняя граница (x случайное от 0 до 1156, y = 640).
  • Возвращает вектор vmath.vector3 с координатами.

Функция init(self):

  • Вызывается при создании зомби.
  • Инициализирует self.target_id = nil (идентификатор цели, например, хитмана).
  • Устанавливает начальную цель (self.target_position) как случайную позицию на стене, используя random_position_on_wall().

Функция update(self, dt):

  • Вызывается каждый кадр для обновления состояния зомби.
  • Поворот:
    • Вычисляет угол (target_angle) между текущей позицией зомби и целевой позицией (self.target_position).
    • Создаёт кватернион (target_quat) для поворота в сторону цели.
    • Использует сферическую интерполяцию (vmath.slerp) с коэффициентом 0.08 для плавного поворота зомби к цели.
    • Устанавливает новый поворот с помощью go.set_rotation(q).
  • Движение:
    • Получает текущую позицию зомби (go.get_world_position()).
    • Вычисляет направление движения (d) на основе поворота.
    • Перемещает зомби в направлении цели со скоростью self.speed * dt (где dt — время кадра).
  • Обновление цели:
    • Если у зомби есть цель (self.target_id), предполагается, что цель (хитман) будет уничтожена в этом кадре, поэтому выбирается новая случайная позиция на стене (random_position_on_wall()).
    • Сбрасывается self.target_id = nil, чтобы зомби мог выбрать новую цель в следующем кадре при новом столкновении

Функция on_message(self, message_id, message, sender):

  • Обрабатывает сообщения о столкновениях:
    • Столкновение с хитманом (COLLISION_RESPONSE и message.group == HITMAN):
      • Получает текущую позицию зомби (my_pos) и позицию объекта, с которым произошло столкновение (other_pos).
      • Вычисляет расстояние до текущей цели (distance_to_target) и до объекта столкновения (distance_to_collision).
      • Если объект столкновения ближе, чем текущая цель, или у зомби нет цели (self.target_id), обновляет цель на позицию хитмана (self.target_position = other_pos) и сохраняет его идентификатор (self.target_id = message.other_id).
    • Контакт со стеной (CONTACT_POINT_RESPONSE и message.group == WALL):
      • Сдвигает зомби назад на величину message.distance вдоль нормали (message.normal), чтобы избежать застревания в стене.
      • Выбирает новую случайную позицию на стене как новую цель.
Рассмотрим game.script:

Этот скрипт управляет общей логикой игры, включая создание зомби и хитманов, а также обработку ввода игрока.

Что делает game.script:

  • Инициализирует игру, создавая 10 зомби в случайных позициях.
  • Обрабатывает клики игрока, создавая хитманов в местах кликов.
  • Хранит списки активных зомби и хитманов.
  • Управляет фокусом ввода для обработки действий игрока.
local function random_position()
	return vmath.vector3(math.random(64, 1156 - 128), math.random(64, 640 - 128), 0)
end

Функция random_position():

  • Генерирует случайную позицию внутри игрового поля (с отступом от краёв: x от 64 до 1156-128, y от 64 до 640-128).
  • Используется для начального размещения зомби.
local function spawn_zombie()
	return factory.create("#zombiefactory", random_position())
end

Функция spawn_zombie():

  • Создаёт зомби с помощью фабрики #zombiefactory в случайной позиции, возвращаемой random_position().
local function spawn_hitman(position)
	return factory.create("#hitmanfactory", position)
end

Функция spawn_hitman(position):

  • Создаёт хитмана с помощью фабрики #hitmanfactory в указанной позиции (например, по координатам клика игрока).
function init(self)
	math.randomseed(os.time())
	self.hitmen = {}
	self.zombies = {}
	for i = 1, 10 do
		table.insert(self.zombies, spawn_zombie())
	end
	msg.post(".", "acquire_input_focus")
end

Функция init(self):

  • Вызывается при инициализации игры.
  • Устанавливает начальное значение генератора случайных чисел с помощью math.randomseed(os.time()).
  • Создаёт пустые таблицы self.hitmen и self.zombies для хранения идентификаторов хитманов и зомби.
  • Создаёт 10 зомби, добавляя их идентификаторы в self.zombies.
  • Запрашивает фокус ввода с помощью msg.post(".", "acquire_input_focus"), чтобы игра могла обрабатывать действия игрока.
function final(self)
	msg.post(".", "release_input_focus")
end

Функция final(self):

  • Вызывается при завершении игры.
  • Освобождает фокус ввода с помощью msg.post(".", "release_input_focus").

Функция on_input(self, action_id, action):

  • Обрабатывает ввод игрока.
  • Если действие — touch (хэш “touch”) и кнопка отпущена (action.released), создаётся новый хитман в позиции клика (action.screen_x, action.screen_y) и его идентификатор добавляется в self.hitmen.
    Файл настроек input_binding:
Как всё работает вместе:
  1. Инициализация:
  • При запуске игры (game.script) создаётся 10 зомби, которые появляются в случайных позициях внутри игрового поля (с отступом от краёв).
  • Каждый зомби (zombie.script) выбирает случайную позицию на одной из стен как начальную цель и начинает двигаться к ней.
  1. Движение зомби:
  • Зомби плавно поворачиваются и движутся к своей цели со скоростью 50 единиц в секунду.
  • Если зомби сталкивается со стеной, он корректирует свою позицию и выбирает новую случайную цель на стене.
  • Если зомби сталкивается с хитманом, он начинает преследовать его, если хитман ближе текущей цели.
  1. Создание хитманов:
  • Игрок кликает по экрану, и в месте клика создаётся хитман (game.script).
  • Хитман статичен (в скрипте нет логики движения).
  1. Столкновения:
  • При столкновении зомби с хитманом:
    • Зомби обновляет цель на позицию хитмана и начинает двигаться к нему.
    • Хитман удаляется (hitman.script).
  • Зомби, столкнувшийся со стеной, корректирует позицию и выбирает новую цель.
  1. Игровой цикл:
  • Зомби продолжают двигаться к своим целям (либо к хитманам, либо к случайным позициям на стенах).
  • Игрок может создавать новых хитманов, кликая по экрану, чтобы уничтожать зомби
Исправление бага:

Если во время игры тайловая карта не отображается, исправьте позицию игрового объекта level на -0.5, как пример:



Надеюсь, кому-нибудь этот материал будет полезен.
Всем спасибо за внимание :light_blue_heart:

Другие разборы:
  1. Разбор проектов на Defold 1. Tilemap Collisions [tilemap]
  2. Разбор проектов на Defold 2. Параллакс [parallax]
  3. Разбор проектов на Defold 3. Движение игровых объектов [animation, movement, input]
  4. Разбор проектов на Defold 4. Воспроизвести анимацию [animation, movement, input]
  5. Разбор проектов на Defold 5. Меню и игра. Прокси-коллекции [proxy-collection, gameloop, collection, gui]
  6. Разбор проектов на Defold 6. Пауза [пауза]
  7. Разбор проектов на Defold 7. Простая кнопка [простая кнопка]
  8. Разбор проектов на Defold 8. Фабрики и свойства [фабрики и свойства]
  9. Разбор проектов на Defold 9. Селектор уровня [селектор уровня].
  10. Разбор проектов на Defold 10. Боец комбо [ввод и анимация]
  11. Разбор проектов на Defold 11. Coin Magnet (Магнит монет) [фабрика]
  12. Разбор проектов на Defold 12. Simple Ai (простой ИИ) [ИИ][AI]
2 лайка

Очень классно, когда есть живой пример, и его можно вот так, под :microscope: микроскопом, можно тщательно рассмотреть пощупать.

1 лайк