Help with making npc's attack dodgable

I want to make the npc’s attack be able to get dodge by player by simply getting out of the way before the attack animation reach the hit event

as you can see currently if the attack animation started the npc’s attack will gurantee to hit the player and that in my book seem a little unfun to play against

I’m still new to these kind of stuff so if you find my script poorly written please give me a recommendation beside the main problem

script
local npc = script.Parent
local MaxDistance = 30
local OriginalPosition = npc.PrimaryPart.Position
local AttackRange = 5
local dmg = 1

local AttackAnim = script.Attack
local AttackTrack = npc.Humanoid:LoadAnimation(AttackAnim)

local Debounce = false

local function IsAttacking(closest_player)
	if Debounce == true then return end
	Debounce = true
	npc.Humanoid.WalkSpeed = 0
	AttackTrack:Play()
	AttackTrack:GetMarkerReachedSignal("Hit"):Connect(function()
		local player = closest_player
		game:GetService("ReplicatedStorage").Event.Hit:FireClient(player, dmg)
	end)
	AttackTrack.Stopped:Connect(function()
		task.delay(2, function()
			npc.Humanoid.WalkSpeed = 10
			Debounce = false
		end)
	end)
end

local function Attack()
	local closest_player, closest_distance = nil, AttackRange
	for i, player in pairs(game.Players:GetChildren()) do
		local character = player.Character
		if character then
			local Humanoid = character:FindFirstChild("Humanoid")
			if Humanoid.Health ~= 0.1 then
				local distance = (npc.HumanoidRootPart.Position - character.HumanoidRootPart.Position).Magnitude
				if distance < closest_distance then
					closest_player = player
					closest_distance = distance
				end	
			end
		end
	end
	if closest_player then
		IsAttacking(closest_player)
	end
end

local function Chasing()
	local closest_player, closest_distance = nil, MaxDistance
	for i, player in pairs(game.Players:GetChildren()) do
		local character = player.Character
		if character then
			local Humanoid = character:FindFirstChild("Humanoid")
			if Humanoid.Health ~= 0.1 then
				local distance = (npc.HumanoidRootPart.Position - character.HumanoidRootPart.Position).Magnitude
				if distance < closest_distance then
					closest_player = player
					closest_distance = distance
				end	
			end
		end
	end
	if closest_player then
		npc.Humanoid:MoveTo(closest_player.Character.HumanoidRootPart.Position)
	else
		npc.Humanoid:MoveTo(OriginalPosition)
	end
end

game:GetService("RunService").Stepped:Connect(function()
	Chasing()
	Attack()
end)

you could check if the arms are getting touched while the animation plays? So like something like this

local unloadedAnim = script.Parent.Animation
local LoadedAnim = script.Parent.Humanoid:LoadAnimation(unloadedAnim)

local function Attack()
--Do whatever blah blah

LoadedAnim:Play()

Script.Parent.LeftArm.Touched:Connect(function()
--make it deal damage and stuff
end)
end

this should most likely work

1 Like

not sure if u already managed to solve ur issue, but in case u haven’t:

  • i’d strongly suggest using hitboxes instead of checking the distance (personal preference)
  • u should define the main variables u plan on using at the top of ur script to reduce the repetitiveness and increase readability
  • using hitboxes after a short delay is also better because that lets u dodge quickly

this is the result:

the npc's script after the minor behavior tweaks
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

--\\ bot stuff
local bot = script.Parent
local botNoid = bot:FindFirstChildWhichIsA("Humanoid")
local botRoot = bot.PrimaryPart

local botStats = { -- table of constants
	SCAN_RANGE = 30,
	ATTACK_DISTANCE = 7,
	DEFAULT_WALK_SPEED = 11,
	ATTACK_WALK_SPEED = 0,
	HITBOX_SIZE = Vector3.new(4, 4, 6), -- hitbox size (for easy dodging)
	ANIM_OFFSET_TIME = 0.2,
}

--\\ misc variables
local currentTime = 0
local refreshTime = 0.5
local debounce = false

local windupAnim = script:WaitForChild("windup-anim")
local attackAnim = script:WaitForChild("attack-anim")

