Unity ์ˆ™๋ จ - 3

์ด์ค€ํ˜ธยท2023๋…„ 12์›” 12์ผ
0

๐Ÿ“Œ Unity ๊ฒŒ์ž„ ๊ฐœ๋ฐœ ์ˆ™๋ จ



๐Ÿ“Œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปจํŠธ๋กค

โž” ๐Ÿ”ฅ ํ•ต์‹ฌ ๋‚ด์šฉ

Light ์ปดํฌ๋„ŒํŠธ

  • ๋ผ์ดํŠธ ์†Œ์Šค : ๊ฒŒ์ž„ ๋˜๋Š” 3D ๋žœ๋”๋ง์— ๊ด‘์›์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ์ด๊ฒƒ์€ ํŠน์ • ์œ„์น˜ ๋˜๋Š” ๋ฐฉํ–ฅ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋น›์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

  • ์œ ํ˜• : ๋ผ์ดํŠธ๋Š” ์—ฌ๋Ÿฌ์œ ํ˜•์ด ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ์œ ํ˜• ์ค‘ ๋ช‡๊ฐ€์ง€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

    	- ์  ๊ด‘์›(Point Light) : ํŠน์ • ์ง€์ ์—์„œ ๋ชจ๋“  ๋ฐฉํ–ฅ์œผ๋กœ ๋น›์„ ๋ฐฉ์‚ฐํ•˜๋Š” ๋ผ์ดํŠธ์ด๋‹ค.
    • ๋ฐฉํ–ฅ์„ฑ ๋ผ์ดํŠธ(Directional Light) : ํŠน์ • ๋ฐฉํ–ฅ์—์„œ ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ๋น„์ถ”๋Š” ๋ผ์ดํŠธ์ด๋‹ค. ์ด ๋ผ์ดํŠธ๋Š” ๊ฑฐ๋ฆฌ์— ๊ด€๊ฒŒ์—†์ด ๋™์ผํ•œ ๊ฐ•๋„๋กœ ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ๋น„์ถ˜๋‹ค.
    • ์ŠคํฌํŠธ๋ผ์ดํŠธ(Spot Light) : ํŠน์ • ๋ฐฉํ–ฅ์œผ๋กœ ์›๋ฟ”ํ˜•์˜ ๋น›์„ ๋ฐœ์‚ฐํ•˜๋Š” ๋ผ์ดํŠธ์ด๋‹ค.
    • ์˜์—ญ ๋ผ์ดํŠธ(Area Light) : ํŠน์ • ์˜์—ญ์—์„œ ๋ฐœ์ƒํ•˜๊ณ  ๊ทธ ์ฃผ๋ณ€์— ๋น›์„ ๋ฐœ์‚ฐํ•˜๋Š” ๋ผ์ดํŠธ์ด๋‹ค. (์˜ˆ : ์‹ค๋‚ด ์กฐ๋ช…).
  • ์†์„ฑ : ๊ฐ ๋ผ์ดํŠธ์—๋Š” ์—ฌ๋Ÿฌ ์†์„ฑ์ด ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ์†์„ฑ์—๋Š” ์œ„์น˜, ๋ฐฉํ–ฅ, ๊ฐ•๋„(intensiy), ์ƒ‰์ƒ(color), ๋ฒ”์œ„(range), ๊ฐ๋„(angle) ๋“ฑ์ด ํฌํ•จ๋œ๋‹ค.

  • ๊ทธ๋ฆผ์ž : ๋ผ์ดํŠธ๋Š” ๊ทธ๋ฆผ์ž๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ผ์ดํŠธ์™€ ๊ฐ์ฒด ์‚ฌ์ด์˜ ๊ด€๊ณ„์— ๋”ฐ๋ผ ๊ทธ๋ฆผ์ž๋Š” ๋ผ์ดํŠธ๊ฐ€ ๋ถ€๋”ชํžˆ๋Š” ๊ฐ์ฒด ๋’ค์— ์ƒ์„ฑ๋œ๋‹ค.

  • ์„ฑ๋Šฅ : ๋ผ์ดํŠธ๋Š” ๋ Œ๋”๋ง ์„ฑ๋Šฅ์— ํฐ ์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค. ๋งŽ์€ ๋ผ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠนํžˆ ๋™์  ๊ทธ๋ฆผ์ž๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ ๋ Œ๋”๋ง ์„ฑ๋Šฅ์— ๋ถ€์ •์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ตœ์ ํ™”๋Š” ์ค‘์š”ํ•œ ๊ณ ๋ ค์‚ฌํ•ญ์ด๋‹ค.

  • ๋น› ๋ฐ˜์‚ฌ ๋ฐ ์‚ฐ๋ž€ : ๋ผ์ดํŠธ๋Š” ํ‘œ๋ฉด์— ๋ถ€๋”ชํžˆ๊ณ  ๋ฐ˜์‚ฌ๋˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ฐฉํ–ฅ์œผ๋กœ ์‚ฐ๋ž€๋˜์–ด ์žฌ์งˆ๊ณผ ํ‘œ๋ฉด์˜ ์‹ค์ œ์„ฑ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์ด๋Ÿฌํ•œ ํšจ๊ณผ๋Š” ๋ฌผ๋ฆฌ ๊ธฐ๋ฐ˜ ๋ Œ๋”๋ง(PBR)์—์„œ ์ค‘์š”ํ•œ ์š”์†Œ๋‹ค.

