본문 바로가기

프로젝트/FPS+레이싱게임

레이싱 게임 트랙포인트(체크포인트) 설치 자동화(코너 자동 판단)

반응형

 

댓글과 공감클릭은 더 좋은글을 위한 큰 힘이 되니 댓글과 공감클릭 부탁드립니다!!

 

 

레이싱 게임 실시간 순위 계산 로직 만들기

댓글과 공감클릭은 더 좋은 글을 위한 큰 힘이 되니 댓글과 공감클릭 부탁드립니다!! 레이싱 게임을 팀 프로젝트로 개발하고 있었는데, 팀원 중 한 분이 차량 간의 실시간 순위를 계산하는 로

reonji.tistory.com

트랙포인트가 뭔지 궁금하신 분은 위 글을 참조하세요.

그냥 코너 자동 판단 로직만 알고 싶으시면 굳이 위 글은 안 읽으셔도 됩니다.

 

실시간 순위 계산을 위해 트랙포인트를 코너마다 일일이 수동으로 설치해야 하는데, 너무 노가다다...

그래서, 이 트랙포인트의 생성과 설치를 자동화하기로 했다.

 

일단, 구현해야 하는 요구사항은 다음과 같다.

1. 코너 자동 판단

2. 그곳에 트랙포인트 생성

트랙포인트 생성은 간단하니, 코너 자동 판단 로직에 대해서만 자세히 다룬다.

 

일단, 이걸 구현하기 위해, 이미 구현된 어떤 기능을 이용했다.

waypoint라는 건데, 트랙의 중앙선을 따라 일정 간격으로 생성돼 있는 작은 구형 투명 게임오브젝트라고 보면 된다.

이 waypoint들은 게임이 시작되자마자 자동으로 생성된다.

더보기

waypoint 구현방법: 우리 팀이 만드는 레이싱 게임의 트랙에는 양 옆 가장자리에 vertex들이 있다.

왼쪽 가장자리 vertex 하나와, 그에 대응하는 오른쪽 vertex 하나의 위치의 평균값을 구한다.

그 평균값 위치는 당연히 도로 중앙선에 위치해 있을 테고, 여기에 waypoint를 하나 생성하면 된다.

이런 방식으로 모든 가장자리 vertex들에 대해서 waypoint들을 생성하면 된다.

 

코너 자동 판단 및 트랙포인트 설치 로직

waypoint들을 이용하여 코너를 자동으로 판단하고, 그곳에 트랙포인트를 설치하는 로직은 다음과 같다.

 

1. 3개의 waypoint를 기준으로 부호 있는 꺾는 각도 구하기
2. 그다음 waypoint로 이동
3. 1~2 과정을 반복하면서, 꺾는 각도의 합 누적
4. 누적합의 절댓값이 일정 값보다 커지면, 그곳에 트랙포인트 생성
5. 누적합 0으로 변경
6. 그다음 waypoint로 이동 후, 1번 과정부터 다시 반복

 

각 과정에 대해 자세히 알아보자.

 

1. 3개의 웨이포인트를 기준으로 부호 있는 꺾는 각도 구하기

(부호 있는)꺾는 각도란?

쉽게 말해서, 차량이 코너링을 할 때, 얼마나 꺾었는지를 나타내는 각도가 꺾는 각도라고 보면 된다.

이때, 왼쪽으로 꺾으면 음수 각도, 오른쪽으로 꺾으면 양수 각도가 되는 것이다.

 

그런데 이걸 3개의 waypoint를 기준으로 구한다는 말이 무슨 말이냐.

아래 그림을 보자.(작은 동그라미들이 waypoint다)

 

작은 동그라미들이 waypoint이다.

위 그림을 보면 무슨 말인지 알 수 있을 것이다.

즉, 어떤 차량이 waypoint들을 따라(=중앙선을 따라) 이동한다고 가정하고,

3개의 waypoint를 기준으로, 두 번째 waypoint에서 세 번째 waypoint로 코너링할 시, 이때의 꺾는 각도를 구하는 것이다.

 

이 꺾는 각도를 구하는 상세한 방법은 아래와 같다.

1. 먼저 3개의 waypoint를 각각 직선으로 잇고, 그 사이의 각도(=일반 각도)를 구한다.(이것도 마찬가지로 왼쪽이면 음수 각도, 오른쪽이면 양수 각도이다)

