Skip to content

Inheriting Classes

Older/Work In Progress Page

  • This page was initially written for an older version of Helix, has not been updated, and may be out of date!
  • Contributions are more than welcome (see buttons at the top right of the page).

How to inherit HELIX Classes. HELIX provides a built-in way of inheriting the built-in Classes

Warning

This feature is still experimental, you can try it out and provide feedback before it's full release!

Inheriting a Class#

Inheriting a HELIX Class is really easy, for that you just need to use the Inherit static method on the Class you want to inherit:

-- Creates a new Class called "MyNewClass" inheriting from Prop
-- and stores it in the variable MyNewClass
MyNewClass = Prop.Inherit("MyNewClass")

-- Spawn it using the default constructor
local my_new_class_instance = MyNewClass(Vector(), Rotator(), "helix::SM_Cube")

Multiple Inheritance#

You can also inherit from other inherited classes:

-- Creates a new Class called "MyNewSubClass" inheriting from MyNewClass
MyNewSubClass = MyNewClass.Inherit("MyNewSubClass")

-- Spawn it using the default constructor
local instance = MyNewSubClass(Vector(), Rotator(), "helix::SM_Cube")

Overriding the Constructor#

You can create your own Constructor for your entities, for that you need to define the Constructor method:

-- Defines my constructor with any parameters you desire
function MyNewClass:Constructor(location, rotation)
    -- Do any kind of logic here
    location = location + Vector(0, 0, 100)

    -- Calls Super Constructor to finalize the construction
    -- This is the original constructor (in this case from Prop)
    -- This is mandatory, if you don't call it, it will throw an error
    -- You will only be able to access original and your own class
    -- methods after calling it, when the class is completely spawned
    self.Super:Constructor(location, rotation, "helix::SM_Cube")

    -- Now it's allowed to class methods
    self:SetMaterialColorParameter("Tint", Color.RED)
end

-- Spawn it using your custom constructor
local my_new_class_instance = MyNewClass(Vector(123, 456, 100), Rotator())

Tip

Inside the constructor, the entity is not fully spawned yet, so you cannot call other entity methods besides self.Super:Constructor. Here you should just use to validate constructor parameters, and use Spawn event to fully setup the entity.

Global Registry#

Through the parent class, we can get a list of all children classes of that class, having a global registry of all existing classes!

local children_classes = ToolGun.GetInheritedClasses()
for _, class in pairs(children_classes) do
    -- 'class' is a custom inherited class! we can spawn it
    local p = class()
end

Adding new Methods#

Adding new methods for new classes is very straightforward, let's say we want to add a new method for MyNewClass, we just do that:

function MyNewClass:Explode()
    -- Spawns a particle
    Particle(self:GetLocation(), Rotator(), "helix::P_Explosion")

    -- Destroys myself
    self:Destroy()
end

Tip

Within your methods, you can access the called entity instance with self.

And then you are able to call it as usual:

my_new_class_instance:Explode()

Overriding Existing Methods#

Besides creating new methods, it's possible to override existing ones, for that just redefine them:

function MyNewClass:SetLocation(new_location)
    -- Do any kind of logic here
    new_location = new_location + Vector(0, 0, 100)

    -- Call Super to set the location to the parent Prop
    self.Super:SetLocation(new_location)
end

Calling Native Methods#

To call native Class methods, you can use the special variable self.Super, which will allow you accessing the native and original methods directly:

function MyNewClass:GetRotation()
    -- Calls original GetRotation and adds 90 to yaw
    return self.Super:GetRotation() + Rotator(0, 90, 0)
end

Calling Parent Methods#

Besides calling the original/native method with self.Super, we can also call parent methods if you have nested inheritance.

For that, you must use a special Lua syntax with PARENT_CLASS.METHOD_NAME(self, ...), for example:

Tip

In Lua, passing a value as the first parameter to a method while calling it with . will make that value appear as self inside the called method (if the method was defined with :).

