참고
https://tonikat.tistory.com/10
https://lee-seokhyun.gitbook.io/workspace/client/easy-mathematics/gdc2012/3
만들어 본 이유
평소에도 리그오브레전드 게임의 챔피언, 카이사의 Q 스킬 '이케시아 폭우'를 쓸 때마다 토도도독 박히는 느낌이 좋았다.
그런 느낌은 못 냈지만, 비슷하게 구현한 게시글을 봐서 나도 내 방식대로 구현해보았다.
베지어 곡선에 대해 많이 공부하게 되었다.
핵심 코드
가장 핵심은 역시 베지어 곡선이다.
3차 베지어 곡선을 썼는데, 3차 베지어 곡선은 점 4개로 위치를 구하는 것이다.
방정식으로 정석으로 코딩하면 이렇게 되는데, 수포자인 나는 이해가 한번에 가지 않는다.
/// <summary>
/// 3차 베지어 곡선.
/// </summary>
/// <param name="a">시작 위치</param>
/// <param name="b">시작 위치에서 얼마나 꺾일 지 정하는 위치</param>
/// <param name="c">도착 위치에서 얼마나 꺾일 지 정하는 위치</param>
/// <param name="d">도착 위치</param>
/// <returns></returns>
private float CubicBezierCurve(float a, float b, float c, float d)
{
// (0~1)의 값에 따라 베지어 곡선 값을 구하기 때문에, 비율에 따른 시간을 구했다.
float t = m_timerCurrent / m_timerMax; // (현재 경과 시간 / 도착 위치에 도착할 시간)
// 방정식.
return Mathf.Pow((1 - t), 3) * a
+ Mathf.Pow((1 - t), 2) * 3 * t * b
+ Mathf.Pow(t, 2) * 3 * (1 - t) * c
+ Mathf.Pow(t, 3) * d;
}
베지어 곡선은 결국에는 원리가 간단하므로, 그대로 코드로 짜면 이렇게 쉽게 짤 수 있다.
구글링 하다보니, 그래프로 슥슥 설명하시던데
private float CubicBezierCurve(float a, float b, float c, float d)
{
float t = m_timerCurrent / m_timerMax;
// 이해한대로 편하게 쓰면.
float ab = Mathf.Lerp(a, b, t);
float bc = Mathf.Lerp(b, c, t);
float cd = Mathf.Lerp(c, d, t);
float abbc = Mathf.Lerp(ab, bc, t);
float bccd = Mathf.Lerp(bc, cd, t);
return Mathf.Lerp(abbc, bccd, t);
}
그 외에 생각했던 것들
a와 d는 시작 위치와 도착 위치이므로, 쉽게 값을 대입할 수 있는데, b와 c는 따로 위치를 잡아야한다.
랜덤하게 위치를 잡아주었는데 랜덤으로 하니, 오브젝트를 기준으로 앞으로 나가거나 뒤로 나가거나 별로 안 이뻤다.
// 시작 지점.
m_points[0] = _startTr.position;
// 시작 지점을 기준으로 랜덤 포인트 지정.
m_points[1] = _startTr.position +
(_newPointDistanceFromStartTr * Random.Range(-1.0f, 1.0f) * _startTr.right) + // X (좌, 우 전체)
(_newPointDistanceFromStartTr * Random.Range(-1.0f, 1.0f) * _startTr.up) + // Y (위, 아래 전체)
(_newPointDistanceFromStartTr * Random.Range(-1.0f, 1.0f) * _startTr.forward); // Z (앞, 뒤 전체)
// 도착 지점을 기준으로 랜덤 포인트 지정.
m_points[2] = _endTr.position +
(_newPointDistanceFromEndTr * Random.Range(-1.0f, 1.0f) * _endTr.right) + // X (좌, 우 전체)
(_newPointDistanceFromEndTr * Random.Range(-1.0f, 1.0f) * _endTr.up) + // Y (위, 아래 전체)
(_newPointDistanceFromEndTr * Random.Range(-1.0f, 1.0f) * _endTr.forward); // Z (앞, 뒤 전체)
// 도착 지점.
m_points[3] = _endTr.position;
이 부분은 어느 정도의 고정한 랜덤 값을 줘서 해결했다.
플레이어의 뒤쪽 윗부분에서 시작. -> 도착지점의 앞 부분으로 도착. 느낌으로 랜덤 값을 주었다.
// 시작 지점.
m_points[0] = _startTr.position;
// 시작 지점을 기준으로 랜덤 포인트 지정.
m_points[1] = _startTr.position +
(_newPointDistanceFromStartTr * Random.Range(-1.0f, 1.0f) * _startTr.right) + // X (좌, 우 전체)
(_newPointDistanceFromStartTr * Random.Range(-0.15f, 1.0f) * _startTr.up) + // Y (아래쪽 조금, 위쪽 전체)
(_newPointDistanceFromStartTr * Random.Range(-1.0f, -0.8f) * _startTr.forward); // Z (뒤 쪽만)
// 도착 지점을 기준으로 랜덤 포인트 지정.
m_points[2] = _endTr.position +
(_newPointDistanceFromEndTr * Random.Range(-1.0f, 1.0f) * _endTr.right) + // X (좌, 우 전체)
(_newPointDistanceFromEndTr * Random.Range(-1.0f, 1.0f) * _endTr.up) + // Y (위, 아래 전체)
(_newPointDistanceFromEndTr * Random.Range(0.8f, 1.0f) * _endTr.forward); // Z (앞 쪽만)
// 도착 지점.
m_points[3] = _endTr.position;
참고로 위에 사용한 2개의 움짤은 같은 속성 값으로 사용했다.
코드
참고에 올린 사이트의 코드를 참고해서, 클래스는 동일한 상태에서 안의 코드만 내 방식대로 바꾸었다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shooter : MonoBehaviour
{
public GameObject m_missilePrefab; // 미사일 프리팹.
public GameObject m_target; // 도착 지점.
[Header("미사일 기능 관련")]
public float m_speed = 2; // 미사일 속도.
[Space(10f)]
public float m_distanceFromStart = 6.0f; // 시작 지점을 기준으로 얼마나 꺾일지.
public float m_distanceFromEnd = 3.0f; // 도착 지점을 기준으로 얼마나 꺾일지.
[Space(10f)]
public int m_shotCount = 12; // 총 몇 개 발사할건지.
[Range(0, 1)] public float m_interval = 0.15f;
public int m_shotCountEveryInterval = 2; // 한번에 몇 개씩 발사할건지.
private void Update()
{
if(Input.GetKeyDown(KeyCode.A))
{
// Shot.
StartCoroutine(CreateMissile());
}
}
IEnumerator CreateMissile()
{
int _shotCount = m_shotCount;
while (_shotCount > 0)
{
for(int i = 0; i < m_shotCountEveryInterval; i++)
{
if(_shotCount > 0)
{
GameObject missile = Instantiate(m_missilePrefab);
missile.GetComponent<BezierMissile>().Init(this.gameObject.transform, m_target.transform, m_speed, m_distanceFromStart, m_distanceFromEnd);
_shotCount--;
}
}
yield return new WaitForSeconds(m_interval);
}
yield return null;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BezierMissile : MonoBehaviour
{
Vector3[] m_points = new Vector3[4];
private float m_timerMax = 0;
private float m_timerCurrent = 0;
private float m_speed;
public void Init(Transform _startTr, Transform _endTr, float _speed, float _newPointDistanceFromStartTr, float _newPointDistanceFromEndTr)
{
m_speed = _speed;
// 끝에 도착할 시간을 랜덤으로 줌.
m_timerMax = Random.Range(0.8f, 1.0f);
// 시작 지점.
m_points[0] = _startTr.position;
// 시작 지점을 기준으로 랜덤 포인트 지정.
m_points[1] = _startTr.position +
(_newPointDistanceFromStartTr * Random.Range(-1.0f, 1.0f) * _startTr.right) + // X (좌, 우 전체)
(_newPointDistanceFromStartTr * Random.Range(-0.15f, 1.0f) * _startTr.up) + // Y (아래쪽 조금, 위쪽 전체)
(_newPointDistanceFromStartTr * Random.Range(-1.0f, -0.8f) * _startTr.forward); // Z (뒤 쪽만)
// 도착 지점을 기준으로 랜덤 포인트 지정.
m_points[2] = _endTr.position +
(_newPointDistanceFromEndTr * Random.Range(-1.0f, 1.0f) * _endTr.right) + // X (좌, 우 전체)
(_newPointDistanceFromEndTr * Random.Range(-1.0f, 1.0f) * _endTr.up) + // Y (위, 아래 전체)
(_newPointDistanceFromEndTr * Random.Range(0.8f, 1.0f) * _endTr.forward); // Z (앞 쪽만)
// 도착 지점.
m_points[3] = _endTr.position;
transform.position = _startTr.position;
}
void Update()
{
if (m_timerCurrent > m_timerMax)
{
return;
}
// 경과 시간 계산.
m_timerCurrent += Time.deltaTime * m_speed;
// 베지어 곡선으로 X,Y,Z 좌표 얻기.
transform.position = new Vector3(
CubicBezierCurve(m_points[0].x, m_points[1].x, m_points[2].x, m_points[3].x),
CubicBezierCurve(m_points[0].y, m_points[1].y, m_points[2].y, m_points[3].y),
CubicBezierCurve(m_points[0].z, m_points[1].z, m_points[2].z, m_points[3].z)
);
}
/// <summary>
/// 3차 베지어 곡선.
/// </summary>
/// <param name="a">시작 위치</param>
/// <param name="b">시작 위치에서 얼마나 꺾일 지 정하는 위치</param>
/// <param name="c">도착 위치에서 얼마나 꺾일 지 정하는 위치</param>
/// <param name="d">도착 위치</param>
/// <returns></returns>
private float CubicBezierCurve(float a, float b, float c, float d)
{
// (0~1)의 값에 따라 베지어 곡선 값을 구하기 때문에, 비율에 따른 시간을 구했다.
float t = m_timerCurrent / m_timerMax; // (현재 경과 시간 / 최대 시간)
// 방정식.
/*
return Mathf.Pow((1 - t), 3) * a
+ Mathf.Pow((1 - t), 2) * 3 * t * b
+ Mathf.Pow(t, 2) * 3 * (1 - t) * c
+ Mathf.Pow(t, 3) * d;
*/
// 이해한대로 편하게 쓰면.
float ab = Mathf.Lerp(a, b, t);
float bc = Mathf.Lerp(b, c, t);
float cd = Mathf.Lerp(c, d, t);
float abbc = Mathf.Lerp(ab, bc, t);
float bccd = Mathf.Lerp(bc, cd, t);
return Mathf.Lerp(abbc, bccd, t);
}
void OnTriggerEnter(Collider collision)
{
Destroy(this.gameObject, 0.35f); // 한쪽에 Trigger 체크하는 것과 Rigidbody 컴포넌트 추가 잊지 말기.
}
}
구현해봤으니까 제일 멋있어보이게 움짤 하나 남겨야겠다!!
생각보단 멋있지않다... Trail 머터리얼이 이뻐야 할 듯.
'기타 > Unity' 카테고리의 다른 글
[포톤] 콜백 함수들 (1) | 2021.09.27 |
---|---|
포톤 연동하기 (0) | 2021.09.27 |
평면 상의 그림자 만들어보기 (0) | 2021.09.23 |
UV 좌표로 텍스쳐 크기 조절하기 (0) | 2021.09.16 |
Dissolve 효과 (0) | 2021.08.10 |