Skip to content

Objects

vitellaryjr edited this page Jun 26, 2022 · 49 revisions

Relevant files: object.lua, colliders, drawfx

Using Objects

Objects are the basic form of entity in Kristal. Everything in Kristal is an object: UI elements, characters, the overworld, bullets, everything. As such, understanding the basics of how they work is important to being able to use Kristal.

The essential properties of an object are its position, width and height, and parent. When an object is parented to another object, its position becomes relative to that object. All objects must be parented to something, otherwise the game will not recognize them (see Creating New Objects below for more detail). The highest object is Stage, and it is what most essential objects are parented to, such as the World and Battle objects.

The following is a list of all variables and functions that objects can use. Every object will have all of these variables and functions, though some may override functions to change their behavior.

Variables

x, y: Numbers defining the position of the object, in pixels. Positive numbers will go right and down, and negative numbers will go left and up. An object's position will always be relative to its parent.
width, height: The size of the object, in pixels.
color: A table of 3 numbers between 0 and 1, defining the RGB of the object's color. By default, it's {1,1,1}, which is white.
alpha: A number between 0 and 1, representing the transparency of the object. Defaults to 1.
scale_x, scale_y: Numbers defining the scale of the object, multiplying. Does not affect the object's width or height variables.
rotation: Number defining the rotation of the object, in radians.
flip_x, flip_y: Boolean variables that flip the object horizontally and vertically. Always flips it around its center.
inherit_color: Whether the object multiplies its own color and alpha by that of its parent.
origin_x, origin_y: Numbers between 0 and 1 usually, representing where the topleft of the object is relative to its width and height. 0, 0 means its topleft is at the object's position, 1, 1 means its bottomright is at its position (meaning its topleft is width pixels to the left of and height pixels above its position), and 0.5, 0.5 means the object is centered at its position. By default, all origin values are 0. Origin values can be less than 0 or greater than 1, which puts the origin outside of the object's size.
origin_exact: A boolean determining whether the object's origin values should be measured in pixels instead of a proportion. False by default.
scale_origin_x, scale_origin_y, scale_origin_exact, rotation_origin_x, rotation_origin_y, rotation_origin_exact: Same rules as origin_x, origin_y, and origin_exact, affecting where scaling and rotation originates from.
parallax_x, parallax_y: Numbers between 0 and 1 usually, representing how much the object will be moved when the camera moves. 0 means the object is unaffected by camera movement, and will stay in the same position on-screen regardless of it, and 1 means it will move along with the camera. By default, it is 1.
cutout_left, cutout_top, cutout_right, cutout_bottom: Determines a visual cut from the object if specified, in number of pixels. For example, if cutout_top is 5, then the object will have the top 5px of its rendering cut off. Note that these variables will not work properly if the object is rotated.
physics: A table of values that will be automatically used to move the object when it updates. All values are 0 by default. The values that can be defined in it are:

  • speed_x, speed_y: How many pixels the object will move horizontally and vertically per frame at 30fps.
  • speed: How many pixels the object will move in direction per frame at 30fps.
  • direction: The direction the object will move in if speed is defined, in radians.
  • spin: How much direction will change per frame at 30fps.
  • match_rotation: If set to true, then direction will be updated to match the object's rotation.
  • friction: The amount the object's speed_x, speed_y, and speed will be reduced by per frame at 30fps.
  • gravity: The amount the object's will accelerate towards gravity_direction per frame at 30fps. If speed is defined, then gravity will affect it; otherwise, gravity affects speed_x and speed_y.
  • gravity_direction: The direction the object will accelerate towards, in radians, if gravity is not 0. If speed is defined, then direction will approach gravity_direction (or be set directly if it isn't yet defined); otherwise, speed_x and speed_y will accelerate towards the angle defined. Defaults to math.pi / 2, which is directly downwards.
  • move_target: A table of data defining where the object is moving, if slideTo() or slideToSpeed() is used. Should not be set directly, see the mentioned functions below; however, it can be set to nil during a movement to cancel a movement.
  • move_path: A table of data defining the path the object is moving on, if slidePath() is used. Should not be set directly, see the mentioned function below; however, it can be set to nil during a movement to cancel a movement.

graphics: A table of values that will be automatically used to alter the object's visual properties when it updates. The values that can be defined in it are:

  • fade: How much the object's alpha will approach fade_to per frame at 30fps. By default, it is 0, and thus fade_to will not be approached.
  • fade_to: The alpha that the object's alpha will approach.
  • fade_callback: A function, taking in self as an argument, that will be called when the object's alpha reaches its fade_to value.
  • grow_x, grow_y: How much the object's scale_x and scale_y will change per frame at 30fps.
  • grow: How much the object's scale will change per frame at 30fps (affecting both scale_x and scale_y simultaneously).
  • remove_shrunk: If set to true, the object will be removed if its scale becomes 0 or negative.
  • spin: How much the object's rotation will change per frame at 30fps.

timescale: A number that the object's updating will be multiplied by.
layer: A number representing the order the object will be rendered and updated in. The higher this number is, the later it will render and update (appearing in front of and updating after objects with lower layer values). Should only be changed directly before it's added to a parent; for changing layer after being added to a parent, see setLayer() below in Functions. A list of common layer values for objects can be found in the global variables list.
collider: Specifies how other objects collide with this object. See Collision below for more detail.
collidable: Determines whether other objects can collide with this object. See Collision below for more detail.
active: Whether the object updates.
visible: Whether the object renders.
draw_fx: A table of visual effects to apply when rendering. See DrawFX below for more detail.
parent: The parent of the object. Should not be changed directly, see addChild() and removeChild() below in Functions.
children: A table of objects, if the object itself is a parent. Should not be changed directly, see addChild() and removeChild() below in Functions.
debug_select: Whether the object can be selected by the Object Selection debug menu (see Debug Menus for more detail). Defaults to true.
debug_rect: A table of 4 values, defining the x, y, width and height of a rectangle that will be used by the Object Selection debug menu.

Functions

All functions for objects require that you pass the object into them. While this could be done by doing obj.move(obj, x, y, speed), Lua has simpler syntax for this, in the form of obj:move(x, y, speed). Unless specified otherwise, all functions for any objects on this wiki should be called in this way.

move(x, y, speed): Moves the object x pixels horizontally and y pixels vertically, multiplied by speed if speed is provided.
setPosition(x, y), setSize(width, height), setScale(x, y), setColor(r, g, b, a), setParallax(x, y), setCutout(left, top, right, bottom): Functions used to set multiple values at once.
setOrigin(x, y), setScaleOrigin(x, y), setRotationOrigin(x, y): Sets the origin values to the specified x and y values, and sets the associated origin_exact variable to false.
setOriginExact(x, y), setScaleOriginExact(x, y), setRotationOriginExact(x, y): Sets the origin values to the specified x and y values, and sets the associated origin_exact variable to true.
setSpeed(x, y): If x and y are defined, set the object's physics.speed_x and physics.speed_y values to x and y respectively; if only x is defined, set the object's physics.speed to x; and if no variables are defined, set all of the object's physics speed variables to 0.
setHitbox(x, y, width, height): Sets the object's collider to a new Hitbox with the provided values. See Collision below for more detail.
setLayer(layer): Sets the layer of the object. Must be used instead of changing layer directly.
getPosition(), getSize(), getScale(), getColor(), getParallax(), getCutout(), getLayer(), canDebugSelect(): Functions used to get values for the object. When returning multiple values, these always returns each value individually, as opposed to a table of values (eg getColor() returns r, g, b, a as opposed to {r,g,b}, a).
getOrigin(), getScaleOrigin(), getRotationOrigin(): If the associated origin_exact is false, return the values as is; otherwise, returns values calculated from the exact origin values to get the relative proportional values.
getOriginExact(), getScaleOriginExact(), getRotationOriginExact(): If the associated origin_exact is true, return the values as is; otherwise, returns values calculates from the proportional origin values to get the relative pixel values.
setParallaxOrigin(x, y): Sets the object's parallax origin to the specified position, in pixels relative to its parent. Allows for accurately defining the starting position of an object's parallax.
getSpeed(): Returns physics.speed if it is defined, then physics.speed_x and physics.speed_y if those are defined and physics.speed isn't, then 0 otherwise.
getHitbox(): Returns the coordinates and dimensions of the object's Hitbox if it exists.
shiftOrigin(x, y): Moves the origin of the object while not moving the object itself.
fadeTo(target, speed, callback): Sets fade_to, fade, and fade_callback respectively for the object's graphics table.
fadeOutAndRemove(speed): Sets fade_to to 0 and fade to the specified speed for the object's graphics table. When the object's alpha reaches 0, the object is removed.
slideTo(x, y, time, ease, after), slideTo(marker, time, ease, after): Moves the object to the specified position over a period of time. x and y are coordinates to move to, or marker is a marker name to move to the position of; time is the amount of time, in seconds, it will take to move to the destination, ease is an optional string defining the ease type to use for the movement, and after is an optional function that will be called once the object reaches its destination.
slideToSpeed(x, y, speed, after), slideToSpeed(marker, speed, after): Moves the object to the specified position by the specified speed. x and y are coordinates to move to, or marker is a marker name to move to the position of; speed is a number referring to how fast the object will approach its destination in pixels per frame at 30fps, and after is an optional function that will be called once the object reaches its destination.
slidePath(path, options): Moves the object across a series of positions. path is either a table of tables with 2 values each, defining the coordinates of a point, or a string referring to a map path, if the object is in the overworld. options is a table of data, defining information the path should use. The following is a list of fields that can be defined:

  • time: How long it should take the object to reach the end of the path, in seconds.
  • speed: How fast the object should move along the path. Overrides time if defined.
  • loop: Whether the path should loop back to its first position infinitely.
  • ease: How the movement along the path should be eased. Defaults to linear.
  • after: A function that will be called when the object reaches the end of the path, if defined. Will not be called if loop is true.
  • move_func: A function that, if defined, will be responsible for handling the movement of the object along the path, receiving the object being moved and the x and y of the position being moved to per frame as arguments.
  • relative: If true, the specified path will be relative to the object's current position (eg. {0, 0} on the path will refer to the object's starting position).
  • reverse: If true, the path will be reversed. Only works if relative is not true.
  • skip: A number defining how many of the positions in the path to skip over when starting.
  • snap: If true, the object's position will be immediately set to the first position of the path; otherwise, the object's current position will be added to the path. Automatically true if loop is true.

