Dear Fellow Devs,
Recently, I have worked on a game like most of you out there. The game is inspired by Stickman:legacy and Field of Battle. A strategic game basically.
I wanna showcase an early iteration of the game with the core mechanics done.
The entities are spawned in with OOP and module scripts mostly. The scripts will be in this post.
Gyazos:
https://gyazo.com/a7149f189d9ad588f342704d3e88ccb7 (Placing)
https://gyazo.com/24157f2f91fe2fcf5c8c1fec6c8a0fcd(Archer and Dummy Interaction)
https://gyazo.com/a0e1e6744420b8263051e7d0ad539b28(Dummy v Dummy)
https://gyazo.com/6bbf6d078ee6eb4b5a457b48661033c7(Dummy v wall)
The dummy v dummy interaction needs work thou!
Scripts:
EntityEngine(Variables are messy because of debugging and the import tables):
local ENGINE = {}
ENGINE.__index = ENGINE
--[[-
=============== THIS ENGINE IS MADE BY PLANET8ATER56 OTHERWISE KNOWN AS RESPECTEDCOW=====================
DO NOT COPY THIS ENGINE OR ACCESS THIS ENGINE WITHOUT PERMISSION.
TO GET PERMISSION CONTACT RespectedCow#5338 via Discord.
--//HOW THIS ENGINE WORKS//--
I'M LAZY TO UPDATE
-]]
--//Services//--
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local pathfinding = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local PhysService = game:GetService("PhysicsService")
PhysService:CreateCollisionGroup("t")
PhysService:CollisionGroupSetCollidable("t","t",false)
--//Variables//--
local EntityData = require(script.Parent.EntityData)
local EntityAnimator = require(script.Parent.EntityAnimator)
local newTroopEvent = script.NewTroop
--//Functions//--
function NoCollide(model)
for k,v in pairs(model:GetChildren()) do
if v:IsA"BasePart" then
PhysService:SetPartCollisionGroup(v,"t")
end
end
end
local function findTarget(Troop, enemyTeam, maxDist, entityType)
local myRoot = Troop.PrimaryPart
local target = nil
local potentialTargets = {}
local seeTargets = {}
for i,v in ipairs(game.Workspace[enemyTeam..entityType.."Troops"]:GetChildren()) do
local human = v:FindFirstChild("Humanoid")
local torso = v:FindFirstChild("Torso") or v:FindFirstChild("HumanoidRootPart")
if human and torso and v.Name ~= script.Parent.Name then
if (myRoot.Position - torso.Position).magnitude < maxDist and human.Health > 0 then
table.insert(potentialTargets,torso)
end
end
end
if #potentialTargets > 0 then
for i,v in ipairs(potentialTargets) do
table.insert(seeTargets, v)
end
end
if #seeTargets > 0 then
for i,v in ipairs(seeTargets) do
if (myRoot.Position - v.Position).magnitude < maxDist then
target = v
end
end
end
return target
end
function ENGINE:OffensiveStart()
--//VARIABLES//--
print(self.entityTeam.Name)
local entityTeamWall = game.Workspace.Map:FindFirstChild(self.entityTeam.Name):FindFirstChild(self.entityTeam.Name.."Wall")
local target
local CURRENT_STATUS
if self.entityEnemy.dataFolder.WallDestroyed == true then
target = entityTeamWall.Parent.Parent[self.entityEnemy.Team.Name].EndPoint
else
target = entityTeamWall.Parent.Parent[self.entityEnemy.Team.Name].WallTarget
end
--//CREATION//--
print(self.entityModel)
local newClone = self.entityModel:Clone()
newClone.Name = self.entityTeam.Name.."Dummy"
newClone.Parent = game.Workspace[self.entityTeam.Name.."OffensiveTroops"]
newClone.PrimaryPart.CFrame = entityTeamWall.StartingPoint.CFrame
newClone:SetPrimaryPartCFrame(CFrame.lookAt( -- Make the npc look at the opposit team's wall targets
newClone.PrimaryPart.Position,
target.Position * Vector3.new(1, 0, 1) +
newClone.PrimaryPart.Position * Vector3.new(0, 1, 0)
))
local humanoid = newClone.Humanoid
local datatableParameters = {-- FOR GOING INTO FUNCTIONS
damage = self.damage,
attackrate = self.attackrate,
maxDist = self.maxDist,
range = self.range,
speed = self.speed,
troopDamage = self.troopDamage,
health = self.health,
defense = self.defense,
--//EVENTS//--
Attack = self.attackEvent,
Enabled = self.enabledEvent,
Death = self.Dead,
BehaviorChanged = self.BehaviorChanged
}
--//SET THE TROOP'S HUMANOID STATS TO THE CORRECT VALUES
newClone.Humanoid.WalkSpeed = self.speed
newClone.Humanoid.Health = self.health
--//ACTIVATION//--
self.BehaviorChanged.Event:Connect(function(behavior)
CURRENT_STATUS = behavior
end)
newTroopEvent:Fire(self, newClone)
NoCollide(newClone) -- PREVENT LAG
--ENGINE:CreateOppositTroop(enemyTroopImportData, self.entityOwner, "Dummy") -- TESTING THE FUNCTION
newClone.PrimaryPart:SetNetworkOwner(nil) -- PREVENT LAG
EntityAnimator:Animate(newClone)
ENGINE:OffensiveStartBehaviour(self, newClone, datatableParameters, target, self.entityEnemy.Team.Name, self.entityType) -- ORGANIZING
end
function ENGINE:DefensiveStart()
--//VARIABLES//--
local newClone = self.entityModel:Clone()
local targetFolder = game.Workspace[self.entityEnemy.Team.Name.."OffensiveTroops"]
local testingPartPosition = game.Workspace.TestingPart
local GameEnded = game.ServerStorage.Values.GameEnded
--//ORGANIZING//--
newClone.Parent = game.Workspace[self.entityTeam.Name.."DefensiveTroops"]
newClone.PrimaryPart.CFrame = self.placeCFrame
local datatableParameters = {-- FOR GOING INTO FUNCTIONS
damage = self.damage,
attackrate = self.attackrate,
maxDist = self.maxDist,
range = self.range,
speed = self.speed,
troopDamage = self.troopDamage,
health = self.health,
defense = self.defense,
--//EVENTS//--
Attack = self.attackEvent,
Enabled = self.enabledEvent,
Death = self.Dead,
BehaviorChanged = self.BehaviorChanged
}
--//ACTIVATION//--
EntityAnimator:Animate(newClone)
NoCollide(newClone)
newClone.PrimaryPart.Anchored = true
while GameEnded.Value do
local target = findTarget(newClone, self.entityEnemy.Team.Name, self.maxDist, "Offensive")
if target ~= nil and target.Parent.Humanoid.Health > 0 then
ENGINE:DefensiveAttack(newClone, target, datatableParameters)
wait(self.attackrate)
end
wait()
end
end
--//Behaviour//--
function ENGINE:Attack(self, Troop, target, dataParametertable)
--//DEFINING DATA//--
local speed = dataParametertable.speed
local defense = dataParametertable.defense
local range = dataParametertable.range
local attackrate = dataParametertable.attackrate
local damage = dataParametertable.damage
local troopDamage = dataParametertable.troopDamage
local maxDist = dataParametertable.maxDist
local health = dataParametertable.health
local attackType
--//RUN THE CHECKS//--
if target.Parent:FindFirstChild("Humanoid") == nil then -- TWO CHECKS
if (Troop.PrimaryPart.Position - target.Position).magnitude < range then
local attack = Troop.Humanoid:LoadAnimation(Troop.Configuration.Animations.Attack)
attackType = "ENTITY"
self.Attack:Fire(self, Troop, target, attackType)
attack:Play()
target.Health.Value = target.Health.Value - damage
end
else
if (Troop.PrimaryPart.Position - target.Position).magnitude < range then
local attack = Troop.Humanoid:LoadAnimation(Troop.Configuration.Animations.Attack)
attackType = "ENVIRONMENT"
self.Attack:Fire(self, Troop, target, attackType)
attack:Play()
target.Parent.Humanoid:TakeDamage(troopDamage)
end
end
end
function ENGINE:PATHFIND(newClone, targetPosition)
local TroopPath = pathfinding:CreatePath()
TroopPath:ComputeAsync(newClone.PrimaryPart.Position,targetPosition)
local wayPoints = TroopPath:GetWaypoints()
if TroopPath.Status == Enum.PathStatus.Success then
for i,wayPoint in pairs(wayPoints) do
newClone.Humanoid:MoveTo(wayPoint.Position)
if wayPoint.Action == Enum.PathWaypointAction.Jump then
newClone.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
newClone.Humanoid.MoveToFinished:Wait()
end
else
newClone.Humanoid:MoveTo(newClone.PrimaryPart.Position)
end
end
function ENGINE:DefensiveAttack(newClone, target, dataParametertable)
--//DEFINING DATA//--
local speed = dataParametertable.speed
local defense = dataParametertable.defense
local range = dataParametertable.range
local attackrate = dataParametertable.attackrate
local damage = dataParametertable.damage
local troopDamage = dataParametertable.troopDamage
local maxDist = dataParametertable.maxDist
local health = dataParametertable.health
if (target.Position - newClone.PrimaryPart.Position).magnitude < range then
newClone:SetPrimaryPartCFrame(CFrame.lookAt( -- MAKE THE CLONE LOOK AT THE ENEMY/TARGET
newClone.PrimaryPart.Position,
target.Position * Vector3.new(1, 0, 1) +
newClone.PrimaryPart.Position * Vector3.new(0, 1, 0)
))
local animation = newClone.Humanoid:LoadAnimation(newClone.Configuration.Animations.Attack)
animation:Play()
target.Parent.Humanoid:TakeDamage(troopDamage)
end
end
function ENGINE:OffensiveStartBehaviour(self, newClone, dataParametertable, mainTarget, enemyTeam, entityType)
--//DEFINING DATA//--
local speed = dataParametertable.speed
local defense = dataParametertable.defense
local range = dataParametertable.range
local attackrate = dataParametertable.attackrate
local damage = dataParametertable.damage
local troopDamage = dataParametertable.troopDamage
local maxDist = dataParametertable.maxDist
local health = dataParametertable.health
--//VARIABLES//--
local IsAttacking = false
local gameEnded = game.ServerStorage.Values.GameEnded
local targetFolder = game.Workspace[enemyTeam.."OffensiveTroops"]
while gameEnded.Value do
local target = findTarget(newClone, enemyTeam, maxDist, entityType)
if newClone.Humanoid.Health <= 1 then
newClone:Destroy()
break
end
if target ~= nil then
if (target.Position - newClone.PrimaryPart.Position).magnitude < range then
newClone.Humanoid:MoveTo(newClone.PrimaryPart.Position)
self.BehaviorChanged:Fire("ATTACK", target, newClone)
ENGINE:Attack(self, newClone, target, dataParametertable)
IsAttacking = true
wait(attackrate)
else
newClone.Humanoid:MoveTo(target.Position+Vector3.new(0, 0, -2))
end
else
IsAttacking = false
spawn(function()
if IsAttacking == false then
newClone.Humanoid:MoveTo(mainTarget.Position)
end
end)
if (newClone.PrimaryPart.Position - mainTarget.Position).magnitude < range then
if mainTarget.Health.Value ~= 0 then
ENGINE:Attack(self, newClone, mainTarget, dataParametertable)
end
wait(attackrate)
end
end
wait()
end
end
--//CONSTRUCTOR//--
function ENGINE.new(name, lvl, entitytype, team, player, enemy, troopFile, placeCframe)
--//Events//--
print(enemy)
local BehaviourChangedEvent = Instance.new("BindableEvent")
local DeadEvent = Instance.new("BindableEvent")
local enabledEvent = Instance.new("BindableEvent")
local AttackEvent = Instance.new("BindableEvent")
-- Create the table
local newEntityDataTable = setmetatable(
{
--//Properties//--
entityName = name,
entityModel = ReplicatedStorage[troopFile]:FindFirstChild(name),
entityType = entitytype,
entityTeam = team,
entityOwner = player,
entityEnemy = enemy,
placeCFrame = placeCframe,
attackrate = EntityData[name]["Lvl"..lvl].attackrate,
damage = EntityData[name]["Lvl"..lvl].damage,
speed = EntityData[name]["Lvl"..lvl].speed,
health = EntityData[name]["Lvl"..lvl].health,
defense = EntityData[name]["Lvl"..lvl].defense,
range = EntityData[name]["Lvl"..lvl].range,
maxDist = EntityData[name]["Lvl"..lvl].maxDist,
troopDamage = EntityData[name]["Lvl"..lvl].troopDamage,
--//Events//--
BehaviorChanged = BehaviourChangedEvent,
Dead = DeadEvent.Event,
enableEvent = enabledEvent.Event,
Attack = AttackEvent
},
ENGINE
)
return newEntityDataTable
end
--//INSTRUCTIONS//--
return ENGINE
Building Module:
local SYSTEM = {}
--//VARIABLES//--
local towerFolder = game.ReplicatedStorage:WaitForChild("PlaceClones") -- Default is defensive
local CreateEntityEvent = game.ReplicatedStorage:WaitForChild("Remotes").Events.CreateEntity
--//SERVICES//--
local RunService = game:GetService("RunService")
local PhysService = game:GetService("PhysicsService")
local function GetTouchingParts(part)
local connection = part.Touched:Connect(function() end)
local results = part:GetTouchingParts()
connection:Disconnect()
return results
end
function NoCollide(model)
for k,v in pairs(model:GetChildren()) do
if v:IsA"BasePart" or v.Name == "Collider" then
PhysService:SetPartCollisionGroup(v,"t")
end
end
end
function DistanceFromOtherTowers(tower, player)
local defensiveFolder = game.Workspace[player.Team.Name.."DefensiveTroops"]
local hasChild = false
local canPlace = false
local collideTroop = {}
for i,troop in pairs(defensiveFolder:GetChildren()) do
hasChild = true
if troop:IsA("Model") then
if (tower.PrimaryPart.Position - troop.PrimaryPart.Position).magnitude < 6 then
table.insert(collideTroop, troop)
end
end
end
if hasChild and #collideTroop == 0 then
return true
end
if hasChild == false and canPlace == false then
return true
end
end
function SYSTEM:PlaceTower(player, tower, mouse)
if player.dataFolder.IsPlacing.Value == true then return end
player.dataFolder.IsPlacing.Value = true
-- CLONE THE TOWER FROM REPLICATED STORAGE FIRST
local newClone = towerFolder:FindFirstChild(tower):Clone()
newClone.Parent = game.Workspace.PlaceFolder
local isPlaced = false
local canPlace = false
local terminate = false
player.PlayerGui.LocalGUI.HealthIndictor.Enabled = false
NoCollide(newClone)
assert(mouse ~= nil, "BUILDING SYSTEM: MOUSE IS INVALID")
assert(newClone ~= nil, "BUILDING SYSTEM: CANNOT CLONE SOMETHING THAT DOESN'T EXIST!")
mouse.Button1Down:Connect(function()
if canPlace == true and isPlaced == false then
isPlaced = true
local placeCFrame = newClone.PrimaryPart.CFrame
newClone:Destroy()
player.dataFolder.IsPlacing.Value = false
CreateEntityEvent:FireServer("Archer", 1, "Defensive", placeCFrame)
player.PlayerGui.LocalGUI.HealthIndictor.Enabled = true
terminate = true
end
end)
mouse.Button2Down:Connect(function()
newClone:Destroy()
isPlaced = false
canPlace = false
player.dataFolder.IsPlacing.Value = false
player.PlayerGui.LocalGUI.HealthIndictor.Enabled = true
terminate = true
end)
-- GET THE PLAYER'S MOUSE POSITION EVERYTIME THE SCREEN RENDERS
RunService.RenderStepped:Connect(function()
if isPlaced == false and terminate == false then
local newcframe = mouse.Hit -- GET DA POSITION
newClone.HumanoidRootPart.CFrame = CFrame.new(newcframe.X, 24, newcframe.Z)
newClone.HumanoidRootPart.Orientation = Vector3.new(0,0,0)
newClone.Collider.Transparency = 0.5
if mouse.Target.Name == "PlacePart" and DistanceFromOtherTowers(newClone, player) and mouse.Target.Parent.Name == player.Team.Name then
newClone.Collider.BrickColor = BrickColor.new("Sage green")
canPlace = true
else
newClone.Collider.BrickColor = BrickColor.new("Really red")
canPlace = false
end
end
wait()
end)
end
return SYSTEM
Animator Module(Helpful for some people):
-- LAZY TO COMMENT LMAO
local module = {}
local vectorZero = Vector3.new(0, 0, 0)
local animatedBeings = {}
local function ifIdle(humanoid, NPC)
if NPC.HumanoidRootPart == nil then return end
if NPC.HumanoidRootPart.Velocity.X >= -5 and NPC.HumanoidRootPart.Velocity.X <= 5 and NPC.HumanoidRootPart.Velocity.Z >= -5 and NPC.HumanoidRootPart.Velocity.Z <= 5 then
return true
end
end
local constantAnimation = function(v)
if v == nil then return end
if v:GetState() == Enum.HumanoidStateType.Dead then
coroutine.yield()
end
local WalkAnimation = v:LoadAnimation(v.Parent.Configuration.Animations.Walk)
local IdleAnimation = v:LoadAnimation(v.Parent.Configuration.Animations.Idle)
game:GetService("RunService").Heartbeat:Connect(function()
if v.Parent then
if ifIdle(v, v.Parent) then
WalkAnimation:Stop()
if IdleAnimation.IsPlaying == false then
IdleAnimation:Play()
end
else
IdleAnimation:Stop()
if WalkAnimation.IsPlaying == false then
WalkAnimation:Play()
IdleAnimation:AdjustSpeed(v.WalkSpeed)
end
end
end
end)
end
function module:Animate(NPC)
local humanoid = NPC.Humanoid
spawn(function()
constantAnimation(humanoid)
end)
end
return module
What do you think? I will work on the gold mining and currency system today! After I watch some youtube videos of course…
Side note: I know this is out of topic but this is a side note so maybe this is ok. I am hiring a builder that can make characters and maps for around 20 dollars ish and also around 20 percent. You will have to work for the entire game. Half up front and half after the game is at beta or the important stuff are done.
Have to show work.
Anyways, thank you for reading this post. I think this game idea might work. But who knows. I wonder how I’ll even advertise it…
Yours truly,
RespectedCow/Planet8ater56
I really need 1000 Robux to change my name. Tell me if this is in the wrong category. Good luck!
Edit: I did the GoldMine system. It works well. But can’t fit in a 7 secs gyazo.
Edit: did the Gui.
Discord Server: Official CastleDonia Server The server is done now. Need more members pls join.