์Šค์นด์ด๋ฐ•์Šค

๊ฒŒ์ž„ ์„ธ๊ฒŒ์˜ ๋ฐฐ๊ฒฝ์„ ๋‘˜๋Ÿฌ์‹ธ๋Š” ํ™˜๊ฒฝ ๋งคํ•‘ ๊ธฐ์ˆ ์ด๋‹ค. ํ๋ธŒ ๋งต(Cube Map)๊ณผ ๊ตฌ์ฒดํ˜• ์Šค์นด์ด๋ฐ•์Šค(Sphere Map)๋“ฑ์ด ์žˆ์œผ๋ฉฐ, ์ฃผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ง„๋‹ค.

  • ์Šค์นด์ด๋ฐ•์Šค๋Š” 6๊ฐœ์˜ ํ…์Šค์ฒ˜๋กœ ๊ตฌ์„ฑ๋œ ํ๋ธŒ ๋งต ๋˜๋Š” ํ•˜๋‚˜์˜ ๊ตฌ์ฒด๋กœ ํ…์Šค์ฒ˜๊ฐ€ ๋งคํ•‘๋œ ๊ตฌ์ฒดํ˜• ์Šค์นด์ด๋ฐ•์Šค๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.

  • Unity์—์„œ๋Š” ์”ฌ์˜ ๋ฐฐ๊ฒฝ์œผ๋กœ ์‚ฌ์šฉ๋˜๋ฉฐ, ๊ฒŒ์ž„ ํ™˜๊ฒฝ์„ ํ™•์žฅ์‹œํ‚ค๋Š”๋ฐ ํ™œ์šฉ๋œ๋‹ค.

  • ์ฃผ๋กœ ํ•˜๋Š˜, ๊ตฌ๋ฆ„, ์‚ฐ ๋“ฑ์˜ ์ž์—ฐ์ ์ธ ๋ฐฐ๊ฒฝ์„ ํ‘œํ˜„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

  • ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ์Šค์นด์ด๋ฐ•์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ง์ ‘ ๋งŒ๋“ค์–ด์„œ Unity์—์„œ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๊ฒŒ์ž„ ์ค‘์— ์Šค์นด์ด๋ฐ•์Šค๋ฅผ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ๋‚ฎ๊ณผ ๋ฐค ๋“ฑ์˜ ์‹œ๊ฐ„๋Œ€๋‚˜ ํŠน์ • ์ด๋ฒคํŠธ์— ๋งž๊ฒŒ ๋ฐฐ๊ฒฝ์„ ๋ณ€ํ™”์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

  • ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋ฏ€๋กœ ์ตœ์ ํ™”์— ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.

Rigidbody - ForceMode

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 ์ด๋ฆ„ ๋ณ€๊ฒฝ - ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝ(์ƒ‰์€ ์ž์œ ๋กญ๊ฒŒ)

  • Window - Rendering - Lighting โž” Skybox Material ์— ์„ค์ •





โž” ํ”Œ๋ ˆ์ด์–ด ์ค€๋น„ํ•˜๊ธฐ

์˜ค๋ธŒ์ ํŠธ ์ค€๋น„

  • ๋นˆ ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ - Player ์ด๋ฆ„ ๋ณ€๊ฒฝ

  • ํ•˜์œ„์— ๋นˆ ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ - CameraContainer ์ƒ์„ฑ

  • Main Camera ํ•˜์œ„๋กœ ๋ณ€๊ฒฝ

  • Capsule Collider ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€

  • Rigidbody ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€

  • ์•„๋ž˜์™€ ๊ฐ™์ด Input Action ์ค€๋น„

  • Player Input ์ถ”๊ฐ€

  • Invoke Unity Events๋กœ ๋ณ€๊ฒฝ

์ŠคํŠธ๋ฆฝํŠธ ์ค€๋น„ (PlayerController.cs)

  • PlayerController ์Šคํฌ๋ฆฝํŠธ ์ƒ์„ฑ

  • ์ถ”๊ฐ€
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);
    }
}











๐Ÿ“Œ ํ”Œ๋ ˆ์ด์–ด ์ƒํƒœ ๋ฐ UI

โž” ๐Ÿ”ฅ ํ•ต์‹ฌ ๋‚ด์šฉ

