ROBLOX — Создание умного врага

Автор: | 21 января, 2022
Поделиться...

Периодически спрашивают — как сделать умного зомби? На этот вопрос я создал видеоурок. Здесь же помещу код с него. И не большие комментарии. Видео стоит посмотреть ради того, чтобы знать на будущее какие стандартные ошибки и проблемы возникают при решении данного вида задач.

Начинается всё с алгоритма поведения врага. Так как это уроки для начинающих, то я не использую машину состояний нашего зомби. Всё будет элементарно и вульгарно — в бесконечном цикле.

Начнём с … спавнера мобов.

Этот простой код периодически создаёт клонов, от ранее созданного клона.

-- спавнер мобов

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
	]]
--]]

Вот собственно и всё.

Разве что попутно занимались не только написанием скрипта, но и отладкой. В том числе и мультиплеера. И ещё.. показал, что не всегда нужны сервисы. 🙂


Поделиться...

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *