@hex-engine/core
@hex-engine/core
is the package that provides the core Entity and Component logic in Hex Engine. Although you likely won't use it directly, it's good to know it exists- many of the hooks and Component functions exported from @hex-engine/2d
are just being re-exported from @hex-engine/core
.
The @hex-engine/core
package has several named exports, which are each documented here.
Types
Entity
Available since version: 0.0.0
import { Entity } from "@hex-engine/core";
An Entity instance. The most common way to make one of these is via useChild
,
but you also get one returned by createRoot
. You can get the Entity for the current
component by using useEntity
.
Properties
name
Available since version: 0.0.0
name: string | null
A name that will be shown in the inspector, for debugging purposes.
You can set this using useEntityName
. If you don't set one, we will
try to infer one, based on the name of the root Component
.
id
Available since version: 0.0.0
id: number
A unique id for this Entity. These start at zero and go up by one whenever you create an Entity.
children
Available since version: 0.0.0
children: Set<Entity>
This Entity's child entities. These get here from components on
this Entity calling useChild
, or from code anywhere calling Entity.createChild
, Entity.addChild
, or Entity.takeChild
.
parent
Available since version: 0.0.0
parent: Entity
The parent Entity
for this Entity, or null if there isn't one.
Generally, this will only ever be null if you're dealing with the root Entity.
components
Available since version: 0.0.0
components: Set<Component>
All the Component
instances that belong to this Entity.
Methods
descendants
Available since version: 0.0.0
descendants(): Array<Entity>
Search this Entity's children recursively for their children, and return an Array containing every Entity that is a descendant of this one.
ancestors
Available since version: 0.0.0
ancestors(): Array<Entity>
Find this Entity's parent, then this Entity's parent's parent, and so on, until there aren't any parents left. Then return the list as an Array.
The Array will be ordered such that the earliest ancestor (the root entity) is first in the Array, and the most-recent ancestor (this Entity's parent) will be last in the Array.
createChild
Available since version: 0.9.1
createChild(componentFunction: Function): Entity
Constructs a new Entity using the provided component function, then adds it to this Entity.
This is the same as useChild
, but instead of being bound to the current Entity, it adds it to the Entity you call createChild
on.
addChild
Available since version: 0.9.1
addChild(child: Entity): void
Adds a child Entity to this Entity.
If the child already has a parent, an Error will be thrown. You might want to call Entity.takeChild
in that situation instead.
removeChild
Available since version: 0.9.1
removeChild(child: Entity): void
Removes a child Entity from this Entity.
If the child is not a child of this Entity, an Error will be thrown.
NOTE: If you are not going to move this child to a new parent, then you MUST call Entity.destroy
on it, or else any destroy callbacks registered by its components (via useDestroy
) will not be called.
getComponent
Available since version: 0.0.0
getComponent(componentFunc: Function): Component | null
Searches the entity for a Component
with the type specified by componentFunc
,
and returns the first one found. If none are found, it returns null.
Note that in order to be found by this function, the Component must
have registered its type using useType
.
hasComponent
Available since version: 0.5.2
hasComponent(componentFunc: Function): boolean
Searches the entity for a Component
with the type specified by componentFunc
,
and returns true if one is found. If none are found, it returns false.
Note that in order to be found by this function, the Component must
have registered its type using useType
.
addComponent
Available since version: 0.5.2
addComponent(componentFunc: Function): Component
Creates a new Component and adds it to the entity. Returns the component instance for the created component.
This behaves the same as useNewComponent
, but can be used to add a Component later in an Entity's lifecycle.
removeComponent
Available since version: 0.5.2
removeComponent(component: Component): void
Removes a component instance from the entity. The component will be disabled prior to being removed. To define what should happen when your Component is disabled, use useEnableDisable
.
enable
Available since version: 0.0.0
enable(): void
Enable all Component
s on this Entity and its descendants.
Use the useEnableDisable
hook to specify what happens when a Component
is enabled or disabled.
disable
Available since version: 0.0.0
disable(): void
Disable all Component
s on this Entity and its descendants.
Use the useEnableDisable
hook to specify what happens when a Component
is enabled or disabled.
takeChild
Available since version: 0.5.0
takeChild(entity: Entity)
Transfer the ownership of entity
to this Entity, so that it becomes the
parent of entity
.
destroy
Available since version: 0.1.2
destroy(): void
Destroy this entity and remove it from the tree.
This runs all onDestroy callbacks registered via useDestroy
,
on this Entity and all of its children.
Additionally, all entities are disabled prior to being destroyed.
Component
Available since version: 0.0.0
import { Component } from "@hex-engine/core";
A Component instance. Every Component created via useNewComponent
, useChild
or createRoot
has these properties and methods available on it, in addition to any properties or methods on the object returned by its Component function (if any).
Properties
type
Available since version: 0.0.0
type: Function | null
The Component function that this Component instance cooresponds to.
This gets set when you call useType
, and it must be set in order
for this Component instance to be returned from Entity.getComponent
.
entity
Available since version: 0.0.0
entity: Entity
The Entity this Component belongs to. Inside of a Component function,
you can call useEntity
to get this.
isEnabled
Available since version: 0.0.0
isEnabled: boolean
Whether this Component is currently "enabled". To define what should
happen when your Component is enabled or disabled, use useEnableDisable
.
Methods
enable
Available since version: 0.0.0
enable(): void
Enable this Component.
Use the useEnableDisable
hook to specify what happens when a Component
is enabled or disabled.
disable
Available since version: 0.0.0
disable(): void
Disable this Component.
Use the useEnableDisable
hook to specify what happens when a Component
is enabled or disabled.
Functions
createRoot
Available since version: 0.0.0
createRoot(componentFunction: Function)
import { createRoot } from "@hex-engine/core";
Creates the root Entity
for your game. Pass it a Component function; it will use it to make a new Component
and add it to a new Entity, which it then returns.
Usage
import { createRoot } from "@hex-engine/core";const rootEntity = createRoot(() => {// Your component code goes here});// rootEntity is an Entity
Hooks
Hooks are special functions that interact with the "current" Component. The current Component gets set by Hex Engine when it instantiates a Component for you. The most common ways to instantiate Components are through createRoot
, useChild
, and useNewComponent
useType
Available since version: 0.0.0
useType(componentFunction: Function): void
import { useType } from "@hex-engine/core";
Registers the type of the current component.
In order for components to be retrievable by type, every Component must have a type
registered. useType
is how you register that type.
99.9% of the time, you should pass in the constructor function for the current component.
Usage
import { useType } from "@hex-engine/core";function MyComponent() {// Sets `MyComponent` as the current component's `type`.// The first line of every Component you write should set// its type using `useType`.useType(MyComponent);}
useNewComponent
Available since version: 0.0.0
useNewComponent(componentFunction: Function): Component
import { useNewComponent } from "@hex-engine/core";
Create a new Component
and add it to the current Component's Entity.
Returns an object that has all the properties and methods of a Component
instance as well as all the properties and methods of the object returned by the provided Component function (if any).
Note that the Component function passed into useNewComponent
will only be called once, in order to create the Component instance.
Usage
import { useType, useNewComponent } from "@hex-engine/core";function MyComponent() {useType(MyComponent);const { color } = useNewComponent(() => MyOtherComponent("red"));}function MyOtherComponent(color: string) {useType(MyOtherComponent);return { color };}
useEntity
Available since version: 0.0.0
useEntity(): Entity
import { useEntity } from "@hex-engine/core";
Retrieves and returns the current Component's Entity
.
Usage
import { useType, useEntity } from "@hex-engine/core";function MyComponent() {useType(MyComponent);const entity = useEntity();}
useChild
Available since version: 0.0.0
useChild(componentFunction: Function): Entity
import { useChild } from "@hex-engine/core";
Create a new Entity
(using the provided Component function) and add it as a child to the current Entity.
Usage
import { useType, useChild } from "@hex-engine/core";function MyComponent() {useType(MyComponent);useChild(MyOtherComponent);}function MyOtherComponent() {useType(MyOtherComponent);}
useCallbackAsCurrent
Available since version: 0.0.0
useCallbackAsCurrent(callback: Function): Function
import { useCallbackAsCurrent } from "@hex-engine/core";
Wraps the provided callback function such that it is bound to the current Component instance.
After a function has been wrapped by useCallbackAsCurrent
, then when it is called in the future, hooks within it will always be run against the Component instance it had when it was wrapped, rather than the current component instance.
Generally, you shouldn't need to use this yourself, but many Components
in @hex-engine/2d
rely on this to register event handler and animation
frame callbacks.
Usage
import {useType,useCallbackAsCurrent,useChild,Mouse,Entity,} from "@hex-engine/core";function MyComponent() {useType(MyComponent);// This creates a version of `useChild` that is "bound" to the current Component.// When it gets called in MyChildComponent, the new Entity will get added to// the MyComponent instance, not the MyChildComponent instance, because the// function has been "bound" to the current Component.const useChildAsMyComponent = useCallbackAsCurrent(useChild);useChild(() => MyChildComponent(useChildAsMyComponent));}function MyChildComponent(useSibling: (componentFunction: Function) => Entity) {useType(MyChildComponent);const mouse = useNewComponent(Mouse);mouse.onClick(() => {useSibling(MyChildComponent);});}
useDestroy
Available since version: 0.0.1
useDestroy(): { onDestroy(callback: () => void), destroy(): void }
import { useDestroy } from "@hex-engine/core";
Return an object with two functions on it: onDestroy
and destroy
.
onDestroy
registers a function to be run if the current Entity is destroyed.destroy
destroys the current Entity.
Note: Calling destroy
will also run any "disable" callbacks on the Entity's components that were registered via useEnableDisable
.
Usage
import { useType, useDestroy, useNewComponent, Mouse } from "@hex-engine/core";function MyComponent() {useType(MyComponent);const { onDestroy, destroy } = useDestroy();onDestroy(() => {console.log("Entity destroyed", useEntity());});const mouse = useNewComponent(Mouse);mouse.onClick(() => {destroy();});}
useEnableDisable
Available since version: 0.0.0
useEnableDisable(): { onEnabled(callback: () => void), onDisabled(callback: () => void) }
import { useEnableDisable } from "@hex-engine/core";
Returns an objest with three properties on it: isEnabled
, onEnabled
and onDisabled
.
isEnabled
is a writable boolean indicating whether the component is currently enabled.onEnabled
is a function that registers another function to be called when the current Component is enabled.onDisabled
is a function that registers another function to be called when the current Component is disabled.
In versions prior to 0.3.0, the
isEnabled
property was not present.
Note: If the Component is already enabled when you call onEnabled
, then the function you provide to onEnabled
will be called immediately. Likewise, if the Component is already disabled when you call onDisabled
, then the function you provide to onDisabled
will be called immediately.
Usage
import { useType, useEnableDisable } from "@hex-engine/core";function MyComponent() {useType(MyComponent);function handleResize() {console.log("Window is now this size:",window.innerWidth,window.innerHeight);}const { onEnabled, onDisabled } = useEnableDisable();onEnabled(() => {window.addEventListener("resize", handleResize);});onDisabled(() => {window.removeEventListener("resize", handleResize);});}
useEntityName
Available since version: 0.0.0
useEntityName(name?: string): string | null
import { useEntityName } from "@hex-engine/core";
Sets or gets the name of the current Entity.
This is just a nice-to-have for debugging purposes; if you don't do this, we will do our best to give the Entity a name based on its root Component.
In version 0.0.0, this hook could only set the entity name. The functionality to get the entity name was added in version 0.0.2, but the
name
parameter mistakenly remained required until version 0.1.3.
Usage
import { useType, useEntityName } from "@hex-engine/core";function MyComponent() {useType(MyComponent);// To set the entity name:useEntityName("MyComponent instance");// To get the entity name:const name = useEntityName();}
useFrame
Available since version: 0.0.0
useFrame(frameCallback: (delta: number) => void): void
import { useFrame } from "@hex-engine/core";
Register a function to be called once every animation frame, via the root Entity's RunLoop
Component. If you are using @hex-engine/2d
, you probably don't want to use this; use useUpdate
or useDraw
instead.
Usage
import { useType, useFrame } from "@hex-engine/core";function MyComponent() {useType(MyComponent);useFrame((delta) => {console.log("This much time has elapsed since the last frame:", delta);});}
useRootEntity
Available since version: 0.0.0
useRootEntity(): Entity
import { useRootEntity } from "@hex-engine/core";
Searches upwards through the current Entity
's parents and finds the first
Entity without a parent; namely, the root entity.
This will always be the Entity you created via createRoot
.
Note: This hook is equivalent to running useEntity().ancestors()[0]
.
Usage
import { useType, useRootEntity } from "@hex-engine/core";function MyComponent() {useType(MyComponent);const rootEnt = useRootEntity();}
useCurrentComponent
Available since version: 0.3.0
useCurrentComponent(): Component
import { useCurrentComponent } from "@hex-engine/core";
Gives you the current Component instance; the instance for the Component whose Component function is currently executing.
This component is an escape hatch; the only useful thing you can do with it (that you can't do with other hooks) is use its return value as a WeakMap key. The main time you would want to do this is when you want a hook to transparently correlate some state with a component instance so that another Component can retrieve it later.
Usage
To demonstrate how you can use useCurrentComponent
, I'm going to make a hook that associates a "color" with each component. Then, I'll make a component that uses those colors.
In order to persist state related to each component instance (rather than the entire entity instance), I'll use useCurrentComponent
to get the component instance, so I can use it as a WeakMap key. Then, when I want to retrieve the state later, I can use the component instance as a WeakMap key again.
It's a pretty contrived example, but this pattern is how hooks like useDraw
, useUpdate
, useEnableDisable
,and useInspectorHover
are implemented.
import {useType,Component,useEntity,useNewComponent,useCurrentComponent,useRootEntity,} from "@hex-engine/core";function StorageForUseColor() {useType(StorageForUseColor);return {componentColors: new WeakMap<Component, string>(),};}function useColor(color: string) {// Get the "StorageForUseColor" component off of the current component's Entity, if it has onelet storage = useEntity().getComponent(StorageForUseColor);if (!storage) {// If there's no StorageForUseColor on the entity yet, create one and add itstorage = useNewComponent(StorageForUseColor);}// Now, write the color into the WeakMap, using the component instance as the key:const component = useCurrentComponent();storage.componentColors.set(component, color);}// Now, let's make a component that gets the colors for all the components in the tree and prints them:function ColorPrinter() {useType(ColorPrinter);const rootEntity = useRootEntity();const allEntities = [rootEntity, ...rootEntity.descendants()];for (const entity of allEntities) {const storage = entity.getComponent(StorageForUseColor);if (storage) {for (const component of entity.components) {const colorForComponent = storage.componentColors.get(component);if (colorForComponent) {console.log(colorForComponent);}}}}}
useNewRootComponent
Available since version: 0.3.0
useNewRootComponent(): Component
import { useNewRootComponent } from "@hex-engine/core";
Like useNewComponent
, but instead of placing the newly-created component on the current Entity instance, it gets placed on the root Entity.
Usage
This is mostly used by hook functions that need some "global" state or "global" listeners.
For instance, consider a hypothetical useWindowSize
hook function that returns the current window size.
You could create a new window resize event listener every time useWindowSize
is called:
function useWindowSize() {const size = { x: window.innerWidth, y: window.innerHeight };window.addEventListener("resize", () => {size.x = window.innerWidth;size.y = window.innerHeight;});return size;}
But since every consumer of useWindowSize
wants the same values, it's more efficient to only create one event listener, and re-use its results:
import { useType, useRootEntity, useNewRootComponent } from "@hex-engine/core";function WindowSizeListener() {useType(WindowSizeListener);// We moved all the code that *was* in useWindowSize into a Component functionconst size = { x: window.innerWidth, y: window.innerHeight };window.addEventListener("resize", () => {size.x = window.innerWidth;size.y = window.innerHeight;});return { size };}function useWindowSize() {// If there's already a WindowSizeListener component on the root entity, use it...let listenerComponent = useRootEntity().getComponent(WindowSizeListener);// But if there's not one yet, put one there, then use it.if (!listenerComponent) {listenerComponent = useNewRootComponent(WindowSizeListener);}return listenerComponent.size;}
Components
RunLoop
Available since version: 0.0.0
RunLoop()
An internal requestAnimationFrame
-based RunLoop to be placed on the root Entity
.
It lets you register callbacks that should be run every frame, and also has controls to pause, step, and resume frames.
In @hex-engine/2d
, this Component is included as part of the root Canvas
component.
The pause
, step
, resume
, isPaused
, and frameNumber
functions on the API object for this Component are used by @hex-engine/inspector
.
If you are using @hex-engine/2d
, you do not need to use this Component directly; use Canvas
instead.
Usage
import { useType, useNewComponent, RunLoop } from "@hex-engine/core";function MyComponent() {useType(MyComponent);const runLoop = useNewComponent(RunLoop);}
ErrorBoundary
Available since version: 0.0.0
ErrorBoundary(onError: (error: Error) => void)
Define what should happen if an Error occurs in this Entity or its descendants.
When an Error occurs, it propagates upwards through all ancestor Entities until one of them has an ErrorBoundary
component that can catch it.
If onError
throws, then the Error it threw will be passed up to the next
parent error handler.
If no ErrorBoundary
can be found, the error will be logged via console.error
.
Usage
import { useType, useNewComponent, ErrorBoundary } from "@hex-engine/core";function MyComponent() {useType(MyComponent);useNewComponent(() =>ErrorBoundary((err) => {console.error("Something bad happened:", err);}));}