์ธํ„ฐํŽ˜์ด์Šค ๋ณต์Šต

์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ํด๋ž˜์Šค๋“ค์€ ๊ณตํ†ต์ ์ธ ๋™์ž‘์„ ์ •์˜ํ•˜๊ณ , ์ด๋Ÿฌํ•œ ๋™์ž‘๋“ค์„ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค๋“ค์€ ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„(implement)ํ•จ์œผ๋กœ์จ ๊ณตํ†ต ๊ทœ์•ฝ์„ ์ค€์ˆ˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์„ค๋ช…ํ•˜๋Š” ์ฃผ์š” ํŠน์ง•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์ถ”์ƒํ™” : ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ถ”์ƒ์ ์ธ ๊ฐœ๋…์œผ๋กœ, ์‹ค์ œ๋กœ ๊ตฌํ˜„๋œ ๋ฉ”์„œ๋“œ๊ฐ€ ์—†๊ณ , ๋ฉ”์„œ๋“œ์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋งŒ์„ ๊ฐ€์ง„๋‹ค. ๋”ฐ๋ผ์„œ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ธ์Šคํ„ด์Šคํ™”๋  ์ˆ˜ ์—†์œผ๋ฉฐ, ๊ตฌํ˜„์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

  • ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ : ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๊ตฌํ˜„ ํด๋ž˜์Šค๊ฐ€ ๋ฐ˜๋“œ์‹œ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์ •์˜ํ•œ๋‹ค. ๋ฉ”์„œ๋“œ์˜ ์ด๋ฆ„, ๋งค๊ฐœ๋ณ€์ˆ˜, ๋ฐ˜ํ™˜ ํƒ€์ž…์ด ํฌํ•จ๋œ๋‹ค.

  • ๋‹ค์ค‘ ์ƒ์† ๊ฐ€๋Šฅ : ํด๋ž˜์Šค๋Š” ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋งŒ ์ƒ์†๋ฐ›์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์—ฌ๋Ÿฌ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋™์‹œ์— ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์ค‘ ์ƒ์†์„ ํ‰๋‚ด๋‚ด๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

  • ๊ฐ•์ œ์  ๊ตฌํ˜„ : ํด๋ž˜์Šค๊ฐ€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด, ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์ •์˜ํ•œ ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜๋“œ์‹œ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. ์ด๋กœ ์ธํ•ด ํด๋ž˜์Šค๋Š” ์ธํ„ฐํŽ˜์ด์Šค์— ์ •์˜๋œ ๋™์ž‘์„ ๊ฐ•์ œ๋กœ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋œ๋‹ค.

  • ์ธํ„ฐํŽ˜์ด์Šค ๊ฐ„ ํ™•์žฅ : ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋‹ค๋ฅธ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™•์žฅ(extends)ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฆฌ๋ฅผ ํ†ตํ•ด ๋” ํฐ ๋ฒ”์œ„์˜ ๊ณตํ†ต ๋™์ž‘์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.






โž” UI ๋งŒ๋“ค๊ธฐ

UI ์ƒ์„ฑ

  • Canvas ์ƒ์„ฑ - Canvas Scaler ์„ค์ •

  • Health ๋ฐฐ๊ฒฝ ์ค€๋น„

  • Image โž” Icon ์ถ”๊ฐ€

  • 2D Sprite Package ์„ค์น˜ - Squre ์ƒ์„ฑ

  • Image ์ถ”๊ฐ€ ์ƒ์„ฑ

  • ๋นˆ ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ โž” Conditions ์ƒ์„ฑ

  • ์•ž์„œ ๋งŒ๋“  Health๋ฅผ ์ด์šฉํ•ด 3๊ฐœ์˜ ์†์„ฑ ์ƒ์„ฑ

  • Image ์ถ”๊ฐ€ โž” Crosshair ์ƒ์„ฑ

  • Image ์ถ”๊ฐ€ โž” DamageIndicator ์ƒ์„ฑ






โž” ํ”Œ๋ ˆ์ด์–ด ์†์„ฑ

PlayerConditions.cs ์ถ”๊ฐ€

// ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ํšจ์œจ์ ์ด๋‹ค ์ด๋Ÿฐ๊ฑด ์—†๋‹ค. ๊ตฌ์กฐ์— ๋งž์ถฐ์„œ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๊ฒŒ ํ•  ๊ฒƒ์ธ์ง€.
// ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ์ง€ ๊ณ ๋ฅด๋ฉด ๋œ๋‹ค.
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

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.");
    }
}

Invoke Repeating

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

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 ์ถ”๊ฐ€

CampFire ๋งŒ๋“ค๊ธฐ

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);
        }
    }
}





โž” ๋ฐ๋ฏธ์ง€ ์ธ๋””์ผ€์ดํ„ฐ ์ ์šฉํ•˜๊ธฐ

