Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/Vehicle/WorldCarService.cs
2025-06-24 23:49:13 +08:00

533 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using BITKit;
using BITKit.Entities;
using BITKit.Physics;
using BITKit.WorldNode;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Net.Project.B.UX;
using Net.Project.B.World;
using Net.Project.B.WorldNode;
using Project.B.CharacterController;
using Project.B.Player;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
namespace Net.Project.B.Vehicle
{
public class WorldCarService:IDisposable
{
private class RuntimeCarInfo
{
public float CurrentSteerAngle = 0f;
public float InnerAngle;
public float OuterAngle;
public float WheelBase;
public float TrackWidth;
public Vector3 AngularVelocity;
}
private readonly ILogger<WorldCarService> _logger;
private readonly IFixedTicker _ticker;
private readonly IUXHud _uxHud;
private readonly IEntitiesService _entitiesService;
private readonly ConcurrentDictionary<int, UnityVehicleNode> _vehicles = new();
private readonly ConcurrentDictionary<int, RuntimeCarInfo> _runtimeCarInfos=new();
private readonly IWorldSeatService _seatService;
private readonly ICarKeyMap<InputAction> _carKeyMap;
private readonly InputActionGroup _inputActionGroup = new();
private readonly HashSet<int> _isControlVehicles = new();
private readonly HashSet<Rigidbody> _waitLoaded = new();
private float _currentVertical;
private float _currentHorizontal;
private float _currentHandBrake;
public WorldCarService(IEntitiesService entitiesService, IFixedTicker ticker, ICarKeyMap<InputAction> carKeyMap, IWorldSeatService seatService, ILogger<WorldCarService> logger, IUXHud uxHud)
{
_entitiesService = entitiesService;
_ticker = ticker;
_carKeyMap = carKeyMap;
_seatService = seatService;
_logger = logger;
_uxHud = uxHud;
_entitiesService.OnAdd += OnAdd;
_entitiesService.OnRemove += OnRemove;
_ticker.Add(OnTick);
_inputActionGroup.RegisterCallback(_carKeyMap.VerticalKey, OnVertical);
_inputActionGroup.RegisterCallback(_carKeyMap.HorizontalKey, OnHorizontal);
_inputActionGroup.RegisterCallback(_carKeyMap.HandBrakeKey, OnHandBrake);
_inputActionGroup.allowInput.AddElement(this);
_seatService.OnSeatOccupied += OnSeatOccupied;
}
private void OnSeatOccupied(UnitySeatNode arg1, IEntity arg2, IEntity arg3)
{
foreach (var (vehicleId, vehicleNode) in _vehicles)
{
if (!vehicleNode.seatObject) continue;
var vehicleSeatId = vehicleNode.seatObject.GetInstanceID();
if (vehicleSeatId != arg1.Id) continue;
if (arg3 is not null)
{
if (_isControlVehicles.Add(vehicleId)) ;
{
if (vehicleNode.rigidbody.velocity.sqrMagnitude < 1)
{
foreach (var wheelCollider in vehicleNode.wheels)
{
wheelCollider.motorTorque = 0;
wheelCollider.brakeTorque = vehicleNode.handbrakeForce;
}
}
}
}
else
{
if (_isControlVehicles.Remove(vehicleId))
{
foreach (var wheelCollider in vehicleNode.wheels)
{
wheelCollider.motorTorque = 0;
wheelCollider.brakeTorque = 0;
}
}
}
}
}
private void OnHandBrake(InputAction.CallbackContext obj)
{
_currentHandBrake = obj.ReadValue<float>();
}
private void OnHorizontal(InputAction.CallbackContext obj)
{
_currentHorizontal = obj.ReadValue<float>();
}
private void OnVertical(InputAction.CallbackContext obj)
{
_currentVertical = obj.ReadValue<float>();
}
private void OnTick(float obj)
{
foreach (var rigidbody in _waitLoaded)
{
if (!rigidbody)
{
_waitLoaded.Remove(rigidbody);
break;
}
if (Physics.Raycast(rigidbody.worldCenterOfMass+Vector3.up, Vector3.down, out var hit,64, LayerMask.GetMask("Default")))
{
Debug.DrawRay(rigidbody.worldCenterOfMass,Vector3.down,Color.yellow,obj);
rigidbody.isKinematic = false;
_waitLoaded.Remove(rigidbody);
break;
}
}
foreach (var (id,vehicleNode) in _vehicles)
{
var runtimeInfo = _runtimeCarInfos.GetOrCreate(id);
if (_isControlVehicles.Contains(id))
{
RotateSteeringWheel();
Steering();
Acceleration();
HandBraking();
foreach (var wheelCollider in vehicleNode.wheels)
{
var text = $"" +
$"Drive Wheel: {wheelCollider.name}" +
$"\nTorque: {wheelCollider.motorTorque:F2}" +
$"\nBrake: {wheelCollider.brakeTorque:F2}" +
$"\nGround:{wheelCollider.isGrounded}" +
$"\nRPM:{wheelCollider.rpm}";
wheelCollider.GetWorldPose(out var pos,out _);
DrawXXL.DrawText.Write(text,pos);
}
}
//Slowdown();
/*
foreach (var wheelCollider in vehicleNode.wheels)
{
if (wheelCollider.isGrounded)
{
vehicleNode.rigidbody.AddForce(Vector3.down * vehicleNode.rigidbody.velocity.GetLength());
break;
}
}
*/
for (var index = 0; index < vehicleNode.wheelMeshes.Length; index++)
{
UpdateWheel(vehicleNode.wheels[index], vehicleNode.wheelMeshes[index]);
}
continue;
void Steering()
{
if(_isControlVehicles.Contains(id) is false)return;
runtimeInfo.CurrentSteerAngle = vehicleNode.maxSteerAngle * _currentHorizontal;
switch (vehicleNode.steeringWheelColliders.Length)
{
case 0:
{
var newRot = Quaternion.Euler(Vector3.up * ( runtimeInfo.CurrentSteerAngle * Time.fixedDeltaTime));
newRot = vehicleNode.rigidbody.rotation * newRot;
vehicleNode.rigidbody.MoveRotation(newRot);
}
break;
case 2 when vehicleNode.allowAckermannSteering:
{
// 获取当前车轮的转向角度(用于计算)
var steerAngle = vehicleNode.maxSteerAngle*_currentHorizontal;
var normalizedSteerAngle =
Mathf.Clamp(steerAngle, -vehicleNode.maxSteerAngle, vehicleNode.maxSteerAngle);
// 计算内外侧车轮的转向角度(阿克曼转向公式)
runtimeInfo.InnerAngle = Mathf.Atan(Mathf.Tan(normalizedSteerAngle * Mathf.Deg2Rad) *
(runtimeInfo.WheelBase /
(runtimeInfo.WheelBase + runtimeInfo.TrackWidth))) *
Mathf.Rad2Deg;
runtimeInfo.OuterAngle = Mathf.Atan(Mathf.Tan(normalizedSteerAngle * Mathf.Deg2Rad) *
(runtimeInfo.TrackWidth /
(runtimeInfo.WheelBase + runtimeInfo.TrackWidth))) *
Mathf.Rad2Deg;
// 设置前轮的转向角度
switch (runtimeInfo.CurrentSteerAngle)
{
case > 0:
vehicleNode.steeringWheelColliders[0].steerAngle = runtimeInfo.OuterAngle;
vehicleNode.steeringWheelColliders[1].steerAngle = runtimeInfo.InnerAngle;
break;
case < 0:
vehicleNode.steeringWheelColliders[0].steerAngle = runtimeInfo.InnerAngle;
vehicleNode.steeringWheelColliders[1].steerAngle = runtimeInfo.OuterAngle;
break;
case 0:
vehicleNode.steeringWheelColliders[0].steerAngle = 0;
vehicleNode.steeringWheelColliders[1].steerAngle = 0;
break;
}
break;
}
default:
{
runtimeInfo.InnerAngle = runtimeInfo.OuterAngle = runtimeInfo.CurrentSteerAngle;
foreach (var steeringWheelCollider in vehicleNode.steeringWheelColliders)
{
steeringWheelCollider.steerAngle = runtimeInfo.CurrentSteerAngle;
}
break;
}
}
}
void Acceleration()
{
var selfVelocityZ = vehicleNode.rigidbody.transform.InverseTransformDirection(vehicleNode.rigidbody.velocity).z;
float motorTorque = 0;
float brakeTorque = 0;
// 驱动逻辑
if (_currentVertical != 0)
{
// 判断是否方向相反,如果相反就刹车
if ((_currentVertical > 0 && selfVelocityZ < -vehicleNode.stopThreshold) ||
(_currentVertical < 0 && selfVelocityZ > vehicleNode.stopThreshold))
{
brakeTorque = vehicleNode.brakePower;
}
else
{
// 正常加速
motorTorque = _currentVertical * vehicleNode.horsePower;
}
}
// 应用驱动轮
foreach (var wheelCollider in vehicleNode.driveWheelColliders)
{
wheelCollider.motorTorque = motorTorque;
wheelCollider.brakeTorque = brakeTorque;
}
}
void HandBraking()
{
var handBrakeTorque = (_currentHandBrake > 0) ? _currentHandBrake * vehicleNode.handbrakeForce : 0;
foreach (var wheelCollider in vehicleNode.handBrakeWheelColliders)
{
wheelCollider.brakeTorque = handBrakeTorque;
}
}
void RotateSteeringWheel()
{
if(!vehicleNode.steeringWheel)return;
var currentXAngle =
vehicleNode.steeringWheel.transform.localEulerAngles.x; // Maximum steer angle in degrees
var normalizedSteerAngle =
Mathf.Clamp(vehicleNode.steeringWheelColliders.First().steerAngle, -vehicleNode.maxSteerAngle,
vehicleNode.maxSteerAngle);
var rotation = Mathf.Lerp(vehicleNode.steeringWheelAngle,-vehicleNode.steeringWheelAngle,
(normalizedSteerAngle + vehicleNode.maxSteerAngle) / (2 * vehicleNode.maxSteerAngle));
// Set the local rotation of the steering wheel
vehicleNode.steeringWheel.localRotation = Quaternion.Lerp(vehicleNode.steeringWheel.localRotation, Quaternion.Euler(currentXAngle, 0, rotation),8*obj);
}
void UpdateWheel(WheelCollider col, Transform mesh)
{
col.GetWorldPose(out Vector3 position, out Quaternion rotation);
mesh.SetPositionAndRotation(position, rotation);
}
void Slowdown()
{
if (!vehicleNode.rigidbody || vehicleNode.rigidbody.isKinematic)return;
{
if (_currentVertical == 0 && _currentHandBrake == 0)
{
vehicleNode .rigidbody.velocity =
Vector3.Lerp( vehicleNode .rigidbody.velocity, Vector3.zero, Time.deltaTime * 0.5f);
}
}
}
}
}
private void OnRemove(IEntity obj)
{
_vehicles.TryRemove(obj.Id, out _);
}
private void OnAdd(IEntity obj)
{
if (obj.ServiceProvider.GetService<UnityVehicleNode>() is not { } vehicleNode) return;
_vehicles.TryAdd(obj.Id, vehicleNode);
vehicleNode.rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
vehicleNode.rigidbody.automaticCenterOfMass = false;
vehicleNode.rigidbody.centerOfMass = default;
obj.ServiceProvider.QueryComponents(out GameObject gameObject, out Transform transform);
gameObject.layer= LayerMask.NameToLayer("Vehicle");
gameObject.isStatic = false;
var colliderReady = false;
foreach (var boxCollider in gameObject.GetComponentsInChildren<BoxCollider>())
{
if (boxCollider.attachedRigidbody == vehicleNode.rigidbody && boxCollider.isTrigger is false && boxCollider.gameObject.layer==gameObject.layer)
{
colliderReady = true;
break;
}
}
try
{
if (colliderReady is false)
{
var meshRenderers = gameObject
.GetComponentsInChildren<MeshRenderer>()
.Where(x=>x.gameObject.name.Contains("Wheel",StringComparison.CurrentCultureIgnoreCase) is false)
.ToArray()
;
if (meshRenderers.Length == 0)
{
Debug.LogWarning($"没有 MeshRenderer 可用于生成 BoxCollider{gameObject.name}"); throw new OperationCanceledException();
}
Bounds bounds = meshRenderers[0].bounds;
for (int i = 1; i < meshRenderers.Length; i++)
{
bounds.Encapsulate(meshRenderers[i].bounds);
}
var box = gameObject.AddComponent<BoxCollider>();
// 将 world bounds 转换为本地坐标系
var centerLocal = gameObject.transform.InverseTransformPoint(bounds.center);
var sizeLocal = gameObject.transform.InverseTransformVector(bounds.size);
box.center = centerLocal;
box.size = sizeLocal;
}
}
catch (OperationCanceledException)
{
}
var colliders = obj.ServiceProvider.GetRequiredService<GameObject>().GetComponentsInChildren<Collider>(true);
foreach (var x in colliders)
{
foreach (var y in colliders)
{
Physics.IgnoreCollision(x, y, true);
}
if (x.attachedRigidbody == vehicleNode.rigidbody)
x.gameObject.layer = gameObject.layer;
}
if (!vehicleNode.seatObject)
{
var nodes = gameObject.GetComponentsInChildren<UnityNode>().Select(x=>x.WorldNode);
if (nodes.QueryComponent(out UnitySeatNode seatNode))
{
seatNode.Rigidbody = vehicleNode.rigidbody;
vehicleNode.seatObject = seatNode.SeatObject.gameObject;
}
else
{
_logger.LogWarning(
$"VehicleNode {gameObject.name} 没有找到座位节点,请添加 UnitySeatNode 或者 UnitySeatNode 的子节点");
}
}
if (vehicleNode.seatObject)
{
vehicleNode.seatObject.layer = LayerMask.NameToLayer("Default");
}
if (vehicleNode.steeringWheelColliders.Length is 2)
{
var runtimeInfo = _runtimeCarInfos.GetOrCreate(obj.Id);
runtimeInfo.WheelBase = Mathf.Abs(
vehicleNode.rigidbody.transform
.InverseTransformPoint(vehicleNode.steeringWheelColliders[0].transform.position).z -
vehicleNode.rigidbody.transform
.InverseTransformPoint(vehicleNode.handBrakeWheelColliders[0].transform.position).z);
runtimeInfo.TrackWidth =
Mathf.Abs(
vehicleNode.rigidbody.transform
.InverseTransformPoint(vehicleNode.steeringWheelColliders[0].transform.position).x -
vehicleNode.rigidbody.transform
.InverseTransformPoint(vehicleNode.steeringWheelColliders[1].transform.position).x);
}
if (vehicleNode.rigidbody)
{
vehicleNode.rigidbody.isKinematic = true;
_waitLoaded.Add(vehicleNode.rigidbody);
//var physicsController = vehicleNode.rigidbody.gameObject.AddComponent<UnityCollisionController>();
//physicsController.OnUnityCollisionEnter += OnUnityCollisionEnter;
void OnUnityCollisionEnter(Collision collision)
{
if (_entitiesService.TryGetEntity(collision.transform.gameObject.GetInstanceID(), out var hitEntity))
{
Debug.Log(hitEntity.Id);
}
}
var obstable =vehicleNode.rigidbody.gameObject.GetOrAddComponent<NavMeshObstacle>();
var maxVolume = 0f;
foreach (var collider in colliders.OfType<BoxCollider>())
{
if(ReferenceEquals(collider.attachedRigidbody,vehicleNode.rigidbody) is false)continue;
var volume = collider.bounds.size.x * collider.bounds.size.y * collider.bounds.size.z;
if (volume > maxVolume)
{
maxVolume = volume;
obstable.center = collider.center;
obstable.size = collider.size;
}
}
foreach (var wheelCollider in vehicleNode.wheels)
{
wheelCollider.brakeTorque = vehicleNode.handbrakeForce;
}
}
}
public void Dispose()
{
_entitiesService.OnAdd -= OnAdd;
_ticker.Remove(OnTick);
_seatService.OnSeatOccupied -= OnSeatOccupied;
_inputActionGroup.allowInput.RemoveElement(this);
_inputActionGroup.UnRegisterCallback(_carKeyMap.VerticalKey, OnVertical);
_inputActionGroup.UnRegisterCallback(_carKeyMap.HorizontalKey, OnHorizontal);
_inputActionGroup.UnRegisterCallback(_carKeyMap.HandBrakeKey, OnHandBrake);
}
}
}