๋ผ์ดํธ ์์ค : ๊ฒ์ ๋๋ 3D ๋๋๋ง์ ๊ด์์ ์ถ๊ฐํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ์ด๊ฒ์ ํน์ ์์น ๋๋ ๋ฐฉํฅ์์ ๋ฐ์ํ๋ ๋น์ ๋ํ๋ธ๋ค.
์ ํ : ๋ผ์ดํธ๋ ์ฌ๋ฌ์ ํ์ด ์๋ค. ์ด๋ฌํ ์ ํ ์ค ๋ช๊ฐ์ง๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ์ ๊ด์(Point Light) : ํน์ ์ง์ ์์ ๋ชจ๋ ๋ฐฉํฅ์ผ๋ก ๋น์ ๋ฐฉ์ฐํ๋ ๋ผ์ดํธ์ด๋ค.
์์ฑ : ๊ฐ ๋ผ์ดํธ์๋ ์ฌ๋ฌ ์์ฑ์ด ์๋ค. ์ด๋ฌํ ์์ฑ์๋ ์์น, ๋ฐฉํฅ, ๊ฐ๋(intensiy), ์์(color), ๋ฒ์(range), ๊ฐ๋(angle) ๋ฑ์ด ํฌํจ๋๋ค.
๊ทธ๋ฆผ์ : ๋ผ์ดํธ๋ ๊ทธ๋ฆผ์๋ฅผ ์์ฑํ ์ ์๋ค. ๋ผ์ดํธ์ ๊ฐ์ฒด ์ฌ์ด์ ๊ด๊ณ์ ๋ฐ๋ผ ๊ทธ๋ฆผ์๋ ๋ผ์ดํธ๊ฐ ๋ถ๋ชํ๋ ๊ฐ์ฒด ๋ค์ ์์ฑ๋๋ค.
์ฑ๋ฅ : ๋ผ์ดํธ๋ ๋ ๋๋ง ์ฑ๋ฅ์ ํฐ ์ํฅ์ ๋ฏธ์น๋ค. ๋ง์ ๋ผ์ดํธ๋ฅผ ์ฌ์ฉํ๋ฉด ํนํ ๋์ ๊ทธ๋ฆผ์๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ ๋ ๋๋ง ์ฑ๋ฅ์ ๋ถ์ ์ ์ธ ์ํฅ์ ๋ฏธ์น ์ ์๋ค. ๋ฐ๋ผ์ ์ต์ ํ๋ ์ค์ํ ๊ณ ๋ ค์ฌํญ์ด๋ค.
๋น ๋ฐ์ฌ ๋ฐ ์ฐ๋ : ๋ผ์ดํธ๋ ํ๋ฉด์ ๋ถ๋ชํ๊ณ ๋ฐ์ฌ๋๊ฑฐ๋ ๋ค๋ฅธ ๋ฐฉํฅ์ผ๋ก ์ฐ๋๋์ด ์ฌ์ง๊ณผ ํ๋ฉด์ ์ค์ ์ฑ์ ๋ํ๋ธ๋ค. ์ด๋ฌํ ํจ๊ณผ๋ ๋ฌผ๋ฆฌ ๊ธฐ๋ฐ ๋ ๋๋ง(PBR)์์ ์ค์ํ ์์๋ค.
๊ฒ์ ์ธ๊ฒ์ ๋ฐฐ๊ฒฝ์ ๋๋ฌ์ธ๋ ํ๊ฒฝ ๋งคํ ๊ธฐ์ ์ด๋ค. ํ๋ธ ๋งต(Cube Map)๊ณผ ๊ตฌ์ฒดํ ์ค์นด์ด๋ฐ์ค(Sphere Map)๋ฑ์ด ์์ผ๋ฉฐ, ์ฃผ๋ก ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋ค.
์ค์นด์ด๋ฐ์ค๋ 6๊ฐ์ ํ ์ค์ฒ๋ก ๊ตฌ์ฑ๋ ํ๋ธ ๋งต ๋๋ ํ๋์ ๊ตฌ์ฒด๋ก ํ ์ค์ฒ๊ฐ ๋งคํ๋ ๊ตฌ์ฒดํ ์ค์นด์ด๋ฐ์ค๋ก ๊ตฌ์ฑ๋๋ค.
Unity์์๋ ์ฌ์ ๋ฐฐ๊ฒฝ์ผ๋ก ์ฌ์ฉ๋๋ฉฐ, ๊ฒ์ ํ๊ฒฝ์ ํ์ฅ์ํค๋๋ฐ ํ์ฉ๋๋ค.
์ฃผ๋ก ํ๋, ๊ตฌ๋ฆ, ์ฐ ๋ฑ์ ์์ฐ์ ์ธ ๋ฐฐ๊ฒฝ์ ํํํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
๋ฏธ๋ฆฌ ๋ง๋ค์ด์ง ์ค์นด์ด๋ฐ์ค๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ง์ ๋ง๋ค์ด์ Unity์์ ์ ์ฉํ ์ ์๋ค.
๊ฒ์ ์ค์ ์ค์นด์ด๋ฐ์ค๋ฅผ ๋์ ์ผ๋ก ๋ณ๊ฒฝํ์ฌ ๋ฎ๊ณผ ๋ฐค ๋ฑ์ ์๊ฐ๋๋ ํน์ ์ด๋ฒคํธ์ ๋ง๊ฒ ๋ฐฐ๊ฒฝ์ ๋ณํ์ํฌ ์ ์๋ค.
์ฑ๋ฅ์ ์ํฅ์ ๋ฏธ์น๋ฏ๋ก ์ต์ ํ์ ์ฃผ์ํด์ผ ํ๋ค.
Rigidbody ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒ์ ์ค๋ธ์ ํธ์ ๋ฌผ๋ฆฌ์ ์ธ ํ์ ๊ฐํ ๋, ์ด
ForceMode
๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ํ ํ ์ ์ฉ ๋ฐฉ์์ ์ค์ ํ ์ ์๋ค.
์ฃผ์ํ ForceMode
์ข
๋ฅ๋ ๋ค์๊ณผ ๊ฐ๋ค.
Force
: ํ์ ์ง์์ ์ผ๋ก ์ ์ฉํ๋ค.
Rigidbody.AddForce(Vector3 force, ForceMode.Force);
Acceleration
: ๊ฐ์๋๋ฅผ ์ ์ฉํ๋ค. ์ด์ ํ์ ๋์ ์ ๋ฐ๋ผ ์ ์ง์ ์ผ๋ก ๋ ๋น ๋ฅด๊ฒ ์์ง์ด๊ฒ ๋๋ค.
Rigidbody.AddForce(Vector3 force, ForceMode.Acceleration);
Impulse
: ์๊ฐ์ ์ธ ํ์ ์ ์ฉํ๋ค. ์งง์ ์๊ฐ์ ๊ฐ์์ค๋ฌ์ด ์์ง์์ด ๋ฐ์ํ๋ค.
Rigidbody.AddForce(Vector3 force, ForceMode.Impulse);
VelocityChange
: ๋ณํํ๋ ์๋๋ฅผ ์ ์ฉํ๋ค. ๋ฌผ์ฒด์ ํ์ฌ ์๋๋ฅผ ๋ณ๊ฒฝํ๋ฉด์ ์์ง์ธ๋ค.
Rigidbody.AddForce(Vector3 force, ForceMode.VelocityChange);
์ด๋ฌํ ForceMode
๋ฅผ ์ ์ ํ ํ์ฉํ์ฌ ๊ฒ์ ์ค๋ธ์ ํธ์ ์ํ๋ ๋ฌผ๋ฆฌ์ ์ธ ์์ง์๊ณผ ํจ๊ณผ๋ฅผ ๋ถ์ฌํ ์ ์๋ค.
Externals - _Environments โ Scene์ ๋ฐฐ์น
Material ์์ฑ - SkyBox ์ด๋ฆ ๋ณ๊ฒฝ - ์๋์ ๊ฐ์ด ๋ณ๊ฒฝ(์์ ์์ ๋กญ๊ฒ)
๋น ์ค๋ธ์ ํธ ์์ฑ - Player ์ด๋ฆ ๋ณ๊ฒฝ
ํ์์ ๋น ์ค๋ธ์ ํธ ์์ฑ - CameraContainer ์์ฑ
Main Camera ํ์๋ก ๋ณ๊ฒฝ
Player Input ์ถ๊ฐ
Invoke Unity Events๋ก ๋ณ๊ฒฝ
public class PlayerController : MonoBehaviour
{
[Header("Movement")]
public float moveSpeed;
private Vector2 curMovementInput;
public float jumpForce;
public LayerMask groundLayerMask;
[Header("Look")]
public Transform cameraContainer;
public float minXLook;
public float maxXLook;
private float camCurXRot;
public float lookScnsitivity;
private Vector2 mouseDelta;
[HideInInspector]
public bool canLook = true;
private Rigidbody _rigidbody;
public static PlayerController instance;
private void Awake()
{
instance = this;
_rigidbody = GetComponent<Rigidbody>();
}
private void Start()
{
Cursor.lockState = CursorLockMode.Locked; // 1์ธ์นญ ์ด๊ธฐ์ ์ปค์๋ฅผ ์ ๊ถ๋๋ค.
}
// ๋ฌผ๋ฆฌ์ ์ฒ๋ฆฌ
private void FixedUpdate()
{
Move();
}
// ๋ณดํต ์นด๋ฉ๋ผ ์ฒ๋ฆฌ
private void LateUpdate()
{
if(canLook)
{
CameraLook();
}
}
#region Actual Processing With Input Values (์
๋ ฅ๋ฐ์ ๊ฐ์ผ๋ก ์ค์ ์ฒ๋ฆฌ)
private void Move()
{
// ์บ๋ฆญํฐ๊ฐ ์์๋ ์ํ * ์
๋ ฅํ ๊ฐ
Vector3 dir = transform.forward * curMovementInput.y + transform.right * curMovementInput.x;
dir *= moveSpeed;
dir.y = _rigidbody.velocity.y; // y๊ฐ์ ์ด์ฐจํผ ์์ ์ผ ํ๋๋ฐ, y๊ฐ์ velocity์ ์๋ y๊ฐ์ผ๋ก ๊ฐ์ ธ์จ๋ค. (๊ทธ๋ฌ๋ฉด ๊ทธ ์์น ์ ๋๋ฅผ ์ธ ์ ์๋ค.)
_rigidbody.velocity = dir;
}
void CameraLook()
{
// ๋ง์ฐ์ค๊ฐ y๋ก ์์ง์๋ค๋๊ฑด ์์๋๋ก ์์ง์ด๋ ๊ฒ์ด์ง๋ง ๋กํ
์ด์
์ x๊ฐ ๋ฐ๋๊ฒ ํ๊ณ ์๋ค.
// 3์ถ์ผ๋ก ๋ณด๋ฉด x์ถ์ผ๋ก ํ์ ์ ํ ๋์ ์บ๋ฆญํฐ๊ฐ ์์๋๋ฅผ ๋ณด๊ฒ ๋๊ธฐ ๋๋ฌธ์
// ๋ง์ฐ์ค๋ฅผ ๋ฐ๊ณ ๋
๊ฒผ์ ๋, x์ถ์ด ๋ฐ๋๋ ๊ฒ์ด ๋ง๋ค.
camCurXRot += mouseDelta.y * lookScnsitivity;
camCurXRot = Mathf.Clamp(camCurXRot, minXLook, maxXLook); // ๊ฐ๋๋ฒ์ ์ ํ
cameraContainer.localEulerAngles = new Vector3(-camCurXRot, 0, 0); // ๋ง์ฐ์ค์ ์ํ์ ๋ฐ๋ผ ์์ผ๋ฅผ ์์ง์ด๊ฒ ํด์ค๋ค.
transform.eulerAngles += new Vector3(0, mouseDelta.x * lookScnsitivity, 0); // ๋ง์ฐ์ค๋ก ์นด๋ฉ๋ผ๋ฅผ ์์ง์ผ ์ ์๊ฒ ํด์ฃผ๋ ์ค๋น
}
#endregion
#region InputValue (์
๋ ฅ๊ฐ)
public void OnLookInput(InputAction.CallbackContext context) // InputAction.CallbackContext : InputAction ์์ ์๋ CallbackContext
{
mouseDelta = context.ReadValue<Vector2>(); // ์
๋ ฅ ์ฒ๋ฆฌ๊ฐ ์ผ์ด๋ ๊ฑฐ์์(context) Vector2๋ก ์ฝ์ด์จ๋ค(ReadValue<Vector2>)
}
public void OnMoveInput(InputAction.CallbackContext context)
{
// phase : ์ํ, context.phase ? context์ ์ํ, InputActionPhase.Performed ? InputAction์ ์ํ๋ Performed
// Startid, Performed, Cancle ๊ฐ๊ฐ ๊ฐ์ ์ธ์ ๊ฐ์ ธ์ค๋๋
// Started : ์ฒ์ ํ๋ฒ, ๋งจ ์ฒ์ ํ๋ ์์์ ์ด๋ฃจ์ด ์ก์๋, ๊ทธ ๋ ํ๋ฒ ๊ฐ์ ธ์ด. ๊ทธ ๋ค์๋ถํฐ๋ ์ ๋๋ก ๋ฐํ์ด x
// Performed : ๋๋ฌ์ง๋ ์ค
// Canceled : ๋๋ค๋ฉด
if(context.phase == InputActionPhase.Performed)
{
curMovementInput = context.ReadValue<Vector2>();
}
else if(context.phase == InputActionPhase.Canceled)
{
curMovementInput = Vector2.zero; // ํค๋ฅผ ๋ผ๋ฉด ์์ง์ด๋ฉด ์๋๋๊น
}
}
public void OnJumpInput(InputAction.CallbackContext context)
{
if(context.phase == InputActionPhase.Started)
{
if(IsGrounded()) // IsGrounded() : ๋
์ธ์ง ์ฒดํฌ
{
// ForceMode์ ์ข
๋ฅ 4๊ฐ์ง Force, Impulse, Acceleration, VelocityChange
// Impulse : ์ง๋์ ๊ฐ์ง๊ณ ์ ์ฒ๋ฆฌ
_rigidbody.AddForce(Vector2.up * jumpForce, ForceMode.Impulse);
}
}
}
#endregion
// ๋
์ ๋ฐ๊ณ ์๋์น ์ฒดํฌ ( ๋ฌดํ ์ ํ ๋ง๊ธฐ )
private bool IsGrounded()
{
// ์บ๋ฆญํฐ ๊ธฐ์ค ์,๋ค,์ผ,์ค 4๋ฐฉํฅ์ * 0.2๋งํผ ๊ฐ ์๋๋ฅผ ์ฒดํฌ (3D๋งต์ ์์ ๋กญ๊ธฐ ๋๋ฌธ์)
// (Vector3.up * 0.01f) ํ์ฌ ์บ๋ฆญํฐ์ ๋ฐ๋ฅ๋ณด๋ค ์กฐ๊ธ ๋ ์์์ ์ด์ค์ ์์ธ ๋ฐฉ์ง
Ray[] rays = new Ray[4]
{
new Ray(transform.position + (transform.forward * 0.2f) + (Vector3.up * 0.01f), Vector3.down),
new Ray(transform.position + (-transform.forward * 0.2f) + (Vector3.up * 0.01f), Vector3.down),
new Ray(transform.position +(transform.right * 0.2f) +(Vector3.up * 0.01f), Vector3.down),
new Ray(transform.position +(-transform.right * 0.2f) +(Vector3.up * 0.01f), Vector3.down)
};
for(int i = 0; i < rays.Length; i++)
{
if (Physics.Raycast(rays[i], 0.1f, groundLayerMask))
{
return true; // Ray๊ฐ ํ๋๋ผ๋ ๋ฐ๋ฅ์ ๋ฟ์ผ๋ฉด true๋ฐํ (์ ํ๊ฐ ๊ฐ๋ฅํ๊ฒ)
}
}
return false;
}
// OnDrawGizmos : ๊ธฐ์ฆ๋ชจ๊ฐ ํญ์ ๋ณด์ธ๋ค.
// OnDrawGizmosSelected : ์ปจํ์ ํ์๋ ์๋ง Gizmos๊ฐ ๋ณด์ธ๋ค. (๋ณต์กํ ์ํ์์ ์ ๋ฆฌ)
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawRay(transform.position + (transform.forward * 0.2f), Vector3.down);
Gizmos.DrawRay(transform.position + (-transform.forward * 0.2f), Vector3.down);
Gizmos.DrawRay(transform.position + (transform.right * 0.2f), Vector3.down);
Gizmos.DrawRay(transform.position + (-transform.right * 0.2f), Vector3.down);
}
}
์ธํฐํ์ด์ค๋ฅผ ํตํด ํด๋์ค๋ค์ ๊ณตํต์ ์ธ ๋์์ ์ ์ํ๊ณ , ์ด๋ฌํ ๋์๋ค์ ๊ตฌํํ๋ ํด๋์ค๋ค์ ํด๋น ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ(implement)ํจ์ผ๋ก์จ ๊ณตํต ๊ท์ฝ์ ์ค์ํ ์ ์๋ค.
์ธํฐํ์ด์ค๋ฅผ ์ค๋ช ํ๋ ์ฃผ์ ํน์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
์ถ์ํ : ์ธํฐํ์ด์ค๋ ์ถ์์ ์ธ ๊ฐ๋ ์ผ๋ก, ์ค์ ๋ก ๊ตฌํ๋ ๋ฉ์๋๊ฐ ์๊ณ , ๋ฉ์๋์ ์๊ทธ๋์ฒ๋ง์ ๊ฐ์ง๋ค. ๋ฐ๋ผ์ ์ธํฐํ์ด์ค๋ ์ธ์คํด์คํ๋ ์ ์์ผ๋ฉฐ, ๊ตฌํ์ฒด๊ฐ ํ์ํ๋ค.
๋ฉ์๋ ์๊ทธ๋์ฒ : ์ธํฐํ์ด์ค๋ ๊ตฌํ ํด๋์ค๊ฐ ๋ฐ๋์ ๊ตฌํํด์ผ ํ๋ ๋ฉ์๋๋ค์ ์๊ทธ๋์ฒ๋ฅผ ์ ์ํ๋ค. ๋ฉ์๋์ ์ด๋ฆ, ๋งค๊ฐ๋ณ์, ๋ฐํ ํ์ ์ด ํฌํจ๋๋ค.
๋ค์ค ์์ ๊ฐ๋ฅ : ํด๋์ค๋ ํ๋์ ํด๋์ค๋ง ์์๋ฐ์ ์ ์์ง๋ง, ์ฌ๋ฌ ์ธํฐํ์ด์ค๋ฅผ ๋์์ ๊ตฌํํ ์ ์๋ค. ์ด๋ฅผ ํตํด ๋ค์ค ์์์ ํ๋ด๋ด๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค.
๊ฐ์ ์ ๊ตฌํ : ํด๋์ค๊ฐ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ฉด, ์ธํฐํ์ด์ค์์ ์ ์ํ ๋ชจ๋ ๋ฉ์๋๋ฅผ ๋ฐ๋์ ๊ตฌํํด์ผ ํ๋ค. ์ด๋ก ์ธํด ํด๋์ค๋ ์ธํฐํ์ด์ค์ ์ ์๋ ๋์์ ๊ฐ์ ๋ก ๊ตฌํํ๊ฒ ๋๋ค.
์ธํฐํ์ด์ค ๊ฐ ํ์ฅ : ์ธํฐํ์ด์ค๋ ๋ค๋ฅธ ์ธํฐํ์ด์ค๋ฅผ ํ์ฅ(extends)ํ ์ ์๋ค. ๋ฆฌ๋ฅผ ํตํด ๋ ํฐ ๋ฒ์์ ๊ณตํต ๋์์ ์ ์ํ ์ ์๋ค.
2D Sprite Package ์ค์น - Squre ์์ฑ
Image ์ถ๊ฐ ์์ฑ
// ์ธํฐํ์ด์ค๊ฐ ํจ์จ์ ์ด๋ค ์ด๋ฐ๊ฑด ์๋ค. ๊ตฌ์กฐ์ ๋ง์ถฐ์ ํด๋์ค๋ฅผ ์์ํ๊ฒ ํ ๊ฒ์ธ์ง.
// ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ ๊ฒ์ธ์ง ๊ณ ๋ฅด๋ฉด ๋๋ค.
public interface IDamagable
{
void TakePhysicalDamage(int damageAmout);
}
// Cass Condition : ์ฒด๋ ฅ, ๋ฐฐ๊ณ ํ, ์คํ๋ฏธ๋์ ๊ฐ๋ค
[System.Serializable] // ์ง๋ ฌํ
public class Condition
{
[HideInInspector]
public float curValue; // ์ต์๊ฐ
public float maxValue; // ์ต๋๊ฐ
public float startValue; // ์์๊ฐ
public float regenRate; // ํ๋ณต๋ฅ
public float decayRate; // ๊ฐ์๋ฅ
public Image uiBar;
public void Add(float amount)
{
curValue = Mathf.Min(curValue + amount, maxValue);
}
public void Subtract(float amount)
{
curValue = Mathf.Max(curValue - amount, 0.0f);
}
// ํผ์ผํธ๊ฐ๋ค์ ๋๋ถ๋ถ 0๊ณผ 1์ ์ฌ์ฉํ๋ค.
public float GetPercentage()
{
return curValue / maxValue;
}
}
public class PlayerConditions : MonoBehaviour, IDamagable
{
public Condition health;
public Condition hunger;
public Condition stamina;
public float noHungerHealthDecay; // ๋ฐฐ๊ณ ํ์ด ๋ค ๋ณ์ผ๋ฉด ํผ๊ฐ ์ค์ด๋ค๊ฒ
public UnityEvent onTakeDamage; // ๋ฐ๋ฏธ์ง๋ฅผ ๋ฐ์์ ๋, ์ฒ๋ฆฌํ ์ด๋ฒคํธ ๋ฐ์๋๊ธฐ ์ํ
void Start()
{
health.curValue = health.startValue;
hunger.curValue = hunger.startValue;
stamina.curValue = stamina.startValue;
}
void Update()
{
hunger.Subtract(hunger.decayRate * Time.deltaTime); // ๋ฐฐ๊ณ ํ์ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ณ๋๋ค.
stamina.Add(stamina.regenRate * Time.deltaTime); // ์คํ๋ฏธ๋๋ ์ฃผ๊ธฐ์ ์ผ๋ก ํ๋ณตํ๋ค.
if (hunger.curValue == 0.0f) // ๋ฐฐ๊ณ ํ์ด 0 ? ์ฒด๋ ฅ์ด ๋ณ๋๋ค
health.Subtract(noHungerHealthDecay * Time.deltaTime);
if (health.curValue == 0.0f) // ์ฒด๋ ฅ์ด 0 ? ์ฌ๋ง
Die();
// UIํ๋ฉด ๊ฒ์ด์ง ์
๋ฐ์ดํธ
health.uiBar.fillAmount = health.GetPercentage();
hunger.uiBar.fillAmount = hunger.GetPercentage();
stamina.uiBar.fillAmount = stamina.GetPercentage();
}
public void Heal(float amount)
{
health.Add(amount);
}
public void Eat(float amount)
{
hunger.Add(amount);
}
// ์คํ๋ฏธ๋๋ฅผ ์ฌ์ฉํ ์ ์๋์ง ์ฒดํฌ
public bool UseStamina(float amount)
{
if (stamina.curValue - amount < 0)
return false;
stamina.Subtract(amount);
return true;
}
public void Die()
{
Debug.Log("ํ๋ ์ด์ด ์ฌ๋ง");
}
public void TakePhysicalDamage(int damageAmount)
{
health.Subtract(damageAmount);
onTakeDamage?.Invoke();
}
}
Invoke(string methodName, float time) : ์ง์ ๋ ์๊ฐ (time) ํ์ ์ง์ ๋ ๋ฉ์๋ (methodName)๋ฅผ ์คํํ๋ค.
methodName
: ์คํํ ๋ฉ์๋์ ์ด๋ฆ์ ๋ฌธ์์ด๋ก ์ง์ ํ๋ค.
time
: ๋ฉ์๋๋ฅผ ์คํํ ์๊ฐ์ ์ด ๋จ์๋ก ์ง์ ํ๋ค.
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
private void Start()
{
Invoke("DelayedMethod", 2.0f);
}
private void DelayedMethod()
{
Debug.Log("This method is called after 2 seconds.");
}
}
InvokeRepeating(string methodName, float time, float repeatRate) : ์ง์ ๋ ์๊ฐ (time) ํ์ ์ง์ ๋ ๋ฉ์๋ (methodName)๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ฐ๋ณตํด์ ์คํํ๋ค.
methodName
: ์คํํ ๋ฉ์๋์ ์ด๋ฆ์ ๋ฌธ์์ด๋ก ์ง์
time
: ๋ฉ์๋๋ฅผ ์ฒ์ ์คํํ ๋ ๊น์ง์ ์๊ฐ์ ์ด ๋จ์๋ก ์ง์ ํ๋ค.
repeatRate
: ๋ฉ์๋๋ฅผ ๋ฐ๋ณตํด์ ์คํํ ์ฃผ๊ธฐ๋ฅผ ์ด ๋จ์๋ก ์ง์ ํ๋ค.
csharpCopy code
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
private void Start()
{
InvokeRepeating("RepeatingMethod", 2.0f, 3.0f);
}
private void RepeatingMethod()
{
Debug.Log("This method is called every 3 seconds after 2 seconds delay.");
}
}
TryGetComponent๋ Unity์์ ์ฌ์ฉํ๋ ๋ฉ์๋๋ก, ๊ฒ์ ์ค๋ธ์ ํธ์ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. ์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ํน์ ํ ์ปดํฌ๋ํธ๊ฐ ๊ฒ์ ์ค๋ธ์ ํธ์ ์ฐ๊ฒฐ๋์ด ์๋์ง ํ์ธํ๊ณ , ์ฐ๊ฒฐ๋์ด ์๋ค๋ฉด ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
TryGetComponent
๋ฉ์๋์ ํ์์ ๋ค์๊ณผ ๊ฐ๋ค.
public bool TryGetComponent<T>(out T component) where T : Component;
T
: ๊ฐ์ ธ์ค๋ ค๋ ์ปดํฌ๋ํธ์ ํ์
๋๋ค. MonoBehaviour
๋ฅผ ์์ํ ์ปดํฌ๋ํธ๋ ๋ชจ๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
component
: ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ ธ์ฌ ๋ ์ฌ์ฉ๋๋ out ๋งค๊ฐ๋ณ์์ด๋ค.
์ฌ์ฉ๋ฒ์ ์๋๊ณผ ๊ฐ๋ค.
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
private void Start()
{
// ๊ฒ์ ์ค๋ธ์ ํธ์ Rigidbody ์ปดํฌ๋ํธ๊ฐ ์๋์ง ํ์ธํ๊ณ ๊ฐ์ ธ์ต๋๋ค.
Rigidbody rb;
if (TryGetComponent<Rigidbody>(out rb))
{
// Rigidbody ์ปดํฌ๋ํธ๊ฐ ์๋ค๋ฉด ํด๋น ์ปดํฌ๋ํธ๋ก ์ํ๋ ๋์์ ์ํํฉ๋๋ค.
rb.AddForce(Vector3.up * 100f);
}
else
{
// Rigidbody ์ปดํฌ๋ํธ๊ฐ ์๋ค๋ฉด ๋ค๋ฅธ ์ฒ๋ฆฌ๋ฅผ ์ํํฉ๋๋ค.
Debug.Log("Rigidbody component not found.");
}
}
}
TryGetComponent
๋ ์ปดํฌ๋ํธ๊ฐ ์์ด๋ ์์ธ๋ฅผ ๋ฐ์์ํค์ง ์๊ณ , ์ปดํฌ๋ํธ๊ฐ ์์ผ๋ฉด ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ ธ์์ ์ฌ์ฉํ ์ ์๋ค. ์ด๋ฅผ ํตํด ๋ ์์ ํ๊ฒ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ฌ์ฉํ ์ ์๋ค.
CampFire ํ๋ฆฌํน์ Scene์ ๋ฐฐ์น
Capsule Collider ์ถ๊ฐ
public class CampFire : MonoBehaviour
{
public int damage;
public float damageRate;
private List<IDamagable> thingsToDamage = new List<IDamagable>(); // ์์ฃผ ๋ฃ๊ณ ๋นผ๊ณ ํ๊ธฐ์ ๊ท๋ชจ๊ฐ ์ปค์ง๋ฉด ๋ฆฌ์คํธ ๋ณด๋ค๋ ํด์ฌ๋ฅผ ์ฌ์ฉ ๊ถ์ฅ?
private void Start()
{
InvokeRepeating("DealDamage", 0, damageRate); // InvokeRepeating : n์ ์ง์ฐ์๊ฐ์ ์ฃผ๊ณ m๋งํผ ๊ณ์ ์ง์ฐ์คํ์ ํด๋ผ
}
void DealDamage()
{
for(int i = 0; i < thingsToDamage.Count; i++)
{
thingsToDamage[i].TakePhysicalDamage(damage);
}
}
// ์บ ํํ์ด์ด ๋ถ์ ๋ฟ์ผ๋ฉด ํธ๋ฆฌ๊ฑฐ ์ํฐ ํ์ฌ thingsToDamage ๋ฆฌ์คํธ์ ๋ฐ๋ฏธ์ง ์ ๋ณด๊ฐ ์ถ๊ฐ๋๊ณ
// InvokeRepeating ํ์ฌ DealDamage๊ฐ ๋์๊ฐ๋ฉฐ ๋ค์ด๊ฐ ๋งํผ์ ๋ฐ๋ฏธ์ง๋ฅผ ์ค๋ค. damageRate ์๋๋ก ๊ณ์ ์คํ
// ํธ๋ฆฌ๊ฑฐ ์์ํธ ํ๋ฉด ๋ค์ thingsToDamage ๋ฆฌ์คํธ์ ๋ฐ๋ฏธ์ง ์ ๋ณด๊ฐ ๋น ์ง๋ค.
private void OnTriggerEnter(Collider other)
{
// TryGetComponent : boolํ์ ํจ์๋ก ํด๋น ํ์
์ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์๋ค๋ฉด true ๋ฐํ ์๋๋ฉด falseํ๋ ํจ์
// ์ฐพ์๋ค๋ฉด out๋๋ component์ ํด๋น ํ์
์ ์ปดํฌ๋ํธ๋ฅผ ํ ๋นํด์ค๋ค.
if (other.gameObject.TryGetComponent(out IDamagable damagable))
{
thingsToDamage.Add(damagable); // thingsToDamage ์ IDamagable ์ Add
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.TryGetComponent(out IDamagable damagable))
{
thingsToDamage.Remove(damagable);
}
}
}
public class DamageIndicator : MonoBehaviour
{
public Image image;
public float flashSpeed;
private Coroutine coroutine;
public void Flash()
{
if (coroutine != null) // coroutine์ด null์ด ์๋๋ผ๋ ๊ฒ์ ์ด์ ์ coroutine์ด ๋์๊ฐ ์ ์ด ์๋ค๋ ๊ฒ
{
StopCoroutine(coroutine); // ๊ทธ๋์ ์ค์ง๋ฅผ ๋จผ์ ์์ผ์ค๋ค.
}
image.enabled = true;
image.color = Color.red;
coroutine = StartCoroutine(FadeAway());
}
private IEnumerator FadeAway()
{
float startAlpha = 0.3f;
float a = startAlpha;
while(a > 0.0f)
{
a -= (startAlpha / flashSpeed) * Time.deltaTime; // ํ๋ฒ์ ๊น์ ๊ฐ
image.color = new Color(1.0f, 0.0f, 0.0f, a);
yield return null;
}
image.enabled = false;
}
}
AnimationCurve๋ Unity์์ ์ ๋๋ฉ์ด์ ์ ํคํ๋ ์(Keyframe)์ ์ด์ฉํ์ฌ ๊ฐ์ ๋ณด๊ฐ(interpolate)ํ๋๋ฐ ์ฌ์ฉ๋๋ ํด๋์ค์ด๋ค. ์ด ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ์๊ฐ์ ๋ฐ๋ผ ๊ฐ์ ๋ถ๋๋ฝ๊ฒ ๋ณํ์ํค๋ ์ปค๋ธ๋ฅผ ์ ์ํ๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ๋๋ฉ์ด์ ์ ๋ง๋ค ์ ์๋ค.
AnimationCurve
ํด๋์ค์ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์ฑ ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
ํค ํ๋ ์(Keyframe) : ์๊ฐ์ ๋ฐ๋ฅธ ๊ฐ์ ์ ์ํ๋ ์ ์ ์๋ฏธํ๋ค. ํคํ๋ ์์ ์๊ฐ(t)๊ณผ ํด๋น ์๊ฐ์ ๋์ํ๋ ๊ฐ(value)์ผ๋ก ์ด๋ฃจ์ด์ง๋ค.
๋ณด๊ฐ ๋ฐฉ์(Interpolation Mode) : ์ธ์ ํ ํคํ๋ ์ ์ฌ์ด์ ๊ฐ์ ๋ณด๊ฐํ๋ ๋ฐฉ๋ฒ์ ์ง์ ํ๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก๋ Cubic Bezier ๋ณด๊ฐ์ด ์ฌ์ฉ๋๋ฉฐ, ์ ํ, ์คํ , ๋ฑ ๋ค์ํ ๋ณด๊ฐ ๋ฐฉ์์ ์ ํํ ์ ์๋ค.
์ฃผ์ํ AnumationCurve
๋ฉ์๋ ๋ฐ ์ฌ์ฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
AddKey
: ์๋ก์ด ํคํ๋ ์์ ์ถ๊ฐํ๋ค.using UnityEngine;
public class ExampleScript : MonoBehaviour
{
private AnimationCurve curve;
private void Start()
{
// ์๋ก์ด AnimationCurve ์์ฑ
curve = new AnimationCurve();
// ํคํ๋ ์ ์ถ๊ฐ (์๊ฐ, ๊ฐ)
curve.AddKey(0f, 0f);
curve.AddKey(1f, 1f);
}
private void Update()
{
// ์๊ฐ์ ๋ฐ๋ผ ๊ฐ์ ๋ณด๊ฐํ์ฌ ์ถ๋ ฅ
float time = Time.time;
float value = curve.Evaluate(time);
Debug.Log("Time: " + time + ", Value: " + value);
}
}
Evaluate
: ํน์ ์๊ฐ์ ํด๋นํ๋ ๊ฐ์ ๋ณด๊ฐํ์ฌ ๋ฐํํ๋ค.
Keys
: ํคํ๋ ์์ ๋ฐฐ์ด์ ๊ฐ์ ธ์จ๋ค. ์ด๋ฅผ ํตํด ํคํ๋ ์๋ค์ ์ถ๊ฐ, ์์ , ์ญ์ ํ ์ ์๋ค.
AnimationCurve
๋ ์ฃผ๋ก ์ ๋๋ฉ์ด์
์๊ฐ์ ๋ฐ๋ฅธ ๊ฐ์ ์ ์ํ๋๋ฐ ์ฌ์ฉ๋๋ฉฐ, ํนํ ๋ ๋ณต์กํ ์ ๋๋ฉ์ด์
์ ์ ์ดํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ์๋ฅผ ๋ค์ด, ์ค๋ธ์ ํธ์ ์์ง์, ํฌ๊ธฐ ์กฐ์ , ํ์ ๋ฑ์ ๋ํ ์ ๋๋ฉ์ด์
์ ์ ์ํ๊ฑฐ๋, ์ฌ๋ฏธ์๋ ๊ฒ์ ์์๋ค์ ํ์ฉํ ์ ์๋ค.
public class DayNightCycle : MonoBehaviour
{
[Range(0.0f, 1.0f)]
public float time;
public float fullDayLength; // ํ๋ฃจ์ ๊ธธ์ด(์๊ฐ)
public float startTime = 0.4f;
private float timeRate;
public Vector3 noon;
[Header("Sun")]
public Light sun;
public Gradient sunColor; // Gradient : ๊ทธ๋ผ๋ฐ์ด์
public AnimationCurve sunIntensity; // AnimationCurve : ๊ทธ๋ํ๋ฅผ ๋ง๋ค ์ ์๋ค. ๊ทธ๋ฌ๋ฉด ์ํ๋ ํ์๊ฐ์ ๊ทธ๋ ๊ทธ๋ ๊บผ๋ด์ ์ฌ์ฉ๊ฐ๋ฅ.
[Header("Moon")]
public Light moon;
public Gradient moonColor;
public AnimationCurve moonIntensity;
[Header("Other Lighting")]
public AnimationCurve lightingIntensityMultiplier; // ํ๊ฒฝ๊ด
public AnimationCurve reflectionIntensityMultiplier; // ๋ฐ์ฌ๊ด
private void Start()
{
timeRate = 1.0f / fullDayLength;
time = startTime;
}
private void Update()
{
time = (time + timeRate * Time.deltaTime) % 1.0f; // time ๊ฐ์ ํผ์ผํธ(%)๋ก ์ธ ๊ฒ์ด๋ผ % 1.0 ์ ํด์ค๋ค. (๊ทธ๋์ time์ 0 ~ 0.9999... ๊น์ง๋ง ์ค์ ๊ฐ๋ฅ)
UpdateLighting(sun, sunColor, sunIntensity);
UpdateLighting(moon, moonColor, moonIntensity);
// ๋น์ด ๋ฐ๋๋ ๊ฒ์ด์ง ํ๋ก์ ํธ์ ์ ์ฒด์ ์ธ ํ๊ฒฝ์ด ๋ฐ๋๋ ๊ฒ์ด ์๋๋ค.
// ์ค์ Sun์ด๋ Moon ์ค๋ธ์ ํธ๋ฅผ ๋๊ฑฐ๋ ์ผ๋ ์ฐจ์ด๊ฐ ํฌ์ง ์๋ค.
// ๊ทธ๋์ ๊ทธ ๋ถ๋ถ์ ์กฐ์ ํด์ค๋ค.
RenderSettings.ambientIntensity = lightingIntensityMultiplier.Evaluate(time); // ๋น์ ๊ณ์ ์ต์ํ ์์ผ์ค๋ ค ํ๋ค.
RenderSettings.reflectionIntensity = reflectionIntensityMultiplier.Evaluate(time);
}
void UpdateLighting(Light lightSource, Gradient colorGradiant, AnimationCurve intensityCurve)
{
float intensity = intensityCurve.Evaluate(time); // AnimationCurve๋ ์๊ฐ๊ฐ์ ์ฃผ๋ฉด ๊ทธ ์๊ฐ์ ๋ง๋ ๊ทธ๋ํ ๊ฐ์ ๊ฐ์ ธ์จ๋ค.
// ์ค์ ๋ก ์ด Direction Light๊ฐ ๊ฐ๋๊ฐ ๋ณํ๋ ๊ฒ์ ๋ง์ถฐ์ ๋น์ด ๋ฐ๋๊ธฐ ๋๋ฌธ์, ํ๋น์ ๋ณํ์ ๊ฑฐ์ ์ ์ฌํ๋ค.
// noon = ์ ์ฌ , ์ ์ฌ์ ํ๋น์ ์์์ ์๋ค(90๋) ๋ฎ์ด ๋๊ณ ๋ฐค์ด ๋๋๊ฑธ ๊ตฌ์ฒด๋ก ๋ฐ์ ธ 4๋ฑ๋ถ์ ํ๋ฉด ๋ฎ์ด 2๋ฒ ๋๊ณ ๋ฐค์ด 2๋ฒ ๋๋ ๊ฒ์ด๋ค.
// noon * 4.0f : 90๋ * 4.0f = 360๋ ์ธ๋ฐ ightSource == sun ? 0.25f : 0.75f ์์
// lightSource๊ฐ sun์ผ ๋, ์์ ๋ดค์ ๋, ์์ชฝ ์ค์์์ ๋ ์ผํ๋ค ๊ทธ๋์ ์์ 90๋ ์ง์ , 1 / 4 ์ง์ , 25%์ง์ ์ฆ 0.25f
// ๋ฐค ์ฆ, ๋ฌ์ด ๊ฐ์ฅ ๊ผญ๋๊ธฐ์ ์๋ ์ํ๋ 0.75f, ๋ฐ๋ฐํด์ ๋ฐ๋ฐ๋ฐํด ๋์ 3 / 4 ๋์์ ๋
// 0.25f || 0.75f ์ํ (sun, moon) ์ผ ๋, 90๋๋ฅผ ๊ณฑํด์ฃผ๋ ๊ฒ
// 360๋๋ฅผ ๊ธฐ์ค์ผ๋ก ํด, ๋ฌ์ด ๋จ๋ ์์น๋ฅผ ์ก์์ฃผ๋ ๊ฒ์ด๋ค.
lightSource.transform.eulerAngles = (time - (lightSource == sun ? 0.25f : 0.75f)) * noon * 4.0f;
lightSource.color = colorGradiant.Evaluate(time);
lightSource.intensity = intensity;
GameObject go = lightSource.gameObject;
if (lightSource.intensity == 0 && go.activeInHierarchy)
go.SetActive(false);
else if (lightSource.intensity > 0 && !go.activeInHierarchy)
go.SetActive(true);
}
}
Other Lighting ๋ ์์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์ค์
๋ค์ํ๊ฒ ์ค์ ์ ๋ณ๊ฒฝํ๋ฉด์ ์ํ๋ ํจ๊ณผ๋ฅผ ๋ง๋ค์ด ๋ณผ ์ ์๋ค.
public enum ItemType
{
Resource, // ๋ฆฌ์์ค
Equipable, // ์ฅ์ฐฉ
Consumable // ์๋ชจ
}
public enum ConsumableType
{
Hunger,
Health
}
[CreateAssetMenu(fileName = "Item", menuName = "New Item")]
public class ItemData : ScriptableObject
{
[Header("Info")]
public string displayName;
public string description;
public ItemType type;
public Sprite icon;
public GameObject dropPrefab;
[Header("Stacking")]
public bool canStack;
public int maxStackAmount;
}
public class ItemObject : MonoBehaviour, IInteractable
{
public ItemData item;
public string GetInteractPrompt()
{
return string.Format("Pickup {0}", item.displayName);
}
public void OnInteract()
{
Destroy(gameObject);
}
}
public interface IInteractable
{
string GetInteractPrompt();
void OnInteract();
}
public class InteractionManager : MonoBehaviour
{
public float checkRate = 0.05f;
private float lastCheckTime;
public float maxCheckDistance;
public LayerMask layerMask;
private GameObject curInteractGameObject;
private IInteractable curInteractable;
public TextMeshProUGUI promptText;
private Camera camera;
void Start()
{
camera = Camera.main; // main ํ๊ทธ๋ฅผ ์ฐ๊ณ ์๋ ์นด๋ฉ๋ผ ํ๋๋ง ์๋์ ์ผ๋ก ์กํ๋ค.
}
void Update()
{
if (Time.time - lastCheckTime > checkRate)
{
lastCheckTime = Time.time;
// RaycastHit์ ์ ๋๋ Ray๋ฅผ ํ๋ ์ค๋นํด์ค์ผ ํ๋ค. ์ด ๋ ์ด์ ๋ฅผ ์ด๋๋ค ์ ๊ฑด์ง ์ค๋น๋ฅผ ํ๊ณ
// ๋ ์ด์ ๋ฅผ ์ด๋ฐ์ ์ถฉ๋์ด ์ผ์ด๋ ์ ๋ณด๋ฅผ ๋ฐ์์จ๋ค.
Ray ray = camera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2)); // ํ๋ฉด์ ์ ์ค์์์ ์๊ฒ๋ค.
RaycastHit hit; // ์ถฉ๋ ์ ๋ณด๋ฅผ ๋ด์ hit
if (Physics.Raycast(ray,out hit, maxCheckDistance, layerMask)) // ray๊ฐ ์ถฉ๋ ํ๋ค๋ฉด?
{
if (hit.collider.gameObject != curInteractGameObject) // ์ถฉ๋ํ ์ค๋ธ์ ํธ๊ฐ ์ ์ฅํด๋จ๋ ์ค๋ธ์ ํธ๋ ๋ค๋ฅธ๊ฐ?
{
curInteractGameObject = hit.collider.gameObject;
curInteractable = hit.collider.GetComponent<IInteractable>();
SetPromtText();
}
}
else // ray๊ฐ ์ถฉ๋ ํ๊ฒ ์๋ค๋ฉด?
{
curInteractGameObject = null;
curInteractable = null;
promptText.gameObject.SetActive(false);
}
}
}
private void SetPromtText()
{
promptText.gameObject.SetActive(true);
promptText.text = string.Format("<b>[E]</b> {0}", curInteractable.GetInteractPrompt());
}
public void OnInteractInput(InputAction.CallbackContext callbackContext)
{
if (callbackContext.phase == InputActionPhase.Started && curInteractable != null) // ํค๊ฐ ๋๋ฌ์ก๊ณ ๋๋ ค์๋ ์์ ์ ๋ฐ๋ผ๋ณด๋ ๋ฌผ์ฒด๊ฐ null์ด ์๋๋ผ๋ฉด
{
// ์์ดํ
์ ๋จน์ผ๋ฉด ์์ดํ
์ ๋ํ ์ํธ์์ฉ์ ์งํํ๊ณ ์ด๊ธฐํ๋ฅผ ์งํ.
curInteractable.OnInteract();
curInteractGameObject = null;
curInteractable = null;
promptText.gameObject.SetActive(false);
}
}
}
Character Sequence
32-126,44032-55203,12593-12643,8200-9900์
์์ด ๋ฒ์ : 32-126
ํ๊ธ ๋ฒ์ : 44032-55203
ํ๊ธ ์๋ชจ : 12593-12643
ํน์ ๋ฌธ์ : 8200-9900
๊ธ๊ผด์ ๋ฐ๋ผ ์ง์ํ์ง ์๋ ํน์ ๋ฌธ์๋ missing
์์ ํ Save