Shifting Origin
Support for large worlds by repositioning the world origin to prevent floating-point precision issues.
Problem: Floating-Point Precision
Unity uses 32-bit floating-point numbers for positions. At distances >1000m from origin (0,0,0), precision degrades causing:
- Object jitter
- Physics instability
- Collision detection errors
- Visual artifacts
Solution: Shift the world origin to keep player near (0,0,0).
How Shifting Origin Works
Instead of moving the player far from origin, move the world around the player:
Normal: Shifting Origin:
Player at 5000m Player at 0m
World at 0m World at -5000m
→ Low precision → High precision
The player stays near origin, everything else moves.
ShiftingOrigin Component
Setup
- Create empty GameObject "ShiftingOrigin" at (0,0,0)
- Add
ShiftingOrigincomponent - Configure settings
Inspector Fields
- Shift Threshold - Distance from origin before shift occurs (default: 1000m)
- Player Transform - Transform to keep centered (usually player/vehicle)
- Shift On Startup - Shift immediately when scene loads
- Debug Visualize - Show debug info in Scene view
Basic Usage
using NWH.Common.Utility;
public class GameSetup : MonoBehaviour
{
public Transform player;
void Start()
{
// Find or create ShiftingOrigin
ShiftingOrigin shifter = FindObjectOfType<ShiftingOrigin>();
if (shifter == null)
{
GameObject go = new GameObject("ShiftingOrigin");
shifter = go.AddComponent<ShiftingOrigin>();
}
// Configure
shifter.playerTransform = player;
shifter.shiftThreshold = 1000f;
}
}
What Gets Shifted
ShiftingOrigin moves everything in the scene except:
- The player/vehicle (stays at origin)
- UI elements
- Cameras (stay with player)
Shifted Objects:
- Terrain
- Buildings
- Vehicles (other than player)
- Particles
- Lights
- All scene GameObjects
Integration with NWH Assets
Vehicle Physics 2
Automatically supported:
// No special setup needed
// ShiftingOrigin works with all vehicles
Dynamic Water Physics 2
Water objects shift correctly:
// Water surface shifts with origin
// Buoyancy calculations remain accurate
Aerodynamics
Aircraft support large worlds:
// Flying long distances works correctly
// Wind system shifts with origin
Advanced Configuration
Custom Shift Handlers
Register custom behavior when shift occurs:
using NWH.Common.Utility;
public class CustomShiftHandler : MonoBehaviour
{
void Start()
{
ShiftingOrigin shifter = FindObjectOfType<ShiftingOrigin>();
shifter.onShift += OnOriginShift;
}
void OnOriginShift(Vector3 shiftAmount)
{
// Custom logic when origin shifts
Debug.Log($"Origin shifted by {shiftAmount}");
// Update custom systems
UpdateCustomPositions(shiftAmount);
}
void UpdateCustomPositions(Vector3 shift)
{
// Adjust positions of custom objects
myCustomObject.position -= shift;
}
}
Excluding Objects
Prevent specific objects from shifting:
public class NonShiftingObject : MonoBehaviour
{
void Start()
{
ShiftingOrigin shifter = FindObjectOfType<ShiftingOrigin>();
shifter.onShift += OnShift;
}
void OnShift(Vector3 shiftAmount)
{
// Counteract the shift for this object
transform.position += shiftAmount;
}
}
Large World Design
Recommended Setup
public class LargeWorldManager : MonoBehaviour
{
public Transform player;
public float worldSize = 10000f; // 10km x 10km
void Start()
{
SetupShiftingOrigin();
SetupStreamingSystem();
}
void SetupShiftingOrigin()
{
ShiftingOrigin shifter = gameObject.AddComponent<ShiftingOrigin>();
shifter.playerTransform = player;
shifter.shiftThreshold = 500f; // Shift every 500m
shifter.shiftOnStartup = true;
}
void SetupStreamingSystem()
{
// Implement object streaming
// Load/unload objects based on distance
}
}
Streaming Integration
Combine with object streaming for massive worlds:
public class ObjectStreamer : MonoBehaviour
{
public float streamDistance = 1000f;
public Transform player;
private ShiftingOrigin shifter;
void Start()
{
shifter = FindObjectOfType<ShiftingOrigin>();
shifter.onShift += OnOriginShift;
}
void Update()
{
StreamObjects();
}
void StreamObjects()
{
// Load objects near player
// Unload objects far from player
}
void OnOriginShift(Vector3 shiftAmount)
{
// Update streaming system after shift
RecalculateStreamingZones();
}
}
Considerations
Physics
- Physics remains stable at any distance
- Collisions work correctly after shift
- Rigidbodies maintain momentum
Particles
- Active particles shift correctly
- Long-lived particles may need special handling
- Particle systems should be world-space, not local
Networking
For multiplayer games:
// All clients must shift together
public class NetworkedShiftingOrigin : MonoBehaviour
{
void Start()
{
ShiftingOrigin shifter = GetComponent<ShiftingOrigin>();
if (IsServer())
{
shifter.onShift += BroadcastShift;
}
else
{
shifter.enabled = false; // Only server initiates shifts
}
}
void BroadcastShift(Vector3 shiftAmount)
{
// Send shift command to all clients
SendShiftCommand(shiftAmount);
}
}
Pathfinding
Nav meshes need updating after shift:
void OnOriginShift(Vector3 shiftAmount)
{
// Update AI waypoints
foreach (var waypoint in aiWaypoints)
{
waypoint.position -= shiftAmount;
}
// Recalculate nav mesh if needed
NavMeshSurface.UpdateNavMesh();
}
Performance
Shift Cost:
- Iterates through all scene GameObjects
- Moves transforms
- Typically <1ms for scenes with <1000 objects
Optimization:
// Shift less frequently for better performance
shifter.shiftThreshold = 2000f; // Shift every 2km instead of 1km
// Or disable when player is stationary
if (playerVelocity.magnitude < 0.1f)
{
shifter.enabled = false;
}
else
{
shifter.enabled = true;
}
Example: Open World Vehicle Game
using UnityEngine;
using NWH.Common.Utility;
public class OpenWorldSetup : MonoBehaviour
{
public Transform playerVehicle;
public float mapSize = 20000f; // 20km x 20km
void Start()
{
SetupShiftingOrigin();
SetupTerrainStreaming();
SetupObjectCulling();
}
void SetupShiftingOrigin()
{
GameObject shifterGO = new GameObject("ShiftingOrigin");
ShiftingOrigin shifter = shifterGO.AddComponent<ShiftingOrigin>();
shifter.playerTransform = playerVehicle;
shifter.shiftThreshold = 1000f;
shifter.shiftOnStartup = true;
shifter.debugVisualize = true;
shifter.onShift += OnWorldShift;
}
void OnWorldShift(Vector3 shiftAmount)
{
Debug.Log($"World origin shifted by {shiftAmount.magnitude:F1}m");
// Update any custom systems
UpdateMinimapPosition(shiftAmount);
UpdateQuestMarkers(shiftAmount);
}
void SetupTerrainStreaming()
{
// Implement terrain streaming
}
void SetupObjectCulling()
{
// Implement object culling/LOD
}
void UpdateMinimapPosition(Vector3 shift)
{
// Adjust minimap to show correct position
}
void UpdateQuestMarkers(Vector3 shift)
{
// Shift quest marker positions
}
}
Best Practices
- Always Use for Large Worlds - Any map >1km should use shifting origin
- Shift Threshold - 500-1000m is optimal balance
- Test at Distance - Test gameplay at >5km from origin
- UI Separation - Keep UI in separate camera/canvas
- World Space Particles - Use world space for particle systems
- Save/Load - Save player's world position, not local position
- Networking - Synchronize shifts across all clients
Troubleshooting
Objects jittering:
- Increase shift threshold
- Check Fixed Timestep in Project Settings
- Verify objects are being shifted correctly
Physics errors after shift:
- Check Rigidbodies are awake after shift
- Verify collision layers are correct
- Test with simpler collision shapes
Particles disappearing:
- Use world space particle systems
- Handle particle position updates in onShift
Networking desync:
- Ensure all clients shift simultaneously
- Synchronize shift threshold across network
API Reference
Internal API:
- ShiftingOrigin - Complete API reference available through the navigation menu
Related Unity Documentation: