So i have a Gun class that uses composition and these components are stuff like an ammo component, a firing component, a bullet component, etc.
-- Gun class script
GunClass = {}
GunClass.__index = GunClass
function GunClass.new(AmmoComponent, FiringComponent, BulletComponent)
local self = setmetatable({}, GunClass)
self.AmmoComponent = AmmoComponent
self.FiringComponent = FiringComponent
self.BulletComponent = BulletComponent
return self
end
function GunClass:Reload()
self.AmmoComponent:Reload()
end
function GunClass:Fire()
self.FiringComponent:Fire()
self.BulletComponent:CreateBullet()
end
And then each gun has unique components that differ from gun to gun. For example, a gun can have an ammo component that reloads a magazine or an ammo component that reloads each bullet individually like a shotgun
-- Gun script
local AmmoComponent = SomeAmmoComponent.new()
local FiringComponent = SomeFiringComponent.new()
local BulletComponent = SomeBulletComponent.new()
local Gun = GunClass.new(AmmoComponent, FiringComponent, BulletComponent)
But the problem is that the gun class script doesn’t know what components are being used. It just assumes that for example its ammo component has a :Reload() function or that its firing component has a :Fire() function.
So would I have to make a class script for each unique gun (which will probably repeat a lot of code) or is there a better solution?
What do you mean when it doesn’t know what components are being used? If you’re sure that all the methods exist, you can call them without the autocomplete knowing they exist. To resolve the autocomplete being unsure, make sure you are type annotating everything, even the modules themselves. This way, you can explicitely define types of objects and methods, parameters, variables, constants, etc. to help resolve these.
You can use strict mode to view any type casting/type conflicting issues.
Basically each gun has their own components. For example, a rifle gun would have a magazine ammo component and an automatic firing component. While a shotgun would have a shotgun ammo component and a semi automatic firing component. These components can have different methods or work differently and the GunClass wouldn’t know
So i was wondering if i’d have to ditch the whole general GunClass and make a unique class for each gun or if there was some better way
If you use a general gun class for methods all the guns use, and then use a subclass for each gun, you could seperate different gun methods, you’d just need to modify the subclass class constructor.
function Subclass:new(): SubclassType
local current: {[any]: any} = getmetatable(self)
local new: {[number]: {[any]: any}} = {current, Subclass}
--using this method, we can make the script search multiple 'metatables' as such
local newMetatable: {["__index"]: (_tbl: SubclassType, index: any) -> (any?)} = {
["__index"] = function(_tbl, index)
for _, metatable in next, new, nil do
if metatable[index] then
return metatable[index]
end
end
end
}
setmetatable(self, newMetatable)
--assign any other info the subclass needs here
return self
end
From my experience with OOP composition the core component in order to avoid assumptions you can just use a generalized function that all the components share like :Update().
You can tie this generalized update function to input or runservice depending if you want to update every frame or every key press.
function PhysicsCharacterController:Update(moveDirection : Vector3, deltaTime)
for i, component in pairs(self._MovementComponents) do
--also assumes each component has an update function
--Hence if statement
if component.Update and component.ShouldUpdate then
component:Update(self, deltaTime)
end
Then these components can access the other component, for example firing component can access the ammo component to know if the gun has ammo requirements or no ammo which means infinite ammo.
function Running:Update(data : PhysicsCharacterController)
local hipHeightObject = data:GetComponent("HipHeight")
assert(hipHeightObject, "Running Component requires HipHeight Component make sure the :Add(HipHeightModuleScript) on Physics Character Controller")
local onGround = hipHeightObject.OnGround
Reference project:
In ECS libraries this if statement is in the form of a query it checks if an entity/object has certain components then only does logic to it if it does.