How to Make a Boss Battle [Basic; Not Multi-Staged]

Hello there! It is I, InfernalProgrammer.

Today I’ll be showing you How to Make a Boss Battle using functions and such.

Also, A quick Little Note before we begin. The Player’s RigType will be R6, not R15 as most weapons aren’t R15 Compatible.

I WILL BE GOING QUITE IN DEPTH HERE! THIS WILL COVER MAKING A.I AND SUCH!

Step 1 - Design

The first thing that goes Into making a Boss battle is Not only the scripting, But Coming up with an idea that Fits the Theme of your game. I won’t be going in-depth about building as I am not that great with it. I’ll be going with a Blank template for this Tutorial.

(Ah yes, The stereotypical Sword-Wielding Dummy)

Scripting Pt.1

Alright, So you (hopefully) have your Boss all Modeled, Rigged, and Maybe even Animated. Let’s do this!

NOTE: YOU DO NOT NEED TO COPY THIS LINE FOR LINE! FEEL FREE TO CUSTOMIZE HOWEVER YOU MAY LIKE!

First off, Let’s put a “ServerScript” in “ServerScriptService”.

I’d recommend naming it “BossMain”, to help with Organization.

Step 1 - Variables


local Boss = game.Workspace.BossandEnemies.Boss

local BossHum = Boss:WaitForChild("Humanoid")

local BossRoot = Boss:WaitForChild("HumanoidRootPart")

local char -- This will Be Declared Later.

local players = game:GetService("Players")

In Lua (Which is the Programming Language Roblox Studio uses), “Local” is declaring either a Shortcut, or a Variable.
Now, I know I could’ve done

game.Players

For the “players” Variable, But, This is the way most people do it. (Including me lol)

Scripting Pt.2 - Animations

Now, Animations are a little bit tricky to learn, Especially if you are…Somewhat new, Like I am.
To load an Animation, You want to
A) Make the Animation
and
B) Load The Animation
To Load The Animation, You want to do
(This is also declaring a Variable)
NOTE: YOU WILL NEED A SCRIPT INSIDE OF YOUR BOSS CALLED “ANIMATE” AND A “ANIMATOR” OBJECT INSIDE YOUR HUMANOID!

local [animation Name here]Anim = Boss.Animate.[animation Name here]
local [animation Name here]Track = BossHum.Animator:LoadAnimation([animation Name here]Anim)

The “[animation Name here]Track” Global will play a HUGE part later on. If you don’t have any Animations, I’d make them now.

Scripting Pt.3 - Functions and Such

First, Before we get into the meat of this, We need to declare some more variables, and Get our Players Character! You may be thinking to use “Player.Character”. Your partially right.
If we we’re to do “players.Character” we would get the Error:
"Attempt to index nil with “Character”.
This is why we need to do this:

game.Players.PlayerAdded:Connect(function(plr)
	char = plr.Character or plr.CharacterAdded:Wait()
end)

This Function is telling the Client:
Hey, If a Player joins the game, make the global named “char” (Short for “character”) that players Character.

Now, Lets get onto the MEAT of this code.
Setting the Bosses Health
Now, Lets set the Boss’s Maximum Allotted Health. (Allotted is a fancier term for “Allowed”)

if game.Players.PlayerAdded then
	BossHum.MaxHealth = 500 --Sets Boss's Maximum Allotted Health
	BossHum.Health = 500 --Sets Boss's Health
	pcall(main)
end

This is saying “If a Player joins the game, Then Declare the Boss’s Maximum Allotted Health, then Set the Boss’s Health to that Maximum Health.”
You can set the Boss’s Max Health to whatever you want. Like I said, Be Creative!

NOTE: THIS SECTION GETS VERY COMPLICATED VERY QUICKLY! You’ve been Warned.

Setting Up the First Function
function main()
	decide = math.random(1,4) -- Picks a Random Number Between 1 and 4
	print(decide)
	--This tells the Boss to Face the player While the Function is doing its Job.
	while true do
		wait(0.1)
		BossRoot.CFrame = CFrame.new(BossRoot.Position,Vector3.new(char.Torso.Position.X,BossRoot.Position.Y,char.Torso.Position.Z))
	end
end

This Function is the “Brain” of the Boss. Just like our own Brains, It Tells us what to do, just like this Function. Now, Pay attention to this Line here.

BossRoot.CFrame = CFrame.new(BossRoot.Position,Vector3.new(char.Torso.Position.X,BossRoot.Position.Y,char.Torso.Position.Z))

