Thunder.UnitsNET.Vectors.MonoGame 0.7.0

dotnet add package Thunder.UnitsNET.Vectors.MonoGame --version 0.7.0
                    
NuGet\Install-Package Thunder.UnitsNET.Vectors.MonoGame -Version 0.7.0
                    
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="Thunder.UnitsNET.Vectors.MonoGame" Version="0.7.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Thunder.UnitsNET.Vectors.MonoGame" Version="0.7.0" />
                    
Directory.Packages.props
<PackageReference Include="Thunder.UnitsNET.Vectors.MonoGame" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Thunder.UnitsNET.Vectors.MonoGame --version 0.7.0
                    
#r "nuget: Thunder.UnitsNET.Vectors.MonoGame, 0.7.0"
                    
#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.
#:package Thunder.UnitsNET.Vectors.MonoGame@0.7.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Thunder.UnitsNET.Vectors.MonoGame&version=0.7.0
                    
Install as a Cake Addin
#tool nuget:?package=Thunder.UnitsNET.Vectors.MonoGame&version=0.7.0
                    
Install as a Cake Tool

Thunder.UnitsNET.Vectors.MonoGame

MonoGame / XNA integration for Thunder.UnitsNET.Vectors.

Provides a Camera2 type for world-to-screen projection and extension methods to convert unit-aware LengthVector2/LengthVector3 (and their derived types) to and from XNA Vector2/Vector3. Scaling is expressed as a PixelScale — a typed value that bundles the pixels-per-unit ratio and the world unit together, so the scale is always self-consistent and zoom is a first-class operation.

Supported frameworks: net8.0, net10.0

Quick start

// dotnet add package Thunder.UnitsNET.Vectors
// dotnet add package Thunder.UnitsNET.Vectors.MonoGame

using Thunder.UnitsNET.Vectors;
using Thunder.UnitsNET.Vectors.MonoGame;
using UnitsNet.Units;
using Microsoft.Xna.Framework;

// Physics world position (e.g. from your physics update)
var worldPos = new LengthVector2(
    Length.FromMeters(1.5),
    Length.FromMeters(-2.0));

// Convert to screen pixels (64 px per metre)
const double ppm = 64.0;
Vector2 screen = worldPos.ToXnaVector2(LengthUnit.Meter, ppm);

// Convert back (e.g. mouse pick ray → world coords)
LengthVector2 picked = screen.ToLengthVector2(LengthUnit.Meter, ppm);

Camera2 — world-to-screen projection

Camera2 captures a camera's world position, zoom level, and Y-axis convention. Pass it once to WorldToScreen for every entity in the render loop:

using Thunder.UnitsNET.Vectors;
using Thunder.UnitsNET.Vectors.MonoGame;
using Microsoft.Xna.Framework;

// Camera centred on the player, 32 pixels per metre, Y-down (MonoGame default)
var camera = new Camera2(
    Position: player.Position,
    Scale: PixelScale.PerMeter(32));   // YAxisMode.YDown is the default

var screenSize = new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);

// Render all entities
foreach (var entity in entities)
{
    Vector2 screenPos = camera.WorldToScreen(entity.Position, screenSize);
    spriteBatch.Draw(entity.Texture, screenPos, Color.White);
}

// Mouse picking — convert cursor position back to world space
LengthVector2 worldMouse = camera.ScreenToWorld(mouseState.Position.ToVector2(), screenSize);
bool clicked = triggerZone.Contains(worldMouse);

// Scale a world length to pixels (e.g. circle radius for debug draw)
Single pixelRadius = camera.WorldToScreenScale(circle.Radius);

Zoom

Changing the PixelScale is how zoom works — more pixels per world unit means the world appears larger on screen. ZoomBy returns a new Camera2 with the scale multiplied:

// Snap zoom — scale only, camera stays centred on its current position
camera = camera.ZoomBy(2.0);   // zoom in 2×
camera = camera.ZoomBy(0.5);   // zoom out to half

// Incremental zoom — call each frame while a button is held
camera = camera.ZoomBy(1.05);  // +5% per frame

