Периодически спрашивают — как сделать умного зомби? На этот вопрос я создал видеоурок. Здесь же помещу код с него. И не большие комментарии. Видео стоит посмотреть ради того, чтобы знать на будущее какие стандартные ошибки и проблемы возникают при решении данного вида задач.
Начинается всё с алгоритма поведения врага. Так как это уроки для начинающих, то я не использую машину состояний нашего зомби. Всё будет элементарно и вульгарно — в бесконечном цикле.
Начнём с … спавнера мобов.
Этот простой код периодически создаёт клонов, от ранее созданного клона.
-- спавнер мобов 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 ]] --]]
Вот собственно и всё.
Разве что попутно занимались не только написанием скрипта, но и отладкой. В том числе и мультиплеера. И ещё.. показал, что не всегда нужны сервисы. 🙂