Classes

A class is used to define a reusable type that contains properties (data) and functions (behavior). You see a class as a blueprint that can be used to create multiple objects from the class.

class Character {
    Name = "Unknown"
    Health = 100

    Print() {
        print("Name: " + this.Name)
        print("Health: " + this.Health)
    }
}
local hero = new Character()
hero.Name = "Lithrun"
hero.Health = 150;
hero.Print()
// Output:
// Name: Lithrun
// Health: 150

In above example:

  • Character is the name of the class
  • Name and Health are fields
  • Print is a function that can be invoked on any Character object
  • new Character() creates an instance of the class

Why classes are useful:

  • It groups data and logic together
  • It makes the code easier to organize
  • It allows you to create many instances from the data predefined structure

Fields

A field is a variable that is stored in the instance of a class.

// A character has a Name field and a Health field
class Character {
    Name = "Unknown";
    Health = 100;
}
local hero = neww Character()
print(hero.Name) // Unknown
hero.Name = "Lithrun" // We now override the field with a new value
print(hero.Name) // Lithrun

Methods

A class can also define functions, which we refer to as methods. A method defines behavior of a class and can read or change its fields.

class Character {
    Name = "Unknown";
    Health = 100;

    ChangeHealth(health) {
        this.Health += health
    }

    IsAlive() {
        return this.Health >= 0
    }

    PrintStatus() {
        if (this.IsAlive()) {
            print(this.Name + " is alive with " + this.Health + "HP")
        } else {
            print(this.Name + " has died")
        }
    }
}
local hero = new Character()
hero.Name = "Lithrun"
hero.PrintStatus() // Lithrun is alive with 100HP
hero.ChangeHealth(-100)
hero.PrintStatus() // Lithrun has died

Constructor

A constructor is a special function that runs when a new instance of a class is created. This is used to set up the object with some default values.

class Character {
    Name = "Unknown";
    Health = 100;

    // Constructor
    Character(name, health) {
        this.Name = name
        this.Health = health
    }
}
local hero = new Character("Lithrun", 150) // { "Name": "Unknown", "Health": 150 }

It’s also possible to define multiple constructors

class Character {
    Name = "Unknown";
    Health = 0;

    Character(name) {
        this.Name = name;
        this.Health = 100;
    }

    Character(name, health) {
        this.Name = name;
        this.Health = health;
    }
}
local heroA = new Character("Lithrun") // Health is 100 as the 1st constructor was used
local heroB = new Character("Hergan", 200) // Health is 200, since we used the 2nd constructor

This

As seen in the constructor example, there is a this keyword. This refers to the current object inside a class. It lets a function or constructor access that objects fields and other methods.

class Character {
    Name = "Unknown";
    Health = 0;

    Character(name, health) {
        this.Name = name; // Using this to refer to the field of the Character
        this.Health = health;
    }

    TakeDamage(Health) {
        this.Health -= Health // Using this also helps to avoid confusion between fields and function parameters
    }
}
local hero = new Character("Lithrun", 100)
hero.TakeDamage(25) // Health is now 75

Static

Static is useful for:

  • Shared values
  • Helper functions related to the class
  • Data that should not exist on every instance

Static Field

A static field stores a value that is shared by the whole class. To access a static field, use the class name followed by the field name.

class Math {
    static PI = 3.1415926535
}

print(Math.PI)
local math = new Math()
print(math.PI) // Error, because PI belongs to the class, not the instance

Static Method

A static method is a function that belongs to the class itself. To call a static method, use the class name followed by the method name.

class Math {
    static function Add(a, b) {
        return a + b
    }
}

print(Math.Add(5, 10)) // 15

Static Class

When a class is marked as static, it cannot be instantiated. A static class cannot have a constructor, because no instances of it can be created. A static class is useful for utlity classes that only contain shared values or helpers functions.

static class MathHelpers {
    static PI = 3.14
}
print(MathHelpers.PI) // 3.14
local math = new MathHelpers() // Error, as a static class cannot have an instance

Inheritance

Classes also support inheritence. This lets one class build on another class. A derived class inherits the fields and functions of a base class, and can then add its own extra behavior.

class Item {
    Name = "Unknown Item"

    ShowName() {
        print("Item: " + this.Name)
    }
}

class Weapon : Item {
    Damage = 0;

    ShowStats() {
        print(this.Name + " deals " + this.Damage + " damage")
    }
}

local sword = new Weapon()
sword.Name = "Iron Sword" // The Weapon Class inherited the Name field
sword.Damage = 12

sword.ShowName() // Item: Iron Sword
sword.ShowStats() // Iron Sword deals 12 damage

Why use inheritence:

  • It reduces repeated code
  • It lets related classes share common behavior
  • It helpers organize types into clear relationships

Base

The base keyword refers to the parent class from inside a child class. It is mainly used for two things:

  • Calling the parent constructor
  • Calling a parent method

A constructor or method can be overriden from the parent class if it has the same name and parameters.

Base Constructor

Use base(...) inside the child constructor to initialize the parent of the object

class Item {
    Item(name) {
        this.Name = name
    }
}