resetPhysics(): Resets the physics table for the object to its default values.
resetGraphicsTransform(): Resets the graphics table for the object to its default values.
setScreenPos(x, y): Sets the object's position relative to the topleft of the screen.
getScreenPos(): Gets the object's position relative to the topleft of the screen.
localToScreenPos(x, y): Gets the screen position of a point specified, relative to the object.
screenToLocalPos(x, y): Gets the position relative to the object of a point on the screen.
A note for screen position functions: the screen position accounts for the window scale, such that any coordinates will be the same in-game position regardless of window size.
setRelativePos(x, y, other): Sets the position of the object x units horizontally and y units vertically from another object.
getRelativePos(x, y, other): Gets the position of the object x units horizontally and y units vertically from another object.
getStage(): Gets the highest parent of the object. In other words, if the object's parent has its own parent, return parent:getStage() instead.
getDrawColor(): Gets the color of the object, multiplied by its parent's color if inherit_color is true.
collidesWith(other): Returns whether the object is colliding with other. other can be either a Collider or an Object with collider defined. See Collision below for more detail.
addFX(effect, id): Adds the specified visual effect to the object. effect is an instance of a DrawFX class, and id is an optional string that allows the player to define the effect's ID. See Effects below for more detail.
getFX(id): Returns the specified visual effect from the object if it has it. id can either be a string, referring to an effect ID, or it can be an effect class (eg. ColorMaskFX), returning a visual effect that extends the specified class. See Effects below for more detail.
removeFX(id): Removes the specified visual effect from the object if it has it. id can either be a string, referring to an effect ID, or an effect instance. See Effects below for more detail.
remove(): Removes the object.
explode(x, y, dont_remove): x and y are positions relative to the object, and dont_remove is a boolean determining whether the object should be removed, defaulting to false.
addChild(child): Makes the object the parent of the specified child.
removeChild(child): Removes a child from an object.
setParent(parent): Makes the object the child of the specified parent, and, if it had a parent previously, removes itself from that parent.
isFullyActive(): Returns whether the object and all of its parents are active.
isFullyVisible(): Returns whether the object and all of its parents are visible.
getDebugRectangle(): Returns a table of 4 values, defining the x, y, width and height of a rectangle that will be used by the Object Selection debug menu. By default, returns debug_rect if defined, and a rectangle fitting the object's width and height otherwise.

