Blogpost - Creating and manipulating a 2D Sprite in 3D space in trial game engine

Table of Contents

1. Creating and manipulating a 2D Sprite in 3D space in trial game engine (common Lisp)

Since I struggled a bit with this myself, I decided to write a short post about it. Almost all the code is cobbled together from the trial examples (add link) and the getting started section (add link

My End goal was to have a 2D Skeleton sprite in my 3D game that I can rotate so that it faces the player. At the bottom of the post will be the full file for you to copy, if you just copy these lines on their own you might be missing a lil bit of setup.

So first off you create a pool for your assets and add all assets with a certain extension:

main.lisp

(define-pool my-pool :base #p"../../data/")
(define-assets-from-path (my-pool sprite-data "*.json"))

Keep in mind that this is my folder structure:

.
├── assets.lisp
├── build.lisp
├── data
│   ├── skeleton.json
│   ├── skeleton.png
├── game.asd
├── game-test.asd
├── README.markdown
├── src
│   ├── main.lisp
└── testing
    └── tests.lisp

So, as far as I understand, this means that one of the ../ in the path is used to get out of main and the second goes from main to the root.

I do not quite understand why thats the case and I honestly struggled quite a bit with these paths, but thats probably just me being a silly goose on the loose.

What the code above does is take every file in the Given directory, check whether it has the json ending and if it does, add it to the pool. It will have the file name as its accessor. So we can now use it:

(define-shader-entity simple-sprite (animated-sprite located-entity oriented-entity rotated-entity scaled-entity)
                      ((orientation :initform (vec 1 1 1))
                       (scaling :initform (vec 0.1 0.1 0.1))
                       (location :initform (vec 0 0 0))))

This code says "let define a new entity called simple-sprite and give it the traits that are given as parameters". What that means is that you can use the functionalities of these entity types. Rotation gives a rotation to the sprite for example, otherwise you cant change the rotation to make it spin in 3D (or even 2D space).

This is something I struggled with because I tried to rotate it without this. The entities themselves dont necessarily have any traits youd assume they have.

After this you can instantiate it and add it to the scene:

(defmethod setup-scene ((main main) scene)
           (enter (make-instance 'simple-sprite :sprite-data (asset 'my-pool 'skeleton)) scene)
           (enter (make-instance 'directional-light) scene)
           (enter (make-instance 'freeroam-camera :location (vec 0 3 5)) scene)
           (enter (make-instance 'ambient-light :color (vec3 0.25)) scene)
           (enter (make-instance 'vertex-entity :vertex-array (// 'trial 'grid)) scene)
           (enter (make-instance 'vertex-entity :vertex-array (// 'trial 'axes)) scene)


The setup also adds a freeroam camera so that you can fly arout with WASD and a basic scene. This should work out of the box. As you see we can now access the pool we created earlier and just get the sprite we want to have used.

The last thing we need to do is to actually spin it:

(define-handler (simple-sprite tick) (tt dt)
                (setf (rotation simple-sprite) (qfrom-angle +vy+ tt)))

We used the built-in handler with tick. This calls the function on the object every tick. We then just set the rotation. You can chnage vy to vz or vx to get other rotations.

Note: I got a float error sometimes, but I did not truly understand what caused / fixed it

And thats basically it. Below is the full file for reference. All the setup stuff is taken straight from the offical trial - getting started guide. You can also find a template project for trial here:

1.1. Finished Config

(defpackage #:org.my.project2
  (:use #:cl+trial) ;; The cl+trial package includes everything from CL and Trial with the needed shadowing in place.
  (:shadow #:main #:launch)
  (:local-nicknames
   (#:v #:org.shirakumo.verbose)
   (#:sequences #:org.shirakumo.trivial-extensible-sequences))
  (:export #:main #:launch))

(in-package #:org.my.project2)

(defclass main (trial:main)
  ())


(defun launch (&rest args)
  (let ((*package* #.*package*))
    (apply #'trial:launch 'main args)))


(setf +app-system+ "dungeon-lice")


  (define-shader-entity simple-sprite (animated-sprite located-entity oriented-entity rotated-entity scaled-entity)
                      ((orientation :initform (vec 1 1 1))
                       (scaling :initform (vec 0.1 0.1 0.1))
                       (location :initform (vec 0 0 0))))


(define-pool my-pool :base #p"../../data/")
  (define-assets-from-path (my-pool sprite-data "*.json"))

(defmethod setup-scene ((main main) scene)
  (enter (make-instance 'simple-sprite :sprite-data (asset 'my-pool 'skeleton)) scene)
  (enter (make-instance 'directional-light) scene)
  (enter (make-instance 'ambient-light :color (vec3 0.25)) scene)
  (enter (make-instance 'vertex-entity :vertex-array (// 'trial 'grid)) scene)
  (enter (make-instance 'vertex-entity :vertex-array (// 'trial 'axes)) scene)
  (enter (make-instance 'freeroam-camera :name :my-camera :location (vec 0 3 20)) scene)
  (enter (make-instance 'phong-render-pass) scene))


(define-handler (simple-sprite tick) (tt dt)
  (setf (rotation simple-sprite) (qfrom-angle +vy+ tt)))

(maybe-reload-scene)

Created: 2026-04-20 Mon 07:34

Validate