Venomaus.FlowVitae 1.3.9

dotnet add package Venomaus.FlowVitae --version 1.3.9                
NuGet\Install-Package Venomaus.FlowVitae -Version 1.3.9                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Venomaus.FlowVitae" Version="1.3.9" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Venomaus.FlowVitae --version 1.3.9                
#r "nuget: Venomaus.FlowVitae, 1.3.9"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Venomaus.FlowVitae as a Cake Addin
#addin nuget:?package=Venomaus.FlowVitae&version=1.3.9

// Install Venomaus.FlowVitae as a Cake Tool
#tool nuget:?package=Venomaus.FlowVitae&version=1.3.9                

FlowVitae

FlowVitae is a memory and performance efficient 2D grid library designed for small to large scale procedural worlds. Can be easily integrated with most render engines.

Supports:

  • net7.0
  • net6.0
  • netstandard2.1

Tested with:

  • SadConsole
  • MonoGame

Features

Different grid layouts

  • Static grid
  • Static chunked grid
  • Procedural chunked grid

Infinite chunking terrain

  • Chunking is automatically done
  • Viewport and chunk size is configurable
  • Method to center viewport on a coordinate, (this handles the chunking)
  • Procedural generation algorithm can be passed straight to the Grid

Easy to use

  • Possible to configure custom Grid, Cell, ProceduralGeneration classes
  • Has some visualizer projects that serves as examples on how to integrate with a render engine, such as SadConsole or Unity.

Setup

FlowVitae grids use 2 generic types

  • TCellType is constrained to a struct, and will represent the unique cell value kept in memory. (eg, an int or byte that points to the real cell id)
  • TCell is the real cell that represents the TCellType, it uses the ICell<TCellType> interface

FlowVitae provides some basic implementations already out of the box.

Grid<TCellType, TCell>
Cell<TCellType>

Static Grid Creation

var grid = new Grid<int, Cell<int>>(width, height);

Static Chunked Grid Creation

var staticGen = new StaticGenerator<int, Cell<int>>(baseMap, width, height, outOfBoundsCellType);
var grid = new Grid<int, Cell<int>>(width, height, chunkWidth, chunkHeight, staticGen, extraChunkRadius = 1);

The baseMap here represents the full static grid.

Procedural Grid Creation

var procGen = new ProceduralGenerator<int, Cell<int>>(Seed, GenerateChunkMethod);
var grid = new Grid<int, Cell<int>>(width, height, chunkWidth, chunkHeight, procGen, extraChunkRadius = 1);

GenerateChunkMethod can look something like this:

public void GenerateChunkMethod(Random random, int[] chunk, int width, int height, (int x, int y) chunkCoordinate)
{
	// Every position contains default value of int (0) which could represent grass
	for (int x = 0; x < width; x++)
	{
		for (int y = 0; y < height; y++)
		{
			// Add a random chance for a cell to be a tree
			if (random.Next(0, 100) < 10)
				chunk[y * width + x] = 1;
		}
	}
}

The random already has a unique seed based on the provided Seed in the ProceduralGenerator and the chunk coordinate. int[] chunk represent the chunk, int[] will be your TCellType[] and the chunkCoordinate is provided too, in case you want to sample noise based on coordinates.

Chunks are generated automatically and they will use this method as reference to build the chunk.

Setting custom chunk data

It is possible to set custom data, per chunk which can be directly retrieved from the grid. This custom data, can be any class that implements IChunkData interface. An example implementation:

internal class TestChunkData : IChunkData
{
	public int Seed { get; set; }
	public List<(int x, int y)>? Trees { get; set; }
}
// Custom chunk generation implementation
Func<Random, int[], int, int, TestChunkData> chunkGenerationMethod = (random, chunk, width, height, chunkCoordinate) =>
{
	// Define custom chunk data
	var chunkData = new TestChunkData
	{
		Trees = new List<(int x, int y)>()
	};
	for (int x = 0; x < width; x++)
	{
		for (int y = 0; y < height; y++)
		{
			chunk[y * width + x] = random.Next(-10, 10);
			// Every 0 value is a tree, lets keep it for easy pathfinding access
			if (chunk[y * width + x] == 0)
				chunkData.Trees.Add((x, y));
		}
	}
	return chunkData;
};

// Initialize the custom implementations
var customProcGen = new ProceduralGenerator<int, Cell<int>, TestChunkData>(Seed, chunkGenerationMethod);
var customGrid = new Grid<int, Cell<int>, TestChunkData>(ViewPortWidth, ViewPortHeight, ChunkWidth, ChunkHeight, customProcGen, extraChunkRadius = 1);

// Retrieve the chunk data, for the whole chunk where position (5, 5) resides in
var chunkData = customGrid.GetChunkData(5, 5);
Console.WriteLine("Trees in chunk: " + chunkData.Trees != null ? chunkData.Trees.Count : 0);

You can store the chunkdata within the internal cache buffer, which GetChunkData will then return instead

customGrid.StoreChunkData(chunkData);
customGrid.RemoveChunkData(chunkData, reloadChunk); // chunkdata only refreshes after chunk is reloaded