local windupTrack = botNoid:LoadAnimation(windupAnim)
local attackTrack = botNoid:LoadAnimation(attackAnim)

--\\ utility functions
local function initialize()
	botNoid.WalkSpeed = botStats.DEFAULT_WALK_SPEED
	windupTrack.Priority = Enum.AnimationPriority.Action2
	attackTrack.Priority = Enum.AnimationPriority.Action3
	
	-- disabling humanoid states that i'm assuming are gonna remain unused
	botNoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)
	botNoid:SetStateEnabled(Enum.HumanoidStateType.Seated, false)
	botNoid:SetStateEnabled(Enum.HumanoidStateType.Physics, false)
	botNoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)
	botNoid:SetStateEnabled(Enum.HumanoidStateType.Climbing, false)
	botRoot:SetNetworkOwner(nil) -- keep exploiters from moving the npc around
end

local function getDistance(a: Vector3, b: Vector3): number
	return (b - a).Magnitude
end

local function checkHit(victim: Player)
	attackTrack:Play()
	local plrChar = victim.Character
	if not plrChar then return end

	local victimRoot = plrChar.PrimaryPart
	if not victimRoot then return end

	local botPos = botRoot.Position
	local plrPos = victimRoot.Position

	local hitbox = Instance.new("Part")
	hitbox.CFrame = botRoot.CFrame * CFrame.new(0, 0, -botStats.HITBOX_SIZE.Z / 1.6) -- forward offset
	hitbox.Anchored = true
	hitbox.Material = Enum.Material.Neon
	hitbox.Transparency = 0.8
	hitbox.Color = Color3.fromRGB(156, 75, 255)
	hitbox.CanCollide = false
	hitbox.Name = `hitbox`
	hitbox.Size = botStats.HITBOX_SIZE
	hitbox.Parent = workspace

	hitbox.Touched:Connect(function(hit)
		if hit and hit:IsDescendantOf(plrChar) then
			print(`breaking news: {victim} viciously mauled`)
			-- now u can place ur event here and whatnot
		end
	end)

	task.delay(0.5, function()
		hitbox:Destroy()
	end)
end

--\\ this is where the fun begins
function botMain()
	local function performManhunt(): Player?
		local closestPlayer = nil
		local closestDistance = botStats.SCAN_RANGE

		for _, player in Players:GetPlayers() do
			local character = player.Character
			if character then
				local humanoid = character:FindFirstChildWhichIsA("Humanoid")
				local rootPart = character:FindFirstChild("HumanoidRootPart")

				if humanoid and rootPart and humanoid.Health > 0 then
					local distance = getDistance(botRoot.Position, rootPart.Position)
					if distance < closestDistance then
						closestPlayer = player
						closestDistance = distance
					end
				end
			end
		end

		return closestPlayer
	end

	local function attackVictim(victim: Player)
		if victim and not debounce then
			debounce = true
			botNoid.WalkSpeed = botStats.ATTACK_WALK_SPEED
			botNoid:MoveTo(botRoot.Position) -- HALT!

			windupTrack:Play()
			task.wait(windupTrack.Length + botStats.ANIM_OFFSET_TIME)
			checkHit(victim)

			task.delay(1, function()
				botNoid.WalkSpeed = botStats.DEFAULT_WALK_SPEED
				debounce = false
			end)
		end
	end

	local currentVictim = performManhunt()
	if currentVictim then
		local plrChar = currentVictim.Character
		local victimRoot = plrChar.PrimaryPart

		if getDistance(botRoot.Position, victimRoot.Position) <= botStats.ATTACK_DISTANCE then
			attackVictim(currentVictim)
		else
			botNoid:MoveTo(victimRoot.Position)
		end
	end
end

--\\ run logic
RunService.Heartbeat:Connect(function(dt)
	currentTime += dt

	if currentTime >= refreshTime then
		currentTime = 0 -- reset timer
		botMain()
	end
end)

initialize()

the npc itself:
hitbox-npc.rbxm (13.9 KB)

1 Like

I did get my solution already but I still want to know how to create hitbox so thank you for this anyway