Мне наконец-то удалось решить эту проблему.
В первую очередь я проверял, заземлена ли середина персонажа или нет. Я добился этого, запустив луч прямо в положение объекта.
Затем я хотел, чтобы двое знали, какая сторона не сталкивается с поверхностью. Я выпустил еще четыре луча, по одному в каждой точке кардинала вокруг капсульного коллайдера.
Я извлек каждую точку, которая не была заземлена, и рассчитал разницу с текущей позицией персонажа. Это дает мне координату, которую я добавил к движению объекта, который будет увеличиваться, пока не упадет.
Давайте посмотрим:
Так я создал каждый луч. Мне нужно было два значения (x, z), чтобы изменить точку кардинала, в которую должен быть помещен каждый луч. Затем я просто установил y в землю, вычтя половину высоты капсульного коллайдера.
private Ray CreateEdgeCheckRay(float x, float z) {
Vector3 rayPos = transform.position;
rayPos.y -= characterController.height / 2;
rayPos.x += x;
rayPos.z += z;
return new Ray(rayPos, Vector3.down);
}
Я использовал этот метод только для создания каждого луча одновременно:
private Ray[] CreateSideRays() {
Ray[] sideRays = new Ray[4];
sideRays[0] = CreateEdgeCheckRay(0.5f, 0);
sideRays[1] = CreateEdgeCheckRay(-0.5f, 0);
sideRays[2] = CreateEdgeCheckRay(0, 0.5f);
sideRays[3] = CreateEdgeCheckRay(0, -0.5f);
return sideRays;
}
Я использовал Physics.RaycastNonAlloc, получая результаты в массиве длины 1 для повышения производительности.
private bool RayGrounded(Ray ray) {
return Physics.RaycastNonAlloc(ray, rayResults, groundRayLength) < 1;
}
Наконец, я объединил все вместе. Мне не нужно никаких вертикальных движений, так как ранее я применил гравитацию.
private void CheckGroundRays() {
Ray middleRay = CreateEdgeCheckRay(0, 0);
if (RayGrounded(middleRay)) {
Vector3 inEdgeMovement = Vector3.zero;
foreach(Ray ray in CreateSideRays()) {
if(RayGrounded(ray))
inEdgeMovement += (ray.origin - transform.position);
}
inEdgeMovement.y = 0;
movement += inEdgeMovement;
}
}