๋๋ค ๋ฒ ์ง์ด ๊ณก์ ์๋ก ์ ๋ ํฌ์ฌ์ฒด ๊ตฌํ

๊ฒ์์ค๋ธ์ ํธ ์ค์
- Launcher ์ค๋ธ์ ํธ : ๋น GameObject ์์ฑ โ ProjectileLauncher ์ปดํฌ๋ํธ ๋ถ์ฐฉ
- Start Point : ๋น GameObject ์์ฑ(์: StartPoint) โ Launcher์ startPoint ์ฌ๋กฏ์ ๋๋๊ทธ
- Target Point : ๋น GameObject ์์ฑ(์: TargetPoint) โ Launcher์ targetPoint ์ฌ๋กฏ์ ๋๋๊ทธ
- Projectile Prefab : ํ๋ธ Mesh + URP Lit ๋จธํฐ๋ฆฌ์ผ ์ ์ฉ ํ Prefabํ โ projectilePrefab ์ฌ๋กฏ์ ๋๋๊ทธ
Prefab ์ค์
- Mesh: ์ํ๋ ํฌ์ฌ์ฒด Prefab
- Material: URP Lit ์
ฐ์ด๋, Surface Type = Opaque
- Rigidbody: ์์ (๋ฌผ๋ฆฌ ๋ถํ์)
- Collider: ํ์ ์์ผ๋ฉด ์ ๊ฑฐ
Start / End Point์ ๋ถ์ ํ๋ ๋ชจ์
์ ์ฉ
FloatingMotion.cs๋ฅผ ๋ถ์ฌ์ ๊ณต๊ฐ์ ๋ ๋ค๋๋ ๋ฏํ ์ ๋๋ฉ์ด์
์ ๋ถ์ฌํจ.
ํ๋ผ๋ฏธํฐ ์ค์
- Travel Time 0.8 ~ 1.5 ๋ชฉํ๊น์ง ๋๋ฌ ์๊ฐ(์ด)
- Rotation Speed 500 ~ 1000 ํฌ์ฌ์ฒด ์์ ์๋(ยฐ/s)
- Curve Strength 4 ~ 10 ๋ฒ ์ง์ด ๊ณก๋ฅ ๋๋ค ๋ฒ์ โ ํด์๋ก ๊ฒฝ๋ก๊ฐ ํจ
- Control Point Count 2 ๋ฒ ์ง์ด ์ ์ด์ ์ (1~4) โ ๋ง์์๋ก S์ปค๋ธ ๊ฐ๋ฅ
- Start Color (1, 0.4, 0.7, 1) ๋ฐ์ฌ ์ ํํฌ
- End Color (1, 1, 1, 1) ๋๋ฌ ์ ํ์ดํธ
์คํ
- Play Mode ์ง์
โ Space ์ฐํ๋ก ํฌ์ฌ์ฒด ์ฐ์ ๋ฐ์ฌ
- Curve Strength๋ฅผ Runtime์ ์กฐ์ ํ๋ฉด ์ค์๊ฐ์ผ๋ก ๊ฒฝ๋ก ๋ณํ ํ์ธ ๊ฐ๋ฅ
- ํฌ์ฌ์ฒด๊ฐ Target์ ๋๋ฌํ๋ฉด ์๋ Destroy โ Scene์ด ์ค์ผ๋์ง ์์
Code
๐ProjectileLauncher.cs
using UnityEngine;
using UnityEngine.InputSystem;
public class ProjectileLauncher : MonoBehaviour
{
[Header("References")]
public Transform startPoint;
public Transform targetPoint;
public GameObject projectilePrefab;
[Header("Motion")]
public float travelTime = 1.2f;
public float rotationSpeed = 720f;
[Header("Bezier Randomness")]
[Range(0f, 20f)] public float curveStrength = 6f;
[Range(1, 4)] public int controlPointCount = 2;
[Header("Color")]
public Color startColor = new Color(1f, 0.4f, 0.7f);
public Color endColor = Color.white;
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
Launch();
}
void Launch()
{
GameObject go = Instantiate(projectilePrefab, startPoint.position, Random.rotation);
go.AddComponent<ProjectileMotion>().Init(
startPoint.position, targetPoint.position,
travelTime, rotationSpeed,
curveStrength, controlPointCount,
startColor, endColor
);
}
}
๐ProjectileMotion.cs
using UnityEngine;
[RequireComponent(typeof(Renderer))]
public class ProjectileMotion : MonoBehaviour
{
Vector3[] _pts;
float _duration;
float _rotSpeed;
Color _colStart, _colEnd;
float _elapsed;
Material _mat;
static readonly int ColorID = Shader.PropertyToID("_BaseColor");
public void Init(Vector3 start, Vector3 target,
float duration, float rotSpeed,
float curveStrength, int cpCount,
Color colStart, Color colEnd)
{
_duration = duration;
_rotSpeed = rotSpeed;
_colStart = colStart;
_colEnd = colEnd;
_pts = BuildControlPoints(start, target, curveStrength, cpCount);
_mat = GetComponent<Renderer>().material;
SetColor(0f);
}
void Update()
{
_elapsed += Time.deltaTime;
float t = Mathf.Clamp01(_elapsed / _duration);
transform.position = EvaluateBezier(_pts, t);
if (t < 0.999f)
{
Vector3 next = EvaluateBezier(_pts, Mathf.Clamp01(t + 0.01f));
Vector3 dir = next - transform.position;
if (dir.sqrMagnitude > 0.0001f)
transform.rotation = Quaternion.LookRotation(dir) *
Quaternion.Euler(0f, 0f, _rotSpeed * _elapsed);
}
SetColor(t);
if (t >= 1f) Destroy(gameObject);
}
void SetColor(float t)
{
Color c = Color.Lerp(_colStart, _colEnd, t);
if (_mat.HasProperty(ColorID)) _mat.SetColor(ColorID, c);
else if (_mat.HasProperty("_Color")) _mat.SetColor("_Color", c);
}
static Vector3[] BuildControlPoints(Vector3 start, Vector3 end,
float strength, int cpCount)
{
var pts = new Vector3[cpCount + 2];
pts[0] = start;
pts[pts.Length - 1] = end;
for (int i = 1; i <= cpCount; i++)
{
float ratio = (float)i / (cpCount + 1);
Vector3 mid = Vector3.Lerp(start, end, ratio);
pts[i] = mid + Random.insideUnitSphere * strength;
}
return pts;
}
static Vector3 EvaluateBezier(Vector3[] pts, float t)
{
Vector3[] tmp = (Vector3[])pts.Clone();
int n = tmp.Length;
for (int r = 1; r < n; r++)
for (int i = 0; i < n - r; i++)
tmp[i] = Vector3.LerpUnclamped(tmp[i], tmp[i + 1], t);
return tmp[0];
}
}
๐FloatingMotion.cs
using UnityEngine;
public class FloatingMotion : MonoBehaviour
{
[Header("Range")]
[SerializeField] private Vector3 range = new Vector3(10f, 10f, 10f);
[Header("Speed")]
[SerializeField] private float speed = 0.2f;
[Header("Noise Offsets")]
[SerializeField] private Vector3 noiseOffset;
private Vector3 _originPosition;
void Start()
{
_originPosition = transform.position;
if (noiseOffset == Vector3.zero)
noiseOffset = new Vector3(
Random.Range(0f, 100f),
Random.Range(0f, 100f),
Random.Range(0f, 100f)
);
}
void Update()
{
float t = Time.time * speed;
float x = (Mathf.PerlinNoise(t + noiseOffset.x, 0f) - 0.5f) * range.x;
float y = (Mathf.PerlinNoise(t + noiseOffset.y, 1f) - 0.5f) * range.y;
float z = (Mathf.PerlinNoise(t + noiseOffset.z, 2f) - 0.5f) * range.z;
transform.position = _originPosition + new Vector3(x, y, z);
}
}