-- Inherits Prop
MyNewClass = Prop.Inherit("MyNewClass")

function MyNewClass:SetScale(scale)
    -- Does some logic
    scale = scale * 2

    -- Calls Super (original Prop method)
    self.Super:SetScale(scale) * 2
end

-- Inherits MyNewClass
MyNewSubClass = MyNewClass.Inherit("MyNewSubClass")

function MyNewSubClass:SetScale(scale)
    -- Does some logic
    scale = scale + Vector(2, 2, 2)

    -- Calls Parent MyNewClass method with special syntax
    MyNewClass.SetScale(self, scale)
end

Tip

This same rule applies for calling inherited Constructors!

Overriding __newindex#

It is also possible to add a custom __newindex metamethod on Inherited Classes.

Tip

__newindex metamethod is a function which is triggered when you attempt to set a value in an entity. E.g.: my_entity.something = 123.

For that, we just add a custom method called newindex:

Note

The method name must be newindex and not __newindex as __newindex is the native method used internally to make the inheritance to work.

function MyNewClass:newindex(key, value)
    Console.Log("Setting a %s value: %s = %s", tostring(self), key, tostring(value))
end

An useful way of using __newindex is overriding it to SetValue automatically:

function MyNewClass:newindex(key, value)
    self:SetValue(key, value)
end

-- Example usage
local my_entity = MyNewClass()
my_entity.amazing_value = 123

Overriding __index#

Tip

__index metamethod is a function which is triggered when you attempt to get a value from an entity. E.g.: local value = my_entity.something.

For that, we just add a custom method called index:

Note

The method name must be index and not __index as __index is the native method used internally to make the inheritance to work.

function MyNewClass:index(key)
    Console.Log("Getting %s value: %s", tostring(self), key)
    -- ... do something
    return some_value
end

You can also use __index to return a method:

function MyNewClass:index(key)
    Console.Log("%s key not found: %s", tostring(self), key)

    -- inside the redirected method you will have all the parameters passed originally
    return function(self, param1, param2...)
        -- ... do something
        return "triggered!"
    end

    -- or you can even redirect to other member functions
    return MyClass.SetLocation
end

local my_entity = MyNewClass()
my_entity:NonExistentMethod(123, "456")

An useful way of using __index is overriding it to GetValue automatically:

function MyNewClass:index(key)
    return self:GetValue(key)
end

local my_entity = MyNewClass()
local amazing_value = my_entity.amazing_value

Overriding __tostring#

You can override __tostring as well as usual:

function MyNewClass:__tostring()
    return "My Incredible Class!"
end

Native Events#

All events which are triggered on an inherited Class will only trigger in that Class and it's parents, also the parameter passed is the custom entity itself, example:

Prop.Subscribe("Spawn", function(self)
    Console.Log("Spawned Prop: %s", tostring(self))
end)

MyNewClass.Subscribe("Spawn", function(self)
    Console.Log("Spawned MyNewClass: %s", tostring(self))
end)

local my_entity = MyNewClass()
local my_prop = Prop()
local my_other_entity_inherited_from_prop = MyOtherClass()

-- Will output:
--  Spawned Prop: MyNewClass
--  Spawned MyNewClass: MyNewClass
--  Spawned Prop: Prop
--  Spawned Prop: MyOtherClass

Another way of subscribing is separating the definition and the subscription, this way you don't need the first self parameter anymore:

function MyNewClass:OnSpawn()
    -- self is present is this context automatically
    Console.Log("Spawned MyNewClass: %s", tostring(self))
end

MyNewClass.Subscribe("Spawn", MyNewClass.OnSpawn)

Multiple Parents Example:#

MyNewClass = Prop.Inherit("MyNewClass")
MyNewSubClass = MyNewClass.Inherit("MyNewSubClass")
MyNewOtherSubClass = MyNewClass.Inherit("MyNewOtherSubClass")

