Converting Inheritence To Composition?

I’m making an item system, and currently each item inherits from a main item class and from some type of sub class like equipment. I want to learn to use composition, so I can avoid the headache later.

Item > Item Equipment > Item Sword / Item Armor

My items have things such as durability, enchantments, etc, i’m just not sure how to convert the items to composition. What would the component for a sword be, or for armor? Would durability be a component, and if so, does it REALLY need a whole class for it or could I just make it a value and save time?

ItemInit

local Item = require(script.Parent.Parent.Item.Item)
local ItemSword = require(script.Parent.Parent.Item.ItemSword)
local ItemArmor = require(script.Parent.Parent.Item.ItemArmor)
local EnumMaterial = require(script.Parent.Parent.Util.EnumMaterial)
local EnumRarity = require(script.Parent.Parent.Util.EnumRarity)

-- ItemName, Value, Rarity, Material(If Equipment)
return {
	["iron"] = function() return Item.new("iron", 10, EnumRarity.Common) end,
	["iron_sword"] = function(attributes) return ItemSword.new("iron_sword", 100, EnumRarity.Common, EnumMaterial.Iron, attributes) end,
	["iron_chestplate"] = function(attributes) return ItemArmor.new("iron_chestplate", 150, EnumRarity.Common, EnumMaterial.Iron, attributes) end,
}

ItemEquipment Class

-- Authorized Use By Dissonant Realms and Galicate
-- Programmer : Galicate @ 6/20/2024 2:51 PM

local HttpService = game:GetService("HttpService")
local Item = require(script.Parent.Item)
local ItemEquipment = {}

function ItemEquipment.new(itemId: string, value: number, rarity: {}, material: {}, attributes)
	local self = setmetatable(Item.new(itemId, value, rarity), ItemEquipment)
	
	self.Material = material
	self.Attributes.Durability = attributes and attributes.Durability or self.Material:GetDurability()
	
	return self
end

function ItemEquipment:GetMaterial()
	return self.Material
end

-- Durability determined by material value
function ItemEquipment:GetDurability()
	return self.Attributes.Durability
end

function ItemEquipment:DamageItem()
	self.Attributes.Durability = math.clamp(self.Attributes.Durability - 1, 0, self.Attributes.Durability)
	if self.Attributes.Durability then
		-- Destroy Item
	end
end

function ItemEquipment:RepairItem()
	self.Attributes.Durability = self:GetMaterial().Durability
end

function ItemEquipment:AddEnchanment(enchantment: {})
	
end

function ItemEquipment:RemoveEnchantment(enchantment: {})
	
end

ItemEquipment.__index = ItemEquipment
setmetatable(ItemEquipment, Item)

return ItemEquipment

Item Class

-- Authorized Use By Dissonant Realms and Galicate
-- Programmer : Galicate @ 6/20/2024 2:51 PM

local HttpService = game:GetService("HttpService")
local Item = {}

local REGISTRY = {}

function Item.new(itemId: string, value: number, rarity: {})
	local self = setmetatable({}, Item)
	
	self.Id = itemId
	self.GUID = HttpService:GenerateGUID()
	self.BaseValue = value
	self.Rarity = rarity
	self.Attributes = {}
	self.Attributes.Enchantments = {}
	
	REGISTRY[self.GUID] = self
	
	return self
end

function Item:GetId()
	return self.Id
end

-- SHOULD ONLY BE USED BY THE CLIENT WHEN RECIEVING A GUID FROM THE SERVER
function Item:_SetGUID(guid: {})
	self.GUID = guid
end

function Item:GetGUID()
	return self.GUID
end

-- Returns the item from the provided GUID
function Item.GetItemFromGUID(guid: string)
	return REGISTRY[guid] or nil
end

-- Returns the Id of the item from the provided GUID
function Item.GetIdFromItemGUID(guid: string)
	return Item.GetItemFromGUID(guid).Id
end

function Item:GetBaseValue()
	return self.BaseValue
end

-- Item Value = Base Value + Enchantments Value / Durability
function Item:GetTotalValue()
	local durabilityValue = self.Durability and (self.Durability / self.Material:GetDurability()) or 1
	return self:GetBaseValue() / durabilityValue
end

function Item:GetRarity()
	return self.Rarity
end

function Item:SetRarity(rarity: {})
	self.Rarity = rarity
end

function Item:GetEnchantments()
	return self.Enchantments
end

Item.__index = Item
return Item
`

Hey, I’d say durability doesn’t need to be an entire class unless you want to add extra functionality to it apart from just being a pre-defined value. The enchantments are more complex, but if you make a separate system to handle them, they could be something as simple as an array of enchantments strings.

I’d say that if you like this class system, and think it’s easy to manage, stick to it. Focus on what works for you – and your team if you’re working with someone else.

I could also suggest an ECS Library that I think really fits your use case, if you want to take a look if you prefer to change how your code currently is.

This is what I ended up doing, however i’m unsure if I like it.

return {
	Durability = function(durability: number) return durability end,
	MaxDurability = function(maxDurability: number) return maxDurability end,
	Food = function(maxFood: number) return {MaxFood = maxFood, Food = maxFood} end,
}
local Item = require(script.Parent.Parent.Item.Item)
local Components = require(script.Parent.Parent.Util.Components)

return {
	["iron_sword"] = function(components)
		return Item.new("iron_sword", {MaxDurability = Components.MaxDurability(35)})
	end,
}
-- Authorized Use By Dissonant Realms and Galicate
-- Programmer : Galicate @ 7/16/2024 4:37 PM

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Knit = require(ReplicatedStorage.KnitPackage.Knit)
local ItemService = Knit.CreateService{Name = "ItemService", Client = {
	ItemSpawned = Knit.CreateSignal(),
	ItemDespawned = Knit.CreateSignal()
}}
local ItemsInit = require(ReplicatedStorage.Shared.Init.Items)
local Components = require(ReplicatedStorage.Shared.Util.Components)

function ItemService.SpawnItem(itemId: string, components: {})
	local item = ItemsInit[itemId]()
	if components then
		for componentName, componentData in pairs(components) do
			item:AddComponent(componentName, componentData)
		end
	end
	ItemService.Client.ItemSpawned:FireAll(item:GetId(), item:GetGUID(), item:GetComponents())
	return item
end

function ItemService:KnitStart()
	task.wait(2)
	local sword = self.SpawnItem("iron_sword", {Durability = Components.Durability(23)})
	print(sword)
end

return ItemService
1 Like

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