유니티 트레일 렌더러를 활용한 검기 제작
목표
트레일 렌더러를 사용해서 정점 List를 지정해주면, 지정해준 정점을 따라서 이동하면서 남긴 궤적을 이용해서 검기를 구현해보자
목표를 세분화하면 다음과 같다
- Step 1 : 주어진 정점을 따라 이동하면서 궤적을 남기기
- Step 2 : 보간법을 이용해 검기의 궤적을 완만하게 만들기
Step 1 : 주어진 정점을 따라 이동하면서 궤적을 남기기
단순히 정점 List를 따라서 직선 이동을 해서 궤적을 남기게 하는 것이 목표다
플레이어 오브젝트 아래에 빈 오브젝트를 만들고 Trail Renderer 컴포넌트를 추가해주자
우선 컴포넌트를 추가하고 나서 해야 할 것은 트레일 렌더러에 머티리얼을 추가해주는 것이다. 추가를 안 하면 못생긴 핑크색이 나오니 주의!
Width 아래의 숫자를 클릭해서 트레일의 두께를 조절할 수 있다.
Color탭에 들어가면 시작 부분, 끝부분의 색깔과 알파 값을 조절할 수 있다.
그다음 저 Trail오브젝트가 주어진 위치로 이동하는 스크립트를 짜 보자
//트레일 멤버 변수
private bool canAttack
public TrailRenderer trail;
public Vector3[] trail_offsets; //이동할 정점 Set
public float waitTime = 0.01f; //다음 정점으로 이동하기 전 대기시간
private void Awake()
{
trail.emitting = false;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.X)&&canAttack)
{
StartCoroutine(Attack());
}
}
IEnumerator Attack()
{
canAttack = false; //공격 중복 방지용 flag
playeranim.SetBool("isAttack", true);
//트레일 구현
yield return new WaitForSeconds(waitTime);
trail.transform.localPosition = trail_offsets[0];
yield return new WaitForSeconds(waitTime);
trail.emitting = true;
for (int i = 1;i<trail_offsets.Length;i++)
{
yield return new WaitForSeconds(waitTime);
trail.transform.localPosition = trail_offsets[i];
}
yield return new WaitForSeconds(waitTime);
trail.emitting=false;
playeranim.SetBool("isAttack", false);
canAttack = true;
yield return null;
}
코루틴을 이용해서 간단하게 구현해봤다. 생성한 스크립트를 플레이어 오브젝트에 붙이고 인스펙터는 다음과 같이 설정했다.
결과를 보자
이렇게 구현한 검기에는 몇 가지 문제가 있는데 가장 큰 문제는 회전이 안 먹힌다는 것이다.
이 문제는 간단히 해결했는데, Trail 오브젝트 부모로 빈오브젝트를 만들어주고, 그 빈 오브젝트에서 회전을 하면 회전이 잘 적용된다
부모 오브젝트(GameObject)에서 x축으로 60도만큼 회전하고 나서 결과를 보자.
언뜻 보면 잘 된 것처럼 보이지만 x축으로 기울였음에도 불구하고 일정한 두께를 가져서 깊이감이 보이지 않는다.
이 문제를 해결하기 위해서 Trail오브젝트에 Trail Renderer로 가서 Alignment를 Transform Z로 변경하자
수정한 결과를 보자
Step 2 : 보간법을 이용해 검기의 궤적을 완만하게 만들기
Step 1에서 만든 검기는 정해진 정점을 직선으로 이동하면서 궤적을 그려서 날카롭고 뾰족하다.
그러나 내가 설정한 목표는 "완만한 곡선"형태의 검기를 만드는 것이므로 추가적인 구현이 필요하다.
곡선 형태의 검기를 구현하기 위해서는 Trail object가 곡선이동을 해야 한다. 여기서 곡선 이동을 한다는 것은 무수히 많은 정점을 이용해서 마치 곡선처럼 보이게 한다는 것을 의미한다.
따라서 무수히 많은 정점이 필요한데, 30개가 넘어가는 정점을 일일히 설정한다고 생각해보면 정말 끔찍하다.
따라서 우리는 최소한의 정점만 설정해서 완만한 곡선형태의 정점을 얻어야 한다. 따라서 보간법을 사용해야 한다.
다양한 방법이 있겠지만 나는 다항식 보간법인 라그랑주 보간법을 이용해서 구현했다.
라그랑주 보간법은 다음 블로그에서 참고했다
https://phy64ev1.tistory.com/13
라그랑주 보간법(Lagrangian Interpolation)
라그랑주 보간법 보간법이란 불연속적인 데이터를 이용하여 사이 구간의 값을 추정하는 방법입니다. 라그랑주 보간법(Lagrangian Interpolation)은 n+1개의 좌표로 n차 다항식을 만드는 방법입니다. 계
phy64ev1.tistory.com
구현한 코드는 다음과 같다
//트레일 멤버 변수
private bool canAttack = true;
public TrailRenderer trail;
public Vector3[] trail_offsets;
public float waitTime = 0.01f;
public int interpolateSize; //점과 점사이에 나누어질 구간 수
//y값을 인자로 받으면 trail_offsets으로 생성된 다항식에서 x값을 리턴해준다.
double InterPolate(double input_y)
{
double output = 0;
//보간식
foreach(Vector3 i in trail_offsets)
{
double frontValue = 1;
double std_y = i.y;
double std_x = i.x;
List<double> y_list = new List<double>();
foreach(Vector3 j in trail_offsets)
{
if(j != i)
{
y_list.Add(j.y);
}
}
foreach(double j in y_list)
{
frontValue = frontValue * (input_y - j);
frontValue = frontValue/(std_y-j);
}
frontValue = frontValue * std_x;
output += frontValue;
}
return output;
}
IEnumerator Attack()
{
canAttack = false;
playeranim.SetBool("isAttack", true);
//보간점 생성
List<Vector3> vertexList = new List<Vector3>();
//정점과 정점 사이의 구간을 interpolateSize만큼 나눠고 나뉜 부분의 정점을 추가한다
for(int i = 0; i<trail_offsets.Length-1; i++)
{
float term = (trail_offsets[i].y - trail_offsets[i + 1].y) / interpolateSize;
vertexList.Add(trail_offsets[i]);
for(int j=1;j<interpolateSize;j++)
{
float new_y = trail_offsets[i].y - term * j;
float new_x = (float)InterPolate(new_y);
Vector3 newVector = new Vector3(new_x, new_y,this.transform.position.z);
vertexList.Add(newVector);
}
}
vertexList.Add(trail_offsets[trail_offsets.Length-1]);
yield return new WaitForSeconds(waitTime);
//트레일 구현
trail.transform.localPosition = trail_offsets[0];
yield return new WaitForSeconds(waitTime);
trail.emitting = true;
//vertexList의 위치로 이동
for (int i = 1; i < vertexList.Count;i++)
{
yield return new WaitForSeconds(waitTime);
trail.transform.localPosition = vertexList[i];
}
yield return new WaitForSeconds(waitTime);
trail.emitting=false;
playeranim.SetBool("isAttack", false);
canAttack = true;
yield return null;
}
결과를 보자
Step 1때와 비교해보면 검기가 매우 부드러워진 것을 확인할 수 있다. interpolateSize의 크기를 늘리면 검기는 더욱 곡선 형태를 띄우겠지만 그만큼 정점의 개수가 많아지니 적당히 조절하자.