Prop.Subscribe("Spawn", function(self)
    Console.Log("Spawned Prop: %s", tostring(self))
end)

MyNewClass.Subscribe("Spawn", function(self)
    Console.Log("Spawned MyNewClass: %s", tostring(self))
end)

MyNewSubClass.Subscribe("Spawn", function(self)
    Console.Log("Spawned MyNewSubClass: %s", tostring(self))
end)

MyNewOtherSubClass.Subscribe("Spawn", function(self)
    Console.Log("Spawned MyNewOtherSubClass: %s", tostring(self))
end)

local my_entity = MyNewSubClass()

-- Will output:
--  Spawned Prop: MyNewClass
--  Spawned MyNewClass: MyNewClass
--  Spawned MyNewSubClass: MyNewClass

Tip

Note that Prop and all parent Classes will still trigger events for all child Classes!

Client/Server Synchronization#

If you define your entities on both Client and Server side, they will behave properly and in a synchronized way! Example:

```lua title=Server/Index.lua MyNewClass = Prop.Inherit("MyNewClass")

MyNewClass.Subscribe("Spawn", function(self) Console.Log("Spawned MyNewClass: %s", tostring(self)) end)

local my_entity = MyNewClass()

-- Will output: -- Spawned MyNewClass: MyNewClass

```lua title=Client/Index.lua
MyNewClass = Prop.Inherit("MyNewClass")

MyNewClass.Subscribe("Spawn", function(self)
    -- It was spawned on server and will spawn on Client as a MyNewClass properly
    Console.Log("Spawned MyNewClass: %s", tostring(self))
end)

-- Will output:
--  Spawned MyNewClass: MyNewClass

Custom Remote Events#

It is also possible to trigger custom events on remote instances of your Class, using the methods CallRemoteEvent or BroadcastRemoteEvent, it works like the Events class:

```lua title=Client/Index.lua -- inherits the Class MyNewClass = Prop.Inherit("MyNewClass")

-- defines a custom method function MyNewClass:OnMyCustomRemoteEvent(a, b) Console.Log("OnMyCustomRemoteEvent!", tostring(self), a, b) self:CallRemoteEvent("AnotherRemoteEvent", 456, "def") end

-- subscribes for a custom remote event MyNewClass.SubscribeRemote("MyCustomRemoteEvent", MyNewClass.OnMyCustomRemoteEvent)

```lua title=Server/Index.lua
-- inherits the Class
MyNewClass = Prop.Inherit("MyNewClass")

-- Note that server-side received remote events have the 'player as first parameter
function MyNewClass:OnAnotherRemoteEvent(player, a, b)
    Console.Log("OnAnotherRemoteEvent!", tostring(self), tostring(player), a, b)
end

-- subscribes for a custom remote event
MyNewClass.SubscribeRemote("AnotherRemoteEvent", MyNewClass.OnAnotherRemoteEvent)

-- spawns an entity and calls the custom remote event on that entity
local p = MyNewClass(...)
p:BroadcastRemoteEvent("MyCustomRemoteEvent", 123, "abc")

Class Custom Default Values#

It is possible to set a list of default values to the Inherited Class when creating it, just pass it as the 2nd parameter to Inherit:

-- inherits the Class
MyNewClass = Prop.Inherit("MyNewClass", {
    name = "My Name",
    category = "breakable",
    my_custom_param = 123
})

Console.Log(MyNewClass.category)
-- outputs "breakable"

Class Register Event#

When you inherit a new class, the event ClassRegister will be triggered on the parents classes, allowing Packages to know when a new Class is registered.

Prop.Subscribe("ClassRegister", function(class)
    -- here we see an useful case for the default values
    -- as we can access it here
    Console.Log(MyNewClass.name) -- outputs "My Name
    -- now we can do something (add to spawn menu?)
end)

-- inherits the Class
MyNewClass = Prop.Inherit("MyNewClass", {
    name = "My Name",
    category = "breakable",
    my_custom_param = 123
})