見出し、例、コード、表、リンク、参照画像を含む原文を表示しています。
MonoGame Math Implementation Guide
This skill covers math types and patterns in MonoGame. For bounding volume details, see references/math.md.
Vectors
Vector2, Vector3, and Vector4 are value types (structs). Do not allocate them with new inside Update() or Draw() — mutate fields instead.
// BAD — allocates every frame:
_velocity = new Vector2(_velocity.X + acceleration, _velocity.Y);
// GOOD — mutate in place:
_velocity.X += acceleration;Common Vector2 operations
Vector2 a, b;
float length = a.Length(); // expensive (sqrt)
float lengthSq = a.LengthSquared(); // cheap — use for distance comparisons
float distance = Vector2.Distance(a, b); // expensive
float distSq = Vector2.DistanceSquared(a, b); // cheap for "is within range X" checks
Vector2 norm = Vector2.Normalize(a); // unit vector (new instance)
Vector2 lerped = Vector2.Lerp(a, b, t); // linear interpolation, t in [0,1]
Vector2 clamped = Vector2.Clamp(a, min, max); // per-component clamp
float dot = Vector2.Dot(a, b); // cos(angle) * |a| * |b|Direction from angle (radians)
float angle = MathHelper.ToRadians(45f);
Vector2 dir = new Vector2(MathF.Cos(angle), MathF.Sin(angle));Rectangle (2D AABB Collision)
Rectangle is a value type. Use it for 2D axis-aligned bounding box (AABB) collision — the most common pattern in 2D games.
Rectangle playerBounds = new Rectangle((int)_pos.X, (int)_pos.Y, 32, 32);
Rectangle wallBounds = new Rectangle(100, 100, 64, 64);
bool collides = playerBounds.Intersects(wallBounds);
bool contains = wallBounds.Contains(new Point(x, y));
// Overlap area:
Rectangle.Intersect(ref playerBounds, ref wallBounds, out Rectangle overlap);When hitboxes don't match the sprite size, define the hitbox dimensions as separate constants rather than using texture width/height directly.
Matrix (Transforms)
Matrix is a value type. Build it once per frame in Update() and cache it as a field — do not rebuild in Draw().
// Translation
Matrix t = Matrix.CreateTranslation(x, y, 0f);
// Scale
Matrix s = Matrix.CreateScale(2f, 2f, 1f);
// Rotation around Z (2D)
Matrix r = Matrix.CreateRotationZ(angleInRadians);
// Combine (order matters: scale × rotation × translation for SRT)
Matrix world = Matrix.CreateScale(scale) *
Matrix.CreateRotationZ(rotation) *
Matrix.CreateTranslation(position.X, position.Y, 0f);2D Camera matrix
Matrix camera =
Matrix.CreateTranslation(-_camPos.X, -_camPos.Y, 0f) *
Matrix.CreateRotationZ(_camRotation) *
Matrix.CreateScale(_camZoom, _camZoom, 1f) *
Matrix.CreateTranslation(screenCenterX, screenCenterY, 0f);Screen-to-world conversion
// Cache the inverse — don't recompute per call:
_inverseCamera = Matrix.Invert(_cameraMatrix);
Vector2 worldPos = Vector2.Transform(screenPos, _inverseCamera);MathHelper Utilities
MathHelper.Clamp(value, min, max) // clamp float
MathHelper.Lerp(value1, value2, amount) // linear interpolation
MathHelper.SmoothStep(value1, value2, t) // smooth S-curve interpolation
MathHelper.ToDegrees(radians)
MathHelper.ToRadians(degrees)
MathHelper.WrapAngle(angle) // wrap to [-π, π]
MathHelper.Pi // 3.14159...
MathHelper.TwoPi // 6.28318...
MathHelper.PiOver2 // π/2Distance Checks — Fast vs Exact
For "is the enemy within X units?" comparisons, always use `DistanceSquared`:
float rangeSquared = detectionRange * detectionRange;
if (Vector2.DistanceSquared(enemyPos, playerPos) <= rangeSquared)
Chase();Only call Vector2.Distance() (which uses sqrt) when you need the actual distance value (e.g., for UI display or audio attenuation).
Bounding Volumes (3D)
Choosing a volume
| Type | Pros | Cons | Best for |
|---|---|---|---|
BoundingSphere | Fastest test (distance vs radii sum). Rotation-independent — no rebuild needed. | False positives on long/narrow objects. | Rough first-pass culling, bullets, explosions |
BoundingBox | Tight fit on axis-aligned rectangles. Fast axis-aligned test. | Must rebuild on rotation. Worst fit at 45°. | Level tiles, non-rotating objects |
BoundingFrustum | Matches exactly what the camera sees. | Expensive to construct — rebuild only on camera move. | Visibility culling, skipping off-screen draw calls |
Ray | No volume — a directed line from a point. | Not for volume-vs-volume tests. | Mouse picking, raycasting, line-of-sight |
Plane | Splits space into front/back. | Returns relative position, not contact point. | Portal/sector visibility, reflection planes |
BoundingSphere and BoundingBox
var sphere1 = new BoundingSphere(center1, radius1);
var sphere2 = new BoundingSphere(center2, radius2);
bool hit = sphere1.Intersects(sphere2); // true / false
// ContainmentType: Contains, Intersects, or Disjoint
ContainmentType ct = sphere1.Contains(sphere2);
// Ray against sphere — returns distance or null:
float? dist = ray.Intersects(sphere1);
if (dist.HasValue)
Vector3 hitPoint = ray.Position + ray.Direction * dist.Value;
// BoundingBox from a set of points (e.g., model vertices):
var box = BoundingBox.CreateFromPoints(vertices);
bool hit = box1.Intersects(box2);BoundingFrustum — visibility culling
Rebuild whenever the camera view or projection changes. Skip drawing anything that is Disjoint:
// Rebuild (expensive — once per frame when camera moves):
_frustum = new BoundingFrustum(_camera.View * _camera.Projection);
// Cull in Draw():
foreach (var entity in _entities)
{
if (_frustum.Contains(entity.BoundingSphere) != ContainmentType.Disjoint)
entity.Draw(_spriteBatch);
}Model bounding volumes
The Content Pipeline automatically calculates a BoundingSphere for each ModelMesh. Use it for model-model collision without manual vertex iteration:
// Check all mesh spheres of two models:
bool ModelsCollide(Model a, Matrix aWorld, Model b, Matrix bWorld)
{
foreach (ModelMesh meshA in a.Meshes)
{
BoundingSphere sA = meshA.BoundingSphere.Transform(aWorld);
foreach (ModelMesh meshB in b.Meshes)
{
BoundingSphere sB = meshB.BoundingSphere.Transform(bWorld);
if (sA.Intersects(sB)) return true;
}
}
return false;
}Plane
// Plane defined by normal + distance from origin:
var plane = new Plane(normal, d);
// Or from three points:
var plane = new Plane(pointA, pointB, pointC);
// Returns PlaneIntersectionType: Front, Back, or Intersecting
PlaneIntersectionType side = plane.Intersects(sphere);
PlaneIntersectionType side = plane.Intersects(box);Contains vs Intersects
Intersects()stops as soon as any overlap is found — faster, returnsbool.Contains()checks whether one object fully encloses another — returnsContainmentType(Contains,Intersects,Disjoint). Slightly slower.
Use Intersects in hot paths (per-frame collision). Use Contains when you need to know if an entity is fully inside a zone (e.g., trigger volumes, frustum culling with LOD).
Curve — Value Interpolation Over Time
Curve maps a float input (time) to a float output (value) via Hermite spline. Use it for easing, non-linear property animation, or any value that needs to change over time without hardcoding every frame.
var curve = new Curve();
curve.Keys.Add(new CurveKey(0f, 0f)); // at t=0, value=0
curve.Keys.Add(new CurveKey(0.5f, 1f)); // at t=0.5, value=1
curve.Keys.Add(new CurveKey(1f, 0f)); // at t=1, value=0
// Smooth tangents automatically (call after adding all keys):
curve.ComputeTangents(CurveTangent.Smooth);
// Evaluate at any time:
float value = curve.Evaluate(t); // t can be outside [0,1] — controlled by loop typeControl what happens outside the defined range with PreLoop / PostLoop:
curve.PreLoop = CurveLoopType.Constant; // hold first/last value
curve.PostLoop = CurveLoopType.Cycle; // repeat
curve.PostLoop = CurveLoopType.Oscillate; // ping-pong
curve.PostLoop = CurveLoopType.Linear; // extrapolate linearly
curve.PostLoop = CurveLoopType.CycleOffset; // repeat, accumulating the total deltaStep (discontinuous) transitions:
// Use CurveContinuity.Step to snap instead of interpolate between two keys:
curve.Keys.Add(new CurveKey(0.5f, 1f, 0f, 0f, CurveContinuity.Step));Typical patterns:
// Ease-in / ease-out for a move animation (0→1 over duration):
float _elapsed;
float _duration = 0.4f;
float Progress => MathHelper.Clamp(_elapsed / _duration, 0f, 1f);
float EasedPos => _easeCurve.Evaluate(Progress);
// Camera shake intensity that decays over 0.3 s:
// Keys: (0, 1), (0.3, 0) → ComputeTangents(CurveTangent.Smooth)
float shakeAmount = _shakeCurve.Evaluate(_shakeTimer);Curve only interpolates scalars. For Vector2/Vector3 paths, use one Curve per component (X and Y separately).
Rules
- Use
DistanceSquaredfor range/proximity checks — avoidsqrtin hot paths. - Never write
new Vector2(...)insideUpdate()orDraw()— mutate fields or use static helpers. - Cache
Matrix.Invert(camera)as a field, not recomputed per frame. Rectangleintersection gives you an overlap region — use it to resolve push-out collision response.- 2D rotations use
floatin radians;Quaternionis only for 3D.