This works similar for Static chunked grids, you can pass along a chunk data generation method to the constructor.

// chunkGenerationMethod signature: (seed, baseMap, width, height, chunkCoordinate)
new StaticGenerator<int, Cell<int>, TestChunkData>(_baseMap, Grid.Width, Grid.Height, NullCell, chunkGenerationMethod, extraChunkRadius = 1)

Rendering to a render engine

FlowVitae provides an event that is raised when a cell on the viewport is updated

grid.OnCellUpdate += Grid_OnCellUpdate;

private void Grid_OnCellUpdate(object? sender, CellUpdateArgs<int, Cell<int>> args)
{
  // Pseudo code
  var screenGraphic = ConvertCellTypeToGraphic(args.Cell.CellType);
  SomeRenderEngine.SetScreenGraphic(args.ScreenX, args.ScreenY, screenGraphic);
}

This event is by default only raised when the TCellType value on the viewport is changed during a SetCell/SetCells If you want this event to always be raised when a TCell is set, (even if CellType doesn't change, but some properties do) Then we also provided this functionality. You can adjust it like so:

grid.RaiseOnlyOnCellTypeChange = false;

Custom cell conversion

There are some ways to convert the underlying TCellType to TCell.

  • You can implement your own Grid class based on the GridBase<TCellType, TCell> and override the Convert method.
  • You can call the method SetCustomConverter(converter) on the Grid which is then used in Convert instead of default new() constructor

Here is an example of the method:

_grid.SetCustomConverter(ConvertCell);
	
public Cell<int> ConvertCell(int x, int y, int cellType)
{
	switch (cellType)
	{
		case 0:
			return new Cell<int>(x, y, cellType);
		case 1:
			return new Cell<int>(x, y, walkable: false, cellType);
		default:
			return new Cell<int>(x, y, walkable: false, cellType);
	}
}

Creating your own Cell implementation

It can be easily done by inheriting from CellBase or ICell<TCellType>

When you want your cell to be able to base of some render engine cell such as ColoredGlyph from Sadconsole, you can easily do it by using ColoredGlyph, ICell<TCellType> as your inheritance.

Here is an example of just a regular CellBase inheritance:

internal class VisualCell<TCellType> : CellBase<TCellType>
where TCellType : struct
{
	public bool Walkable { get; set; } = true;
	public bool BlocksFieldOfView { get; set; } // Some custom properties
	public bool HasLightSource { get; set; } // Some custom properties

	public VisualCell() { }

	public VisualCell(int x, int y, TCellType cellType)
	{
		X = x;
		Y = y;
		CellType = cellType;
	}
}

Interaction with grids

Getting and setting cells

var cell = grid.GetCell(x, y); // returns TCell
var cellType = grid.GetCelLType(x,y); // returns TCellType
var neighbors = grid.GetNeighbors(x, y, AdjacencyRule);
grid.SetCell(x, y, cellType, storeState);
grid.SetCell(cell, storeState);
var cells = grid.GetCells(new [] {(0,0), (1,1)}); // returns collection of TCell
grid.SetCells(cells, storeState);
grid.RemoveStoredCell(x, y);
grid.HasStoredCell(x, y);

Center viewport on a coordinate for procedural grids

This is especially useful when you want your player to always be centered in the middle of the screen. But during movement, the viewport adjusts to show the right cells based on the position of the player For this you can use the Center(x, y) method Grid provides. This method is also what controls the chunk loading.

// Pseudo code (make sure player doesn't actually move, or you'll end up with desync)
if (player.MovedTowards(x, y))
    grid.Center(x, y);

Retrieve all cells within the viewport

// Returns all world positions that are within the current viewport
grid.GetViewPortWorldCoordinates();
grid.GetViewPortWorldCoordinates(cellType => cellType == 1 || cellType == 2); // with custom criteria

Checking bounds for static grids

// Returns true or false if the position is within the viewport
// Works only for screen coordinates if you're using a chunked grid
var isInBounds = grid.InBounds(x, y);

See if a cell is currently displayed on the viewport

var isInViewPort = grid.IsWorldCoordinateOnViewPort(x,y);

Reset grid state

grid.ClearCache(); // Removes all stored cell data
grid.RemoveStoredCell(x, y);

Resize grid viewport

This will resize the surface of the screen, reinitialize all the chunks and send out render updates for the new screen surface.

grid.ResizeViewport(width, height);

Be notified of main chunk loading/unloading

Following events will be raised when one of the chunks around the center chunk (center chunk included) gets loaded or unloaded.

OnChunkLoad
OnChunkUnload

Some chunk related methods: (x, y) is automatically converted to a chunk coordinate, so it can take any world position.

Grid.GetChunkSeed(x, y);
Grid.IsChunkLoaded(x, y);
Grid.GetChunkCoordinate(x, y);
Grid.GetChunkCellCoordinates(x, y);
Grid.GetLoadedChunkCoordinates();

Integration with SadConsole / MonoGame

Checkout the SadConsoleVisualizer project, it is an example project that integrates the FlowVitae grid with SadConsole and MonoGame.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.9 208 10/15/2023