This Line here is telling the Boss to Face the Player AT ALL TIMES. (At Least Thats what I hope It’ll do for you.)
If it Didn’t work, Answer This to yourself: Is my Character R15?
If the answer was yes, Then, I’d change it if You want to use weapons, as a Majority of them aren’t R15 Compatible.

Now, How are we going to get the Action functions to Activate? Well, thats why we have this Life saver:

--Calls functions based on the result of the Variable "Decide"
if decide == 1 then
	pcall(avoid) --Replace this With your Neutral Function
	print(decide)
elseif decide ==2 then
	pcall(attack01) --Replace this with the Attack You Want
	print(decide)
elseif decide == 3 then
	pcall(swing02) --Replace this With the Attack you Want
	print(decide)
elseif decide == 4 then
	pcall(charge) --Replace this with the Attack you want
	print(decide)
end
"Function 2 - The "Dash" Function

function avoid()
	local dashDecide --Declares the Variable
	dashDecide = math.random(1,2) --Decides whether to Dash or Not
	if dashDecide == 1 then
		BossHum:Move(Vector3.new(math.random(-15,15),0, 0)) -- This says "Hey, Im telling you to move anywhere in between -15 and 15 studs."
		wait(2) -- Wait 2 seconds before continuing
		pcall(main) -- Call the "main" Function
	elseif dashDecide == 2 then --If "dashDecide" is not equal to "1" then Call the "main" function
		pcall(main)
	end
end

This will tell the Boss to Move either Left or right on the “X” Axis, or from side to side.

Attack Functions

Now, This can vary a lot, but here’s what I did for my Attacks.

function attack01() --Tells The Boss to Commence attack
	BossHum:MoveTo(Vector3.new(char.Torso.Position.X,char.Torso.Position.Y,char.Torso.Position.Z)) --Tells The boss to get Up close and personal with the PLayer
	BossHum.MoveToFinished:Wait() --Once the MoveTo Is finished, Play the swing Animation.
	swing01Track:Play()
	Boss.SwordOfDarkness.Handle.Touched:Connect(function (hit) --If the Sword is touched during the attack, Damage the Player
		if hit.Parent == char then
			hit.Parent.Humanoid.Health = hit.Parent.Humanoid.Health - 10 --Damages the Player
			BossHum.Health = BossHum.Health + 10 --Heals the Boss
		end
	end)
	pcall(main)
end

Im gonna break this down into simpler terms.

If the attack Function is Called, then Rush towards the player at a Normal WalkSpeed, then, Once the MoveTo is finished, Play the Swing Animation. If the Sword hits the Player, Damage the Player and Heal the Boss.

2nd Function - The Vulnerability Attack

function swing02()
	--This Function allows for the Boss's Damage to be exponential.
	local ableToBeStunned = true
	Boss.Vulnerable:Play() --Plays the Vulnerable Sounds
	if ableToBeStunned == true and BossHum.Health == BossHum.Health - NumberRange.new(30, 100)  then --If the Boss is ABLE TO BE STUNNED, And the Boss is Damage, Do this
		
		BossHum.WalkSpeed = 0 --Freezes the Boss for More damage
		
		if BossHum.Health == BossHum.Health - NumberRange.new(30,100) then --Damage the Boss
			BossHum.Health = BossHum.Health - math.random(15,30) --Adds Xtra Damage to the Boss
			print(BossHum.Health) --Prints the Boss's Health
			wait(3) --Waits 3 seconds then Resets the Boss's WalkSpeed
			BossHum.WalkSpeed = 16 --Resets WalkSpeed
			pcall(main) -- Calls the Main Function	
		end
		
	elseif ableToBeStunned == true and BossHum.Health == not BossHum.Health - NumberRange.new(30,100) then
		--If ableToBeStunned is True and the Boss HAS NOT BEEN HIT, It'll commence the more Damaging attack.
		wait(1)
		swing01Track:Play()
		--If the Sword is Touched,Then The Player will be Damaged.
		Boss.SwordOfDarkness.Handle.Touched:Connect(function (hitx2)
			if hitx2.Parent == char then
				hitx2.Parent.Humanoid.Health = hitx2.Parent.Humanoid.Health - 40 --Damages the Player for MASSIVE Damage
				BossHum.Health = BossHum.Health + 40 --Heals the Boss
				wait(1.5) --Waits 1.5 seconds
				pcall(main) -- Calls the Main Function
			end
		end)
	end