// Non-metre world — two pixels per centimetre
var scale = Length.FromCentimeters(1).ToPixels(2);
var camera = new Camera2(Position: player.Position, Scale: scale);

Focus-preserving zoom

To zoom toward a specific screen point (e.g. the cursor or a pinch midpoint), use the three-argument overload. The world position under screenFocus stays at the same screen location after the zoom:

// Zoom in 2× toward the cursor
Vector2 cursor    = mouseState.Position.ToVector2();
camera = camera.ZoomBy(2.0, cursor, screenSize);

// Pinch-to-zoom: zoom toward pinch midpoint
Vector2 pinchMid  = (touch1.Position + touch2.Position).ToVector2() / 2f;
camera = camera.ZoomBy(pinchFactor, pinchMid, screenSize);

For high-frequency render loops, compute screenSize once per frame and pass it in rather than recalculating it on every call.

Smooth zoom

PixelScale.Lerp linearly interpolates between two zoom levels. Calling it every frame with a small t produces smooth, asymptotic zoom — fast at first, slowing as the scale approaches the target:

// State held across frames
PixelScale _currentScale = PixelScale.PerMeter(32);
PixelScale _targetScale  = PixelScale.PerMeter(32);

// In your Update loop
void Update(GameTime gameTime)
{
    if (scrollUp)   _targetScale = _targetScale.ZoomBy(1.1);
    if (scrollDown) _targetScale = _targetScale.ZoomBy(0.9);

    // smoothSpeed controls how quickly the zoom settles (game-specific tuning)
    Double t = gameTime.ElapsedGameTime.TotalSeconds * smoothSpeed;
    _currentScale = PixelScale.Lerp(_currentScale, _targetScale, t);

    camera = new Camera2(Position: player.Position, Scale: _currentScale);
}

Lerp at t = 0 returns the start scale; t = 1 returns the target; values in between give a proportional blend. The game owns smoothSpeed and _targetScale — the library provides the interpolation math.

Y-axis convention

MonoGame screen space has Y increasing downward. Physics / world space conventionally has Y increasing upward. Camera2 flips Y by default (YAxisMode.YDown). For non-standard setups, pass YAxis: YAxisMode.YUp to the constructor.

Round-trip precision

ScreenToWorld(WorldToScreen(p, size), size) ≈ p within ~1e-5 m. The precision limit comes from float32 screen coordinates; it is more than sufficient for any rendering use case.


Conversion API

2D extensions (XnaVector2Extensions)

Method From To Notes
ToXnaVector2(unit, scale) LengthVector2 Vector2 Components converted to unit, multiplied by scale
ToXnaVector2(unit, scale) DoubleVector2 Vector2 Direction-only; scale applied
ToLengthVector2(unit, scale) Vector2 LengthVector2 XNA pixels divided by scale, wrapped in unit
ToDoubleVector2() Vector2 DoubleVector2 Raw cast; no scaling

3D extensions (XnaVector3Extensions)

Method From To Notes
ToXnaVector3(unit, scale) LengthVector3 Vector3 Same semantics as 2D
ToXnaVector3(unit, scale) DoubleVector3 Vector3
ToLengthVector3(unit, scale) Vector3 LengthVector3
ToDoubleVector3() Vector3 DoubleVector3

Scale-aware overloads

All ToXna* overloads accept an optional scale parameter (default 1.0). Pass your pixels-per-unit constant at the call site rather than storing it as a global, so the conversion intent is explicit in code.

MonoGame dependency note

MonoGame.Framework.DesktopGL is declared as PrivateAssets="all" in this package. You must add your own platform-specific MonoGame package to your game project (e.g. MonoGame.Framework.DesktopGL, MonoGame.Framework.WindowsDX). This package works with any MonoGame platform.


What's new in v0.7.0

Camera2.TranslateTocamera.TranslateTo(destination) returns a new Camera2 at the given LengthPoint2, leaving zoom and Y-axis convention unchanged. Completes the TranslateBy/TranslateTo pair.

