Popcorning Guinea Pig
Search Results for

    3D Scenes

    The Popcorning Guinea Pig engine provides powerful 3D scene capabilities that support both pure 3D worlds and hybrid scenes that combine 3D environments with 2D UI elements. This system is built on top of Raylib's 3D rendering capabilities and integrates seamlessly with the engine's scene management and PGPSCN format.

    Skybox

    A camera-locked cube of ordinary png faces. Depth-write off, drawn first inside BeginMode3D. The sun or moon is a texel on a face (put it on +Y and it cannot appear under the ground). Bottom face is optional.

    var sky = new Skybox();
    sky.LoadFace(SkyboxFace.PosY, "assets/sky/day-up.png");
    // ... +X −X +Z −Z. Skip NegY if the ground covers the downward look.
    scene.SetSkybox(sky);          // Scene3D draws it
    // or, if you override Render:
    sky.Draw(camera.Position);     // first call inside BeginMode3D
    

    Swap day/night by calling LoadFace / SetFaces again. Recommended face size is Skybox.RecommendedFaceSize (1024). Ship BC1, never 2048 RGBA8. See docs/architecture/skybox.md.

    Overview

    The 3D scene system offers:

    • Pure 3D Scenes: Full 3D environments with minimal UI
    • Hybrid Scenes: 3D worlds with 2D UI overlays
    • 3D UI Elements: UI components positioned in 3D world space
    • Multiple Camera Support: Flexible camera configurations
    • Quake Map Integration: Support for .bsp map files
    • Entity Management: Dynamic object placement and interaction
    • Mixed Rendering: Efficient 2D/3D render pipeline

    Scene Types

    Pure 3D Scenes

    Pure 3D scenes focus primarily on 3D content with minimal UI elements:

    public class GameplayScene : Scene
    {
        private Camera3D camera;
        private Model worldModel;
        
        public override void Load()
        {
            // Set up 3D camera
            camera = new Camera3D
            {
                position = new Vector3(0, 10, 10),
                target = new Vector3(0, 0, 0),
                up = new Vector3(0, 1, 0),
                fovy = 45.0f,
                projection = CameraProjection.CAMERA_PERSPECTIVE
            };
            
            // Load 3D world model
            worldModel = Raylib.LoadModel("assets/world.obj");
        }
        
        public override void Update(double deltaTime)
        {
            // Update camera based on input
            Raylib.UpdateCamera(ref camera, CameraMode.CAMERA_FIRST_PERSON);
        }
        
        public override void Render(int width, int height)
        {
            Raylib.ClearBackground(Color.RAYWHITE);
            
            Raylib.BeginMode3D(camera);
            {
                // Draw 3D world
                Raylib.DrawModel(worldModel, Vector3.Zero, 1.0f, Color.WHITE);
                
                // Draw 3D grid for reference
                Raylib.DrawGrid(20, 1.0f);
            }
            Raylib.EndMode3D();
            
            // Draw minimal UI
            Raylib.DrawFPS(10, 10);
        }
    }
    

    Hybrid Scenes

    Hybrid scenes combine 3D worlds with traditional 2D UI overlays:

    public class HybridGameScene : Scene
    {
        private Camera3D mainCamera;
        private Model worldModel;
        private List<Button> uiButtons;
        private Label healthLabel;
        private ProgressBar healthBar;
        
        public override void Load()
        {
            // Set up 3D camera
            mainCamera = new Camera3D
            {
                position = new Vector3(5, 5, 5),
                target = new Vector3(0, 0, 0),
                up = new Vector3(0, 1, 0),
                fovy = 60.0f,
                projection = CameraProjection.CAMERA_PERSPECTIVE
            };
            
            // Load 3D assets
            worldModel = Raylib.LoadModel("assets/environment/warehouse.obj");
            
            // Create 2D UI overlay
            CreateUIOverlay();
        }
        
        private void CreateUIOverlay()
        {
            uiButtons = new List<Button>();
            
            // Health bar in top-left corner
            healthBar = new ProgressBar
            {
                Position = new Vector2(20, 20),
                Size = new Vector2(200, 20),
                MinValue = 0,
                MaxValue = 100,
                Value = 100,
                FillColor = Color.RED,
                BackgroundColor = Color.DARKRED
            };
            
            // Settings button in top-right corner
            var settingsButton = new Button("Settings", new Vector2(700, 20), new Vector2(80, 30), Color.GRAY);
            settingsButton.SetOnClick(() => OpenSettingsMenu());
            uiButtons.Add(settingsButton);
        }
        
        public override void Update(double deltaTime)
        {
            // Update 3D camera
            Raylib.UpdateCamera(ref mainCamera, CameraMode.CAMERA_FREE);
            
            // Update UI elements
            healthBar.Update();
            foreach (var button in uiButtons)
                button.Update();
        }
        
        public override void Render(int width, int height)
        {
            Raylib.ClearBackground(Color.SKYBLUE);
            
            // Render 3D world
            Raylib.BeginMode3D(mainCamera);
            {
                Raylib.DrawModel(worldModel, Vector3.Zero, 1.0f, Color.WHITE);
            }
            Raylib.EndMode3D();
            
            // Render 2D UI overlay
            healthBar.Draw();
            foreach (var button in uiButtons)
                button.Draw();
        }
    }
    

    Camera System

    Camera Configuration

    The 3D camera system supports multiple camera types and configurations:

    public class CameraManager
    {
        private Camera3D primaryCamera;
        private Camera3D uiCamera;
        private CameraMode currentMode;
        
        public void InitializeCameras()
        {
            // Primary 3D camera
            primaryCamera = new Camera3D
            {
                position = new Vector3(0, 10, 10),
                target = new Vector3(0, 0, 0),
                up = new Vector3(0, 1, 0),
                fovy = 75.0f,
                projection = CameraProjection.CAMERA_PERSPECTIVE
            };
            
            // UI camera for world-space UI elements
            uiCamera = new Camera3D
            {
                position = new Vector3(0, 0, 100),
                target = new Vector3(0, 0, 0),
                up = new Vector3(0, 1, 0),
                fovy = 45.0f,
                projection = CameraProjection.CAMERA_ORTHOGRAPHIC
            };
            
            currentMode = CameraMode.CAMERA_FREE;
        }
        
        public void UpdatePrimaryCamera(double deltaTime)
        {
            switch (currentMode)
            {
                case CameraMode.CAMERA_FREE:
                    Raylib.UpdateCamera(ref primaryCamera, CameraMode.CAMERA_FREE);
                    break;
                    
                case CameraMode.CAMERA_FIRST_PERSON:
                    Raylib.UpdateCamera(ref primaryCamera, CameraMode.CAMERA_FIRST_PERSON);
                    break;
                    
                case CameraMode.CAMERA_THIRD_PERSON:
                    Raylib.UpdateCamera(ref primaryCamera, CameraMode.CAMERA_THIRD_PERSON);
                    break;
            }
        }
        
        public void SetCameraMode(CameraMode mode)
        {
            currentMode = mode;
        }
        
        public Camera3D GetPrimaryCamera() => primaryCamera;
        public Camera3D GetUICamera() => uiCamera;
    }
    

    Dynamic Camera Control

    Implement dynamic camera behaviors:

    public class DynamicCamera
    {
        private Camera3D camera;
        private Vector3 targetPosition;
        private Vector3 targetLookAt;
        private float smoothSpeed = 2.0f;
        
        public void SetTarget(Vector3 position, Vector3 lookAt)
        {
            targetPosition = position;
            targetLookAt = lookAt;
        }
        
        public void Update(double deltaTime)
        {
            // Smoothly interpolate to target
            float t = smoothSpeed * (float)deltaTime;
            
            camera.position = Vector3.Lerp(camera.position, targetPosition, t);
            camera.target = Vector3.Lerp(camera.target, targetLookAt, t);
        }
        
        public void FollowPlayer(Vector3 playerPosition, Vector3 playerForward)
        {
            // Position camera behind and above player
            var offset = new Vector3(0, 3, -5);
            var worldOffset = Vector3.Transform(offset, Matrix.CreateFromYawPitchRoll(0, 0, 0));
            
            SetTarget(playerPosition + worldOffset, playerPosition + playerForward);
        }
    }
    

    PGPSCN 3D Scene Format

    Basic 3D Scene Definition

    Define 3D scenes using the PGPSCN format:

    {
      "version": "1.0",
      "sceneType": "3d",
      "metadata": {
        "name": "Warehouse3D",
        "description": "3D warehouse environment"
      },
      "assets": {
        "maps": {
          "warehouse": "assets/maps/dm_warehouse.bsp"
        },
        "textures": {
          "wall_texture": "assets/textures/brick_wall.png",
          "floor_texture": "assets/textures/concrete_floor.png"
        }
      },
      "world": {
        "type": "quake_map",
        "mapFile": "warehouse",
        "spawn_points": [
          {
            "id": "player_spawn",
            "position": {"x": 256, "y": 256, "z": 32},
            "rotation": {"x": 0, "y": 0, "z": 0},
            "type": "player"
          },
          {
            "id": "enemy_spawn_1",
            "position": {"x": 512, "y": 384, "z": 32},
            "rotation": {"x": 0, "y": 180, "z": 0},
            "type": "enemy"
          }
        ],
        "entities": [
          {
            "id": "health_pickup",
            "type": "HealthPickup",
            "position": {"x": 192, "y": 256, "z": 40},
            "properties": {
              "healthAmount": 25,
              "respawnTime": 30.0,
              "glowColor": "#00FF00"
            }
          }
        ]
      },
      "cameras": [
        {
          "id": "main_camera",
          "type": "perspective",
          "isPrimary": true,
          "properties": {
            "fov": 75,
            "nearPlane": 0.1,
            "farPlane": 1000.0,
            "position": {"x": 0, "y": 100, "z": 100},
            "target": {"x": 0, "y": 0, "z": 0}
          }
        }
      ]
    }
    

    Hybrid Scene Definition

    Define hybrid scenes with both 3D and 2D elements:

    {
      "version": "1.0",
      "sceneType": "hybrid",
      "metadata": {
        "name": "GameplayHybrid",
        "description": "3D gameplay with UI overlay"
      },
      "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"
            }
          ]
        }
      ],
      "renderLayers": [
        {
          "name": "world_geometry",
          "type": "3d",
          "depth": 0,
          "camera": "fps_camera"
        },
        {
          "name": "world_ui",
          "type": "3d_billboard",
          "depth": 1,
          "camera": "fps_camera"
        },
        {
          "name": "screen_ui",
          "type": "2d",
          "depth": 2
        }
      ],
      "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}"
            }
          ]
        },
        {
          "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
            }
          ]
        }
      ]
    }
    

    3D UI Elements

    World-Space UI Components

    Create UI elements positioned in 3D world space:

    public class WorldSpaceUI
    {
        private List<IWorldUIElement> worldElements;
        private Camera3D worldCamera;
        
        public void Initialize(Camera3D camera)
        {
            worldCamera = camera;
            worldElements = new List<IWorldUIElement>();
        }
        
        public void AddFloatingHealthBar(Vector3 worldPosition, float health, float maxHealth)
        {
            var healthBar = new WorldSpaceHealthBar
            {
                WorldPosition = worldPosition,
                Health = health,
                MaxHealth = maxHealth,
                Size = new Vector2(100, 10),
                BillboardMode = BillboardMode.CameraFacing
            };
            
            worldElements.Add(healthBar);
        }
        
        public void AddDamageText(Vector3 worldPosition, int damage)
        {
            var damageText = new WorldSpaceDamageText
            {
                WorldPosition = worldPosition,
                Text = $"-{damage}",
                Color = Color.RED,
                FontSize = 20,
                Duration = 2.0f,
                BillboardMode = BillboardMode.CameraFacing
            };
            
            worldElements.Add(damageText);
        }
        
        public void Update(double deltaTime)
        {
            for (int i = worldElements.Count - 1; i >= 0; i--)
            {
                var element = worldElements[i];
                element.Update(deltaTime);
                
                if (element.ShouldRemove)
                {
                    worldElements.RemoveAt(i);
                }
            }
        }
        
        public void Render()
        {
            foreach (var element in worldElements)
            {
                element.Render(worldCamera);
            }
        }
    }
    
    public interface IWorldUIElement
    {
        Vector3 WorldPosition { get; set; }
        BillboardMode BillboardMode { get; set; }
        bool ShouldRemove { get; }
        
        void Update(double deltaTime);
        void Render(Camera3D camera);
    }
    
    public class WorldSpaceHealthBar : IWorldUIElement
    {
        public Vector3 WorldPosition { get; set; }
        public BillboardMode BillboardMode { get; set; }
        public Vector2 Size { get; set; }
        public float Health { get; set; }
        public float MaxHealth { get; set; }
        public bool ShouldRemove => false;
        
        public void Update(double deltaTime)
        {
            // Update health bar logic
        }
        
        public void Render(Camera3D camera)
        {
            // Convert world position to screen coordinates
            var screenPos = Raylib.GetWorldToScreen(WorldPosition, camera);
            
            // Draw background
            var bgRect = new Rectangle(screenPos.X - Size.X/2, screenPos.Y - Size.Y/2, Size.X, Size.Y);
            Raylib.DrawRectangleRec(bgRect, Color.DARKRED);
            
            // Draw health fill
            var healthPercent = MaxHealth > 0 ? Health / MaxHealth : 0;
            var fillWidth = Size.X * healthPercent;
            var fillRect = new Rectangle(screenPos.X - Size.X/2, screenPos.Y - Size.Y/2, fillWidth, Size.Y);
            Raylib.DrawRectangleRec(fillRect, Color.RED);
            
            // Draw border
            Raylib.DrawRectangleLinesEx(bgRect, 1, Color.WHITE);
        }
    }
    

    Billboard UI Elements

    UI elements that always face the camera:

    public enum BillboardMode
    {
        None,
        CameraFacing,
        CameraFacingY, // Only rotate around Y axis
        ScreenAligned
    }
    
    public class BillboardRenderer
    {
        public static void RenderBillboard(Vector3 worldPosition, Vector2 size, BillboardMode mode, Camera3D camera, Action renderContent)
        {
            Matrix transform = Matrix.Identity;
            
            switch (mode)
            {
                case BillboardMode.CameraFacing:
                    transform = CreateCameraFacingMatrix(worldPosition, camera);
                    break;
                    
                case BillboardMode.CameraFacingY:
                    transform = CreateCameraFacingYMatrix(worldPosition, camera);
                    break;
                    
                case BillboardMode.ScreenAligned:
                    transform = CreateScreenAlignedMatrix(worldPosition, camera);
                    break;
            }
            
            Raylib.rlPushMatrix();
            Raylib.rlMultMatrixf(MatrixToFloatArray(transform));
            
            // Render the billboard content
            renderContent();
            
            Raylib.rlPopMatrix();
        }
        
        private static Matrix CreateCameraFacingMatrix(Vector3 position, Camera3D camera)
        {
            var forward = Vector3.Normalize(camera.position - position);
            var right = Vector3.Normalize(Vector3.Cross(camera.up, forward));
            var up = Vector3.Cross(forward, right);
            
            return new Matrix(
                right.X, right.Y, right.Z, 0,
                up.X, up.Y, up.Z, 0,
                forward.X, forward.Y, forward.Z, 0,
                position.X, position.Y, position.Z, 1
            );
        }
        
        private static Matrix CreateCameraFacingYMatrix(Vector3 position, Camera3D camera)
        {
            var cameraDirection = Vector3.Normalize(camera.position - position);
            cameraDirection.Y = 0; // Lock Y axis
            cameraDirection = Vector3.Normalize(cameraDirection);
            
            var right = Vector3.Cross(Vector3.UnitY, cameraDirection);
            var up = Vector3.UnitY;
            
            return new Matrix(
                right.X, right.Y, right.Z, 0,
                up.X, up.Y, up.Z, 0,
                cameraDirection.X, cameraDirection.Y, cameraDirection.Z, 0,
                position.X, position.Y, position.Z, 1
            );
        }
    }
    

    Entity Management

    Entity Placement System

    Manage dynamic entities in 3D space:

    public class EntityManager
    {
        private Dictionary<string, Entity3D> entities;
        private List<SpawnPoint> spawnPoints;
        
        public EntityManager()
        {
            entities = new Dictionary<string, Entity3D>();
            spawnPoints = new List<SpawnPoint>();
        }
        
        public void LoadFromPGPSCN(SceneData sceneData)
        {
            // Load spawn points
            if (sceneData.World?.SpawnPoints != null)
            {
                foreach (var spawnDef in sceneData.World.SpawnPoints)
                {
                    var spawnPoint = new SpawnPoint
                    {
                        Id = spawnDef.Id,
                        Position = new Vector3(spawnDef.Position.X, spawnDef.Position.Y, spawnDef.Position.Z),
                        Rotation = new Vector3(spawnDef.Rotation.X, spawnDef.Rotation.Y, spawnDef.Rotation.Z),
                        Type = spawnDef.Type
                    };
                    spawnPoints.Add(spawnPoint);
                }
            }
            
            // Load entities
            if (sceneData.World?.Entities != null)
            {
                foreach (var entityDef in sceneData.World.Entities)
                {
                    var entity = CreateEntity(entityDef);
                    entities[entityDef.Id] = entity;
                }
            }
        }
        
        private Entity3D CreateEntity(EntityDefinition def)
        {
            switch (def.Type)
            {
                case "HealthPickup":
                    return new HealthPickup
                    {
                        Position = new Vector3(def.Position.X, def.Position.Y, def.Position.Z),
                        HealthAmount = def.Properties.GetValueOrDefault("healthAmount", 25),
                        RespawnTime = def.Properties.GetValueOrDefault("respawnTime", 30.0f)
                    };
                    
                case "WeaponPickup":
                    return new WeaponPickup
                    {
                        Position = new Vector3(def.Position.X, def.Position.Y, def.Position.Z),
                        WeaponType = def.Properties.GetValueOrDefault("weaponType", "pistol")
                    };
                    
                default:
                    return new GenericEntity
                    {
                        Position = new Vector3(def.Position.X, def.Position.Y, def.Position.Z)
                    };
            }
        }
        
        public Entity3D GetEntity(string id)
        {
            return entities.GetValueOrDefault(id);
        }
        
        public SpawnPoint GetSpawnPoint(string id)
        {
            return spawnPoints.FirstOrDefault(sp => sp.Id == id);
        }
        
        public void Update(double deltaTime)
        {
            foreach (var entity in entities.Values)
            {
                entity.Update(deltaTime);
            }
        }
        
        public void Render(Camera3D camera)
        {
            foreach (var entity in entities.Values)
            {
                entity.Render(camera);
            }
        }
    }
    
    public abstract class Entity3D
    {
        public Vector3 Position { get; set; }
        public Vector3 Rotation { get; set; }
        public Vector3 Scale { get; set; } = Vector3.One;
        public bool IsActive { get; set; } = true;
        
        public abstract void Update(double deltaTime);
        public abstract void Render(Camera3D camera);
    }
    
    public class HealthPickup : Entity3D
    {
        public int HealthAmount { get; set; }
        public float RespawnTime { get; set; }
        public bool IsPickedUp { get; private set; }
        private float respawnTimer;
        private Model model;
        
        public override void Update(double deltaTime)
        {
            if (IsPickedUp)
            {
                respawnTimer -= (float)deltaTime;
                if (respawnTimer <= 0)
                {
                    IsPickedUp = false;
                }
            }
            
            // Rotate the pickup for visual effect
            Rotation = new Vector3(Rotation.X, Rotation.Y + (float)deltaTime * 90f, Rotation.Z);
        }
        
        public override void Render(Camera3D camera)
        {
            if (!IsPickedUp && IsActive)
            {
                Raylib.DrawModel(model, Position, 1.0f, Color.GREEN);
            }
        }
        
        public void OnPickedUp()
        {
            IsPickedUp = true;
            respawnTimer = RespawnTime;
        }
    }
    

    Render Pipeline

    Mixed 2D/3D Rendering

    Implement efficient mixed rendering:

    public class MixedRenderer
    {
        private Camera3D mainCamera;
        private List<IRenderLayer> renderLayers;
        
        public MixedRenderer()
        {
            renderLayers = new List<IRenderLayer>();
        }
        
        public void AddRenderLayer(IRenderLayer layer)
        {
            renderLayers.Add(layer);
            // Sort by depth
            renderLayers.Sort((a, b) => a.Depth.CompareTo(b.Depth));
        }
        
        public void Render(int screenWidth, int screenHeight)
        {
            Raylib.ClearBackground(Color.SKYBLUE);
            
            foreach (var layer in renderLayers)
            {
                layer.Render(mainCamera, screenWidth, screenHeight);
            }
        }
    }
    
    public interface IRenderLayer
    {
        string Name { get; }
        int Depth { get; }
        void Render(Camera3D camera, int screenWidth, int screenHeight);
    }
    
    public class World3DLayer : IRenderLayer
    {
        public string Name => "world_3d";
        public int Depth => 0;
        
        private Model worldModel;
        private List<Entity3D> entities;
        
        public void Render(Camera3D camera, int screenWidth, int screenHeight)
        {
            Raylib.BeginMode3D(camera);
            {
                // Draw world geometry
                if (worldModel.meshCount > 0)
                    Raylib.DrawModel(worldModel, Vector3.Zero, 1.0f, Color.WHITE);
                
                // Draw entities
                foreach (var entity in entities)
                    entity.Render(camera);
            }
            Raylib.EndMode3D();
        }
    }
    
    public class WorldUILayer : IRenderLayer
    {
        public string Name => "world_ui";
        public int Depth => 1;
        
        private List<IWorldUIElement> worldUIElements;
        
        public void Render(Camera3D camera, int screenWidth, int screenHeight)
        {
            // World UI elements are rendered in screen space but positioned in world space
            foreach (var element in worldUIElements)
            {
                element.Render(camera);
            }
        }
    }
    
    public class ScreenUILayer : IRenderLayer
    {
        public string Name => "screen_ui";
        public int Depth => 2;
        
        private List<IUIElement> screenElements;
        
        public void Render(Camera3D camera, int screenWidth, int screenHeight)
        {
            // Standard 2D UI rendering
            foreach (var element in screenElements)
            {
                element.Draw();
            }
        }
    }
    

    Best Practices

    Performance Optimization

    1. Frustum Culling: Only render objects visible to the camera
    2. LOD (Level of Detail): Use lower detail models for distant objects
    3. Batch Rendering: Group similar objects to reduce draw calls
    4. Asset Streaming: Load/unload assets based on camera position
    public class PerformanceOptimizer
    {
        public bool IsInCameraFrustum(Vector3 position, float radius, Camera3D camera)
        {
            // Simple distance-based culling
            var distance = Vector3.Distance(camera.position, position);
            return distance <= camera.fovy * 10; // Adjust multiplier as needed
        }
        
        public Model GetLODModel(float distance, Dictionary<float, Model> lodModels)
        {
            Model selectedModel = lodModels.Values.First();
            
            foreach (var kvp in lodModels.OrderBy(x => x.Key))
            {
                if (distance <= kvp.Key)
                {
                    selectedModel = kvp.Value;
                    break;
                }
            }
            
            return selectedModel;
        }
    }
    

    Memory Management

    public class Scene3DResourceManager : IDisposable
    {
        private List<Model> loadedModels;
        private List<Texture2D> loadedTextures;
        private List<Sound> loadedSounds;
        
        public Model LoadModel(string path)
        {
            var model = Raylib.LoadModel(path);
            loadedModels.Add(model);
            return model;
        }
        
        public void Dispose()
        {
            foreach (var model in loadedModels)
                Raylib.UnloadModel(model);
            
            foreach (var texture in loadedTextures)
                Raylib.UnloadTexture(texture);
                
            foreach (var sound in loadedSounds)
                Raylib.UnloadSound(sound);
            
            loadedModels.Clear();
            loadedTextures.Clear();
            loadedSounds.Clear();
        }
    }
    

    Debugging and Development

    public class Debug3D
    {
        public static void DrawWireframeMode(bool enabled)
        {
            if (enabled)
                Raylib.rlEnableWireMode();
            else
                Raylib.rlDisableWireMode();
        }
        
        public static void DrawEntityBounds(Entity3D entity, Color color)
        {
            var bounds = entity.GetBoundingBox();
            Raylib.DrawBoundingBox(bounds, color);
        }
        
        public static void DrawCameraInfo(Camera3D camera, int x = 10, int y = 10)
        {
            Raylib.DrawText($"Camera Position: {camera.position}", x, y, 12, Color.WHITE);
            Raylib.DrawText($"Camera Target: {camera.target}", x, y + 15, 12, Color.WHITE);
            Raylib.DrawText($"Camera FOV: {camera.fovy}", x, y + 30, 12, Color.WHITE);
        }
    }
    

    Integration Examples

    Loading 3D Scene from PGPSCN

    public class Scene3DLoader
    {
        public void LoadScene(string pgpscnPath, Scene3D scene)
        {
            var sceneData = JsonSerializer.Deserialize<SceneData>(File.ReadAllText(pgpscnPath));
            
            // Load world
            if (sceneData.World != null)
            {
                LoadWorld(sceneData.World, scene);
            }
            
            // Setup cameras
            if (sceneData.Cameras != null)
            {
                SetupCameras(sceneData.Cameras, scene);
            }
            
            // Create UI elements
            if (sceneData.UIElements != null)
            {
                CreateUIElements(sceneData.UIElements, scene);
            }
        }
        
        private void LoadWorld(WorldDefinition world, Scene3D scene)
        {
            if (world.Type == "quake_map")
            {
                var mapModel = Raylib.LoadModel(world.MapFile);
                scene.SetWorldModel(mapModel);
            }
            
            // Load spawn points
            foreach (var spawn in world.SpawnPoints)
            {
                scene.AddSpawnPoint(spawn.Id, new Vector3(spawn.Position.X, spawn.Position.Y, spawn.Position.Z));
            }
        }
    }
    

    The 3D scene system in Popcorning Guinea Pig provides powerful capabilities for creating immersive 3D experiences while maintaining the simplicity and flexibility that makes the engine accessible to developers of all skill levels.

    Further Reading

    • Game Architecture - Core engine architecture
    • UI Components - 2D UI system integration
    • Asset Registry - Asset management for 3D content
    • Input System - 3D camera and movement controls
    • Edit this page
    In this article
    Back to top © 2024 Popcorning Guinea Pig Game Engine. A custom C# game engine for indie developers.