베지에 곡선을 사용해보았다
보통 화살의 궤적을 그릴 때 자연스러움을 위해서 많이 사용하는 것 같았다
나는 보스의 스킬을 만드는데 베지에 곡선을 사용했다
보스 스킬 중에 광범위 공격으로 해당 범위 내의 무작위 지점에 불덩이를 날리는 것이다
그 불덩이의 궤적을 베지에 곡선을 사용해서 구현했다
먼저 베지에 곡선에 대해서 공부할 필요가 있다
베지에 곡선 (Bezier Curve)
일단 곡선을 그리는 공식인데 매개 변수에 따라 그려지는 곡선이다
주어지는 특정한 점에 따라서 곡선이 그려지는 것이다
그래서 게임 프로그래밍에서는 원하는 궤적을 따라 물체를 움직이고 싶을 때 많이 사용된다
시작점과 끝점을 제외하고 주어지는 점의 개수에 따라서 n+1차 베지에 곡선이 된다
공식 몇 가지 나올건데 이 공식에서 t 는 시간 (0 <= t <= 1), P0은 시작점, Pn+1은 끝점, P1..Pn은 주어지는 점이 된다
- 선형 베지에 곡선
주어지는 점이 없다면 선형 베지에 곡선이며 이는 선형 보간과 동일하다
즉, 시작점과 끝점을 단순하게 잇는 선이 나오게 된다
- 2차 베지에 곡선
시작점과 끝점 사이에 점 하나가 더 주어지는 경우로
선형 베지에 곡선이 곡선이 아닌 선 이었던 반면 2차부터는 곡선으로 나타나게 된다
이 식을 정리하면,
이렇게 된다
위 그래프처럼 특점 점에 따라서 곡선이 휘어진다
이는 P0 ~ P1 까지의 선형보간과 P1 ~ P2 까지의 선형보간과 같다
- 3차 베지에 곡선
3차 베지에 곡선은 주어지는 점이 2개인 경우다
나는 3차 베지에 곡선을 사용해서 궤적을 그렸다
3차 베지에 곡선의 공식은
이 식을 정리하면
이렇게 나온다
해당 식을 언리얼 C++ 코드로 나타내보면 이렇게 된다
float ACRandomObject::Trail(float a, float b, float c, float d)
{ // a,b,c,d는 주어지는 점의 좌표값이다
float t = CurrentTime / MaxTime;
return FMath::Pow((1 - t), 3) * a
+ FMath::Pow((1 - t), 2) * 3 * t * b
+ FMath::Pow(t, 2) * 3 * (1 - t) * c
+ FMath::Pow(t, 3) * d;
}
이해가 잘 가지 않을 때는 그림을 직접 그려보면 쉽다
https://www.desmos.com/calculator/cahqdxeshd?lang=ko
Bezier Curves
www.desmos.com
이 사이트에서 직접 점을 옮겨보면서 감을 잡아보면 이해가 훨씬 쉽다
이제 베지에 곡선에 대해서 공부했으니
적용시켜보도록 하자
일단 나는 3차원 공간에서 베지에 곡선을 사용하는 것이니
FVector Pos = FVector(x, y, z); 의 형태로 총 4개의 점이 필요하다
PosA, PosB, PosC, PosD로 변수명을 정했고
PosA가 시작점(Boss의 위치), PosB가 끝점(불덩이가 떨어질 위치)가 된다
1. 시작점과 끝점을 잡아준다
2. 중간 점은 자연스러움을 위해 랜덤값을 줄것이다
2-1. PosB는 시작점 + 랜덤값, PosC는 끝점 + 랜덤값을 주었다
중간에 잡을 점들은 원하는 궤적에 따라 랜덤값 범위를 주고 더하고 빼면 된다
3. 랜덤하게 잡아준 값을 Tick에서 베지에 공식을 거쳐 넣어주면 끝!
나는 BT_TaskNode에서 불덩이(Actor)를 Spawn시키고
불덩이 (Actor)에서 모든 작업을 해주었다
불덩이 액터 코드
void ACRandomObject::Tick(float DeltaTime)
{ // 액터의 위치를 프레임마다 옮겨준다
Super::Tick(DeltaTime);
CheckTrue(CurrentTime > MaxTime);
CurrentTime += DeltaTime * Speed;
FVector TrailLocation = FVector(
Trail(PosA.X, PosB.X, PosC.X, PosD.X),
Trail(PosA.Y, PosB.Y, PosC.Y, PosD.Y),
Trail(PosA.Z, PosB.Z, PosC.Z, PosD.Z));
SetActorLocation(TrailLocation);
}
void ACRandomObject::Init(FVector Start, FVector End)
{ // 스폰 시킬때 시작점과 끝점을 받아온다
PosA = FVector(Start.X, Start.Y, 180);
PosD = FVector(End.X, End.Y, 90);
//중간 랜덤값들을 정해준다 // 랜덤값의 범위는 원하는 궤적에 따라 다르게 넣어줄 것
PosB = PosA + FVector(
DistanceFromStart * UKismetMathLibrary::RandomFloatInRange(-1.0, 1.0) * Start.RightVector
+ DistanceFromStart * UKismetMathLibrary::RandomFloatInRange(-1.0, 1.0) * Start.UpVector
+ DistanceFromStart * UKismetMathLibrary::RandomFloatInRange(-1.0, 1.0) * Start.ForwardVector
);
PosC = PosD + FVector(
DistanceFromEnd * UKismetMathLibrary::RandomFloatInRange(-1.0, 1.0) * End.RightVector
+ DistanceFromEnd * UKismetMathLibrary::RandomFloatInRange(-1.0, 1.0) * End.UpVector
+ DistanceFromEnd * UKismetMathLibrary::RandomFloatInRange(-1.0, 1.0) * End.ForwardVector
);
}
float ACRandomObject::Trail(float a, float b, float c, float d)
{ //베지에 곡선 공식 적용
float t = CurrentTime / MaxTime;
return FMath::Pow((1 - t), 3) * a
+ FMath::Pow((1 - t), 2) * 3 * t * b
+ FMath::Pow(t, 2) * 3 * (1 - t) * c
+ FMath::Pow(t, 3) * d;
}
void ACRandomObject::SetAttacker(ACharacter* attacker)
{
Attacker = attacker;
}
BT_TaskNode의 Execute() 함수에서 스폰 시키는 코드
for (int32 i = 0; i < RandomPoints.Num(); i++)
{
transform.SetLocation(ai->GetActorLocation());
Object = ai->GetWorld()->SpawnActor<ACRandomObject>(RandomObject, transform, params);
Object->SetAttacker(ai);
Object->Init(ai->GetActorLocation(), RandomPoints[i]);
}
결과물
처음에 베지에 곡선으로 구현해봐야지 ! 했을때 베지에 곡선 공식도 너무 어렵게 생겼고
설명이 복잡해 어려웠는데 결국 보간에 보간이었다
공부하면서 유니티로 구현한 다른 블로그들도 많이 참조했는데 어떤분은 그냥 Lerp()함수로 구현하신 분도 있었다
디테일 좀만 더 잡고 값 이것저것 주면 완전 볼만할듯 ㅎㅎ
한 번 이해하고 나니 생각보다 쉽고 쓸만해서(중요..)
나중에 활 디테일 잡을 때나 스킬에 이곳저곳 쓸 예정이다
'> Project > Unreal4 C++' 카테고리의 다른 글
[Unreal C++] AI EQS - Strafing (0) | 2024.02.26 |
---|---|
[Unreal4 C++] 공격하는 Enemy AI의 수 제한 (0) | 2024.01.27 |
[Unreal4 C++] 04 강참조/약참조 (0) | 2023.09.12 |
[Unreal4 C++] 03 (0) | 2023.08.17 |
[Unreal4 C++] 02 Weapon Attachment, BackRoll (0) | 2023.07.18 |