How to structure a soccer ball system?

How should I structure my soccer ball system so that I can keep it clean (not a lot of lines, variables, etc) How can I make it modular, etc? My current system is pretty messy and has a lot of lines so I want to know how I can make it modular and how to structure my code/system.

My current system works this way: There are several tools: Pass tool, shoot tool, dribble tool, Goalkeeper tool, etc. Inside each of these tools is different scripts for different “methods” like for pass tool you can do a high pass or a low pass etc.

2 Likes

Hm well you could structure it around the player (what I’d personally do) or you could structure it around the ball.

I dunno I’ll just give ya an example

local Ball = {}
Ball.__index = Ball

function Ball.new(model)
   local self = setmetatable({}, Ball)
   self.__index = Ball
   self.Model = model
   return self
end

function Ball:Clone()
   if not self.Model then return end
   local Clone = setmetatable(getmetatable(self), Ball)
   Clone.__index = Ball
   Clone.Model = self.Model:Clone()
   Clone.Model.Parent = workspace
   return Clone
end

function Ball:Destroy()
   if not self.Model then return end
   self.Model:Destroy()
   self.__index:Destroy()
   self = nil
end

function Ball:KickTo(cframe: CFrame)
   if not self.Model then return end
   self.Model.CFrame = cframe
end

return Ball

I don’t have much context so uh yeah. Mb if this isn’t actually any help.

I just created a basic OOP module really. feel free to add some extensions

Really just add some of your pre existing code and create a new method for it.

Such as shooting, passing, etc

It shouldn’t be hard considering you have an already working system

I can give you a little more context, So lets say I have a pass tool for the ball. Inside the pass tool there is multiple scripts like “high pass” “low pass” “bouncy pass” etc. so basically there is tools with different methods inside them

1 Like

I guess you could pass a player object and check what tool is equipped. With that you could add some code to y’know do with that info

And by the way I’m writing this on mobile (no it’s not ChatGPT lol I just type fast) so I’m not gonna write a whole bunch; just a outline

local PassMethods = {
   ["Bouncy"] = true,
   ["Normal"] = true
}

function Ball:Pass(passMethod: string)
   if not PassMethods[passMethod] then return end
   -- rest of ur code
end

I will actually provide the other method I was mentioning in my first reply, the player method. Also that’s how I’d do this:

local PlayerMod = {}
PlayerMod.__index = PlayerMod

function PlayerMod.new(Player: Player)
   local self = setmetatable({}, PlayerMod)
   self.__index = PlayerMod
   self.Player = Player
   self.Position = "Viewer"
   return self
end

function PlayerMod:Kick(kickType: string)
   if not self.Player then return end
   --[[ do stuff depending
        on the context
        like player look angle, etc
        maybe do some raycasting
     ]]
end

function PlayerMod:AssignPosition(position: string)
   if not self.Player then return end
   self.Position = position
end

return PlayerMod

Could you give a little more detail about what u mean when ur saying that it should be around the player?

also, is there another good way of doing it without OOP kind-of?

What I meant was like adding methods in the player instead of inside the ball. So you would call a :Kick method with access to the player instead of using a :Kick method without access to the player if that makes sense

There probably is. Maybe the methods you’re using right now. Although I’d suggest trying to use this approach if you can comprehend it.

You could go all functional maybe like this:

local function Kick()
  -- code
end

local function Pass()
  -- code
end

etc, etc

Now you could add a script in each ball, or you could tag them all and apply this to all of the, with CollectionService

local CollectionService = game:GetService("CollectionService")
local Tagged = CollectionService:GetTagged("SoccerBalls")

for i, v in Tagged do
     -- add code
end
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")

local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
local RightArm = Character:WaitForChild("Right Arm")
local LeftArm = Character:WaitForChild("Left Arm")
local RightLeg = Character:WaitForChild("Right Leg")
local LeftLeg = Character:WaitForChild("Left Leg")

local Client = ReplicatedStorage:WaitForChild("Client")
local Modules = Client:WaitForChild("Modules")
local ToolController = require(Modules:WaitForChild("ToolController"))

local Tool = script.Parent

local ExpectingInput = false
local IsMouseDown = false
local IsKicking = false

local Power = 0
local Angle = 0
local Max_Power = 100
local Max_Angle = 10

function ResetPower()
	Power = 0
	Angle = 0
	ToolController:UpdatePowerBar(Power, Max_Power)
end

Tool.Equipped:Connect(function()
	ExpectingInput = true
	ToolController:UpdateTitle("Pass")
	ToolController:UpdateGui(true)
end)

