Nez 2D Game Framework

repository·master·Indexed 24 days ago

https://github.com/prime31/nez

A feature-rich 2D game framework built on top of MonoGame or FNA. Nez provides high-level abstractions for entities, components, physics, rendering, and debugging. It includes specialized tools such as a Sprite Atlas Packer CLI, an FNA Shader Compiler, and a Tiled map integration via a modified TiledSharp fork. The framework also offers robust AI implementation options including SimpleStateMachine, object-based StateMachines, and a fluent BehaviorTreeBuilder with composites, conditionals, decorators, and actions.

Tokens
52.3K
Snippets
104
Records
228
Agent score
81%

What's inside Nez

  1. Overview of Nez features

    master

    Nez is a feature-rich 2D framework built on top of MonoGame/FNA. It provides a comprehensive suite of tools for 2D game development, including:

    • Entity Management: A Scene/Entity/Component system with Component render layer tracking.
    • Physics & Collision:
      • AABB, circle, and polygon collision/trigger detection.
      • SpatialHash for fast broadphase physics lookups (used internally for raycasts and overlap checks).
      • Farseer Physics (Box2D-based) integration for full physics simulations.
      • Verlet physics bodies for particle-based constraints.
    • Task Management:
      • Core.startCoroutine for efficient coroutines to handle tasks across multiple frames.
      • Core.schedule for delayed and repeating tasks.
    • Rendering & Visuals:
      • Extensible rendering system with support for custom Renderers, PostProcessors, and custom sorters.
      • Deferred lighting engine with normal map support.
      • Tween system for animating fields (int, float, Vector, quaternion, color, rectangle).
      • Sprite support (animations, scrolling, repeating, and trails).
      • Line renderer with configurable end caps.
      • Particle system with Particle Designer file support.
    • Debugging Tools:
      • In-game debug console (accessible via the tilde ~ key) with visual physics debugging, asset tracking, and profiling.
      • In-game Component inspector: use the inspect ENTITY_NAME command in the debug console to edit properties and call methods.
      • Dear ImGui integration for custom debug panels via attributes.
    • Data & Pathfinding:
      • Serialization via Nez.Persistence using JSON, NSON (strongly typed, human-readable), or binary formats, supporting polymorphic classes and reference resolution.
      • Pathfinding via Astar and Breadth First Search for tilemaps or custom formats.
    • Scene Management:
      • Per-Scene content managers that automatically unload scene-specific content upon scene changes.
      • Customizable Scene transition system with built-in transitions.
    • Events: Optimized Emitter class for core and custom events.
  2. Choose a Nez Persistence strategy

    master

    Nez Persistence provides zero-dependency persistence strategies that can be selected based on your specific requirements for readability or performance:

    • JSON: Best for human-readable, hand-editable persistence. Supports polymorphic data structures and reference tracking.
    • NSON: A JSON-like format designed to be human-readable and easy to hand-edit, while maintaining most features of the JSON library.
    • High Performance Binary: Best for ultra-high performance needs, such as runtime state persistence. This route is more efficient but requires more implementation effort than JSON.
  3. Overview of Nez Pathfinding Algorithms

    master

    Nez provides three pathfinding algorithms that work on both grid-based graphs and generic graphs of any type. The algorithms are:

    1. Breadth First Search: Best for graphs with uniform traversal costs between edges. Often used for 'flood fill' scenarios.
    2. Dijkstra (Weighted): Best when edges have different traversal costs (e.g., moving on a road vs. through mud). It finds the lowest cost path.
    3. Astar (Weighted with Heuristic): An extension of Dijkstra that uses a heuristic to optimize the search. It also finds the lowest cost path based on edge weights.

    Key Concept: Nodes are not just locations. Pathfinding in Nez is generic. Nodes can represent anything: spatial coordinates (Point), dialog tree branches, AI actions, or even strings. The algorithms only require knowledge of a node and its neighbors (edges).

  4. How to use the Verlet physics system

    master

    Nez provides a Verlet physics system designed for visual effects and interactions rather than full rigid-body simulation.

    Key Concepts:

    • Simulation vs. Rendering: The Verlet system handles physics simulation only. It is not a renderer; you must manually render meshes or textures based on the simulation data. You can use _world.DebugRender(batcher) for quick visualization during development.
    • Interaction: Verlet objects can interact with standard Nez Colliders, but for efficiency, Verlet objects do not interact with each other.
    • Core Components: The simulation is built from Particles (which have mass and can be pinned or set to collide with Nez Colliders) and Constraints (mathematical rules that adjust particle positions).

    To run the simulation, you must create a World object and call its Update() method within your game loop.

    public class VerletDemo : RenderableComponent, IUpdatable
    {
    	public override float Width { get { return 800; } }
    	public override float Height { get { return 600; } }
    
    	World _world;
    
    	public override void OnAddedToEntity()
    	{
    		// create the verlet world which handles simulation
    		_world = new World( new Rectangle( 0, 0, 800, 600 ) );
    
    		// add a couple built-in Composite objects
    		_world.AddComposite( new Tire( new Vector2( 100, 100 ), 50, 20 ) );
    		_world.AddComposite( new Cloth( new Vector2( 10, 10 ), 200, 100 ) );
    	}
    
    	public void Update()
    	{
    		_world.Update();
    	}
    
    	public override void Render( Batcher batcher, Camera camera )
    	{
    		_world.DebugRender( batcher );
    	}
    }
  5. Use Skins for UI styling

    master

    Skins act as containers for UI resources (colors, textures, fonts, and styles). You can use them to quickly apply consistent styling to elements.

    Ways to use Skins:

    1. Default Skin: Use Skin.CreateDefaultSkin for quick prototyping.
    2. JSON Configuration: Define colors, atlases (LibGdxAtlases or TextureAtlases), and specific style types (e.g., ButtonStyle, WindowStyle) in a JSON file. This can be processed by the UI Skin Importer in the Pipeline tool.
    3. Programmatic Creation: Build a skin in code using skin.Add() and skin.GetDrawable().

    When using a skin, you can fetch a specific style by name: skin.Get<T>( "styleName" ). Alternatively, passing the Skin object directly to an element constructor will cause it to look for a style named "default" within that skin.

    var skin = new Skin( "skins/uiskinconfig", Core.Content );
    
    // Fetch style by specific type and name
    var button = new Button( skin.Get<TextButtonStyle>( "default" ) );
    
    // Or pass the skin directly to use the "default" style
    var button = new Button( skin );
    
    // Using a specific style name from JSON
    var bar = new ProgressBar( 0, 1, 0.1f, vertical, skin.Get<ProgressBarStyle>( "default-vertical" ) );
    
    // Using a style that relies on colors defined in the JSON
    var button = new Button( skin.Get<ButtonStyle>( "colored" ) );
    table.Add( button ).SetMinWidth( 100 ).SetMinHeight( 30 );
  6. Use Global and Scene-specific content containers

    master

    Nez provides two primary scopes for resource lifecycles:

    1. Global Content: Use Core.Content to access the global NezContentManager. This is intended for resources that should persist throughout the entire game lifecycle, such as fonts, global animations, or universal sound effects.
    2. Scene Content: Each scene has its own NezContentManager accessible via Scene.Content. Use this for resources specific to a single scene. When transitioning to a new scene, the resources in the old scene are automatically released.
  7. Understand the Nez Scene/Entity/Component model

    master

    Nez uses an Entity-Component (EC) architecture.

    • Scene: The root container for a game state (e.g., a menu or level). It manages Entities, Renderers, and PostProcessors. It provides a NezContentManager (Scene.contentManager) for scene-specific assets that are automatically unloaded when the scene ends.
    • Entity: A container for Components. Entities are managed by the Scene and have a lifecycle including OnAddedToScene, OnRemovedFromScene, and Update.
    • Component: Reusable logic attached to Entities. Components define behavior and have lifecycle methods like Initialize, OnAddedToEntity, OnRemovedFromEntity, and Update.
    • SceneComponent: A specialized component that lives at the Scene level rather than inside an Entity. Use these for systems that don't need an entity container, such as a physics world (e.g., Farseer).
  8. How Nez pathfinding algorithms work

    master

    Nez provides three pathfinding algorithms that work on any graph type, not just grids. Nodes do not have to be spatial locations; they can be any data type (e.g., strings, AI actions, or dialog tree nodes). Edges can be one-way or two-way.

    • Breadth First Search: Best for graphs with uniform traversal costs. It uses an expanding frontier to find a path.
    • Dijkstra (Weighted): Best when edges have different costs (e.g., road vs. mud). It finds the lowest cost path by querying edge weights.
    • Astar (Weighted with heuristic): Similar to Dijkstra but uses a heuristic to optimize the search, making it highly efficient for weighted graphs.
  9. Manage Scene-level logic with SceneComponents

    master

    If you need to add logic that does not depend on a specific Entity (e.g., a physics simulation like Farseer), use a SceneComponent.

    Scene components are managed via addSceneComponent, getSceneComponent, getOrCreateSceneComponent, and removeSceneComponent. They have a simplified lifecycle compared to standard components:

    • onEnabled / onDisabled
    • update
    • onRemovedFromScene
  10. Create custom Scene Transitions

    master

    You can create custom transition effects by subclassing SceneTransition. Transitions generally fall into two categories:

    1. One-part transitions: Obscure the screen with a render of the previous Scene, load the new Scene, and then transition from the old render to the new Scene's render (e.g., sliding the old render off-screen).
    2. Two-part transitions: Perform an initial effect (e.g., fade to black), load the new Scene, and then transition to displaying the new Scene.

    Implementation Steps

    • Constructor: Load any required Effect or textures here. If the transition is for a new Scene, accept a Func<Scene> sceneLoadAction.
    • OnBeginTransition(): Override this method to define the transition logic. It runs in a coroutine.
      • For two-part transitions, perform the first part (e.g., fade to black) before yielding to load the next scene.
      • Use yield return Core.StartCoroutine( LoadNextScene() ) to load the next Scene. Nez handles the _isNewSceneLoaded flag automatically for both intra-Scene and inter-Scene transitions.
      • Call TransitionComplete() to end the transition and trigger cleanup.
    • Render(Batcher batcher): Override this to control the final render output every frame. You can access previousSceneRender (the last render of the previous Scene) and use the _isNewSceneLoaded flag to determine which part of the transition is active.
    • Cleanup: Unload Effects or Textures in OnBeginTransition after calling TransitionComplete(), or override TransitionComplete and call base! to perform cleanup.

    Helper Method

    TickEffectProgressProperty is a helper that allows you to yield in a coroutine to animate an Effect parameter (typically named _progress) from 0 to 1 or 1 to 0 using a specified duration and EaseType.

    public class SuperTransition : SceneTransition
    {
    	public float Duration = 1f;
    	public EaseType EaseType = EaseType.QuartOut;
    
    	Effect _effect;
    	Rectangle _destinationRect;
    
    	public SuperTransition( Func<Scene> sceneLoadAction ) : base( sceneLoadAction, true )
    	{
    		_destinationRect = previousSceneRender.Bounds;
    		_effect = Core.content.loadEffect( "TransitionEffect.mgfxo" );
    	}
    
    	public override IEnumerator OnBeginTransition()
    	{
    		yield return Core.StartCoroutine( LoadNextScene() );
    		yield return Core.StartCoroutine( TickEffectProgressProperty( _effect, duration, easeType ) );
    		TransitionComplete();
    		Core.Content.UnloadEffect( _effect );
    	}
    
    	public override void Render( Batcher batcher )
    	{
    		Core.graphicsDevice.SetRenderTarget( null );
    		batcher.Begin( BlendState.NonPremultiplied, Core.DefaultSamplerState, DepthStencilState.None, null, _effect );
    		batcher.Draw( previousSceneRender, _destinationRect, Color.White );
    		batcher.End();
    	}
    }
  11. Understand Nez Content Management

    master

    Nez uses a custom content management system built on top of the MonoGame ContentManager class via the NezContentManager subclass.

    Key features include:

    • Containers: Nez provides separate containers for Global Content (assets that persist for the life of the application) and Scene Content (assets specific to a scene that are automatically unloaded when the scene changes).
    • Manual Unloading: You can manually unload assets using UnloadAsset<T>. For Effects, you must use the specialized UnloadEffect method.
    • Debugging: You can use the assets command in the Nez debug console to log all currently loaded scene or global assets to monitor memory usage.
  12. Core concepts of Nez

    master
    The Core class is the foundation of Nez and inherits from the XNA Game class. Your game class should also inherit from Core. Core provides access to all major subsystems through static fields and methods. Key subsystems include Graphics, Scene, Physics, TimerManager, CoroutineManager, and Input.