์˜ค๋ธŒ์ ํŠธ ์„ค์ •

  • HUDCanvas - DamageIndicator ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •

  • Player - Player Conditions - OnTake Damage ์„ค์ •

DamageIndicator ๋งŒ๋“ค๊ธฐ

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;
    }
}











๐Ÿ“Œ ๋‚ฎ๊ณผ ๋ฐค ๊ตฌํ˜„ (๊ณต๋ถ€ ํ•„์š” ์ดํ•ด x)

โž” ๐Ÿ”ฅ ํ•ต์‹ฌ ๋‚ด์šฉ

AnimationCurve

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๋Š” ์ฃผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ๊ฐ’์„ ์ •์˜ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋ฉฐ, ํŠนํžˆ ๋” ๋ณต์žกํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์˜ค๋ธŒ์ ํŠธ์˜ ์›€์ง์ž„, ํฌ๊ธฐ ์กฐ์ •, ํšŒ์ „ ๋“ฑ์— ๋Œ€ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ •์˜ํ•˜๊ฑฐ๋‚˜, ์žฌ๋ฏธ์žˆ๋Š” ๊ฒŒ์ž„ ์š”์†Œ๋“ค์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.






โž” ๋‚ฎ๊ณผ ๋ฐค ๊ตฌํ˜„

๋ผ์ดํŠธ ์…‹ํŒ… ์„ค์ •

  • ๊ธฐ์กด์˜ Directional Light - Sun์œผ๋กœ ์ˆ˜์ •

  • Sun - Ctrl + D - Moon์œผ๋กœ ๋ณ€๊ฒฝ

DayNightCycle.cs

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);
    }
}

์˜ค๋ธŒ์ ํŠธ ์„ค์ •

  • ์ตœ์ข…์ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •

  • Sun Intensity

  • Moon Intensity

  • Other Lighting ๋„ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ์„ค์ •

  • ๋‹ค์–‘ํ•˜๊ฒŒ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ ์›ํ•˜๋Š” ํšจ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค.












๐Ÿ“Œ ์•„์ดํ…œ๊ณผ ์ƒํ˜ธ์ž‘์šฉ

โž” ์•„์ดํ…œ ๋ฐ์ดํ„ฐ ๋งŒ๋“ค๊ธฐ

์ค€๋น„

  • ์ด๋ฏธ์ง€ ์Šคํ”„๋ผ์ดํŠธ ํ™•์ธ

  • ๋ ˆ์ด์–ด ์ถ”๊ฐ€

๋ฐ์ดํ„ฐ ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

  • Item Data ์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€

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;
}

๋ฐ์ดํ„ฐ ์ƒ์„ฑ

์˜ค๋ธŒ์ ํŠธ ๋งŒ๋“ค๊ธฐ

  • Wood

  • Carrot

  • Rock

  • Axe

  • Sword

  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ์ ˆํ•œ ์˜ค๋ธŒ์ ํŠธ์™€ ์ฝœ๋ผ์ด๋” ์‚ฌ์ด์ฆˆ ๊ตฌ์„ฑ






โž” ์•„์ดํ…œ ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

ItemObject

public class ItemObject : MonoBehaviour, IInteractable
{
    public ItemData item;

    public string GetInteractPrompt()
    {
        return string.Format("Pickup {0}", item.displayName);
    }
    public void OnInteract()
    {
        Destroy(gameObject);
    }
}

InteractionManager

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);
        }
    }
}





โž” ํ”„๋กฌํ”„ํŠธ ์ ์šฉํ•˜๊ธฐ

UI ๋งŒ๋“ค๊ธฐ

  • Hid Canvas - Text ์ƒ์„ฑ

ํฐํŠธ ๋งŒ๋“ค๊ธฐ

  • Font Asset Creator ํด๋ฆญ

  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ • ํ›„ Generate Font Atlas

  • Character Sequence
    32-126,44032-55203,12593-12643,8200-9900์—
    ์˜์–ด ๋ฒ”์œ„ : 32-126
    ํ•œ๊ธ€ ๋ฒ”์œ„ : 44032-55203
    ํ•œ๊ธ€ ์ž๋ชจ : 12593-12643
    ํŠน์ˆ˜ ๋ฌธ์ž : 8200-9900
    ๊ธ€๊ผด์— ๋”ฐ๋ผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํŠน์ˆ˜ ๋ฌธ์ž๋Š” missing

  • ์™„์š” ํ›„ Save

ํ”„๋กฌํ”„ํŠธ ์ ์šฉํ•˜๊ธฐ

  • PromptText - Font ๋ณ€๊ฒฝ

  • Interaction Manager ์„ค์ •

  • Player Input Event ์„ค์ •












0๊ฐœ์˜ ๋Œ“๊ธ€