Tool.Unequipped:Connect(function()
	ExpectingInput = false
	ResetPower()
	ToolController:UpdateGui(false)
end)

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent or not ExpectingInput then
		return
	end
	
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		IsMouseDown = true
		ResetPower()
		
		while IsMouseDown and ExpectingInput do
			Power = math.min(Power + (Max_Power * .005), Max_Power)
			Angle = math.min(Angle + (Max_Angle * .005), Max_Angle)
			ToolController:UpdatePowerBar(Power, Max_Power)
			task.wait()
		end
	end
end)


UserInputService.InputEnded:Connect(function(input, gameProcessedEvent)
	if gameProcessedEvent or not ExpectingInput then
		return
	end

	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		IsMouseDown = false
		
		IsKicking = true
		task.wait(.6)
		IsKicking = false
		ResetPower()
	end
end)


local function OnTouched(hit: BasePart)
	if hit.Name ~= "Ball" or not IsKicking then
		return
	end
	ToolController:UpdateNetworkOwner(hit)
	
	local KickVelocity = Instance.new("BodyVelocity", hit)
	KickVelocity.MaxForce = Vector3.new(math.huge, 50, math.huge)
	KickVelocity.Velocity = HumanoidRootPart.CFrame.LookVector * Power + HumanoidRootPart.CFrame.UpVector
	
	Debris:AddItem(KickVelocity, .25)
	
	local BallPosition = hit.Position + Vector3.new(0, HumanoidRootPart.Position.Y - hit.Position.Y, 0)
	local DotProduct = HumanoidRootPart.CFrame.LookVector:Dot(BallPosition - HumanoidRootPart.Position)
	local TorsoOrientation = math.acos(DotProduct / (BallPosition - HumanoidRootPart.Position).Magnitude)
	
	local LeftArmDistance = (hit.Position - LeftArm.Position).Magnitude
	local RightArmDistance = (hit.Position - RightArm.Position).Magnitude
	
	if TorsoOrientation > math.rad(18) and LeftArmDistance < RightArmDistance then
		local CurveForce = Instance.new("BodyForce", hit)
		CurveForce.Force = HumanoidRootPart.CFrame.RightVector * -200
		
		local CurveAngularVelocity = Instance.new("BodyAngularVelocity", hit)
		CurveAngularVelocity.MaxTorque = Vector3.new(0, 1000, 0)
		CurveAngularVelocity.AngularVelocity = Vector3.new(0, 1000, 0)
		
		Debris:AddItem(CurveForce, Power/Max_Power)
		Debris:AddItem(CurveAngularVelocity, .1)
	elseif TorsoOrientation > math.rad(30) and RightArmDistance < LeftArmDistance then
		local CurveForce = Instance.new("BodyForce", hit)
		CurveForce.Force = HumanoidRootPart.CFrame.RightVector * 200
		
		local CurveAngularVelocity = Instance.new("BodyAngularVelocity", hit)
		CurveAngularVelocity.MaxTorque = Vector3.new(0, 1000, 0)
		CurveAngularVelocity.AngularVelocity = Vector3.new(0, -1000, 0)

		Debris:AddItem(CurveForce, Power/Max_Power)
		Debris:AddItem(CurveAngularVelocity, .1)
	end
	IsKicking = false
	ResetPower()
end

RightLeg.Touched:Connect(OnTouched)

heres how I’m doing it right now. Its just a script inside a tool that handles all the physics and stuff but I definitely know theres a way to atleast clean it up

This looks fine what is it that you wanna clean up? I genuinely think this is pretty solid already

You think it’s good like that? Maybe I’m just not used to writing long scripts after I took a break for a while haha

1 Like

Yeah I think it’s fine. I thought it was gonna be really messy with a lot of unnecessary stuff.

If you wanna shift it to being more modular then go ahead, but I think this is fine. Just make sure performance doesn’t tank and ya should be good

1 Like

Thanks haha I was thinking of making the velocity/force stuff modular so instead of me doing this

local CurveAngularVelocity = Instance.new("BodyAngularVelocity", hit)
CurveAngularVelocity.MaxTorque = Vector3.new(0, 1000, 0)
CurveAngularVelocity.AngularVelocity = Vector3.new(0, 1000, 0)

I can just do this:

ToolModule:CreateAngular(hit, Vector3.new(0, 1000, 0), Vector3.new(0, 1000, 0))

Since I will be creating a lot of body-movers

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.