2. 부호 있는 꺾는 각도를 구한다: 일반 각도가 양수 각도이면 "180 - 일반각도", 음수 각도이면 "-180 - 음수각도"

 

2~6. 나머지 과정들

2. 그다음 waypoint로 이동
3. 1~2 과정을 반복하면서, 꺾는 각도의 합 누적
4. 누적합의 절댓값이 일정 값보다 커지면, 그곳에 트랙포인트 생성
5. 누적합 0으로 변경
6. 그다음 waypoint로 이동 후, 1번 과정부터 다시 반복

 

위 과정에서 이런 의문이 들 수 있다.

"꺾는 각도의 합 누적이 굳이 필요한가? 그냥 꺾는 각도가 일정값 넘어서면, 거길 코너로 판단하고 바로 트랙포인트 생성하면 되지 않나?"

대답은 아니오다.

 

트랙의 코너는 급코너만이 있는 것이 아니다.

아래 그림(동그라미들이 waypoint들)처럼 서서히 꺾어지는 완만한 코너가 있을 수도 있다.

이런 완만한 코너에서는, 각 waypoint의 꺾는 각도를 계산할 때마다 일정값보다 작은 각도로 계산되기 때문에,

만약 누적합을 안 한다면, 코너라고 인식을 안하게 된다.

따라서, 꺾는 각도를 계속 누적해 줌으로써, 완만한 코너라도 코너라고 인식해 주게 만드는 것이다.

 

각도를 구할 때, 부호가 있는 걸로 구하는 이유도 누적합 때문이다.

예를 들어, 왼쪽으로 완만하게 아주 살짝 꺾어지다, 오른쪽으로 다시 완만하게 아주 살짝 꺾어지는 게 반복되는 코스는 그냥 거의 직선 코스라고 볼 수 있다.

하지만, 부호가 있는 각도로 구하지 않으면, 누적합의 절댓값이 계속 더해지기만 하므로, 결국 일정값보다 커지게 되어 코너로 판단되게 된다.

이걸 방지하기 위해, 부호 있는 각도로 구하는 것이다.

 

구현 코드

//트랙포인트 자동 생성 로직(유니티 Awake함수 내부의 일부분)
float reverseAngleSum = 0f;
for(int i = 0; i < waypoints.Count - 1; i++)
{
    float angle = CaculateWaypointAngle(i, true);
    float reverseAngle; //꺾는 각도
    if(angle >= 0) //각도가 양수라면
    {
        reverseAngle = 180 - angle;
    }
    else
    {
        reverseAngle = -180 - angle;
    }
    reverseAngleSum += reverseAngle;
    if(Mathf.Abs(reverseAngleSum) >= trackPointCreationAngle)//트랙포인트 생성
    {
        GameObject trackpointObj = Instantiate(trackpointPrefab, waypoints[i+1].position, Quaternion.identity);
        trackpointObj.transform.LookAt(rightEdgeForWaypoints[i+1]);
        trackpointObj.transform.Rotate(0, 90, 0);
        trackpointObj.transform.Translate(0, 3.5f, 0);
        trackpoints.Add(trackpointObj.transform);
        trackpointObj.transform.parent = trackPointParent.transform;
        reverseAngleSum = 0f;
    }
}

 

private float CaculateWaypointAngle(int frontIndex, bool isReturnSigned)//이 index의 다음 index와, 그 다음 index 사이의 각도를 구하는 함수
{
    int frontN = frontIndex;
    int midN = frontIndex + 1;
    int backN = frontIndex + 2;
    if (frontIndex + 1 >= waypoints.Count)
    {
        midN -= waypoints.Count;
    }
    if (frontIndex + 2 >= waypoints.Count)
    {
        backN -= waypoints.Count;
    }

    Vector3 front = waypoints[frontN].position;
    Vector3 mid = waypoints[midN].position;
    Vector3 back = waypoints[backN].position;

    front.y = 0;
    mid.y = 0;
    back.y = 0;
    Vector3 line1 = mid - front;
    Vector3 line2 = mid - back;

    float angle;
    if(isReturnSigned)
    {
        angle = Vector3.SignedAngle(line1, line2, line1);
    }
    else
    {
        angle = Vector3.Angle(line1, line2);
    }


    return angle;
}

 

댓글과 공감클릭은 더 좋은글을 위한 큰 힘이 되니 댓글과 공감클릭 부탁드립니다!!

반응형