Creating New Objects

Kristal uses a class-based coding structure implementation in Lua, similar to languages like C#. To create a new instance of an existing class, you call that class's name like a function, passing in values matching what that class's Init function requires. For example, creating a new instance of an Object would be done with Object(x, y, width, height). However, creating instances of just an Object is generally not helpful; instead, you'll generally want to make classes that extend Object.

To extend a class, you'll want to make a new .lua file in scripts/objects. Object files in this folder get loaded automatically; you can place object files in other folders, but you'll have to require them yourself. An example of a new class that extends Object:

-- Create a new class which extends Object
local MyObject, super = Class(Object)
-- A class that extends a different class contains all of the functions of the class it extends.
-- Class(classname) returns two values: the new class you'll be changing,
-- and a copy of the class you're extending.

-- By setting MyObject:init, we replace the function Object:init for this class.
-- This is called overriding a function, and it's important to understand for extending classes.
function MyObject:init(x, y, width, height)
  -- This is what 'super' is used for: we need to still call the original function,
  -- so we call super:init(self, ...) to call the original Object:init(...).
  -- As mentioned earlier, majority of functions for Objects require self to be passed in.
  -- Because of this, we always need to pass in the new object's self
  -- into any super functions as the first argument, followed by the rest of the arguments.
  super:init(self, x, y, width, height)
