Graphics

Graphics

This docs page is under construction. Expect more information coming to this page soon!
If you have questions, please ask them in our Discord server and we'll answer them promptly! 😊
💡

Dreamlab uses Pixi.js for its graphics rendering. Anything you can draw with Pixi you can draw in Dreamlab.

Best Practices

For best performance, you should avoid re-drawing any graphical elements that don’t change from one frame to the next other than their position or rotation, and instead draw them when they are initialized or when they change, and update their position every frame.

If you have multiple graphics elements that share the same position relative to each other, you should group them using a Container.

⚠️

Make sure you call destroy() on any graphics objects in teardown() to free any resources used, otherwise you may run into memory leaks or other performance issues.

TypeScript
import type { SpawnableContext, RenderTime } from '@dreamlab.gg/core'
import { SpawnableEntity } from '@dreamlab.gg/core'
import { Vec } from '@dreamlab.gg/core/math'
import { camera, game, stage } from '@dreamlab.gg/core/labs'
import { z } from '@dreamlab.gg/core/sdk'
import { Container } from 'pixi.js'
 
type Args = typeof ArgsSchema
const ArgsSchema = z.object({})
 
export class ExampleGraphics extends SpawnableEntity<Args> {
  // Field to reference the container
  // Might be undefined when running this entity on the server
  private readonly container: Container | undefined
 
  public constructor(ctx: SpawnableContext<Args>) {
    super(ctx)
 
    // Get a client-only reference to the current game instance
    const $game = game('client')
    if ($game) {
      // Run client-only init
 
      // Create a Container to group any graphics objects
      this.container = new Container()
      // Enable sorting and set the container z-index
      this.container.sortableChildren = true
      this.container.zIndex = this.transform.zIndex
 
      // Update the container z-index when it changes
      this.transform.addZIndexListener(() => {
        if (this.container) this.container.zIndex = this.transform.zIndex
      })
 
      // Add the container to the main graphics stage
      stage().addChild(this.container)
 
      // ... create any other graphics objects and add them to the stage
      // ... with this.container.addChild(..)
    }
  }
 
  public teardown(): void {
    // Destroy the container and its children (if it exists)
    this.container?.destroy({ children: true })
  }
 
  public override onRenderFrame({ delta, time, smooth }: RenderTime): void {
    // Calculate the position in screen-space
    const pos = Vec.add(this.transform.position, camera().offset)
 
    if (this.container) {
      // Set the position and rotation
      this.container.position = pos
      this.container.angle = this.transform.rotation
    }
  }
}

Utilities

Dreamlab has a handful of utility functions to allow you to more easily draw basic shapes, and instantiate Pixi.js sprites.

Basic Shapes

Utility functions to draw basic shapes into Pixi.js Graphics primitives. You can see the full list of them and their arguments here.

TypeScript
import type { SpawnableContext } from '@dreamlab.gg/core'
import { SpawnableEntity } from '@dreamlab.gg/core'
import { game, stage } from '@dreamlab.gg/core/labs'
import { z } from '@dreamlab.gg/core/sdk'
import type { BoxGraphics, CircleGraphics } from '@dreamlab.gg/core/utils'
import { drawBox, drawCircle } from '@dreamlab.gg/core/utils'
import { Container } from 'pixi.js'
 
type Args = typeof ArgsSchema
const ArgsSchema = z.object({})
 
export { ArgsSchema as PhysicsBallArgs }
export class GraphicsEntity extends SpawnableEntity<Args> {
  private readonly container: Container | undefined
  private readonly box: BoxGraphics | undefined
  private readonly circle: CircleGraphics | undefined
 
  public constructor(ctx: SpawnableContext<Args>) {
    super(ctx)
 
    // Ensure graphics components are only created on the client
    const $game = game('client')
    if ($game) {
      this.container = new Container()
      // ... clipped
 
      // Draw a basic rectangle outline
      this.box = drawBox({ width: 100, height: 50 })
 
      // Draw a blue filled-in circle
      this.circle = drawCircle(
        { radius: 25 },
        {
          strokeAlpha: 0,
          fillAlpha: 1,
          fill: 'blue',
        },
      )
 
      // Add to graphics stage
      this.container.addChild(this.box)
      this.container.addChild(this.circle)
      stage().addChild(this.container)
    }
  }
 
  public teardown(): void {
    // Cleanup resources on teardown
    this.container?.destroy({ children: true })
  }
 
  // ... clipped
}

Sprites

While creating Pixi.js Sprites is fairly trivial to do manually, Dreamlab has a helper that can handle sprite tiling and anchoring with a quick one-liner.

💡

Animated Sprites have their own dedicated docs page.

TypeScript
import type { SpawnableContext } from '@dreamlab.gg/core'
import { SpawnableEntity } from '@dreamlab.gg/core'
import { game, stage } from '@dreamlab.gg/core/labs'
import { z } from '@dreamlab.gg/core/sdk'
import { createSprite } from '@dreamlab.gg/core/textures'
import { Container } from 'pixi.js'
 
type Args = typeof ArgsSchema
const ArgsSchema = z.object({})
 
export { ArgsSchema as PhysicsBallArgs }
export class SpriteEntity extends SpawnableEntity<Args> {
  private readonly container: Container | undefined
  private readonly sprite: Container | undefined
 
  public constructor(ctx: SpawnableContext<Args>) {
    super(ctx)
 
    // Ensure graphics components are only created on the client
    const $game = game('client')
    if ($game) {
      this.container = new Container()
      // ... clipped
 
      // Create a sprite from an image URL
      this.sprite = createSprite(
        {
          url: '...',
          tile: true,
          tileScale: 1,
        },
        {
          width: 100,
          height: 100,
        },
      )
 
      // Add sprite and container to graphics stage
      if (this.sprite) this.container.addChild(this.sprite)
      stage().addChild(this.container)
    }
  }
 
  public teardown(): void {
    // Cleanup resources on teardown
    this.container?.destroy({ children: true })
  }
 
  // ... clipped
}