end

This function Tells the Boss: “Hey, Your going to deal a Massively Damaging attack with the Risk of Being stunned”.

FINAL FUNCTION - The Charge Attack
This function will deal Not as much damage as the previous attack, But will momentarily Slow the Player Down.

function charge()
	local success = false
	--Sets WalkSpeed for Dashing
	BossHum.WalkSpeed = 32
	--Dashes towards Player
	Boss.ChargeWarning:Play()
	wait(3.108)
	BossHum:MoveTo(BossHum:MoveTo(Vector3.new(char.Torso.Position.X,char.Torso.Position.Y,char.Torso.Position.Z)))
	BossHum.MoveToFinished:Wait()
	--If the Attack commences and nothing was hit, Prepare for another action
	if wait(3) and success == false then
		pcall(main)
	end
	--Detects if the Attack was a Success
	Boss.SwordOfDarkness.Handle.Touched:Connect(function (extraDamage)
		success = true
		if extraDamage.Parent == char then
			--Adds Boss health
			BossHum.Health = BossHum.Health + 30
			--Removes Players Health
			extraDamage.Parent.BossHumanoid.Health = extraDamage.Parent.BossHumanoid.Health - 30
			
		end
	end)
	--Sets WalkSpeed to Default (16)
	BossHum.WalkSpeed = 16
	char.Humanoid.WalkSpeed = 5 -- Slows down the Player
	wait(2) --Waits 2 seconds
	char.Humanoid.WalkSpeed = 16 --Resets the Players WalkSpeed
	--Recalls Main
	pcall(main)
	
end
The Final Script (With Annotations) and Resolution
local Boss = game.Workspace.BossandEnemies.Boss
local BossHum = Boss:WaitForChild("BossHumanoid")
local BossRoot = Boss:WaitForChild("BossHumanoidRootPart")

local char
local players = game:GetService("Players")

--// ANIMATIONS
local swing01Anim = Boss.Animate.Swing01Anim
local swing01Track = BossHum.Animator:LoadAnimation(swing01Anim)



local decide

game.Players.PlayerAdded:Connect(function(plr)
	char = plr.Character or plr.CharacterAdded:Wait()
end)

game.Players.PlayerAdded:Connect(function(setHealth)
	BossHum.MaxHealth = 500
	BossHum.Health = 500
	pcall(main)
end)

function main()
	decide = math.random(1,4) -- Picks a Number Between 1 and 4
	print(decide)
	--This tells the Boss to Face the player While the Function is doing its Job.
	while true do
		wait(0.1)
		BossRoot.CFrame = CFrame.new(BossRoot.Position,Vector3.new(char.Torso.Position.X,BossRoot.Position.Y,char.Torso.Position.Z))
	end
end

--Calls functions based on the result of the Variable "Decide"
if decide == 1 then
	pcall(avoid)
	print(decide)
elseif decide ==2 then
	pcall(attack01)
	print(decide)
elseif decide == 3 then
	pcall(swing02)
	print(decide)
elseif decide == 4 then
	pcall(charge)
	print(decide)
end

function avoid()
	local dashDecide
	dashDecide = math.random(1,2)
	if dashDecide == 1 then
		BossHum:Move(Vector3.new(math.random(-15,15),0, 0))
		wait(2)
		pcall(main)
	elseif dashDecide == 2 then
		pcall(main)
	end
end

function attack01() --Tells The Boss to Commence attack
	BossHum:MoveTo(Vector3.new(char.Torso.Position.X,char.Torso.Position.Y,char.Torso.Position.Z)) --Tells The boss to get Up close and personal with the PLayer
	BossHum.MoveToFinished:Wait() --Once the MoveTo Is finished, Play the swing Animation.
	swing01Track:Play()
	Boss.SwordOfDarkness.Handle.Touched:Connect(function (hit) --If the Sword is touched during the attack, Damage the Player
		if hit.Parent == char then
			hit.Parent.Humanoid.Health = hit.Parent.Humanoid.Health - 10 --Damages the Player
			BossHum.Health = BossHum.Health + 10 --Heals the Boss
		end
	end)
	pcall(main)
end