end

-- Called every frame by this object's parent
function MyObject:update()
  -- Update code goes here, like moving the object

  -- We're overriding Object:update, so again, we can call the original using 'super'
  -- This calls update on all this object's children, so place it where you want!
  super:update(self)
  -- By default, update() also handles changing the object based on its 'physics'
  -- and 'graphics' tables
end

-- Called every frame too, during rendering
function MyObject:draw()
  -- Draw code goes here, you can draw textures and rectangles and whatever you want basically

  -- Once more, we're overriding Object:draw, so we can call the original using 'super'
  -- This calls draw on all this object's children, so placement is important!
  -- Code before this line will be drawn below children, code after this line will be drawn above children
  super:draw(self)
end

-- Returns the class, to be used later
return MyObject

Other overridable functions are:
onAdd(parent): Called when the object is added as a child to a parent. parent is the Object instance that the object is being parented to.
onRemove(parent): Called when the object is no longer a child of a parent (either as a result of the object being removed via remove(), or as a result of its parent being changed via a new addChild(object) or removeChild(object)). parent is the Object instance that the object is being removed from.
onAddToStage(stage): Called when the object or any of its parents are added to a stage. stage is the Stage object that the object or its parents are parented to.
onRemoveFromStage(stage): Called when the object or any of its parents stop being parented to a stage. stage is the Stage object that the object or its parents are being removed from.
getDebugInfo(): Returns a table of data that will be used by the Object Selection menu to display data about the object. Each value in this table is a string that will be displayed. When overriding this function, you will want to get the table used by the original instance of the function first, then insert values into it. For example, this is how the Character object extends getDebugInfo():

function Character:getDebugInfo()
  local info = super:getDebugInfo(self)
  table.insert(info, "Actor: " .. self.actor.name)
  table.insert(info, "Noclip: " .. (self.noclip and "True" or "False"))
  return info
end

Extra Notes

Sometimes, extending classes will not work with the normal method of passing in the class itself. Circumstances like global variables not being created for a class, or indeterminate loading order of class files, can cause this. To extend a class from these circumstances, you'll want to pass the file path, relative to the current base folder for the class file, of the class you want to extend. For example, if you want an Object file to extend an object located at scripts/objects/ZObject.lua, you would call Class("ZObject"), since you can't guarantee that ZObject would load first.

When a class is made in scripts/objects, a global variable named after the file name is created. This global variable is used to create instances of this new class. For example, with the above code at scripts/objects/MyObject.lua, you can create new instances of MyObject by calling MyObject(x, y, width, height).

As mentioned earlier, in order for new instances of objects to be recognized by the game, they must be parented to another object. This parent will be responsible for updating and rendering its children. When creating a new object, your code will generally look something like this:

local object = MyObject(...)

-- someParent is an already existing object that's been added to something else

someParent:addChild(object)
-- OR
object:setParent(someParent)

When an object is parented to another object, its position and layer become relative to its parent's. This accounts for information like origin from the parent, and thus, position will nearly always be relative to the parent's topleft. For example, when parenting an object at 40, 40 with a layer of 1 to Game.battle.arena, its position will be 40 pixels to the right of and 40 pixels below the arena's topleft, and it will render above the arena, since its layer is effectively 301.

However, it is worth noting that children of an object cannot render above objects that render above its parent, since the parent renders its children directly, and so they inherit its render order overall. This means that an object A with a layer of 200 parented to the arena will not render above an object B parented to Game.battle with a layer of 400, due to object B and the arena both sharing a parent of Game.battle; while object A's layer is effectively 500, the arena's layer is 300, and thus it will render before object B. This all applies to updating too; children of an object will update before any objects with a higher layer than its parent.

