Classes
A class combines data (properties) and behavior (methods) into a single named type. Use the class keyword to declare one.
Declaration
class Vec2 {
x: Float
y: Float
}
Create an instance by calling the class name as a constructor:
var p = Vec2(x: 1.0, y: 2.0)
Properties
Stored Properties
Stored properties hold a value as part of the instance. A stored property may declare a default value.
class Button {
label: String = "Click Me"
width: Float
height: Float
}
Properties without a default value must be supplied when creating an instance.
Computed Properties
Computed properties derive their value from other properties. They are part of the language's reactivity system: a computed property is re-evaluated automatically when one of its dependencies changes, not on every access.
Read-only
A body written directly after the type creates a read-only computed property:
class Circle {
radius: Float = 1.0
circumference: Float {
return 2.0 * 3.14159 * self.radius
}
}
Read-write
Use explicit get and set blocks to make a computed property writable:
class Slider {
raw_value: Float = 0.0
percentage: Float {
get { return self.raw_value * 100.0 }
set(p) { self.raw_value = p / 100.0 }
}
}
Constructors
Default Constructor
Komplete Script generates a default constructor for every class. It accepts values for all stored properties as named arguments. Properties with default values are optional in the call.
class Vec2 {
x: Float
y: Float
}
var v = Vec2(x: 0.5, y: 1.0)
Custom Constructors
Define a custom constructor with the constructor keyword. It must delegate to the default constructor using : (…) to ensure all stored properties are initialized.
Constructors follow the same parameter rules as functions: parameters can have custom argument labels, be made positional with _, or be given default values. See Functions for the full parameter reference.
class Vec2 {
x: Float
y: Float
}
class BoundingBox {
top_left: Vec2
bottom_right: Vec2
constructor(x: Float, y: Float, width: Float, height: Float) : (
top_left: Vec2(x: x, y: y),
bottom_right: Vec2(x: x + width, y: y + height),
)
}
Adding a custom constructor hides the generated default constructor.
A custom constructor may include a body for additional setup steps:
constructor(x: Float, y: Float, width: Float, height: Float) : (
top_left: Vec2(x: x, y: y),
bottom_right: Vec2(x: x + width, y: y + height),
) {
print("BoundingBox created")
}
Multiple constructors can be defined on the same class, as long as each has a distinct parameter signature:
class Vec2 {
x: Float
y: Float
}
class BoundingBox {
top_left: Vec2
bottom_right: Vec2
constructor(x: Float, y: Float, width: Float, height: Float) : (
top_left: Vec2(x: x, y: y),
bottom_right: Vec2(x: x + width, y: y + height),
)
constructor(top_left: Vec2, bottom_right: Vec2) : (
top_left: top_left,
bottom_right: bottom_right,
)
}
var r1 = BoundingBox(x: 0, y: 0, width: 100, height: 50)
var r2 = BoundingBox(top_left: Vec2(x: 0, y: 0), bottom_right: Vec2(x: 100, y: 50))
Methods
Methods are functions declared inside a class body. They have implicit access to self.
class Counter {
count: Int = 0
increment() {
self.count = self.count + 1
}
value() -> (Int) {
return self.count
}
}
String Interpolation
By default, interpolating a class instance into a string produces a representation of its stored properties (computed properties are excluded):
import { sqrt } from math
class Vec2 {
x: Float
y: Float
length: Float {
return sqrt(self.x * self.x + self.y * self.y)
}
}
print("\{Vec2(x: 3.0, y: 4.0)}") // Prints "Vec2(x: 3.0, y: 4.0)"
Define a to_string() -> (String) method to customise this output:
import { sqrt } from math
class Vec2 {
x: Float
y: Float
length: Float {
return sqrt(self.x * self.x + self.y * self.y)
}
to_string() -> (String) {
return "(\{self.x}, \{self.y})"
}
}
print("\{Vec2(x: 3.0, y: 4.0)}") // Prints "(3.0, 4.0)"
self
Inside a class, self refers to the current instance and provides access to all its properties and methods. It is always implicitly available.
Subscript Operator
A class can support bracket subscript access (obj[expr]) by defining an at method with exactly one parameter. The parameter name and label are ignored for subscript calls — only the type matters. at is a regular method and can also be called directly:
class NumberList {
values: [Int] = []
at(_ index: Int) -> (Int) {
return self.values[index]
}
}
var list = NumberList(values: [10, 20, 30])
print("\{list[1]}") // Prints "20" — subscript syntax
print("\{list.at(1)}") // Prints "20" — direct method call, equivalent
To also support subscript assignment (obj[expr] = value), add an assign method with exactly two parameters: the assigned value first, then the subscript expression. It must not return a value. Like at, it can also be called directly:
class NumberList {
values: [Int] = []
at(_ index: Int) -> (Int) {
return self.values[index]
}
assign(_ value: Int, at index: Int) {
self.values[index] = value
}
}
var list = NumberList(values: [10, 20, 30])
list[1] = 99 // subscript assignment
list.assign(99, at: 1) // direct method call, equivalent
print("\{list[1]}") // Prints "99"
Reference Semantics
Classes have reference semantics: assigning a class instance to a variable or passing it to a function does not copy the instance — both the original and the new variable point to the same object. Mutating a property through one reference is visible through all other references to the same instance.
class Counter {
count: Int = 0
}
var a = Counter()
var b = a // b points to the same instance as a
b.count = 10
print("\{a.count}") // Prints "10" — a and b share the same instance
This makes classes well-suited as shared data models: a single instance can be declared globally or passed between components, and any mutation to it is immediately reflected everywhere that instance is used.