Инишиал механика наклона при залазании на стены
Нуждается в доработке (как минимум чтобы вращалось в правильную сторону), но вроде-как выглядит прикольно
This commit is contained in:
1 parent
0ac5ba1e1c
commit
b214bb0f63
7 files changed
+158
-26
No files matched your search
@@ -29,6 +29,12 @@ namespace Code.Configs
|
|||||||
minWallClimbSpeed = 9f, // Минимальная горизонтальная скорость для начала залазания на стену
|
minWallClimbSpeed = 9f, // Минимальная горизонтальная скорость для начала залазания на стену
|
||||||
wallDetectionAngleThreshold = 80f; // Максимальный угол (в градусах) от вертикали, при котором поверхность считается "стеной"
|
wallDetectionAngleThreshold = 80f; // Максимальный угол (в градусах) от вертикали, при котором поверхность считается "стеной"
|
||||||
|
|
||||||
|
// Наклон при залазании на стены
|
||||||
|
public float
|
||||||
|
maxWallLeanAngle = 45f, // Максимальный угол наклона от стены (в градусах)
|
||||||
|
wallLeanSharpness = 50f, // Скорость наклона/выпрямления (чем выше — тем резче)
|
||||||
|
wallLeanHeightScale = 0.1f; // Множитель: на сколько метров подъёма = 1 единица "наклона"
|
||||||
|
|
||||||
// Гравитация
|
// Гравитация
|
||||||
public Vector3 gravity = new(0f, -30f, 0f); // Вектор, отвечающий за применение гравитации
|
public Vector3 gravity = new(0f, -30f, 0f); // Вектор, отвечающий за применение гравитации
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -14,7 +14,7 @@ namespace Code.Gameplay.Character.Controller.Abilities
|
|||||||
// Минимальная горизонтальная скорость для начала залазания на стену
|
// Минимальная горизонтальная скорость для начала залазания на стену
|
||||||
private readonly float _minWallClimbSpeed;
|
private readonly float _minWallClimbSpeed;
|
||||||
// Пороговый косинус стены
|
// Пороговый косинус стены
|
||||||
private readonly float _wallNormalThreshold;
|
public float WallNormalThreshold { get; }
|
||||||
|
|
||||||
// Конструктор механики залазания на стену
|
// Конструктор механики залазания на стену
|
||||||
public WallClimbMechanic(float wallClimbSpeedFactor, float minWallClimbSpeed, float wallDetectionAngleThreshold)
|
public WallClimbMechanic(float wallClimbSpeedFactor, float minWallClimbSpeed, float wallDetectionAngleThreshold)
|
||||||
@@ -22,7 +22,7 @@ namespace Code.Gameplay.Character.Controller.Abilities
|
|||||||
_wallClimbSpeedFactor = wallClimbSpeedFactor;
|
_wallClimbSpeedFactor = wallClimbSpeedFactor;
|
||||||
_minWallClimbSpeed = minWallClimbSpeed;
|
_minWallClimbSpeed = minWallClimbSpeed;
|
||||||
// Преобразуем угол в косинус порога: если |dot| > cos(threshold), то это НЕ стена
|
// Преобразуем угол в косинус порога: если |dot| > cos(threshold), то это НЕ стена
|
||||||
_wallNormalThreshold = Mathf.Cos(Mathf.Deg2Rad * wallDetectionAngleThreshold);
|
WallNormalThreshold = Mathf.Cos(Mathf.Deg2Rad * wallDetectionAngleThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка столкновения при движении (вызывается при ударе о препятствие)
|
// Обработка столкновения при движении (вызывается при ударе о препятствие)
|
||||||
@@ -30,7 +30,7 @@ namespace Code.Gameplay.Character.Controller.Abilities
|
|||||||
{
|
{
|
||||||
// Проверяем, что это вертикальная или почти вертикальная поверхность (стена)
|
// Проверяем, что это вертикальная или почти вертикальная поверхность (стена)
|
||||||
var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp);
|
var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp);
|
||||||
if (Mathf.Abs(dotWithUp) > _wallNormalThreshold) return; // Не стена — это пол или потолок
|
if (Mathf.Abs(dotWithUp) > WallNormalThreshold) return; // Не стена — это пол или потолок
|
||||||
|
|
||||||
// Получаем текущую скорость персонажа
|
// Получаем текущую скорость персонажа
|
||||||
var currentVelocity = motor.Velocity;
|
var currentVelocity = motor.Velocity;
|
||||||
|
|||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using KinematicCharacterController;
|
||||||
|
|
||||||
|
namespace Code.Gameplay.Character.Controller.Abilities
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Механика наклона персонажа при контакте со стеной
|
||||||
|
* Управляет визуальным наклоном модели в зависимости от высоты подъёма по стене
|
||||||
|
*/
|
||||||
|
public class WallLeanMechanic
|
||||||
|
{
|
||||||
|
private readonly float _maxLeanAngle;
|
||||||
|
private readonly float _leanSharpness;
|
||||||
|
private readonly float _heightScale;
|
||||||
|
|
||||||
|
private Transform _characterTransform;
|
||||||
|
private Vector3 _lastWallNormal;
|
||||||
|
private float _lastWallContactHeight;
|
||||||
|
private float _leanFactor;
|
||||||
|
|
||||||
|
public WallLeanMechanic(float maxLeanAngle, float leanSharpness, float heightScale)
|
||||||
|
{
|
||||||
|
_maxLeanAngle = maxLeanAngle;
|
||||||
|
_leanSharpness = leanSharpness;
|
||||||
|
_heightScale = heightScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Назначаем трансформ визуальной модели один раз
|
||||||
|
public void SetCharacterTransform(Transform transform)
|
||||||
|
{
|
||||||
|
_characterTransform = transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вызывается из OnMovementHit
|
||||||
|
public void OnWallContact(Vector3 wallNormal, float contactHeight)
|
||||||
|
{
|
||||||
|
_lastWallNormal = wallNormal;
|
||||||
|
_lastWallContactHeight = contactHeight; }
|
||||||
|
|
||||||
|
// Обновление каждый кадр
|
||||||
|
public void UpdateLean(KinematicCharacterMotor motor, float deltaTime)
|
||||||
|
{
|
||||||
|
if (!_characterTransform) return;
|
||||||
|
|
||||||
|
var isGrounded = motor.GroundingStatus.IsStableOnGround;
|
||||||
|
|
||||||
|
if (!isGrounded)
|
||||||
|
{
|
||||||
|
var heightDelta = motor.TransientPosition.y - _lastWallContactHeight;
|
||||||
|
var targetLeanFactor = Mathf.Clamp01(heightDelta * _heightScale);
|
||||||
|
_leanFactor = Mathf.Lerp(_leanFactor, targetLeanFactor, 1f - Mathf.Exp(-_leanSharpness * deltaTime));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_leanFactor = Mathf.Lerp(_leanFactor, 0f, 1f - Mathf.Exp(-_leanSharpness * deltaTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyLeanRotation(motor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyLeanRotation(KinematicCharacterMotor motor)
|
||||||
|
{
|
||||||
|
if (_leanFactor <= 0.01f)
|
||||||
|
{
|
||||||
|
_characterTransform.localRotation = Quaternion.identity;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var leanDirection = -_lastWallNormal;
|
||||||
|
leanDirection = Vector3.ProjectOnPlane(leanDirection, motor.CharacterUp).normalized;
|
||||||
|
|
||||||
|
if (leanDirection.sqrMagnitude < 0.01f) return;
|
||||||
|
|
||||||
|
var leanAngle = _leanFactor * _maxLeanAngle;
|
||||||
|
var leanRotation = Quaternion.AngleAxis(leanAngle, Vector3.Cross(motor.CharacterUp, leanDirection));
|
||||||
|
_characterTransform.localRotation = leanRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dfaefcb035124edb84b7874c5938defc
|
||||||
|
timeCreated: 1761738483
|
||||||
+26
-7
@@ -21,13 +21,15 @@ namespace Code.Gameplay.Character.Controller
|
|||||||
{
|
{
|
||||||
[Inject] private CharacterConfig _characterConfig; // Конфиг персонажа через DI
|
[Inject] private CharacterConfig _characterConfig; // Конфиг персонажа через DI
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField] private KinematicCharacterMotor motor; // Движок физического перемещения персонажа
|
||||||
private KinematicCharacterMotor motor; // Движок физического перемещения персонажа
|
|
||||||
|
[SerializeField] private Transform characterTransform; // Трансформ персонажа (с точкой отслеживания камеры)
|
||||||
|
|
||||||
// Подсистемы
|
// Подсистемы
|
||||||
private CharacterInputHandler _inputHandler;
|
private CharacterInputHandler _inputHandler;
|
||||||
private SlideMechanic _slideMechanic;
|
private SlideMechanic _slideMechanic;
|
||||||
private WallClimbMechanic _wallClimbMechanic;
|
private WallClimbMechanic _wallClimbMechanic;
|
||||||
|
private WallLeanMechanic _wallLeanMechanic;
|
||||||
private GroundMovementSystem _groundMovement;
|
private GroundMovementSystem _groundMovement;
|
||||||
private AirMovementSystem _airMovement;
|
private AirMovementSystem _airMovement;
|
||||||
|
|
||||||
@@ -48,6 +50,11 @@ namespace Code.Gameplay.Character.Controller
|
|||||||
_characterConfig.minWallClimbSpeed,
|
_characterConfig.minWallClimbSpeed,
|
||||||
_characterConfig.wallDetectionAngleThreshold
|
_characterConfig.wallDetectionAngleThreshold
|
||||||
);
|
);
|
||||||
|
_wallLeanMechanic = new WallLeanMechanic(
|
||||||
|
_characterConfig.maxWallLeanAngle,
|
||||||
|
_characterConfig.wallLeanSharpness,
|
||||||
|
_characterConfig.wallLeanHeightScale
|
||||||
|
);
|
||||||
_groundMovement = new GroundMovementSystem(
|
_groundMovement = new GroundMovementSystem(
|
||||||
_characterConfig.maxStableMoveSpeed,
|
_characterConfig.maxStableMoveSpeed,
|
||||||
_characterConfig.sprintMoveSpeed,
|
_characterConfig.sprintMoveSpeed,
|
||||||
@@ -64,7 +71,9 @@ namespace Code.Gameplay.Character.Controller
|
|||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
motor.CharacterController = this; // Регистрация этого контроллера в движке
|
motor.CharacterController = this; // Регистрация этого контроллера в движке
|
||||||
_inputHandler.FirstPersonCameraRotation = motor.Transform.rotation; // Начальное вращение совпадает с персонажем
|
_inputHandler.FirstPersonCameraRotation =
|
||||||
|
motor.Transform.rotation; // Начальное вращение совпадает с персонажемс
|
||||||
|
_wallLeanMechanic.SetCharacterTransform(characterTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка и преобразование входных данных от персонажа
|
// Обработка и преобразование входных данных от персонажа
|
||||||
@@ -150,7 +159,8 @@ namespace Code.Gameplay.Character.Controller
|
|||||||
// Проверяем запрос слайда и запускаем его если можно
|
// Проверяем запрос слайда и запускаем его если можно
|
||||||
if (!_slideMechanic.IsSliding)
|
if (!_slideMechanic.IsSliding)
|
||||||
{
|
{
|
||||||
_slideMechanic.TryStartSlide(motor, _currentCameraMode, _inputHandler.FirstPersonCameraRotation, _inputHandler.LookInputVector);
|
_slideMechanic.TryStartSlide(motor, _currentCameraMode, _inputHandler.FirstPersonCameraRotation,
|
||||||
|
_inputHandler.LookInputVector);
|
||||||
}
|
}
|
||||||
// Если активен слайд - обрабатываем отдельно
|
// Если активен слайд - обрабатываем отдельно
|
||||||
else
|
else
|
||||||
@@ -188,9 +198,10 @@ namespace Code.Gameplay.Character.Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Метод вызывается после обновления персонажа (можно использовать для пост-обработки)
|
// Метод вызывается после обновления персонажа
|
||||||
public void AfterCharacterUpdate(float deltaTime)
|
public void AfterCharacterUpdate(float deltaTime)
|
||||||
{
|
{
|
||||||
|
_wallLeanMechanic.UpdateLean(motor, deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка валидности коллайдера для обработки столкновений
|
// Проверка валидности коллайдера для обработки столкновений
|
||||||
@@ -200,7 +211,8 @@ namespace Code.Gameplay.Character.Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Обработка столкновения с землей (вызывается при приземлении)
|
// Обработка столкновения с землей (вызывается при приземлении)
|
||||||
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
|
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
||||||
|
ref HitStabilityReport hitStabilityReport)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,10 +221,17 @@ namespace Code.Gameplay.Character.Controller
|
|||||||
ref HitStabilityReport hitStabilityReport)
|
ref HitStabilityReport hitStabilityReport)
|
||||||
{
|
{
|
||||||
_wallClimbMechanic.OnMovementHit(motor, hitNormal);
|
_wallClimbMechanic.OnMovementHit(motor, hitNormal);
|
||||||
|
|
||||||
|
var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp);
|
||||||
|
if (Mathf.Abs(dotWithUp) <= _wallClimbMechanic.WallNormalThreshold)
|
||||||
|
{
|
||||||
|
_wallLeanMechanic.OnWallContact(hitNormal, motor.TransientPosition.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка отчета о стабильности столкновения
|
// Обработка отчета о стабильности столкновения
|
||||||
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition,
|
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
||||||
|
Vector3 atCharacterPosition,
|
||||||
Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
|
Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,7 @@ MonoBehaviour:
|
|||||||
wallClimbSpeedFactor: 0.8
|
wallClimbSpeedFactor: 0.8
|
||||||
minWallClimbSpeed: 9
|
minWallClimbSpeed: 9
|
||||||
wallDetectionAngleThreshold: 80
|
wallDetectionAngleThreshold: 80
|
||||||
|
maxWallLeanAngle: 90
|
||||||
|
wallLeanSharpness: 100
|
||||||
|
wallLeanHeightScale: 0.1
|
||||||
gravity: {x: 0, y: -30, z: 0}
|
gravity: {x: 0, y: -30, z: 0}
|
||||||
@@ -233,11 +233,11 @@ Transform:
|
|||||||
m_GameObject: {fileID: 453575297}
|
m_GameObject: {fileID: 453575297}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
m_LocalPosition: {x: -0, y: 1.395, z: 0.59}
|
m_LocalPosition: {x: 0, y: 1.395, z: 0.59}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 1696686254}
|
m_Father: {fileID: 723324499}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &481156066
|
--- !u!1 &481156066
|
||||||
GameObject:
|
GameObject:
|
||||||
@@ -381,6 +381,39 @@ Transform:
|
|||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 1533098598}
|
- {fileID: 1533098598}
|
||||||
|
m_Father: {fileID: 723324499}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &723324498
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 723324499}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Character
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &723324499
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 723324498}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 678700802}
|
||||||
|
- {fileID: 453575298}
|
||||||
m_Father: {fileID: 1696686254}
|
m_Father: {fileID: 1696686254}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &1376862591
|
--- !u!1 &1376862591
|
||||||
@@ -709,7 +742,7 @@ GameObject:
|
|||||||
- component: {fileID: 1696686256}
|
- component: {fileID: 1696686256}
|
||||||
- component: {fileID: 1696686258}
|
- component: {fileID: 1696686258}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Character
|
m_Name: CharacterController
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
@@ -728,8 +761,7 @@ Transform:
|
|||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 678700802}
|
- {fileID: 723324499}
|
||||||
- {fileID: 453575298}
|
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!114 &1696686255
|
--- !u!114 &1696686255
|
||||||
@@ -824,17 +856,7 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier: Assembly-CSharp::Code.Gameplay.Player.CharacterController
|
m_EditorClassIdentifier: Assembly-CSharp::Code.Gameplay.Player.CharacterController
|
||||||
motor: {fileID: 1696686256}
|
motor: {fileID: 1696686256}
|
||||||
maxStableMoveSpeed: 8
|
characterTransform: {fileID: 723324499}
|
||||||
sprintMoveSpeed: 12
|
|
||||||
slideSpeed: 20
|
|
||||||
slideDuration: 0.8
|
|
||||||
slideCooldown: 1.5
|
|
||||||
stableMovementSharpness: 15
|
|
||||||
orientationSharpness: 10
|
|
||||||
jumpSpeed: 10
|
|
||||||
wallClimbSpeedFactor: 0.8
|
|
||||||
minWallClimbSpeed: 9
|
|
||||||
gravity: {x: 0, y: -30, z: 0}
|
|
||||||
--- !u!1 &1959874989
|
--- !u!1 &1959874989
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
Reference in new issue
Block a user