Skip to content

Persistence

Malcolm Nixon edited this page Sep 17, 2023 · 5 revisions

Persistence System

A persistence system has been created for the Godot XR Template project which can save items, inventory, and state information when switching between zones, and when loading and saving the game.

Overview

The persistence system consists of the following main types:

  • PersistentWorld - a data storage system with file load/save support
  • PersistentItem - a interactable item that synchronizes its state to the PersistentWorld
  • PersistentPocket - a snap-zone that synchronizes its contents to the PersistentWorld
  • PersistentZone - a scene that synchronizes its state to the PersistentWorld

Usage & World Design

Creating Items

Creating a new item involves:

  1. Creating a PersistentItemType resource defining the type information for every type of item
  2. Creating a scene extending from PersistentItem defining the item instance (shape, collision, grab-points, pockets, etc.)
  3. Adding the PersistentItemType to the PersistentItemDatabase resource for the application

If the item is unique (only one in the entire game) then its unique item_id can be set once in the item scene.

If multiple instances of the item will be scattered throughout the world, then the item_id should be left blank in the item scene, and instead unique item_id values should be set for every instance dropped into zones.

Creating Zones

Before creating zones, the application should:

  1. Create a new ZoneBase scene extending from the PersistentZone scene (persistent_zone.tscn)
  2. Set the item_database to refer to the applications PersistentItemDatabase resource
  3. Configure the XROrigin3D with all the standard player movement options and pockets

This ZoneBase scene becomes the base for all zones in the game, and therefore all zones inherit the same player configuration, and the player configuration can be managed in a single location.

Creating a new zone involves:

  1. Creating a PersistentZoneInfo resource defining the zone information
  2. Creating a scene extending from ZoneBase, populated with the appropriate environment, objects and items
  3. Adding the PersistentZoneInfo to the PersistentZoneDatabase resource for the application

Unique Identifiers

Every PersistentItem should have a unique item_id in the world. This ID is used to persist the items state in the PersistentWorld database.

Every PersistentZone should have a unique zone_id defined in its PersistentZoneInfo. This ID is used to persist the state of the zone in the PersistentWorld database.

Every PersistentPocket should have a unique pocket_id. This ID is used to persist the pocket contents in the PersistentWorld database.

Shared Pockets

A zone may have any number of PersistentPocket instances, but each one must have a different pocket_id; however pockets in different zones are allowed to reuse the same pocket_id.

An example of shared pockets is the pockets put on the player body. For example if the player puts an item into their "left_pocket" in zone 1 and then steps into zone 2; the "left_pocket" in zone 2 will synchronize its contents to hold the same item.

Shared pockets also works for pockets on items - for example a unique backpack can have pockets, and the pockets will synchronize as the backpack is carried from one zone to the next.

Shared pockets can also be static and used like a bank - for example zones containing a bank vault can have "bank_slot_1" which synchronizes its contents as the player moves to the different zones.

Resource Relationship

The following UML diagram shows the relationship between the resource definitions.

---
Class Diagram
---
classDiagram
    class PersistentWorld {
        +PersistentWorld instance$
        +String save_file_password
        +PersistentZoneDatabase zone_database
        +PersistentItemDatabase item_database
    }

    class PersistentZoneDatabase~Resource~ {
        +Array~PersistentZoneInfo~ zones
    }

    class PersistentItemDatabase~Resource~ {
        +Array~PersistentItemType~ items
    }

    class PersistentZoneInfo~Resource~ {
        +String zone_id
        +String instance_scene
    }

    class PersistentItemType~Resource~ {
        +String type_id
        +String instance_scene
    }

    PersistentWorld ..> PersistentZoneDatabase : Refers
    PersistentWorld ..> PersistentItemDatabase : Refers
    PersistentZoneDatabase ..> "many" PersistentZoneInfo : Refers
    PersistentItemDatabase ..> "many" PersistentItemType : Refers
Loading

The PersistentWorld (extended by the custom GameState and configured by the developer) refers to the zone and item databases. As the PersistentWorld has a static instance refence, it means any persistence object can access the database of zones and items.

Running State

The following UML diagram shows the relationship between running objects.

---
Class Diagram
---
classDiagram
    class PersistentWorld {
        +PersistentWorld instance$
        -Dictionary data
    }

    class GameState {
        +PersistentZone current_zone
    }

    class PersistentStaging {
        +PersistentStaging instance$
    }

    class PersistentZone~XRToolsSceneBase~ {
        +PersistentZoneInfo zone_info
    }

    class PersistentItem~XRToolsPickable~ {
        +String item_id
        +PersistentItemType item_type
    }

    class PersistentPocket~XRToolsSnapZone~ {
        +String pocket_id
        +PersistentItem picked_up_object
    }

    PersistentWorld <|-- GameState : Inheritance
    GameState <..> PersistentZone : Refers
    PersistentZone ..> "many" PersistentItem : Contains
    PersistentItem ..> "many" PersistentPocket : Contains
    PersistentItem <.. PersistentPocket : Holds
    GameState..> PersistentStaging : Uses
    PersistentZone <..> PersistentStaging : Refers
Loading

Both the PersistentWorld and PersistentStaging have static instance references, so any class can trigger a scene transition and manipulate the game state. PersistentZones, PersistentItems and PersistentPockets read and write their state information to the PersistentWorld.