Периодически спрашивают — как сделать умного зомби? На этот вопрос я создал видеоурок. Здесь же помещу код с него. И не большие комментарии. Видео стоит посмотреть ради того, чтобы знать на будущее какие стандартные ошибки и проблемы возникают при решении данного вида задач.
Начинается всё с алгоритма поведения врага. Так как это уроки для начинающих, то я не использую машину состояний нашего зомби. Всё будет элементарно и вульгарно — в бесконечном цикле.
Начнём с … спавнера мобов.
Этот простой код периодически создаёт клонов, от ранее созданного клона.
-- спавнер мобов local spawnTimer = 10 -- интервал создания клонов local mob = script.DroolingZombie -- указываем объект local main= mob:Clone() -- клонируем его в ОЗУ mob.Parent = nil -- уничтожение оригинала!!! ix=0 while true do ix = ix + 1 wait(spawnTimer) -- пауза между спавнами tmp = main:Clone() -- создаём клон для спавна tmp.Name = "Zombie " .. ix -- новое имя -- задание новой позиции спавна dx = math.random(-30,30) dz = math.random(-30,30) vec = Vector3.new(dx,0,dz) newPos = tmp:GetPivot() + vec tmp:PivotTo(newPos) tmp.Parent = game.Workspace.Enemy end
И второй код, собственно сам AI нашего моба. С примитивным алгоритмом поведения.
- перебираем всех игроков
- если найден игрок в пределах зоны реакции запоминаем его
- если нашли ещё игрока в зоне реакции, проверяем дистанцию к кому ближе и сохраняем его
- создаём путь навигации waypoints до игрока
- перемещаемся по нему пока не настигнем или дистанция не станет большой
- если дистанция меньше дистанции атаки — атакуем
- начинаем с начала
--[[
Умный враг
1 - найти персонажа
2 - найти к нему путь если он в пределах доступности
3 - идти к ближайшему персонажу
4 - подошли? атаковать!
]]
------------------------------------------------------------------
RANGE = 50 -- дистанция реагирования на игрока
RANGEATTACK = 3 -- дистанция атаки
------------------------------------------------------------------
local PathfindingService = game:GetService("PathfindingService") -- активация сервиса поиска пути
-- создание объекта содержащего путь
-- 2,4,8 -- размеры большого персонажа
--local path = PathfindingService:CreatePath()
local path = PathfindingService:CreatePath({
AgentRadius = 2, -- расстояние до стены
AgentHeight = 8, -- высота проёма
AgentCanJump = true,-- можно ли прыгать
-- WaypointSpacing = math.huge, -- math.huge дистанция между точками
Costs = { -- вредительство от материалов
Water = 20
}
})
-- 1
players = game.Players:GetChildren() -- получить потомков
-- print(players)
myCharacter = script.Parent
-- print("MobPos:",myPos)
targetPlayer = nil -- игрок как цель
targetDist = 10000 -- больше, чем дистанция реакции
-- глобальные переменные
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
---local humanoid
-- функция поиска пути и перемещения
local function followPath(destination)
-- русчёт пути
local success, errorMessage = pcall(function()
path:ComputeAsync(myCharacter.PrimaryPart.Position, destination)
end)
local humanoid = myCharacter:WaitForChild("Humanoid")
-- print("Succes=",success)
-- print("path.Status:",path.Status)
if success and path.Status == Enum.PathStatus.Success then
-- Получить вейпоинты пути
waypoints = path:GetWaypoints()
print("Waypoints",waypoints)
-- Initially move to second waypoint (first waypoint is path start; skip it)
maxWaypoints = math.min(5,#waypoints)
-- перемещаемся ко 2-ой позиции
nextWaypointIndex = 2
for nextWaypointIndex = 2,maxWaypoints do
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
if waypoints[nextWaypointIndex].Action == Enum.PathWaypointAction.Jump then -- если попрыгунчик
humanoid.Jump= true
end
end
else
warn("Путь не найден!", errorMessage)
end
end
-- !!! Зацикливание
while true do
wait(1)
myPos = script.Parent:GetPivot() -- позиция моба
-- перебор игроков
for key, plr in pairs (players) do
-- print("key=", key)
-- print("model=", plr)
model = plr.Character
-- проверка на то, что персонаж живой
hum = model:FindFirstChild("Humanoid")
if hum then -- гуманоид
if hum.Health > 0 then -- ещё живой
-- проверка на то, что персонаж в пределах видимости
plPos=model:GetPivot()
-- print("PlayerPos:",plPos)
dist = (plPos.Position - myPos.Position).Magnitude
-- print ("Dist = ", dist)
if (dist < RANGE) then
if (dist<targetDist) then
targetPlayer = model
targetDist = dist
end
end
end
end
-- print("Target:",targetPlayer)
-- print("TargetDist:",targetDist)
end
-- поиск пути
if targetPlayer then -- если есть за кем идти!
dest = targetPlayer:GetPivot().Position
-- print("dest",dest)
-- следование
followPath(dest)
-- атака
plPos=model:GetPivot()
dist = (plPos.Position - myPos.Position).Magnitude
if (dist < RANGEATTACK) then
warn("АТАКУЮ!!")
end
end
end
--[[
-- проверка на заблокированный путь
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
-- Check if the obstacle is further down the path
if blockedWaypointIndex >= nextWaypointIndex then
-- Stop detecting path blockage until path is re-computed
blockedConnection:Disconnect()
-- Call function to re-compute new path
followPath(destination)
end
end)
-- Detect when movement to next waypoint is complete
-- проверка на достижение указанной точки и переход к следующей
--[[if not reachedConnection then
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and nextWaypointIndex < #waypoints then
-- Increase waypoint index and move to next waypoint
nextWaypointIndex += 1
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
reachedConnection:Disconnect()
blockedConnection:Disconnect()
end
end)
end
]]
--]]
Вот собственно и всё.
Разве что попутно занимались не только написанием скрипта, но и отладкой. В том числе и мультиплеера. И ещё.. показал, что не всегда нужны сервисы. 🙂