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
<PackageReference Include="Thunder.UnitsNET.Vectors.MonoGame" Version="0.7.0" />
<PackageVersion Include="Thunder.UnitsNET.Vectors.MonoGame" Version="0.7.0" />
<PackageReference Include="Thunder.UnitsNET.Vectors.MonoGame" />
paket add Thunder.UnitsNET.Vectors.MonoGame --version 0.7.0
#r "nuget: Thunder.UnitsNET.Vectors.MonoGame, 0.7.0"
#:package Thunder.UnitsNET.Vectors.MonoGame@0.7.0
#addin nuget:?package=Thunder.UnitsNET.Vectors.MonoGame&version=0.7.0
#tool nuget:?package=Thunder.UnitsNET.Vectors.MonoGame&version=0.7.0
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.TranslateTo — camera.TranslateTo(destination) returns a new Camera2 at the given LengthPoint2, leaving zoom and Y-axis convention unchanged. Completes the TranslateBy/TranslateTo pair.
Camera2.ClampZoom — camera.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.Lerp — Camera2.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.PerUnit — PixelScale.PerUnit(unit, pixels) factory; completes the PerMeter/PerUnit pair for constructing a PixelScale in any length unit.
PixelScale.Clamp — scale.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 also — CHANGELOG.md for full Phase 7 release notes (M35–M37).
What's new in v0.6.0
Camera2.TranslateBy — camera.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.ElapsedAsDuration — gameTime.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 also — CHANGELOG.md for full Phase 6 release notes (M32–M34.1).
What's new in v0.5.0
Focus-preserving zoom — Camera2.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 clarification — Camera2 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 also — CHANGELOG.md for full Phase 5 release notes (M27–M31.2).
Packages in this family
| Product | Versions 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. |
-
net10.0
- Thunder.UnitsNET.Vectors (>= 0.7.0)
- UnitsNet (>= 5.67.0)
-
net8.0
- Thunder.UnitsNET.Vectors (>= 0.7.0)
- UnitsNet (>= 5.67.0)
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 |