Camera2.ClampZoomcamera.ClampZoom(min, max) returns a new Camera2 with the scale clamped to a [min, max] range. Accepts either Double pixels-per-unit bounds or PixelScale bounds (unit-safe). Useful for enforcing a minimum/maximum zoom level regardless of input.

Camera2.LerpCamera2.Lerp(a, b, t) interpolates position and scale between two camera states. Primary overload takes a Ratio for t; a Double overload delegates to it.

PixelScale.PerUnitPixelScale.PerUnit(unit, pixels) factory; completes the PerMeter/PerUnit pair for constructing a PixelScale in any length unit.

PixelScale.Clampscale.Clamp(min, max) returns a new PixelScale with pixels-per-unit clamped. Overloads: Clamp(Double, Double) for raw bounds; Clamp(PixelScale, PixelScale) for unit-aware bounds.

See alsoCHANGELOG.md for full Phase 7 release notes (M35–M37).


What's new in v0.6.0

Camera2.TranslateBycamera.TranslateBy(delta) returns a new Camera2 with the position shifted by a LengthVector2. Eliminates the new Camera2(Position: camera.Position + delta, Scale: camera.Scale) pattern at every camera-pan call site.

GameTimeExtensions.ElapsedAsDurationgameTime.ElapsedAsDuration() returns Duration.FromSeconds(gameTime.ElapsedGameTime.TotalSeconds) — a UnitsNet.Duration value ready to use in unit-typed physics updates without manual extraction.

Camera2 struct documentation — the Camera2 XML summary now states that it is a readonly record struct (late-bound fields are default, not null) and the <remarks> block shows the recommended pattern for holding screenSize as a frame-level field rather than recomputing it per call.

See alsoCHANGELOG.md for full Phase 6 release notes (M32–M34.1).


What's new in v0.5.0

Focus-preserving zoomCamera2.ZoomBy(factor, screenFocus, screenSize) zooms the camera while keeping the world point under screenFocus at the same screen location. Ideal for cursor-centred zoom and pinch-to-zoom gestures.

Camera2 struct clarificationCamera2 is a readonly record struct. Late-bound camera fields (e.g., a field not set in a constructor) are default, not null. All mutating operations return a new instance.

See alsoCHANGELOG.md for full Phase 5 release notes (M27–M31.2).


📖 API docs · 💻 Source

Packages in this family

NuGet Thunder.UnitsNET.Vectors — Core vector types
NuGet Thunder.UnitsNET.Vectors.MonoGame — XNA/MonoGame conversions (this package)
NuGet Thunder.UnitsNET.Vectors.Geometry — Unit-aware 2D geometry
NuGet Thunder.UnitsNET.Vectors.Geometry.MonoGame — Geometry → MonoGame
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Thunder.UnitsNET.Vectors.MonoGame:

Package Downloads
Thunder.UnitsNET.Vectors.Geometry.MonoGame

MonoGame/XNA integration for Thunder.UnitsNET.Vectors.Geometry. Converts unit-aware 2D geometry shapes to XNA rendering primitives with explicit scale, keeping physics and rendering units separate.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.7.0 114 5/5/2026
0.7.0-preview37 92 5/5/2026
0.7.0-preview36 99 5/5/2026
0.7.0-preview35 109 5/4/2026
0.6.0-preview34 114 4/28/2026
0.6.0-preview33 119 4/27/2026
0.6.0-preview32 110 4/27/2026
0.5.0 122 4/23/2026
0.5.0-preview31 110 4/28/2026
0.5.0-preview30 129 4/28/2026
0.5.0-preview29 111 4/28/2026
0.5.0-preview28 108 4/28/2026
0.5.0-preview27 119 4/28/2026
0.4.0 132 4/22/2026
0.4.0-preview6 114 4/20/2026
0.4.0-preview5 106 4/20/2026
0.4.0-preview4 107 4/20/2026
0.4.0-preview25 109 4/20/2026
0.4.0-preview24 103 4/20/2026
0.4.0-preview23.1 53 4/20/2026
Loading failed