function swing02()
	--This Function allows for the Boss's Damage to be exponential.
	local ableToBeStunned = true
	Boss.Vulnerable:Play() --Plays the Vulnerable Sounds
	if ableToBeStunned == true and BossHum.Health == BossHum.Health - NumberRange.new(30, 100)  then --If the Boss is ABLE TO BE STUNNED, And the Boss is Damage, Do this
		
		BossHum.WalkSpeed = 0 --Freezes the Boss for More damage
		
		if BossHum.Health == BossHum.Health - NumberRange.new(30,100) then --Damage the Boss
			BossHum.Health = BossHum.Health - math.random(15,30) --Adds Xtra Damage to the Boss
			print(BossHum.Health) --Prints the Boss's Health
			wait(3) --Waits 3 seconds then Resets the Boss's WalkSpeed
			BossHum.WalkSpeed = 16 --Resets WalkSpeed
			pcall(main) -- Calls the Main Function	
		end
		
	elseif ableToBeStunned == true and BossHum.Health == not BossHum.Health - NumberRange.new(30,100) then
		--If ableToBeStunned is True and the Boss HAS NOT BEEN HIT, It'll commence the more Damaging attack.
		wait(1)
		swing01Track:Play()
		--If the Sword is Touched,Then The Player will be Damaged.
		Boss.SwordOfDarkness.Handle.Touched:Connect(function (hitx2)
			if hitx2.Parent == char then
				hitx2.Parent.Humanoid.Health = hitx2.Parent.Humanoid.Health - 40 --Damages the Player for MASSIVE Damage
				BossHum.Health = BossHum.Health + 40 --Heals the Boss
				wait(1.5) --Waits 1.5 seconds
				pcall(main) -- Calls the Main Function
			end
		end)
	end
end

function charge()
	local success = false
	--Sets WalkSpeed for Dashing
	BossHum.WalkSpeed = 32
	--Dashes towards Player
	Boss.ChargeWarning:Play()
	wait(3.108)
	BossHum:MoveTo(BossHum:MoveTo(Vector3.new(char.Torso.Position.X,char.Torso.Position.Y,char.Torso.Position.Z)))
	BossHum.MoveToFinished:Wait()
	--If the Attack commences and nothing was hit, Prepare for another action
	if wait(3) and success == false then
		pcall(main)
	end
	--Detects if the Attack was a Success
	Boss.SwordOfDarkness.Handle.Touched:Connect(function (extraDamage)
		success = true
		if extraDamage.Parent == char then
			--Adds Boss health
			BossHum.Health = BossHum.Health + 30
			--Removes Players Health
			extraDamage.Parent.BossHumanoid.Health = extraDamage.Parent.BossHumanoid.Health - 30
			
		end
	end)
	--Sets WalkSpeed to Default (16)
	BossHum.WalkSpeed = 16
	char.Humanoid.WalkSpeed = 5 -- Slows down the Player
	wait(2) --Waits 2 seconds
	char.Humanoid.WalkSpeed = 16 --Resets the Players WalkSpeed
	--Recalls Main
	pcall(main)
	
end

Hello! If you reached the end, Thank you for reading this.
Share any corrections you have made, and I will input them and Credit you.
ALSO, MAKE SURE YOUR BOSS IS NEVER ANCHORED!
This page is (Somewhat) of a WIP.
Also, Share some of you creations you make with this!
-InfernalProgrammer

P.S:
This script isn’t perfect, I know. But, This will give you a basic Idea.

Link to the Model for the Boss:
BossTemplate

42 Likes

I’ve looked in countless places trying to find a way to make AI/NPCs that are meant for Combat games. I see all of these new ‘Anime’ games coming out with AI that is made for Combat. I just never worked with AI before, so all I could make was a NPC that does damage on touch. This by far was the best tutorial I found! This is a great starting point to help me understand how it all works. I appreciate this a ton!

3 Likes

I honestly can’t get it to work. I wrote the code out all by myself in hopes of understanding the process, which I did understand. The problem is, it will run the function (Main), print out the value for Decide, but then it won’t run the other functions once called. How come? Neither of the 4 will run. It’s like it isn’t allowing for a pcall() inside of the If Statements.

2 Likes

Ah! I’ll look into it. Sorry for the inconvenience and the Late reply!

Also, I will make an UPDATED Tutorial on this as I just realized this now. Again, Sorry for the error and Late response.

1 Like

Where do the scripts go and what type of scripts?

  1. insert a “Script” (Or “ServerScript”) into “ServerScriptService” and call “Main”
  2. Copy and Paste the Final script located under
    “Scripting Pt.3 - functions and such”.

Pretty self explanatory if you read it.

6 Likes

instead of “BossHum.Health = BossHum.Health + 30” try “BossHum.Health += 30”, do the same thing but its somehow better