Common objects to be parents are:

  • Game.world for objects in the overworld. Its position will be relative to the topleft of the room the player is in.
  • Game.battle for objects in battle. Its position will be relative to the topleft of the screen.
  • Game.shop for objects in shops. Its position will be relative to the topleft of the screen.
  • Game.stage for miscellaneous objects that should be parented higher than in those (eg. when making an object that should persist from the overworld into a battle). It is always present, and it's what the previous objects are parented to. Its position will be relative to the topleft of the screen.
  • Kristal.stage for objects that should persist into the main menu. Use carefully.

Collision

Objects can use Colliders to check whether they collide with each other. Colliders themselves are not objects, however, and thus, don't need to be added as a child to other objects. To set an object's collider, you only need the following code:

object.collider = Collider(object, ...)

Objects can have other variables defined as colliders as well, but only collider will be checked by other code within Kristal; you'll have to manually collide check for anything else.

All colliders follow the same rules for their arguments: the first argument is the object the collider should be attached to, then it takes arguments correlating to the shape of the collider, then there's an optional final argument called data which allows defining extra options for the collider. The following are the types of colliders available:

Hitbox(parent, x, y, width, height, data): Rectangular collision. x and y refer to the topleft of the rectangle.
CircleCollider(parent, x, y, radius, data): Circular collision. x and y refer to the center of the circle.
LineCollider(parent, x1, y1, x2, y2, data): Linear collision. x1, y1 and x2, y2 refer to the coordinates of the 2 points.
PointCollider(parent, x, y, data): Singular point collision. x and y refer to the coordinates of the point.
PolygonCollider(parent, points, data): Polygon collision. points is a table of tables with 2 values each, referring to the coordinates of each point of the polygon.
ColliderGroup(parent, colliders, data): A group of colliders. colliders is a table of other Collider instances.

The data argument is a table that can define certain boolean fields to change how the collider works. Enabling multiple fields causes their effects to combine as one would expect. The following are fields that can be defined:

inverted: Simply inverts the result of the collision, returning true if it is not being collided with and vice versa.
inside: Causes the collision to only return true if the checking collider is entirely within this collider. In other words, with this enabled, if any point of the checking collider is not colliding with this, the result is false.

Variables and functions that all colliders can use:

collidable: If set to false, the collider will not collide with anything.
collidesWith(other): Returns whether the collider is colliding with other. other can be either a Collider, or an Object with collider defined.
draw(r, g, b, a): Draws the bounds of the collider.
drawFill(r, g, b, a): Draws the filled shape of the collider.

DrawFX

Objects can be given special visual effects, internally called DrawFX, by using addFX(). DrawFX can be used to apply code to an object's Canvas as it's being rendered. Objects can have multiple DrawFX at once, and they will render in priority order; every DrawFX takes a priority argument, which is a number that defines how early an effect should be rendered when there are multiple, with larger priority values rendering before smaller ones.

Kristal implements multiple DrawFX for general usage. These effects are:

AlphaFX(alpha, priority): Renders the object at the specified alpha.
RecolorFX(r,g,b,a, priority): Renders the object multiplied by the specified color.
ColorMaskFX(color, amount, priority): Overlays color over the object. color is a table of RGB (defaulting to white), and amount is the alpha amount of the color (defaulting to 1).
MaskFX(mask, draw_children, priority): Masks the object to another object. mask is either an instance of an Object or a function that returns an instance of an Object, and draw_children is a boolean that determines whether the mask object should consider its children as part of the mask (defaulting to true). If mask is not specified, then the object itself will be a mask for all of its children.
ShaderFX(shader, vars, priority): Renders the object with the specified shader. shader is a string of OpenGL shader code, and vars is a table of variables to be sent to the shader. If a value in vars is a function, the shader will receive whatever the function returns every frame.

Custom DrawFX can also be defined in mods. Similar to custom Objects, the file name determines the name of the global class. DrawFX go in scripts/objects, and should be made by extending the global FXBase class.

The following variables should be defined in the init function of the DrawFX:

priority: As explained earlier, this determines the order in which DrawFX will render. DrawFX with a higher priority will render before others. Defaults to 0.
id: The default ID for the DrawFX.
active: Whether the DrawFX should be rendered. Defaults to true.

Additionally, the following functions can be overridden:

update(): Called every frame.
draw(texture, object): Responsible for drawing the object with the effect desired. texture is a Canvas of the object at its current draw state, and object is the instance of the object being drawn. This function must call love.graphics.drawCanvas(texture), otherwise the object will not be drawn.
isActive(): Returns whether the DrawFX should be rendered. Returns active by default.

Clone this wiki locally