Popcorning Guinea Pig
Search Results for

    PGPSCN Format

    The PGPSCN (PopcorningGuineaPig Scene) format is a JSON-based declarative scene description system that allows developers to define UI layouts, 3D worlds, and interactive behaviors without writing C# code. This format works alongside existing C# scene classes, providing a clean separation between presentation and logic while maintaining full backward compatibility.

    Overview

    The PGPSCN format offers:

    • Unified 2D/3D Support: Single format handles both traditional 2D UI and 3D world scenes
    • Asset Management: Centralized asset referencing with ID-based system
    • Command Integration: Seamless integration with the existing command pattern system
    • Property Binding: Dynamic property binding to game state and settings
    • Backward Compatible: Existing scenes continue to work without modification
    • Extensible: Clean architecture for future enhancements including Rive animation support

    Basic Structure

    All PGPSCN files follow this basic JSON structure:

    {
      "version": "1.0",
      "sceneType": "2d",
      "metadata": {
        "name": "MainMenuScene",
        "description": "Simple main menu with buttons and logo"
      },
      "assets": { /* Asset definitions */ },
      "world": { /* 3D world definition (optional) */ },
      "cameras": [ /* Camera configurations */ ],
      "ui_elements": [ /* UI element definitions */ ]
    }
    

    Scene Types

    • "2d": Traditional 2D UI scenes
    • "3d": 3D world scenes with minimal UI
    • "hybrid": Mixed 2D UI and 3D world elements

    Getting Started

    Your First PGPSCN Scene

    Create a simple main menu by creating a file named MainMenuScene.pgpscn:

    {
      "version": "1.0",
      "sceneType": "2d",
      "metadata": {
        "name": "MainMenuScene",
        "description": "Simple main menu"
      },
      "assets": {
        "textures": {
          "button_bg": "assets/kenney/panel-002.png"
        },
        "sounds": {
          "click": "assets/sounds/click.wav",
          "hover": "assets/sounds/hover.wav"
        }
      },
      "ui_elements": [
        {
          "id": "game_title",
          "type": "Label",
          "properties": {
            "text": "@game_title",
            "position": {
              "type": "anchor",
              "anchor": "TopCenter",
              "offset": {"x": 0, "y": 50}
            },
            "fontSize": 32,
            "color": "#FFFFFF"
          }
        },
        {
          "id": "start_button",
          "type": "Button",
          "properties": {
            "text": "@start_game",
            "position": {
              "type": "anchor",
              "anchor": "MiddleCenter",
              "offset": {"x": 0, "y": -50}
            },
            "size": {"type": "absolute", "width": 200, "height": 50},
            "texture": "button_bg"
          },
          "actions": [
            {
              "trigger": "onClick",
              "command": {
                "type": "scene_transition",
                "target": "GameplayScene"
              }
            },
            {
              "trigger": "onHover",
              "command": {
                "type": "play_sound",
                "asset": "hover"
              }
            }
          ]
        }
      ]
    }
    

    Integrating with C# Scenes

    Your C# scene class works alongside the PGPSCN file:

    public class MainMenuScene : Scene
    {
        private Button startButton;
        
        protected override void LoadScene()
        {
            // PGPSCN file handles UI creation automatically
            // Access elements created by the scene file
            if (SceneFileElements?.ContainsKey("start_button") == true)
            {
                startButton = (Button)SceneFileElements["start_button"];
            }
        }
        
        // Your existing Update and Render methods work unchanged
        public override void Update(double deltaTime) { /* existing code */ }
        public override void Render(int width, int height) { /* existing code */ }
        public override void Unload() { /* existing code */ }
    }
    

    UI Elements

    Supported Element Types

    All existing engine UI components are supported:

    • Button: Interactive buttons with click actions
    • Label: Text display with formatting
    • Panel: Container panels with nine-slice backgrounds
    • Dropdown: Selection dropdowns with callbacks
    • Slider: Value input sliders with ranges
    • Toggle: Boolean toggle controls

    Button Example

    {
      "id": "settings_button",
      "type": "Button",
      "properties": {
        "text": "@settings",
        "position": {
          "type": "percentage",
          "x": 50.0,
          "y": 70.0
        },
        "size": {
          "type": "percentage", 
          "width": 25.0,
          "height": 8.0
        },
        "normalColor": "#0066CC",
        "hoverColor": "#0080FF",
        "pressedColor": "#004499",
        "fontSize": 18,
        "textColor": "#FFFFFF",
        "texture": "button_normal",
        "nineSlice": {
          "enabled": true,
          "cornerSize": 10
        }
      },
      "actions": [
        {
          "trigger": "onClick",
          "command": {
            "type": "scene_method",
            "method": "OpenSettings"
          }
        }
      ]
    }
    

    Label Example

    {
      "id": "score_display",
      "type": "Label",
      "properties": {
        "position": {
          "type": "anchor",
          "anchor": "TopRight",
          "offset": {"x": -20, "y": 20}
        },
        "fontSize": 24,
        "color": "#FFFFFF"
      },
      "bindings": [
        {
          "property": "text",
          "source": "game.score",
          "format": "Score: {0:N0}"
        }
      ]
    }
    

    Slider Example

    {
      "id": "volume_slider",
      "type": "Slider",
      "properties": {
        "position": {
          "type": "anchor",
          "anchor": "MiddleCenter",
          "offset": {"x": 0, "y": 0}
        },
        "size": {"type": "absolute", "width": 300, "height": 30},
        "minValue": 0.0,
        "maxValue": 1.0
      },
      "bindings": [
        {
          "property": "value",
          "source": "settings.masterVolume",
          "type": "number"
        }
      ],
      "actions": [
        {
          "trigger": "onValueChanged",
          "command": {
            "type": "set_property",
            "target": "settings.masterVolume",
            "value": "{value}"
          }
        }
      ]
    }
    

    Positioning System

    The PGPSCN format supports multiple positioning modes for responsive design:

    Absolute Positioning

    Fixed pixel coordinates:

    "position": {
      "type": "absolute",
      "x": 100,
      "y": 50
    }
    

    Percentage Positioning

    Position relative to screen size:

    "position": {
      "type": "percentage",
      "x": 50.0,
      "y": 25.0
    }
    

    Anchor-Based Positioning

    Position relative to screen anchors:

    "position": {
      "type": "anchor",
      "anchor": "TopLeft",
      "offset": {"x": 10, "y": 10}
    }
    

    Available anchors:

    • TopLeft, TopCenter, TopRight
    • MiddleLeft, MiddleCenter, MiddleRight
    • BottomLeft, BottomCenter, BottomRight

    Relative Positioning

    Position relative to other elements:

    "position": {
      "type": "relative",
      "relativeTo": "main_title",
      "relation": "below",
      "offset": {"x": 0, "y": 20}
    }
    

    Size Definitions

    Absolute Size

    Fixed pixel dimensions:

    "size": {
      "type": "absolute", 
      "width": 200,
      "height": 50
    }
    

    Percentage Size

    Size relative to screen dimensions:

    "size": {
      "type": "percentage",
      "width": 25.0,
      "height": 8.0
    }
    

    Content-Fit Size

    Size based on content with padding:

    "size": {
      "type": "content_fit",
      "padding": {"x": 20, "y": 10}
    }
    

    Asset Management

    Asset Registry

    Define all assets in a centralized registry:

    {
      "assets": {
        "textures": {
          "panel_bg": "assets/kenney/panel-000.png",
          "button_normal": "assets/kenney/panel-002.png",
          "button_hover": "assets/kenney/panel-003.png"
        },
        "sounds": {
          "click": "assets/sounds/click.wav",
          "hover": "assets/sounds/hover.wav",
          "success": "assets/sounds/success.ogg"
        },
        "fonts": {
          "ui_font": "assets/fonts/ui_font.ttf",
          "title_font": "assets/fonts/title_font.ttf"
        }
      }
    }
    

    Asset References

    Reference assets by their ID throughout the scene:

    {
      "id": "styled_button",
      "type": "Button",
      "properties": {
        "texture": "button_normal",
        "font": "ui_font"
      },
      "actions": [
        {
          "trigger": "onClick",
          "command": {
            "type": "play_sound",
            "asset": "click"
          }
        }
      ]
    }
    

    Asset Preloading

    Control asset loading behavior:

    {
      "preload": {
        "essential": [
          "panel_bg",
          "button_normal", 
          "click",
          "ui_font"
        ],
        "deferred": [
          "character_portrait",
          "logo_animation"
        ]
      }
    }
    

    Actions and Commands

    Command Types

    The action system integrates with the existing command pattern:

    Scene Method Commands

    Call methods on your scene class:

    {
      "trigger": "onClick",
      "command": {
        "type": "scene_method",
        "method": "OpenSettings",
        "parameters": ["quick"]
      }
    }
    

    Scene Transition Commands

    Navigate between scenes:

    {
      "trigger": "onClick",
      "command": {
        "type": "scene_transition",
        "target": "SettingsScene",
        "transition": "fade",
        "duration": 0.5
      }
    }
    

    Static Method Commands

    Call static methods on any class:

    {
      "trigger": "onClick",
      "command": {
        "type": "static_method",
        "class": "GameManager",
        "method": "SaveGame"
      }
    }
    

    Built-in Commands

    Use built-in commands for common operations:

    {
      "trigger": "onHover",
      "command": {
        "type": "play_sound",
        "asset": "hover_sound",
        "volume": 0.8
      }
    }
    
    {
      "trigger": "onClick",
      "command": {
        "type": "set_property",
        "target": "settings.volume",
        "value": 0.5
      }
    }
    

    Action Triggers

    Available triggers for UI events:

    • onClick - Mouse click or tap
    • onHover - Mouse hover enter
    • onHoverExit - Mouse hover exit
    • onValueChanged - Value change (sliders)
    • onSelectionChanged - Selection change (dropdowns)
    • onFocusGained - Input focus gained
    • onFocusLost - Input focus lost

    Conditional Actions

    Execute actions based on conditions:

    {
      "trigger": "onClick",
      "condition": {
        "type": "property_check",
        "property": "game.canSave",
        "operator": "equals",
        "value": true
      },
      "command": {
        "type": "static_method",
        "class": "GameManager",
        "method": "SaveGame"
      },
      "else_command": {
        "type": "play_sound",
        "asset": "error_sound"
      }
    }
    

    Chained Actions

    Execute multiple actions in sequence:

    {
      "trigger": "onClick",
      "commands": [
        {
          "type": "play_sound",
          "asset": "click"
        },
        {
          "type": "scene_method",
          "method": "ShowConfirmDialog"
        },
        {
          "type": "scene_transition",
          "target": "GameplayScene",
          "delay": 1.0
        }
      ]
    }
    

    Property Binding

    Basic Binding

    Bind UI properties to game state:

    {
      "bindings": [
        {
          "property": "text",
          "source": "player.name",
          "format": "Welcome, {0}!"
        },
        {
          "property": "visible",
          "source": "game.debugMode",
          "type": "boolean"
        }
      ]
    }
    

    Advanced Binding

    Transform values with mappings:

    {
      "bindings": [
        {
          "property": "color",
          "source": "player.healthPercentage",
          "mapping": {
            "type": "color_gradient",
            "stops": [
              {"value": 0.0, "color": "#FF0000"},
              {"value": 0.5, "color": "#FFFF00"},
              {"value": 1.0, "color": "#00FF00"}
            ]
          }
        }
      ]
    }
    

    Binding Sources

    Available binding sources:

    • game.* - Game state properties
    • player.* - Player object properties
    • settings.* - Settings service values
    • scene.* - Scene object properties
    • ui.{elementId}.* - Other UI element properties
    • localization.* - Localization service values

    Value Transformations

    Transform bound values:

    {
      "property": "text",
      "source": "game.score",
      "transform": {
        "type": "format",
        "format": "Score: {0:N0}",
        "fallback": "Score: 0"
      }
    }
    
    {
      "property": "size.width",
      "source": "player.health",
      "transform": {
        "type": "scale",
        "inputRange": {"min": 0, "max": 100},
        "outputRange": {"min": 0, "max": 200}
      }
    }
    

    3D Scene Support

    Basic 3D Scene

    Define 3D scenes with world geometry:

    {
      "version": "1.0",
      "sceneType": "3d",
      "world": {
        "type": "quake_map",
        "mapFile": "assets/maps/dm_warehouse.bsp",
        "spawn_points": [
          {
            "id": "player_spawn",
            "position": {"x": 256, "y": 256, "z": 32},
            "rotation": {"x": 0, "y": 0, "z": 0},
            "type": "player"
          }
        ]
      },
      "cameras": [
        {
          "id": "main_camera",
          "type": "perspective",
          "isPrimary": true,
          "properties": {
            "fov": 75,
            "nearPlane": 0.1,
            "farPlane": 1000.0
          }
        }
      ]
    }
    

    Hybrid Scene with 3D and UI

    Combine 3D worlds with 2D UI overlays:

    {
      "version": "1.0",
      "sceneType": "hybrid",
      "world": {
        "type": "quake_map",
        "mapFile": "dm_warehouse.bsp"
      },
      "cameras": [
        {
          "id": "fps_camera",
          "type": "perspective",
          "isPrimary": true,
          "properties": {
            "fov": 75,
            "nearPlane": 0.1,
            "farPlane": 1000.0
          },
          "bindings": [
            {
              "property": "position",
              "source": "player.position"
            },
            {
              "property": "target",
              "source": "player.lookTarget"
            }
          ]
        }
      ],
      "ui_elements": [
        {
          "id": "health_display",
          "type": "Label",
          "renderSpace": "screen_2d",
          "properties": {
            "position": {
              "type": "anchor",
              "anchor": "TopLeft",
              "offset": {"x": 20, "y": 20}
            },
            "fontSize": 20,
            "color": "#00FF00"
          },
          "bindings": [
            {
              "property": "text",
              "source": "player.health",
              "format": "Health: {0}"
            }
          ]
        }
      ]
    }
    

    World-Space UI Elements

    Position UI elements in 3D world space:

    {
      "id": "floating_damage",
      "type": "Label",
      "renderSpace": "world_3d",
      "properties": {
        "text": "-25",
        "position": {
          "type": "world_position",
          "x": 256, "y": 256, "z": 80
        },
        "fontSize": 16,
        "color": "#FF0000",
        "billboardMode": "camera_facing"
      },
      "animations": [
        {
          "trigger": "onCreate",
          "type": "float_up_and_fade",
          "duration": 2.0
        }
      ]
    }
    

    Localization Support

    Use localization keys with the @ prefix:

    {
      "id": "welcome_message",
      "type": "Label",
      "properties": {
        "text": "@welcome_message",
        "position": {"type": "anchor", "anchor": "TopCenter"},
        "fontSize": 24
      }
    }
    

    The engine's localization service will automatically resolve @welcome_message to the appropriate localized text.

    Migration Guide

    From Manual C# Scenes

    Before (C# only):

    public class MainMenuScene : Scene
    {
        private Button startButton;
        private Button settingsButton;
        
        public override void Load()
        {
            startButton = new Button("Start Game", new Vector2(400, 300), new Vector2(200, 50), Color.Blue);
            settingsButton = new Button("Settings", new Vector2(400, 370), new Vector2(200, 50), Color.Gray);
            
            startButton.SetOnClick(() => SceneManager.LoadScene("GameplayScene"));
            settingsButton.SetOnClick(() => OpenSettings());
        }
    }
    

    After (PGPSCN + C#):

    MainMenuScene.pgpscn:

    {
      "version": "1.0",
      "sceneType": "2d",
      "ui_elements": [
        {
          "id": "start_button",
          "type": "Button",
          "properties": {
            "text": "@start_game",
            "position": {"type": "percentage", "x": 50, "y": 50},
            "size": {"type": "absolute", "width": 200, "height": 50}
          },
          "actions": [
            {
              "trigger": "onClick",
              "command": {"type": "scene_transition", "target": "GameplayScene"}
            }
          ]
        },
        {
          "id": "settings_button",
          "type": "Button",
          "properties": {
            "text": "@settings",
            "position": {"type": "percentage", "x": 50, "y": 60},
            "size": {"type": "absolute", "width": 200, "height": 50}
          },
          "actions": [
            {
              "trigger": "onClick",
              "command": {"type": "scene_method", "method": "OpenSettings"}
            }
          ]
        }
      ]
    }
    

    MainMenuScene.cs:

    public class MainMenuScene : Scene
    {
        protected override void LoadScene()
        {
            // PGPSCN handles UI creation automatically
            // Only custom logic needed now
        }
        
        public void OpenSettings()
        {
            // Your custom settings logic
        }
    }
    

    Best Practices

    Organization

    1. Use descriptive IDs: health_bar, inventory_button, damage_text
    2. Group related elements: Use consistent naming patterns
    3. Separate concerns: Keep presentation in PGPSCN, logic in C#
    4. Document complex bindings: Use clear property paths

    Performance

    1. Minimize binding updates: Use appropriate binding types
    2. Preload essential assets: Use the preload system
    3. Use appropriate positioning: Percentage for responsive, absolute for fixed
    4. Batch related operations: Group actions when possible

    Maintainability

    1. Use version control: PGPSCN files are text-based and diff-friendly
    2. Consistent formatting: Use proper JSON formatting
    3. Validate schemas: Ensure proper structure
    4. Test iteratively: Quick iteration without recompilation

    Error Handling

    Common issues and solutions:

    Invalid JSON

    Error: Invalid JSON syntax at line 15
    Solution: Validate JSON structure, check for missing commas or brackets
    

    Missing Assets

    Error: Asset 'missing_texture' not found
    Solution: Ensure asset path exists and is registered in assets section
    

    Invalid Property Binding

    Error: Cannot bind to 'invalid.property.path'
    Solution: Verify the binding source exists and is accessible
    

    Unknown Element Type

    Error: Element type 'CustomElement' not supported
    Solution: Use supported element types or extend the factory system
    

    Advanced Features

    Future Rive Support

    The PGPSCN format is designed to support Rive animations when available:

    {
      "id": "animated_logo",
      "type": "RiveComponent",
      "properties": {
        "riveFilePath": "logo_animation.riv",
        "artboardName": "MainArtboard",
        "stateMachineName": "LogoStateMachine",
        "autoPlay": true
      },
      "actions": [
        {
          "trigger": "onRiveEvent",
          "eventName": "LogoClicked",
          "command": {
            "type": "scene_transition",
            "target": "MainMenuScene"
          }
        }
      ]
    }
    

    Custom Element Factories

    Extend the system with custom UI elements:

    public class CustomUIElementFactory : UIElementFactory
    {
        public override bool CanHandle(string elementType)
        {
            return elementType == "CustomWidget";
        }
        
        public override object CreateElement(UIElementDefinition definition)
        {
            return new CustomWidget(definition.Properties);
        }
    }
    

    The PGPSCN format provides a powerful, flexible foundation for scene definition that grows with your project needs while maintaining simplicity and backward compatibility.

    Further Reading

    • 3D Scenes - Detailed 3D scene capabilities
    • UI Components - Available UI element types
    • Game Architecture - Core engine integration
    • Asset Registry - Asset management system
    • Edit this page
    In this article
    Back to top © 2024 Popcorning Guinea Pig Game Engine. A custom C# game engine for indie developers.