How would I use OOP composition for guns

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.

Or do you mean you have unwanted methods when creating the objects? You’d need to manually remove these before setting the metatable.

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
1 Like

TL;DR just use an if statement.

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.

Query.All(HealthComponent, DamageComponent).None(ForceFieldComponent)

If it has health and damage and no force fields then do not apply damage.

1 Like

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