When I started working on Pallet Builder for Conestoga Meats, one of the requirements was that learners should be able to leave the module and return later without losing their progress.
That sounds simple until the application runs inside a browser and is launched through a learning management system. The Unity application needs to remember what the learner has completed, where they left off, and which parts of the module should be available when they return.
A separate backend could solve that problem, but it would also introduce another system to build, host, secure, and maintain. In this case, the LMS already had a place to store learner data. SCORM 1.2 provided the connection between the Unity application and the existing system.
Using the LMS for persistence
SCORM courses communicate with an LMS through a JavaScript API provided by the LMS. The course finds the API when it starts, initializes a session, and then reads or writes values through it.
The main calls used for this are:
LMSInitializeLMSGetValueLMSSetValueLMSCommitLMSFinish
For a basic course, these calls might be used to report a score or mark a lesson as complete. An interactive Unity application can use the same API to save its own application state.
The LMS does not need to understand every detail of the learning module. It only needs to store the values provided by the course.
Loading the learner’s progress
When the application starts, it initializes the SCORM session and asks the LMS whether any previous progress exists.
SCORM 1.2 provides values such as cmi.core.lesson_location for a learner’s current position and cmi.suspend_data for additional course data. I used the latter to store the interactive module's state.
That state could include information such as:
- The current learning section
- Completed activities
- Completed knowledge checks
- The learner’s current scenario
- Boxes already placed on the pallet
- Any other information needed to resume the experience
The saved data is serialized into a string before being passed to the LMS. When the application loads again, it retrieves the string, deserializes it, and uses the result to rebuild the learner’s progress.
The application still owns the meaning of the data. The LMS is responsible for storing and returning it.
Saving progress from Unity
The application updates its local state as the learner moves through the module. For example, placing a box correctly may update the current pallet configuration, while completing a knowledge check may unlock the next section.
Instead of saving every small change, the application can save at useful checkpoints. This reduces unnecessary API calls and gives the learner a reasonable point to return to if the session ends unexpectedly.
The general flow looks like this:
Update the application state
Serialize the state
Call LMSSetValue with cmi.suspend_data
Call LMSCommit
LMSSetValue sends the updated value to the LMS session. LMSCommit asks the LMS to persist the data. Without a commit, the value may still only exist in the current session.
The same process can be used for simpler values. The current section might be stored in cmi.core.lesson_location, while the overall status can be written to cmi.core.lesson_status.
Working with a WebGL application
Pallet Builder was built in Unity and delivered as a WebGL application through the LMS. Unity could not call the browser’s JavaScript API directly from ordinary C# code, so the application needed a bridge between the Unity runtime and the page hosting the SCORM content.
That bridge handled operations such as:
- Finding the SCORM API
- Initializing the session
- Reading saved values
- Writing updated values
- Committing progress
- Closing the session
From the Unity side, the rest of the application could work with normal methods, such as LoadProgress() and SaveProgress(). The SCORM-specific details were kept in one place rather than scattered throughout the application logic.
This separation made the application code easier to reason about. The pallet-building system did not need to know how the LMS worked. It only needed to provide the current state to the save system.
Designing the saved data
The saved data needs to be treated as part of the application’s design rather than as an afterthought.
SCORM 1.2 is not a general-purpose database. The amount of data available through cmi.suspend_data is limited, and different LMS platforms can behave differently. A save system should store the smallest amount of information needed to rebuild the experience.
For example, it is usually better to store an identifier for a completed activity than to store an entire copy of the scene. It is also useful to include a version number in the serialized data. If the structure changes in a future release, the application can recognize older saves and handle them deliberately.
A saved payload might look something like this:
{
"version": 1,
"section": "pallet-practice",
"completed": ["intro", "inventory", "quiz-1"],
"currentScenario": 2
}
The exact structure depends on the application. The important part is that the data can be loaded safely and that missing or outdated values have sensible defaults.
Closing the session
When the learner finishes the module, the application reports the final status and performs one last commit before calling LMSFinish.
This is also important when the learner closes the module before completing it. The application should save any useful progress before the session ends rather than relying entirely on the final completion path.
Browser sessions can end unexpectedly. A tab may be closed, a connection may be interrupted, or the learner may navigate away before the application reaches its normal shutdown code. Saving at meaningful checkpoints helps reduce the amount of progress that can be lost.
Why this approach worked
Using SCORM as the persistence layer meant that Pallet Builder could save and restore learner progress without adding a separate API or cloud-storage service.
The LMS already handles learner accounts, course launches, and course data storage. The Unity application only needed to communicate via the LMS interface.
That worked well for this project because the saved state was relatively small and belonged to a single learning module. SCORM would not be the right choice for every type of application. A system that requires detailed analytics, cross-course data, real-time collaboration, or large volumes of user-generated content would likely require a different architecture.
For an interactive learning module, though, SCORM 1.2 can provide a useful save-and-load system. It lets the application keep control of its own state while allowing the LMS to handle persistence.
The result is a Unity experience that can function like an application while still fitting into the organization's existing learning workflow.
