VehicleChanger
VehicleChanger is a scene-level manager that handles switching between vehicles. It supports both instant switching (press button to cycle) and character-based enter/exit (GTA-style walking to vehicles).
Overview
The vehicle enter/exit system uses a minimalist architecture:
- VehicleChanger - Scene-level singleton managing vehicle switching
- Vehicle - Base class tracking active vehicles globally
- EnterExitPoints - Tag-based GameObjects marking entry locations
No dedicated Seat or Portal classes are needed. Entry points are simple tagged GameObjects.
Operation Modes
Instant Mode
Switch directly between vehicles without a character:
characterBased = false
- Press button to cycle through vehicles immediately
- No character object needed
- Useful for vehicle showcases, testing, arcade games
Character-Based Mode
GTA-style walking to vehicles and entering:
characterBased = true
- Player walks to vehicle entry points
- Enter/exit at designated EnterExitPoints
- Character object enables/disables on entry/exit
Quick Setup
Instant Mode Setup
- Add VehicleChanger component to a scene GameObject
- Populate the
vehicleslist with your vehicles - Set
characterBased = false - Configure input for NextVehicle/PreviousVehicle
Character-Based Setup
- Add VehicleChanger component to a scene GameObject
- Populate the
vehicleslist with your vehicles - Set
characterBased = true - Assign your player object to
characterObject - For each vehicle:
- Create an empty GameObject child at each door position
- Tag it with "EnterExitPoint" (or your custom tag)
Properties
Core Settings
| Property | Type | Default | Description |
|---|---|---|---|
vehicles |
List<Vehicle> |
- | All managed vehicles in the scene |
activeVehicleIndex |
int | 0 | Currently active vehicle index |
characterBased |
bool | false | Enable character enter/exit mode |
characterObject |
GameObject | null | Player character object |
Entry Settings
| Property | Type | Default | Description |
|---|---|---|---|
enterDistance |
float | 2.0 | Maximum distance to enter (meters) |
enterExitTag |
string | "EnterExitPoint" | Tag for entry point GameObjects |
maxEnterExitVehicleSpeed |
float | 2.0 | Max vehicle speed for entry/exit (m/s) |
Behavior Settings
| Property | Type | Default | Description |
|---|---|---|---|
putOtherVehiclesToSleep |
bool | true | Disable inactive vehicles |
startInVehicle |
bool | false | Begin scene inside active vehicle |
EnterExitPoint Setup
Entry points are simple tagged GameObjects, not dedicated components.
Creating Entry Points
- Create an empty GameObject at each door/entry position
- Parent it to the vehicle's transform hierarchy
- Tag it with
enterExitTag(default: "EnterExitPoint")
Vehicle (VehicleController)
├── Body
├── Wheels
└── EnterExitPoints
├── DriverDoor (tag: EnterExitPoint)
├── PassengerDoor (tag: EnterExitPoint)
└── RearDoor (tag: EnterExitPoint)
Distance Calculation
- Uses horizontal plane distance (ignores Y axis)
- Calculates from character position to each EnterExitPoint
- Nearest point determines which vehicle can be entered
Character Location States
The system tracks character position relative to vehicles:
public enum CharacterLocation
{
OutOfRange, // Too far from any vehicle
Near, // Close enough to enter (< enterDistance)
Inside // Currently in vehicle
}
Access current state:
CharacterLocation location = VehicleChanger.Instance.characterLocation;
API Reference
Singleton Access
VehicleChanger changer = VehicleChanger.Instance;
Vehicle Switching
// Switch to specific vehicle by index
changer.ChangeVehicle(2);
// Switch to specific vehicle by reference
changer.ChangeVehicle(myVehicle);
// Cycle through vehicles
changer.NextVehicle();
changer.PreviousVehicle();
Enter/Exit
// Enter a vehicle (character mode)
changer.EnterVehicle(vehicle);
// Exit current vehicle (character mode)
changer.ExitVehicle(currentVehicle);
Vehicle Registration
// Register a spawned vehicle
changer.RegisterVehicle(spawnedVehicle);
// Deregister a destroyed vehicle
changer.DeregisterVehicle(destroyedVehicle);
Activation Control
// Deactivate all except active vehicle
changer.DeactivateAllExceptActive();
// Deactivate all including active (exit in character mode)
changer.DeactivateAllIncludingActive();
Events
VehicleChanger Events
// Vehicle changed (includes enter/exit)
VehicleChanger.Instance.onVehicleChanged.AddListener(() => {
Debug.Log("Vehicle changed!");
});
// All vehicles deactivated (exit in character mode)
VehicleChanger.Instance.onDeactivateAll.AddListener(() => {
Debug.Log("Exited all vehicles");
});
Global Vehicle Tracking
// Track active vehicle changes globally
Vehicle.onActiveVehicleChanged.AddListener((previous, current) => {
Debug.Log($"Changed from {previous?.name} to {current?.name}");
});
// Access static vehicle lists
List<Vehicle> allActive = Vehicle.ActiveVehicles;
Vehicle currentActive = Vehicle.ActiveVehicle;
Enter/Exit Flow
Character-Based Entry
1. Character approaches EnterExitPoint
2. Distance < enterDistance
└── characterLocation = Near
3. Check: Vehicle.Speed < maxEnterExitVehicleSpeed
4. Input triggers ChangeVehicle
5. Store relative enter position
6. characterObject.SetActive(false)
7. characterLocation = Inside
8. Vehicle.enabled = true
└── Adds to Vehicle.ActiveVehicles
Character-Based Exit
1. characterLocation == Inside
2. Check: Vehicle.Speed < maxEnterExitVehicleSpeed
3. Input triggers exit
4. DeactivateAllIncludingActive()
5. Character positioned at stored relative location
6. Character.forward = Vehicle.right (face outward)
7. characterObject.SetActive(true)
8. characterLocation = OutOfRange
Instant Mode Switching
1. Input triggers NextVehicle()
2. activeVehicleIndex++
3. DeactivateAllExceptActive()
4. onVehicleChanged fires
Character Positioning
On Enter
The relative position is stored for later use:
_relativeEnterPosition = vehicle.transform.InverseTransformPoint(character.position);
character.SetActive(false);
On Exit
Character is placed back at the relative position, facing outward:
character.position = vehicle.transform.TransformPoint(_relativeEnterPosition);
character.forward = vehicle.transform.right; // Face outward from vehicle
character.SetActive(true);
Integration with Input
Scene Input Provider
// Input is read from SceneInputProviderBase
InputProvider.CombinedInput<SceneInputProviderBase>(i => i.ChangeVehicle())
Default Keys
| Action | Input Manager | Input System |
|---|---|---|
| Next Vehicle | V | ChangeVehicle action |
| Enter/Exit | V (near vehicle) | ChangeVehicle action |
Custom Input
// Trigger vehicle change programmatically
if (Input.GetKeyDown(KeyCode.E))
{
VehicleChanger.Instance.NextVehicle();
}
Performance Optimization
putOtherVehiclesToSleep
When enabled (default), inactive vehicles are disabled:
putOtherVehiclesToSleep = true;
Benefits:
- Reduces physics calculations
- Reduces Update/FixedUpdate overhead
- Saves CPU for active vehicle
Vehicles are re-enabled when switched to.
Scene Loading
EnterExitPoints are cached and refreshed on scene load:
SceneManager.sceneLoaded += OnSceneLoaded;
Points are found using:
GameObject.FindGameObjectsWithTag(enterExitTag)
Usage Examples
Basic Scene Setup
// Scene structure:
// - VehicleChanger (singleton)
// - Player (character, if using character mode)
// - Vehicle1 (VehicleController)
// - EnterExitPoint (tagged)
// - Vehicle2 (VehicleController)
// - EnterExitPoint (tagged)
Runtime Vehicle Spawning
public void SpawnVehicle(GameObject prefab, Vector3 position)
{
GameObject instance = Instantiate(prefab, position, Quaternion.identity);
Vehicle vehicle = instance.GetComponent<Vehicle>();
// Register with VehicleChanger
VehicleChanger.Instance.RegisterVehicle(vehicle);
// Optionally switch to it
VehicleChanger.Instance.ChangeVehicle(vehicle);
}
public void DestroyVehicle(Vehicle vehicle)
{
// Deregister first
VehicleChanger.Instance.DeregisterVehicle(vehicle);
Destroy(vehicle.gameObject);
}
Custom Enter Logic
public class CustomVehicleEntry : MonoBehaviour
{
public void TryEnterNearestVehicle()
{
VehicleChanger changer = VehicleChanger.Instance;
if (!changer.characterBased) return;
if (changer.characterLocation != CharacterLocation.Near) return;
// Find nearest vehicle
Vehicle nearest = FindNearestVehicle();
if (nearest != null && nearest.Speed < changer.maxEnterExitVehicleSpeed)
{
changer.EnterVehicle(nearest);
}
}
}
Listening for Vehicle Changes
public class GameManager : MonoBehaviour
{
void Start()
{
// Listen to VehicleChanger events
VehicleChanger.Instance.onVehicleChanged.AddListener(OnVehicleChanged);
// Or listen to global Vehicle events
Vehicle.onActiveVehicleChanged.AddListener(OnActiveVehicleChanged);
}
void OnVehicleChanged()
{
// Update UI, camera, etc.
UpdateHUD();
}
void OnActiveVehicleChanged(Vehicle previous, Vehicle current)
{
// Handle vehicle change with references
if (previous != null)
DisableVehicleUI(previous);
if (current != null)
EnableVehicleUI(current);
}
}
Limitations
- Single occupant only - No passenger seat system
- No seat assignment - All entry points are equivalent
- Character teleports - No animation support built-in
- Single instance - Singleton pattern, one per scene
For more complex requirements (passengers, animations, seat selection), you may need to extend or replace this system.
Vehicle Base Class
The Vehicle class (base for VehicleController) provides static tracking:
Static Properties
// All currently enabled vehicles
public static List<Vehicle> ActiveVehicles;
// The most recently enabled vehicle
public static Vehicle ActiveVehicle;
// Event when active vehicle changes
public static UnityEvent<Vehicle, Vehicle> onActiveVehicleChanged;
Instance Properties
| Property | Description |
|---|---|
isPlayerControllable |
Can be ActiveVehicle (false for trailers) |
Speed |
Forward speed (always positive) |
SpeedSigned |
Forward speed (can be negative in reverse) |