class Weapon : Item {
    Weapon(name, damage) {
        base(name) // Calls the Item(name) constructor so we don't need to duplicate the logic
        this.Damage = damage
    }
}

var sword = new Weapon("Iron Sword", 12)
print(sword.Name) // Iron Sword
print(sword.Damage) // 12

Base Method

class Item {
    ShowName() {
        print("Item: " + this.Name)
    }
}

class Weapon : Item {
    ShowStats() {
        base.ShowName() // Whenever ShowsStats is called, the ShowName of Item is also called
        print("Damage: " + this.Damage)
    }
}

var sword = new Weapon()
sword.Name = "Iron Sword"
sword.Damage = 12
sword.ShowStats() // Item: IronSword, Damage: 12

Overriding a method

To override a method from the parent class, use the same method name and parameter types.

class Item {
    Describe() {
        print("This is an item")
    }
}

class Weapon : Item {
    Describe() {
        print("This is a weapon")
    }
}

var sword = new Weapon()
sword.Describe() // This is a weapon

When overriding the method, it’s still possible to call the parents original method with the base keyword.

class Item {
    Describe() {
        print("Item: " + this.Name)
    }
}

class Weapon : Item {
    Describe() {
        base.Describe()
        print("Damage: " + this.Damage)
    }
}

var sword = new Weapon()
sword.Name = "Iron Sword"
sword.Damage = 12
sword.Describe() // Item: Iron Sword, Damage: 12

Sealed

The sealed keyword is related to inheritence. When a class is marked as sealed, then it cannot be inherited. If a method or field is marked as field, then it cannot be overwrittene.

Sealed Class

sealed class Item {
}

// Throws an error, since Item is sealed, so Weapon cannot derive from Item
class Weapon : Item {
}

Sealed Method

class Item {
    Name = "Item"
    sealed Describe() {
        print("This is an item named: " + this.Name)
    }
}

class Weapon : Item {
    // Error, as Describe cannot be overridden
    Describe() {
        print("This is a weapon named: + this.Name)
    }
}

Sealed Field

class Item {
    sealed Name = "Item"
    Describe() {
        print("This is an item named: " + this.Name)
    }
}

class Weapon : Item {
    // Error, as Name cannot be overridden
    Name = "Weapon"
    Describe() {
        print("This is a weapon named: + this.Name)
    }
}

Access Modifiers

Access modifiers decide who can see and use a field or method. There are 3 access modifiers:

  • public accessible from anywhere. If no access modifier is specified, then it’s considered public
  • private only accessible inside the same class
  • protected only accessible inside the class and derived classes

Public

public means a field or method can be accessed from anywhere. It can be read, called, or modified by other code. Use public for members that are meant to be used outside the class, such as fields / methods that other objects should interact with.

class Weapon {
    public Damage = 0;
    // If no access modifier is specified, then it is public
    Name = "Test"
    Weapon(damage) {
        this.Damage = damage
    }

    public Print() {
        return FormatString(this.Damage + " Damage")
    }
}

local weapon = new Weapon(10);
print(weapon.Damage) // 10
weapon.Print() // 10 Damage
weapon.Damage = 25 // 25
print(weapon.Name) // Test
weapon.Name = "Lithrun"

Private

private means a field or function can only be accessed inside the class that declares it. It is completed hidden from outside the class and also from derived classes. This is useful for internal implementation details or data that should only get changed through controlled class logic. A common pattern is to keep a field private, and expose a public method that reads or modifies it.

class Weapon {
    private Damage = 0;
    Weapon(damage) {
        this.Damage = damage
    }

    // A common pattern to have immutable fields is to mark them as private
    // Then the value can be retrieved but never changed
    GetDamage() {
        return this.Damage
    }

    public Print() {
        return FormatString(this.Damage + " Damage")
    }

    private FormatString(s) {
        return "[Weapon] " + s;
    }
}

local weapon = new Weapon(10);
print(weapon.GetDamage()) // 10
weapon.Print() // [Weapon] 10 Damage

print(weapon.Damage) // Error, as Damage is private thus it cannot be accessed
weapon.Damage = 25 // Error, as Damage is private thus it cannot be modified
weapon.FormatString("test") // Error, as FormatString is a private method

Protected

protected allows a field or function to be used inside the declaring class and any class that inherits from it, while still hiding it from public access. It is useful when child classes need access to a shared internal state, but that state should not be part of the public API

class Item {
    protected Name = "";

    Item(name) {
        this.Name = name
    }

    protected FormatName() {
        return "Item: " + this.Name
    }
}

class Weapon : Item {
    Weapon(name) {
        base(name)
    }

    PrintName() {
        // Protected fields and methods can be accessed inside derived classes
        print(this.FormatName())
    }
}

local weapon = new Weapon("Iron Sword");
weapon.PrintName() // Item: Iron Sword

print(weapon.Name) // Error, as Name is protected and cannot be accessed from outside the class hierarchy
print(weapon.FormatName()) // Error, as FormatName is protected and cannot be called from outside the class hierarchy
weapon.Name = "Steel Sword" // Error, as Name is protected and cannot be modified from outside the class hierarchy

Categories:

Updated: