์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 13)

2024. 2. 3. 20:02ยท๐Ÿ“‚ Unity Engine Study/๐Ÿ“„ Unity ์ธํ”„๋Ÿฐ ๊ฐ•์˜
๋ชฉ์ฐจ
  1. [ ์„น์…˜ 13. ๋ฏธ๋‹ˆ RPG ]
โ€ข [ ์„น์…˜ 13. ๋ฏธ๋‹ˆ RPG ]
โ€ข ๏ผƒ ํ™˜๊ฒฝ์„ธํŒ…
โ€ข ๏ผƒ ์ด๋™
โ€ข ๏ผƒ ์Šคํƒฏ
โ€ข ๏ผƒ ๋งˆ์šฐ์Šค ์ปค์„œ
โ€ข ๏ผƒ ํƒ€๊ฒŒํŒ… ๋ฝ์˜จ
โ€ข ๏ผƒ ๊ณต๊ฒฉ #1
โ€ข ๏ผƒ ๊ณต๊ฒฉ #2
โ€ข ๏ผƒ ์ฒด๋ ฅ ๊ฒŒ์ด์ง€ #1
โ€ข ๏ผƒ ์ฒด๋ ฅ ๊ฒŒ์ด์ง€ #2
โ€ข ๏ผƒ ๋ชฌ์Šคํ„ฐ AI #1
โ€ข ๏ผƒ๋ชฌ์Šคํ„ฐ AI #2
โ€ข ๏ผƒ Destroy #1
โ€ข ๏ผƒ Destroy #2
โ€ข ๏ผƒ ๋ ˆ๋ฒจ์—…
โ€ข ๏ผƒ ๋ชฌ์Šคํ„ฐ ์ž๋™ ์ƒ์„ฑ

 

 

 

 

[ ์„น์…˜ 13. ๋ฏธ๋‹ˆ RPG ]

๏ผƒ ํ™˜๊ฒฝ์„ธํŒ…

  - Terrain์€ Unity์—์„œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ง€ํ˜•์„ ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณตํ•œ Tool์ด๋‹ค.

  - Terrain์€ [ Hierarchy ] - [ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ] - [ 3D Object ] - [ Terrain ] ์„ ํ†ตํ•ด ์ƒ์„ฑํ•œ๋‹ค.

 

  - Light์˜ Type์—๋Š” Realtime, Mixed, Baked๊ฐ€ ์žˆ๋‹ค.

   > Realtime์€ ์กฐ๋ช…์ด ์”ฌ์— ์ง์ ‘์ ์ธ ๋น›์„ ์ œ๊ณตํ•˜๊ณ  ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์—…๋ฐ์ดํŠธ๋˜์–ด ๊ฒŒ์ž„ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์”ฌ ๋‚ด์—์„œ ์ด๋™์‹œ

     ์กฐ๋ช…์€ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ๋œ๋‹ค. (์ƒ๋‹นํ•œ ๋ถ€ํ•˜๋ฅผ ์œ ๋ฐœ)

   > Baked๋Š” Lightmap์„ Bakingํ•  ๋•Œ Scene์˜ Static Object์— ๋Œ€ํ•œ ์กฐ๋ช… ํšจ๊ณผ๊ฐ€ ๊ณ„์‚ฐ๋˜๊ณ  Texture์— ๊ธฐ๋ก๋œ๋‹ค.

     (์ฆ‰, ์›€์ง์ด์ง€ ์•Š๋Š” ๋ฌผ์ฒด์˜ ๋น›์„ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐํ•ด๋‘๋Š” ๊ฒƒ)

   > ๋ฌผ์ฒด๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก Baking ์‹œ๊ฐ„์ด ์ƒ๋‹นํžˆ ์˜ค๋ž˜ ๊ฑธ๋ฆฐ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐœ๋ฐœ ๋„์ค‘์— ๋ฌผ์ฒด๋ฅผ ์ถ”๊ฐ€ํ• ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ Baking

      ํ•˜๋Š” ๊ฒƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด [ Window ] - [ Rendering ] - [ Lighting Settings ] - [ Lightmapping Setting ] ์˜ Auto Generate์˜

      ์ฒดํฌ๋ฅผ ํ•ด์ œํ•œ๋‹ค. (์ตœ์‹  ๋ฒ„์ „์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ์‹œ Compress Lightmaps์˜ ์ฒดํฌ๋ฅผ ํ•ด์ œ)

 

๏ผƒ ์ด๋™

  - NavMesh๋Š” Navigation Mesh์˜ ์ค„์ž„๋ง๋กœ, ๊ฒŒ์ž„ ์›”๋“œ์—์„œ ๊ฑธ์„ ์ˆ˜ ์žˆ๋Š” ํ‘œ๋ฉด์„ ๋œปํ•œ๋‹ค.

  - NavMesh๋Š” [ Window ] - [ AI ] - [ Navigation ] ์—์„œ ์„ค์ • ๊ฐ€๋Šฅํ•˜๋‹ค.

   > [ Bake ] ์—์„œ ๋ฏธ๋ฆฌ ๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ ๋˜๋Š” ์ œํ•œ ์‚ฌํ•ญ๋“ค์„ Bakeํ•œ๋‹ค.

     (๊ณ„์‚ฐ์— ํฌํ•จ๋˜๋Š” Mesh๋Š” Navigation Static ์†์„ฑ์„ ๊ฐ€์ง„๋‹ค.)

  - ์„ค์ •๋œ NavMesh๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ชฉ์ ์ง€๊นŒ์ง€ ๊ฒฝ๋กœ๋ฅผ ์ฐพ๊ณ  ์ด๋™ํ•˜๋Š” ์บ๋ฆญํ„ฐ์— ๋ถ€์ฐฉ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ Nav Mesh Agent์ด๋‹ค.

NavMesh๋ฅผ ์ด์šฉํ•œ PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : MonoBehaviour
{
    //...
    
    void UpdateMoving()
    {
        Vector3 dir = _destPos - transform.position; // ๋ชฉ์ ์ง€๊นŒ์ง€์˜ ๋ฐฉํ–ฅ๋ฒกํ„ฐ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
        if (dir.magnitude < 0.1f) // ๋งŒ์•ฝ ๋ชฉ์ ์ง€๊นŒ์ง€ ๊ฑฐ์˜ ๋„์ฐฉ์„ ์™„๋ฃŒํ–ˆ๋‹ค๋ฉด
        {
            _state = PlayerState.Idle;
        }
        else
        {
            // NavMesh๋ฅผ ์‚ฌ์šฉ
            NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
            float moveDist = Mathf.Clamp(Time.deltaTime * _speed, 0, dir.magnitude);
            nma.Move(dir.normalized * moveDist);

            Debug.DrawRay(transform.position + Vector3.up * 0.5f, dir.normalized, Color.green); // Ray๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„
            if (Physics.Raycast(transform.position + Vector3.up * 0.5f, dir, 1.0f, LayerMask.GetMask("Block"))) // ๊ฑด๋ฌผ, ์‚ฌ๋ฌผ๊ณผ ์ธ์ ‘์‹œ Player๊ฐ€ ๋ฉˆ์ถ”๋„๋ก ์„ค์ •
            {
                _state = PlayerState.Idle;
                return;
            }

            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime); // LookAt์„ ์œ„ํ•œ ํšŒ์ „์„ ๋ณด๋‹ค ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ•˜๋„๋ก
        }

        // ์• ๋‹ˆ๋งค์ด์…˜
        Animator anim = GetComponent<Animator>();
        anim.SetFloat("speed", _speed);
    }

    //...
}

 

๏ผƒ ์Šคํƒฏ

์Šคํƒฏ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ณต์šฉ Stat Script ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Stat : MonoBehaviour
{
    [SerializeField]
    protected int _level;
    [SerializeField]
    protected int _hp;
    [SerializeField]
    protected int _maxHp;
    [SerializeField]
    protected int _attack;
    [SerializeField]
    protected int _defense;
    [SerializeField]
    protected float _moveSpeed;

    public int Level { get { return _level; } set { _level = value; } }
    public int Hp { get { return _hp; } set { _hp = value; } }
    public int MaxHp { get { return _maxHp; } set { _maxHp = value; } }
    public int Attack { get { return _attack; } set { _attack = value; } }
    public int Defense { get { return _defense; } set { _defense = value; } }
    public float MoveSpeed { get { return _moveSpeed; } set { _moveSpeed = value; } }

    private void Start()
    {
        _level = 1;
        _hp = 100;
        _maxHp = 100;
        _attack = 10;
        _defense = 5;
        _moveSpeed= 5.0f;
    }
}โ€‹

 

Player์˜ ์Šคํƒฏ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด Stat๋ฅผ ์ƒ์†๋ฐ›๋Š” PlayerStat Script ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerStat : Stat
{
    [SerializeField]
    protected int _exp;
    [SerializeField]
    protected int _gold;

    public int Exp { get { return _exp; } set { _exp = value; } }
    public int Gold { get { return _gold; } set { _gold = value; } }

    private void Start()
    {
        _level = 1;
        _hp = 100;
        _maxHp = 100;
        _attack = 10;
        _defense = 5;
        _moveSpeed = 5.0f;
        _exp = 0;
        _gold = 0;
    }
}โ€‹

 

๏ผƒ ๋งˆ์šฐ์Šค ์ปค์„œ

  - Cursor๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ Image๋Š” Inspector ์ฐฝ์—์„œ Texture Type์„ Cursor๋กœ ์„ค์ •ํ•œ๋‹ค. 

๋งˆ์šฐ์Šค ์ปค์„œ๋ฅผ ์œ„ํ•œ PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : MonoBehaviour
{
    // ...

    Texture2D _attackIcon;
    Texture2D _handIcon;

    enum CursorType 
    {
        None,
        Attack,
        Hand,
    }

    CursorType _cursorType = CursorType.None;

    void Start()
    {
        _attackIcon = Managers.Resource.Load<Texture2D>("Textures/Cursor/Attack"); // Image ํŒŒ์ผ์„ Texture2D๋ฅผ ํ†ตํ•ด ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
        _handIcon = Managers.Resource.Load<Texture2D>("Textures/Cursor/Hand"); // Image ํŒŒ์ผ์„ Texture2D๋ฅผ ํ†ตํ•ด ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
    }
    void Update()
    {
        UpdateMouseCursor();
    }

    void UpdateMouseCursor()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, 100.0f, _mask))
        {
            if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
            {
                if (_cursorType != CursorType.Attack)
                {
                    Cursor.SetCursor(_attackIcon, new Vector2(_attackIcon.width / 5, 0), CursorMode.Auto); // ๋งˆ์šฐ์Šค Cursor๋ฅผ ์„ค์ •ํ•œ๋‹ค.
                    _cursorType = CursorType.Attack;
                }
            }
            else
            {
                if (_cursorType != CursorType.Hand)
                {
                    Cursor.SetCursor(_handIcon, new Vector2(_attackIcon.width / 3, 0), CursorMode.Auto); // ๋งˆ์šฐ์Šค Cursor๋ฅผ ์„ค์ •ํ•œ๋‹ค.
                    _cursorType = CursorType.Hand;
                }
            }
        }
    }
    
    // ...
}

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰

 - Cursor.SetCursor() ํ•จ์ˆ˜๋Š” Cursor์˜ Image๋ฅผ ์‚ฌ์šฉ์ž ์ง€์ • Texture๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค.

  > ์ฒซ๋ฒˆ์งธ ์ธ์ž๋Š” Cursor์— ์ง€์ •ํ•  Texture, ๋‘๋ฒˆ์งธ ์ธ์ž๋Š” Cursor์˜ Spot, ์„ธ๋ฒˆ์งธ ์ธ์ž๋Š” ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์ด๋‹ค.

 

๏ผƒ ํƒ€๊ฒŒํŒ… ๋ฝ์˜จ

ํƒ€๊ฒŒํŒ… ๋ฝ์˜จ์„ ์œ„ํ•ด Define์˜ MouseEvent ์ถ”๊ฐ€
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Define
{
    // ...

    public enum MouseEvent
    {
        Press, 
        PointerDown, // ๋งˆ์šฐ์Šค๋ฅผ Clickํ•˜๋Š” ์ˆœ๊ฐ„
        PointerUp, // ๋งˆ์šฐ์Šค Click์„ ๋–ผ๋Š” ์ˆœ๊ฐ„
        Click, // ์ˆœ๊ฐ„์ ์ธ Click (๊ต‰์žฅํžˆ ์งง์€ ์‹œ๊ฐ„)
    }

    // ...
}โ€‹

 

InputManager ์ˆ˜์ •
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class InputManager
{
    // ...
    
    float _pressedTime = 0; // Click๊ณผ PointerUp์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ

    // ์ฒดํฌํ•˜๋Š” ๋ถ€๋ถ„์ด ์œ ์ผํ•ด์ง
    public void OnUpdate()
    {
        // ...

        if (MouseAction != null)
        {
            if (Input.GetMouseButton(0))
            {
                if (!_pressed) // ๋งˆ์šฐ์Šค๋ฅผ Clickํ•˜๋Š” ์ˆœ๊ฐ„
                {
                    MouseAction.Invoke(Define.MouseEvent.PointerDown);
                    _pressedTime = Time.time;
                }
                MouseAction.Invoke(Define.MouseEvent.Press); // Press Event๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์•Œ๋ ค์ค€๋‹ค.
                _pressed = true;
            }
            else
            {
                if (_pressed)
                {
                    if (Time.time < _pressedTime + 0.2f) // 0.2์ดˆ ๋‚ด์— Mouse Click์„ ๋—€ ๊ฒฝ์šฐ
                        MouseAction.Invoke(Define.MouseEvent.Click); // Click Event๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์•Œ๋ ค์ค€๋‹ค.
                    MouseAction.Invoke(Define.MouseEvent.PointerUp);
                } 
                _pressed = false;
                _pressedTime = 0; // ์ดˆ๊ธฐํ™”
            }
        }
    }
}

 

PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : MonoBehaviour
{
    // ...

    void UpdateMouseCursor()
    {
        if (Input.GetMouseButton(0)) // ๋งŒ์•ฝ ๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ๋‹ค๋ฉด ์ปค์„œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ
            return;

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, 100.0f, _mask))
        {
            if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
            {
                if (_cursorType != CursorType.Attack)
                {
                    Cursor.SetCursor(_attackIcon, new Vector2(_attackIcon.width / 5, 0), CursorMode.Auto); // ๋งˆ์šฐ์Šค Cursor๋ฅผ ์„ค์ •ํ•œ๋‹ค.
                    _cursorType = CursorType.Attack;
                }
            }
            else
            {
                if (_cursorType != CursorType.Hand)
                {
                    Cursor.SetCursor(_handIcon, new Vector2(_attackIcon.width / 3, 0), CursorMode.Auto); // ๋งˆ์šฐ์Šค Cursor๋ฅผ ์„ค์ •ํ•œ๋‹ค.
                    _cursorType = CursorType.Hand;
                }
            }
        }
    }

    int _mask = (1 << (int)Define.Layer.Ground) | (1 << (int)Define.Layer.Monster);

    GameObject _lockTarget;
    void OnMouseEvent(Define.MouseEvent evt)
    {
        if (_state == PlayerState.Die)
            return;

        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        bool raycastHit = Physics.Raycast(ray, out hit, 100.0f, _mask);
        //Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f); // Ray๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„

        switch (evt)
        {
            case Define.MouseEvent.PointerDown:
                {
                    if (raycastHit)
                    {
                        _destPos = hit.point;
                        _state = PlayerState.Moving;

                        if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
                            _lockTarget = hit.collider.gameObject;
                        else
                            _lockTarget = null;
                    }
                }
                break;
            case Define.MouseEvent.Press:
                {
                    if(_lockTarget != null)
                        _destPos = _lockTarget.transform.position;
                    else if (raycastHit)
                        _destPos = hit.point;
                }
                break;
            case Define.MouseEvent.PointerUp:
                _lockTarget = null;
                break;
        }
    }
}โ€‹

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://unity-beginner.tistory.com/17)

 - GetMouseButtonDown( )์€ ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ณ  ์žˆ์„๋•Œ ๊ณ„์†ํ•ด์„œ ๋ฐœ์ƒ,

   GetMouseButton( )์€ ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ํ•œ ๋ฒˆ ๋ฐœ์ƒ,

   GetMouseButtonUp( )์€ ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ์„ ๋†“์•˜์„ ๋•Œ ํ•œ ๋ฒˆ ๋ฐœ์ƒ

  > ๊ด„ํ˜ธ์•ˆ์— ์ž…๋ ฅํ•œ ์ˆซ์ž๊ฐ€ 0 : ๋งˆ์šฐ์Šค ์™ผ์ชฝ ๋ฒ„ํŠผ, 1 : ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ, 2 : ๋งˆ์šฐ์Šค ํœ  ๋ฒ„ํŠผ์— ํ•ด๋‹นํ•œ๋‹ค.

 

๏ผƒ ๊ณต๊ฒฉ #1

PlayerController์—์„œ ๋งˆ์šฐ์Šค Cursor์™€ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์„ ๋”ฐ๋กœ ๋นผ CursorController ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CursorController : MonoBehaviour
{
    int _mask = (1 << (int)Define.Layer.Ground) | (1 << (int)Define.Layer.Monster);

    Texture2D _attackIcon;
    Texture2D _handIcon;

    enum CursorType
    {
        None,
        Attack,
        Hand,
    }

    CursorType _cursorType = CursorType.None;

    void Start()
    {
        _attackIcon = Managers.Resource.Load<Texture2D>("Textures/Cursor/Attack"); // Image ํŒŒ์ผ์„ Texture2D๋ฅผ ํ†ตํ•ด ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
        _handIcon = Managers.Resource.Load<Texture2D>("Textures/Cursor/Hand"); // Image ํŒŒ์ผ์„ Texture2D๋ฅผ ํ†ตํ•ด ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
    }

    void Update()
    {
        if (Input.GetMouseButton(0)) // ๋งŒ์•ฝ ๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ๋‹ค๋ฉด ์ปค์„œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ
            return;

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, 100.0f, _mask))
        {
            if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
            {
                if (_cursorType != CursorType.Attack)
                {
                    Cursor.SetCursor(_attackIcon, new Vector2(_attackIcon.width / 5, 0), CursorMode.Auto); // ๋งˆ์šฐ์Šค Cursor๋ฅผ ์„ค์ •ํ•œ๋‹ค.
                    _cursorType = CursorType.Attack;
                }
            }
            else
            {
                if (_cursorType != CursorType.Hand)
                {
                    Cursor.SetCursor(_handIcon, new Vector2(_attackIcon.width / 3, 0), CursorMode.Auto); // ๋งˆ์šฐ์Šค Cursor๋ฅผ ์„ค์ •ํ•œ๋‹ค.
                    _cursorType = CursorType.Hand;
                }
            }
        }
    }
}โ€‹

 

๊ณต๊ฒฉ Animation์„ ์œ„ํ•œ PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : MonoBehaviour
{
    // ...

    public enum PlayerState
    {
        Die,
        Moving,
        Idle,
        Skill,
    }

    [SerializeField]
    PlayerState _state = PlayerState.Idle;

    public PlayerState State // PlayerState๊ฐ€ ๋ณ€ํ•จ๊ณผ ๋™์‹œ์— Animation๋„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœํผํ‹ฐ ์ƒ์„ฑ
    {
        get { return _state; }
        set
        {
            _state = value;

            Animator anim = GetComponent<Animator>();
            switch (_state)
            {
                case PlayerState.Die:
                    anim.SetBool("attack", false);
                    break;
                case PlayerState.Idle:
                    anim.SetFloat("speed", 0);
                    anim.SetBool("attack", false);
                    break;
                case PlayerState.Moving:
                    anim.SetFloat("speed", _stat.MoveSpeed);
                    anim.SetBool("attack", false);
                    break;
                case PlayerState.Skill:
                    anim.SetBool("attack", true);
                    break;
            }
        }
    }

    // ...

    void UpdateMoving()
    {
        // ๋ชฌ์Šคํ„ฐ๊ฐ€ ๋‚ด ์‚ฌ์ •๊ฑฐ๋ฆฌ๋ณด๋‹ค ๊ฐ€๊นŒ์šฐ๋ฉด ๊ณต๊ฒฉ
        if (_lockTarget != null)
        {
            _destPos = _lockTarget.transform.position;
            float distance = (_destPos - transform.position).magnitude;
            if (distance <= 1)
            {
                State = PlayerState.Skill;
                return;
            }
        }

        // ...
    }

    // ...

    void OnHitEvent()
    {
        State = PlayerState.Moving;
    }

    void Update()
    {
        switch (State)
        {
            case PlayerState.Die:
                UpdateDie();
                break;
            case PlayerState.Moving:
                UpdateMoving();
                break;
            case PlayerState.Idle:
                UpdateIdle();
                break;
            case PlayerState.Skill:
                UpdateSkill();
                break;
        }
    }

    int _mask = (1 << (int)Define.Layer.Ground) | (1 << (int)Define.Layer.Monster);

    GameObject _lockTarget;
    void OnMouseEvent(Define.MouseEvent evt)
    {
        if (_state == PlayerState.Die)
            return;

        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        bool raycastHit = Physics.Raycast(ray, out hit, 100.0f, _mask);
        //Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f); // Ray๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„

        switch (evt)
        {
            case Define.MouseEvent.PointerDown:
                {
                    if (raycastHit)
                    {
                        _destPos = hit.point;
                        State = PlayerState.Moving;

                        if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
                            _lockTarget = hit.collider.gameObject;
                        else
                            _lockTarget = null;
                    }
                }
                break;
            case Define.MouseEvent.Press:
                {
                    if(_lockTarget != null)
                        _destPos = _lockTarget.transform.position;
                    else if (raycastHit)
                        _destPos = hit.point;
                }
                break;
        }
    }
}โ€‹

 

๏ผƒ ๊ณต๊ฒฉ #2

  - Animation ์ „ํ™˜์‹œ ๋ฐ˜๋“œ์‹œ Unity์˜ Tool์„ ์‚ฌ์šฉํ•˜๋ผ๋Š” ๋ฒ•์€ ์—†๋‹ค.

   > Parameter์˜ ๋ณ€๊ฒฝ์„ ํ†ตํ•œ Animation์˜ ์ „ํ™˜๋ณด๋‹ค Play( ), CrossFade( ) ์‚ฌ์šฉ์ด ๋” ํŽธ๋ฆฌํ• ๋•Œ๋„ ์žˆ๋‹ค.

   > CrossFade( )๋Š” ์‹ฌ์ง€์–ด Blending๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. (๋‘๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด)

PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : MonoBehaviour
{
    // ...

    public PlayerState State // PlayerState๊ฐ€ ๋ณ€ํ•จ๊ณผ ๋™์‹œ์— Animation๋„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœํผํ‹ฐ ์ƒ์„ฑ
    {
        get { return _state; }
        set
        {
            _state = value;

            Animator anim = GetComponent<Animator>();
            switch (_state)
            {
                case PlayerState.Die:
                    break;
                case PlayerState.Idle:
                    anim.CrossFade("WAIT", 0.1f);
                    break;
                case PlayerState.Moving:
                    anim.CrossFade("RUN", 0.1f);
                    break;
                case PlayerState.Skill:
                    anim.CrossFade("ATTACK", 0.1f);
                    break;
            }
        }
    }

    // ...

    void UpdateSkill()
    {
       if (_lockTarget != null)
        {
            Vector3 dir = _lockTarget.transform.position - transform.position; // ๋ชฉ์ ์ง€๊นŒ์ง€์˜ ๋ฐฉํ–ฅ๋ฒกํ„ฐ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
            Quaternion quat = Quaternion.LookRotation(dir); // ๋ชฉ์ ์ง€๋ฅผ ๋ฐ”๋ผ๋ณด๊ธฐ ์œ„ํ•ด์„œ
            transform.rotation = Quaternion.Lerp(transform.rotation, quat, 20 * Time.deltaTime); // ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐ๋˜๋„๋ก
        }
    }

    void OnHitEvent()
    {
        if (_stopSkill)
        {
            State = PlayerState.Idle;
        }
        else
        {
            State = PlayerState.Skill;
        }
    }

    // ...

    bool _stopSkill = false;
    void OnMouseEvent(Define.MouseEvent evt)
    {
        switch (State)
        {
            case PlayerState.Idle:
                OnMouseEvent_IdleRun(evt);
                break;
            case PlayerState.Moving:
                OnMouseEvent_IdleRun(evt);
                break;
            case PlayerState.Skill:
                {
                    if (evt == Define.MouseEvent.PointerUp)
                        _stopSkill = true;
                }
                break;
        }
    }

    void OnMouseEvent_IdleRun(Define.MouseEvent evt)
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        bool raycastHit = Physics.Raycast(ray, out hit, 100.0f, _mask);
        //Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f); // Ray๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„

        switch (evt)
        {
            case Define.MouseEvent.PointerDown:
                {
                    if (raycastHit)
                    {
                        _destPos = hit.point;
                        State = PlayerState.Moving;
                        _stopSkill = false;

                        if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
                            _lockTarget = hit.collider.gameObject;
                        else
                            _lockTarget = null;
                    }
                }
                break;
            case Define.MouseEvent.Press:
                {
                    if (_lockTarget == null && raycastHit)
                        _destPos = hit.point;
                }
                break;
            case Define.MouseEvent.PointerUp:
                _stopSkill = true;
                break;
        }
    }
}โ€‹

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://ansohxxn.github.io/unitydocs/anim/)

 - CrossFade( )๋Š” Play( ) ์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋‹ค ๋” ๋ถ€๋“œ๋Ÿฝ๊ณ  ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ Blending ํ•˜์—ฌ Animation์„ ์žฌ์ƒํ•œ๋‹ค.

 > ์ฒซ๋ฒˆ์งธ Parameter๋Š” ์žฌ์ƒํ•˜๊ณ ์ž ํ•˜๋Š” Clip์˜ ์ด๋ฆ„

 > ๋‘๋ฒˆ์งธ Parameter๋Š” ์ง€์—ฐ ์‹œ๊ฐ„ (๋‹ค์Œ Animation์œผ๋กœ ๋ฐ”๋€Œ๋Š”๋ฐ ๊ฑธ๋ฆฌ๋Š” Fade ์‹œ๊ฐ„)

 > ์„ธ๋ฒˆ์งธ Parameter๋Š” Animation Layer

 > ๋„ค๋ฒˆ์งธ Parameter๋Š” Animation์˜ ์žฌ์ƒ ์‹œ์ž‘ ์‹œ์  (0 ~ 1 ๋น„์œจ)

  ~> 0.0f๋กœ ์„ค์ •์‹œ ๋‹ค์‹œ ์ฒ˜์Œ์œผ๋กœ ๋Œ์•„๊ฐ€ ์žฌ์ƒํ•œ๋‹ค.

  ~> Clip์˜ Loop Time์ด ์ฒดํฌ๋˜์–ด ์žˆ์ง€ ์•Š๋”๋ผ๋„ ๋ฐ˜๋ณต ์žฌ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

 

๏ผƒ ์ฒด๋ ฅ ๊ฒŒ์ด์ง€ #1

  - UI๋Š” 2D UI์™€ (๊ฒŒ์ž„ ์„ธ์ƒ๊ณผ ๋ณ„๊ฐœ), 3D UI (๊ฒŒ์ž„ ์„ธ์ƒ๊ณผ ๊ณต์กด)๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

   > 3D UI๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” Canvas์˜ Render Mode๋ฅผ World Space๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.

   > 3D UI๋ฅผ ๋งŒ๋“ค๋•Œ Canvas์˜ Scale์„ ์•ฝ 1/100 ๋กœ ์ค„์ด๋ฉด ์ ๋‹นํ•œ ํฌ๊ธฐ๋กœ ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

- Slider์˜ Value๊ฐ€ 0๊ณผ 1์ผ๋•Œ ํ…… ๋น„์–ด ์žˆ์ง€๋„, ๊ฐ€๋“ ์ฐจ์žˆ์ง€๋„ ์•Š๋‹ค.

 > ์ด๋Š” Fill Area์˜ [ Inspector ] - [ Rect Transform ] ์—์„œ Left์™€ Right์˜ ๊ฐ’์„ ์ „๋ถ€ 0์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

์„ค์ • ์ „์˜ ๋ชจ์Šต
Fill Area์˜ Rect Transform์˜ Left์™€ Right์˜ ๊ฐ’์„ ์ „๋ถ€ 0์œผ๋กœ ์„ค์ •
์„ค์ • ํ›„์˜ ๋ชจ์Šต

 

- Slider์˜ Fill์ด Background์˜ ๊ฒฝ๊ณ„์„ ์„ ๋„˜์–ด ํŠ€์–ด๋‚˜์™€ ์žˆ๋‹ค.

 > ์ด๋Š” Fill์˜ [ Inspector ] - [ Rect Transform ] ์—์„œ Left์™€ Right์˜ ๊ฐ’์„ ์ „๋ถ€ 0์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

์„ค์ • ์ „์˜ ๋ชจ์Šต
Fill์˜ Rect Transform์˜ Left์™€ Right์˜ ๊ฐ’์„ ์ „๋ถ€ 0์œผ๋กœ ์„ค์ •
์„ค์ • ํ›„์˜ ๋ชจ์Šต

 

HP_Bar์˜ Canvas๋ฅผ Prefab์œผ๋กœ ์ €์žฅํ•œ ๋’ค UI_HPBar Script ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_HPBar : UI_Base
{
    enum GameObjects
    {
        HPBar
    }
    public override void Init()
    {
        Bind<GameObject>(typeof(GameObjects));
    }

    void Update()
    {
        // HP_Bar๋ฅผ ์บ๋ฆญํ„ฐ์˜ ๋จธ๋ฆฌ ์œ„๋กœ ์œ„์น˜์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ
        Transform parent = transform.parent;
        transform.position = parent.position + Vector3.up * (parent.GetComponent<Collider>().bounds.size.y); // ๋ถ€๋ชจ๋‹˜ ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ Collider๋งŒํผ์˜ ๋†’์ด๋ฅผ ์˜ฌ๋ ค ์ €์žฅ
    }
}โ€‹

 

UIManager ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager
{
    // ...

    public T MakeWorldSpaceUI<T>(Transform parent = null, string name = null) where T : UI_Base
    {
        if (string.IsNullOrEmpty(name))
            name = typeof(T).Name;

        GameObject go = Managers.Resource.Instantiate($"UI/WorldSpace/{name}");

        if (parent != null)
            go.transform.SetParent(parent);

        Canvas canvas = go.GetOrAddComponent<Canvas>();
        canvas.renderMode = RenderMode.WorldSpace;
        canvas.worldCamera = Camera.main;

        return Util.GetOrAddComponent<T>(go);
    }

    // ...
}โ€‹

 

PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : MonoBehaviour
{
    // ...

    void Start()
    {
        _stat = gameObject.GetComponent<PlayerStat>();

        Managers.Input.MouseAction -= OnMouseEvent; // ํ˜น์‹œ๋ผ๋„ ๋‹ค๋ฅธ ๊ณณ์—์„œ ๊ตฌ๋… ์‹ ์ฒญ์„ ํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„
        Managers.Input.MouseAction += OnMouseEvent;

        Managers.UI.MakeWorldSpaceUI<UI_HPBar>(transform);
    }

    // ...
}โ€‹

 

 

๏ผƒ ์ฒด๋ ฅ ๊ฒŒ์ด์ง€ #2

์ƒ์†๋ฐ›๋Š” Class๋“ค์ด Start ํ•จ์ˆ˜ ์„ ์–ธ ์—†์ด Init ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก UI_Base ์ˆ˜์ •
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public abstract class UI_Base : MonoBehaviour
{
    // ...
    
    public abstract void Init();

    void Start()
    {
        Init();
    }

    // ...
}โ€‹

 

Billboard ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๋ฐ ์‹ค์‹œ๊ฐ„ HP ๊ฐ์†Œ๋ฅผ ์œ„ํ•œ UI_HPBar ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_HPBar : UI_Base
{
    enum GameObjects
    {
        HPBar
    }

    Stat _stat;

    public override void Init()
    {
        Bind<GameObject>(typeof(GameObjects));
        _stat = transform.parent.GetComponent<Stat>();
    }

    void Update()
    {
        // HP_Bar๋ฅผ ์บ๋ฆญํ„ฐ์˜ ๋จธ๋ฆฌ ์œ„๋กœ ์œ„์น˜์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ
        Transform parent = transform.parent;
        transform.position = parent.position + Vector3.up * (parent.GetComponent<Collider>().bounds.size.y); // ๋ถ€๋ชจ๋‹˜ ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ Collider๋งŒํผ์˜ ๋†’์ด๋ฅผ ์˜ฌ๋ ค ์ €์žฅ
        transform.rotation = Camera.main.transform.rotation; // ์ž์‹ ์ด ๋ฐ”๋ผ๋ณด๋Š” ๋ฐฉํ–ฅ์„ ์นด๋ฉ”๋ผ๊ฐ€ ๋ฐ”๋ผ๋ณด๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์„ค์ • (Billboard์˜ ๊ฐœ๋…)

        float ratio = _stat.Hp / (float)_stat.MaxHp;
        SetHpRatio(ratio);
    }

    public void SetHpRatio(float ratio)
    {
        GetObject((int)GameObjects.HPBar).GetComponent<Slider>().value = ratio;
    }
}

 

Hit Event ๋ฐœ์ƒ์‹œ ์ƒ๋Œ€๋ฐฉ์˜ HP๋ฅผ ๊ฐ์†Œ์‹œํ‚ค๊ธฐ ์œ„ํ•œ PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : MonoBehaviour
{
    // ...

    void OnHitEvent()
    {
        if (_lockTarget != null)
        {
            Stat targetStat = _lockTarget.GetComponent<Stat>();
            Stat myStat = gameObject.GetComponent<PlayerStat>();
            int damage = Mathf.Max(0, myStat.Attack - targetStat.Defense);
            Debug.Log(damage);
            targetStat.Hp -= damage;
        }

        if (_stopSkill)
        {
            State = PlayerState.Idle;
        }
        else
        {
            State = PlayerState.Skill;
        }
    }

    // ...
}โ€‹

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰

  - Unity์—์„œ 3D ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์นด๋ฉ”๋ผ๋ฅผ ๋ฐ”๋ผ๋ณด๋„๋ก ๋งŒ๋“œ๋Š” ๊ธฐ์ˆ ์„ Billboard๋ผ๊ณ  ํ•œ๋‹ค.

   > UI์ธ Slider๋ฅผ ์นด๋ฉ”๋ผ๋ฅผ ๋ฐ”๋ผ๋ณด๋„๋ก ์ˆ˜์ •ํ•  ๊ฒฝ์šฐ ์ขŒ์šฐ ๋ฐ˜์ „์ด ๋œ ์ฑ„๋กœ ํ‘œ์‹œ๋œ๋‹ค.

      ( transform.LookAt(Camera.main) ์„ ํ†ตํ•ด )

    ~> ๋”ฐ๋ผ์„œ UI์ธ Slider์— Billboard ๊ธฐ์ˆ ์„ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•œ๋‹ค.

         ( transform.rotation = Camera.main.transform.rotation ์œผ๋กœ ์ˆ˜์ • )

๏ผƒ ๋ชฌ์Šคํ„ฐ AI #1

  - ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๊ณ ์ž ํ•˜๋Š” MonsterController๊ฐ€ PlayerController์™€ ๊ฒน์น˜๋Š” ๋ถ€๋ถ„์ด ์ƒ๋‹นํžˆ ๋งŽ์•„์„œ ์ด๋Ÿด๋•Œ๋Š” BaseController๋ฅผ

    ์ƒˆ๋กœ ์ƒ์„ฑํ•˜์—ฌ MonsterController์™€ PlayerController๊ฐ€ BaseController๋ฅผ ์ƒ์†๋ฐ›๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Define ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Define
{
    public enum State
    {
        Die,
        Moving,
        Idle,
        Skill,
    }

    // ...
}โ€‹

 

Base Controller ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class BaseController : MonoBehaviour
{
    [SerializeField]
    protected Vector3 _destPos; // ๋ชฉ์ ์ง€ ์ขŒํ‘œ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜

    [SerializeField]
    protected Define.State _state = Define.State.Idle;

    [SerializeField]
    protected GameObject _lockTarget;

    public virtual Define.State State // State๊ฐ€ ๋ณ€ํ•จ๊ณผ ๋™์‹œ์— Animation๋„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœํผํ‹ฐ ์ƒ์„ฑ
    {
        get { return _state; }
        set
        {
            _state = value;

            Animator anim = GetComponent<Animator>();
            switch (_state)
            {
                case Define.State.Die:
                    break;
                case Define.State.Idle:
                    anim.CrossFade("WAIT", 0.1f);
                    break;
                case Define.State.Moving:
                    anim.CrossFade("RUN", 0.1f);
                    break;
                case Define.State.Skill:
                    anim.CrossFade("ATTACK", 0.1f);
                    break;
            }
        }
    }

    private void Start() 
    {
        Init();
    }

    void Update()
    {
        switch (State)
        {
            case Define.State.Die:
                UpdateDie();
                break;
            case Define.State.Moving:
                UpdateMoving();
                break;
            case Define.State.Idle:
                UpdateIdle();
                break;
            case Define.State.Skill:
                UpdateSkill();
                break;
        }
    }

    public abstract void Init();
    protected virtual void UpdateDie() { }
    protected virtual void UpdateMoving() { }
    protected virtual void UpdateIdle() { }
    protected virtual void UpdateSkill() { }
}โ€‹

 

PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : BaseController
{
    int _mask = (1 << (int)Define.Layer.Ground) | (1 << (int)Define.Layer.Monster);

    PlayerStat _stat;
    bool _stopSkill = false;

    public override void Init()
    {
        _stat = gameObject.GetComponent<PlayerStat>();

        Managers.Input.MouseAction -= OnMouseEvent; // ํ˜น์‹œ๋ผ๋„ ๋‹ค๋ฅธ ๊ณณ์—์„œ ๊ตฌ๋… ์‹ ์ฒญ์„ ํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„
        Managers.Input.MouseAction += OnMouseEvent;

        if (gameObject.GetComponentInChildren<UI_HPBar>() == null)
            Managers.UI.MakeWorldSpaceUI<UI_HPBar>(transform);
    }

    protected override void UpdateMoving()
    {
        // ๋ชฌ์Šคํ„ฐ๊ฐ€ ๋‚ด ์‚ฌ์ •๊ฑฐ๋ฆฌ๋ณด๋‹ค ๊ฐ€๊นŒ์šฐ๋ฉด ๊ณต๊ฒฉ
        if (_lockTarget != null)
        {
            _destPos = _lockTarget.transform.position;
            float distance = (_destPos - transform.position).magnitude;
            if (distance <= 1)
            {
                State = Define.State.Skill;
                return;
            }
        }

        Vector3 dir = _destPos - transform.position; // ๋ชฉ์ ์ง€๊นŒ์ง€์˜ ๋ฐฉํ–ฅ๋ฒกํ„ฐ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
        if (dir.magnitude < 0.1f) // ๋งŒ์•ฝ ๋ชฉ์ ์ง€๊นŒ์ง€ ๊ฑฐ์˜ ๋„์ฐฉ์„ ์™„๋ฃŒํ–ˆ๋‹ค๋ฉด
        {
            State = Define.State.Idle;
        }
        else
        {
            // NavMesh๋ฅผ ์‚ฌ์šฉ
            NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
            float moveDist = Mathf.Clamp(Time.deltaTime * _stat.MoveSpeed, 0, dir.magnitude);
            nma.Move(dir.normalized * moveDist);

            Debug.DrawRay(transform.position + Vector3.up * 0.5f, dir.normalized, Color.green); // Ray๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„
            if (Physics.Raycast(transform.position + Vector3.up * 0.5f, dir, 1.0f, LayerMask.GetMask("Block"))) // ๊ฑด๋ฌผ, ์‚ฌ๋ฌผ๊ณผ ์ธ์ ‘์‹œ Player๊ฐ€ ๋ฉˆ์ถ”๋„๋ก ์„ค์ •
            {
                if (Input.GetMouseButton(0) == false)
                    State = Define.State.Idle;
                return;
            }

            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime); // LookAt์„ ์œ„ํ•œ ํšŒ์ „์„ ๋ณด๋‹ค ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ•˜๋„๋ก
        }
    }

    protected override void UpdateSkill()
    {
       if (_lockTarget != null)
        {
            Vector3 dir = _lockTarget.transform.position - transform.position; // ๋ชฉ์ ์ง€๊นŒ์ง€์˜ ๋ฐฉํ–ฅ๋ฒกํ„ฐ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
            Quaternion quat = Quaternion.LookRotation(dir); // ๋ชฉ์ ์ง€๋ฅผ ๋ฐ”๋ผ๋ณด๊ธฐ ์œ„ํ•ด์„œ
            transform.rotation = Quaternion.Lerp(transform.rotation, quat, 20 * Time.deltaTime); // ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐ๋˜๋„๋ก
        }
    }

    void OnHitEvent()
    {
        if (_lockTarget != null)
        {
            Stat targetStat = _lockTarget.GetComponent<Stat>();
            Stat myStat = gameObject.GetComponent<PlayerStat>();
            int damage = Mathf.Max(0, myStat.Attack - targetStat.Defense);
            Debug.Log(damage);
            targetStat.Hp -= damage;
        }

        if (_stopSkill)
        {
            State = Define.State.Idle;
        }
        else
        {
            State = Define.State.Skill;
        }
    }

    void OnMouseEvent(Define.MouseEvent evt)
    {
        switch (State)
        {
            case Define.State.Idle:
                OnMouseEvent_IdleRun(evt);
                break;
            case Define.State.Moving:
                OnMouseEvent_IdleRun(evt);
                break;
            case Define.State.Skill:
                {
                    if (evt == Define.MouseEvent.PointerUp)
                        _stopSkill = true;
                }
                break;
        }
    }

    void OnMouseEvent_IdleRun(Define.MouseEvent evt)
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        bool raycastHit = Physics.Raycast(ray, out hit, 100.0f, _mask);
        //Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f); // Ray๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„

        switch (evt)
        {
            case Define.MouseEvent.PointerDown:
                {
                    if (raycastHit)
                    {
                        _destPos = hit.point;
                        State = Define.State.Moving;
                        _stopSkill = false;

                        if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
                            _lockTarget = hit.collider.gameObject;
                        else
                            _lockTarget = null;
                    }
                }
                break;
            case Define.MouseEvent.Press:
                {
                    if (_lockTarget == null && raycastHit)
                        _destPos = hit.point;
                }
                break;
            case Define.MouseEvent.PointerUp:
                _stopSkill = true;
                break;
        }
    }
}โ€‹

 

๏ผƒ๋ชฌ์Šคํ„ฐ AI #2

MonsterController ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class MonsterController : BaseController
{
    Stat _stat;

    [SerializeField]
    float _scanRange = 10;

    [SerializeField]
    float _attackRange = 2;

    public override void Init()
    {
        _stat = gameObject.GetComponent<Stat>();

        if (gameObject.GetComponentInChildren<UI_HPBar>() == null)
            Managers.UI.MakeWorldSpaceUI<UI_HPBar>(transform);
    }

    protected override void UpdateIdle()
    {
        GameObject player = GameObject.FindGameObjectWithTag("Player");
        if (player == null)
            return;

        float distance = (player.transform.position - transform.position).magnitude;
        if (distance <= _scanRange) // ์‚ฌ์ • ๊ฑฐ๋ฆฌ ์•ˆ์œผ๋กœ ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ ์›€์ง์ด๊ธฐ ์‹œ์ž‘
        {
            _lockTarget = player;
            State = Define.State.Moving;
            return;
        }
    }

    protected override void UpdateMoving()
    {
        // ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ๋‚ด ์‚ฌ์ •๊ฑฐ๋ฆฌ๋ณด๋‹ค ๊ฐ€๊นŒ์šฐ๋ฉด ๊ณต๊ฒฉ
        if (_lockTarget != null)
        {
            _destPos = _lockTarget.transform.position;
            float distance = (_destPos - transform.position).magnitude;
            if (distance <= _attackRange)
            {
                NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
                nma.SetDestination(transform.position);
                State = Define.State.Skill;
                return;
            }
        }

        Vector3 dir = _destPos - transform.position; // ๋ชฉ์ ์ง€๊นŒ์ง€์˜ ๋ฐฉํ–ฅ๋ฒกํ„ฐ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
        if (dir.magnitude < 0.1f) // ๋งŒ์•ฝ ๋ชฉ์ ์ง€๊นŒ์ง€ ๊ฑฐ์˜ ๋„์ฐฉ์„ ์™„๋ฃŒํ–ˆ๋‹ค๋ฉด
        {
            State = Define.State.Idle;
        }
        else
        {
            // NavMesh๋ฅผ ์‚ฌ์šฉ
            NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
            nma.SetDestination(_destPos);
            nma.speed = _stat.MoveSpeed;

            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime); // LookAt์„ ์œ„ํ•œ ํšŒ์ „์„ ๋ณด๋‹ค ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ•˜๋„๋ก
        }
    }

    protected override void UpdateSkill()
    {
        if (_lockTarget != null)
        {
            Vector3 dir = _lockTarget.transform.position - transform.position; // ๋ชฉ์ ์ง€๊นŒ์ง€์˜ ๋ฐฉํ–ฅ๋ฒกํ„ฐ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
            Quaternion quat = Quaternion.LookRotation(dir); // ๋ชฉ์ ์ง€๋ฅผ ๋ฐ”๋ผ๋ณด๊ธฐ ์œ„ํ•ด์„œ
            transform.rotation = Quaternion.Lerp(transform.rotation, quat, 20 * Time.deltaTime); // ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐ๋˜๋„๋ก
        }
    }

    void OnHitEvent()
    {
        if (_lockTarget != null)
        {
            Stat targetStat = _lockTarget.GetComponent<Stat>();
            Stat myStat = gameObject.GetComponent<Stat>();
            int damage = Mathf.Max(0, myStat.Attack - targetStat.Defense);
            targetStat.Hp -= damage;

            if (targetStat.Hp > 0)
            {
                float distance = (_lockTarget.transform.position - transform.position).magnitude;
                if (distance <= _attackRange)
                    State = Define.State.Skill;
                else
                    State = Define.State.Moving;
            }
            else
            {
                State = Define.State.Idle;
            }
        }
        else
        {
            State = Define.State.Idle;
        }
    }
}โ€‹

 

๏ผƒ Destroy #1

  - Destroy๋œ Object๋Š” ์‚ฌ์‹ค ์‹ค์ œ๋กœ ์‚ญ์ œ๋œ๊ฑด C++ Native Object์ด๊ณ , UnityEngine.Object C# Class ๋ถ€๋ถ„์€ ์•„์ง Memory

    ์ƒ์— ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ๋กœ null์ด ๋œ ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ ์—†์–ด์ง„ ๊ฒƒ์ฒ˜๋Ÿผ ํ–‰๋™ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— "null"๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.

   > ์ด๊ฒƒ์ด ๋ฐ”๋กœ fake null ์ด๋‹ค.

   > ์ด์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ UnityEngine.Object์˜ == ์—ฐ์‚ฐ์ž ์˜ค๋ฒ„๋กœ๋”ฉ์— ๊ตฌํ˜„๋˜์–ด ์žˆ๋‹ค.

      (์ง„์งœ null์ด ๋œ๊ฒŒ ์•„๋‹˜์—๋„ Destroy๋œ Object == null ์„ ํ•˜๋ฉด true๋ฅผ ๋ฆฌํ„ดํ•˜๋„๋ก ๊ตฌํ˜„๋จ)

  - Destroy๋œ Object์˜ Component๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ๋“ค์„ ์ž˜ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

   > Object๊ฐ€ ํ•ด์ œ๋˜๋ฉด ๋‹น์—ฐํžˆ ํ•ด๋‹น Object์˜ Component๋„ ์ฐธ์กฐํ•  ์ˆ˜ ์—†๋‹ค.

MosterController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class MonsterController : BaseController
{
    // ...

    void OnHitEvent()
    {
        // ...

            if (targetStat.Hp <=0)
            {
                GameObject.Destroy(targetStat.gameObject);
            }

        // ...
    }
}โ€‹

 

CameraController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    // ...

    // Player์˜ ์›€์ง์ž„์ด ์‹คํ–‰๋œ ๋’ค Camera์˜ ์œ„์น˜๋ฅผ ์›€์ง์—ฌ์•ผ ๋–จ๋ฆฌ๋Š” ํ˜„์ƒ์ด ์ค„์–ด๋“ ๋‹ค.
    void LateUpdate() // ๊ฒŒ์ž„ Logic์—์„œ LateUpdate()๋Š” Update()๋ณด๋‹ค ๋Šฆ๊ฒŒ ์‹คํ–‰๋œ๋‹ค.
    {
        if (_mode == Define.CameraMode.QuaterView)
        {
            if (_player == null)
            {              
                return;
            }

            // ...
        }
    }

    // ...
}โ€‹

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://ansohxxn.github.io/unitydocs/fakenull/)

  - ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 1 : Object๋ฅผ bool๋กœ ๋ฌต์‹œ์  ํ˜•๋ณ€ํ™˜

  - ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 2 : Destroy ํ›„ ์ง„์งœ null์„ ๋Œ€์ž…

  - ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 3 : System.Object๋กœ ํ˜•๋ณ€ํ™˜ ํ›„ ๋น„๊ต

 

๏ผƒ Destroy #2

Define ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Define
{
    public enum WorldObject
    {
        Unknown,
        Player,
        Monster,
    }
    
    // ...
}โ€‹

 

BaseController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class BaseController : MonoBehaviour
{
    // ...

    public Define.WorldObject WorldObjectType { get; protected set; } = Define.WorldObject.Unknown;

    // ...
}โ€‹

 

PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : BaseController
{
    // ...

    public override void Init()
    {
        WorldObjectType = Define.WorldObject.Player;

        // ...
    }

    // ...
}โ€‹

 

MonsterController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class MonsterController : BaseController
{
    // ...

    public override void Init()
    {
        WorldObjectType = Define.WorldObject.Monster;

        // ...
    }

    // ...

    void OnHitEvent()
    {
        if (_lockTarget != null)
        {
            // ...

            if (targetStat.Hp <=0)
            {
                Managers.Game.Despawn(targetStat.gameObject);
            }

            // ...
        }
        // ...
    }
}โ€‹

 

GameManagerEx ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManagerEx : MonoBehaviour
{
    // ์Šคํฐ๋œ Object๋“ค์€ ID๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค => Dictionary๊ฐ€ ์ ๋‹น
    // ์„œ๋ฒ„์—์„œ๋Š” ํ•ด๋‹น ID๋ฅผ ํ†ตํ•ด ํ†ต์‹ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
    //Dictionary<int, GameObject> _players = new Dictionary<int, GameObject>();
    //Dictionary<int, GameObject> _monsters = new Dictionary<int, GameObject>();
   
    // ํ˜„์žฌ๋Š” ๋ฉ€ํ‹ฐ๊ฐ€ ์•„๋‹Œ ์‹ฑ๊ธ€ ๊ฒŒ์ž„์ด๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ๊ด€๋ฆฌํ•œ๋‹ค.
    GameObject _player;
    HashSet<GameObject> _monsters = new HashSet<GameObject>(); // HashSet์€ Dictionary์™€ ๋น„์Šทํ•˜์ง€๋งŒ Key๊ฐ€ X

    public GameObject Spawn(Define.WorldObject type, string path, Transform parent = null)
    {
        GameObject go = Managers.Resource.Instantiate(path, parent);

        switch (type)
        {
            case Define.WorldObject.Monster:
                _monsters.Add(go);
                break;
            case Define.WorldObject.Player:
                _player = go;
                break;
        }

        return go;
    }

    public Define.WorldObject GetWorldObjectType(GameObject go)
    {
        BaseController bc = go.GetComponent<BaseController>();
        if (bc == null)
            return Define.WorldObject.Unknown;
        return bc.WorldObjectType ;
    }

    public void Despawn(GameObject go) 
    {
        Define.WorldObject type = GetWorldObjectType(go);

        switch (type)
        {
            case Define.WorldObject.Monster:
                {
                    if (_monsters.Contains(go))
                        _monsters.Remove(go);
                }
                break;
            case Define.WorldObject.Player:
                {
                if (_player == go)
                    _player = null;      
                }
                break;
        }

        Managers.Resource.Destroy(go);
    }
}โ€‹

 

Managers ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Managers : MonoBehaviour
{
    // ...
    
    GameManagerEx _game = new GameManagerEx();

    // ...

    public static GameManagerEx Game { get { return Instance._game; } }

    // ...
}โ€‹

 

Extension ์ˆ˜์ •
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public static class Extension
{
    // ...

    // ํด๋งํ•˜๋Š” Object์ผ ๊ฒฝ์šฐ Destroy๊ฐ€ ์•„๋‹Œ ๋น„ํ™œ์„ฑํ™” ์ฒ˜๋ฆฌ๊ฐ€ ๋œ๋‹ค.
    // ๋”ฐ๋ผ์„œ null ์ฒดํฌ๋ฟ๋งŒ์ด ์•„๋‹Œ ํ™œ์„ฑํ™” ์ƒํƒœ ์—ฌ๋ถ€๋„ ์ฒดํฌํ•ด์ค˜์•ผ ํ•œ๋‹ค.
    public static bool IsValid(this GameObject go) 
    {
        return go != null && go.activeSelf;
    }
}โ€‹

 

CameraController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    // ...

    public void SetPlayer(GameObject player) { _player = player; }

    // Player์˜ ์›€์ง์ž„์ด ์‹คํ–‰๋œ ๋’ค Camera์˜ ์œ„์น˜๋ฅผ ์›€์ง์—ฌ์•ผ ๋–จ๋ฆฌ๋Š” ํ˜„์ƒ์ด ์ค„์–ด๋“ ๋‹ค.
    void LateUpdate() // ๊ฒŒ์ž„ Logic์—์„œ LateUpdate()๋Š” Update()๋ณด๋‹ค ๋Šฆ๊ฒŒ ์‹คํ–‰๋œ๋‹ค.
    {
        if (_mode == Define.CameraMode.QuaterView)
        {
            if (_player.IsValid() == false)
            {              
                return;
            }

            // ...
        }
    }

    // ...
}โ€‹

 

GameScene ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameScene : BaseScene
{
    protected override void Init() {
        // ...

        GameObject player = Managers.Game.Spawn(Define.WorldObject.Player, "Player");
        Camera.main.gameObject.GetOrAddComponent<CameraController>().SetPlayer(player);
        Managers.Game.Spawn(Define.WorldObject.Monster, "Knight");
    }

    // ...
}โ€‹

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (https://wlsdn629.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-Dictionary-HashTable-HastSet-%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85)

  - HashSet์€ Key๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉฐ Value(๋“ค)์˜ ๊ตฌ์„ฑ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค.

   > ์ˆœ์„œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.

   > ๊ฐ ํ•ญ๋ชฉ์˜ ์ค‘๋ณต์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. (์ฆ‰, Value ์ค‘๋ณต์„ ํ—ˆ์šฉ X)

   > Add( ), Remove( ), Clear( ), UnionWith( ), IntersectWith( ), ExceptWith( ) ๋“ฑ์˜ ํ•จ์ˆ˜๊ฐ€ ์กด์žฌํ•œ๋‹ค.

 

๏ผƒ ๋ ˆ๋ฒจ์—…

  - Damage์™€ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์€ ๊ณต๊ฒฉ์„ ํ•˜๋Š” ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ ๊ณต๊ฒฉ์„ ๋ฐ›๋Š” ๊ฐ์ฒด์—์„œ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

   (๊ณต๊ฒฉ์„ ๋ฐ›๋Š” ๊ฐ์ฒด์˜ ๋ฐฉ์–ด๋ ฅ, ๋ฒ„ํ”„ ๋“ฑ์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ)

Stat ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Stat : MonoBehaviour
{
    // ...

    public virtual void OnAttacked(Stat attacker) 
    {
        int damage = Mathf.Max(0, attacker.Attack - Defense);
        Hp -= damage;
        if (Hp <= 0) {
            Hp = 0;
            OnDead();
        }
    }

    protected virtual void OnDead() 
    {
        Managers.Game.Despawn(gameObject);
    }
}โ€‹

 

PlayerStat ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerStat : Stat
{
    // ...

    protected override void OnDead() 
    {
        Debug.Log("Player Dead");
    }
}โ€‹

 

MonsterController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : BaseController
{
    // ...

    void OnHitEvent()
    {
        if (_lockTarget != null)
        {
            Stat targetStat = _lockTarget.GetComponent<Stat>();
            targetStat.OnAttacked(_stat);
        }

        // ...
    }

    // ...
}โ€‹

 

PlayerController ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class PlayerController : BaseController
{
    // ...

    void OnHitEvent()
    {
        if (_lockTarget != null)
        {
            Stat targetStat = _lockTarget.GetComponent<Stat>();
            targetStat.OnAttacked(_stat);
        }

        // ...
    }

    // ...
}โ€‹

 

  - Monster๋ฅผ ์ฃฝ์ผ ๊ฒฝ์šฐ Monster์— ๋”ฐ๋ฅธ ๊ฒฝํ—˜์น˜ ํœ™๋“๋Ÿ‰์„ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

  - Level Up ํ™•์ธ ์ฝ”๋“œ๋Š” ์œ ์ผํ•œ ๊ฒƒ์ด ์ข‹๋‹ค. (๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ณ ๋œ ๋ณต๋ถ™ ์ž‘์—…)

StatData json ํŒŒ์ผ ์ˆ˜์ •
{
    "stats": [
        {
            "level": "1",
            "maxHp": "200",
            "attack": "20",
            "totalExp": "0"
        },
        {
            "level": "2",
            "maxHp": "250",
            "attack": "25",
            "totalExp": "10"
        },
        {
            "level": "3",
            "maxHp": "300",
            "attack": "30",
            "totalExp": "20"
        }
    ]
}โ€‹

 

Data.Contents ์ˆ˜์ •
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Data
{
    #region Stat
    [Serializable]
    public class Stat
    {
        public int level;
        public int maxHp;
        public int attack;
        public int totalExp;
    }

    // ...
    #endregion
}โ€‹

 

Stat ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Stat : MonoBehaviour
{
    // ...

    public virtual void OnAttacked(Stat attacker) 
    {
        int damage = Mathf.Max(0, attacker.Attack - Defense);
        Hp -= damage;
        if (Hp <= 0) {
            Hp = 0;
            OnDead(attacker);
        }
    }

    protected virtual void OnDead(Stat attacker) 
    {
        PlayerStat playerStat = attacker as PlayerStat;
        if (playerStat != null)
        {
            playerStat.Exp += 5;
        }

        Managers.Game.Despawn(gameObject);
    }
}โ€‹

 

PlayerStat ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerStat : Stat
{
    // ...

    public int Exp { 
        get { return _exp; } 
        set 
        { 
            _exp = value;

            // Level Up ์ฒดํฌ
            int level = Level;
            while (true)
            {
                Data.Stat stat;
                if (Managers.Data.StatDict.TryGetValue(level + 1, out stat) == false) // ๋‹ค์Œ Level์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ
                    break;
                if (_exp < stat.totalExp)
                    break;
                level++;
            }

            if (level != Level) // Level์— ๋ณ€ํ™”๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ
            {
                Level = level;
                SetStat(Level);
            }
        } 
    }

    // ...

    private void Start()
    {
        _level = 1;
        _exp = 0;
        _defense = 5;
        _moveSpeed = 5.0f;
        _gold = 0;

        SetStat(_level);
    }

    public void SetStat(int level)
    {
        Dictionary<int, Data.Stat> dict = Managers.Data.StatDict;
        Data.Stat stat = dict[level];
        _hp = stat.maxHp;
        _maxHp = stat.maxHp;
        _attack = stat.attack;
    }

    protected override void OnDead(Stat attacker) 
    {
        Debug.Log("Player Dead");
    }
}

 

๏ผƒ ๋ชฌ์Šคํ„ฐ ์ž๋™ ์ƒ์„ฑ

SpawningPool ์ƒ์„ฑ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class SpawningPool : MonoBehaviour
{
    [SerializeField]
    int _monsterCount = 0; // ํ˜„์žฌ ๋ชฌ์Šคํ„ฐ ์ˆ˜
    int _reserveCount = 0; // ํ˜„์žฌ Spawn ์˜ˆ์•ฝ ์ˆ˜

    [SerializeField]
    int _keepMonsterCount = 0; // ์œ ์ง€์‹œ์ผœ์•ผ ํ•˜๋Š” ๋ชฌ์Šคํ„ฐ ์ˆ˜

    [SerializeField]
    Vector3 _spawnPos; // Spawn ์ค‘์‹ฌ์ 

    [SerializeField]
    float _spawnRadius = 15.0f; // Spawn ์ค‘์‹ฌ์ ์„ ๊ธฐ์ค€์œผ๋กœ ๋žœ๋ค ์ƒ์„ฑ์„ ์œ„ํ•œ ์ƒ์„ฑ ๊ฑฐ๋ฆฌ ๋ฒ”์œ„ ์ œํ•œ

    [SerializeField]
    float _spawnTime = 5.0f; // ๋žœ๋ค ์ƒ์„ฑ์„ ์œ„ํ•œ ์ƒ์„ฑ ์‹œ๊ฐ„ ๋ฒ”์œ„ ์ œํ•œ

    public void AddMonsterCount(int value) { _monsterCount += value; }
    public void SetkeepMonsterCount(int count) { _keepMonsterCount = count; }

    void Start()
    {
        Managers.Game.OnSpawnEvent -= AddMonsterCount;
        Managers.Game.OnSpawnEvent += AddMonsterCount;
    }

    void Update()
    {
        while (_reserveCount + _monsterCount < _keepMonsterCount)
        {
            StartCoroutine("ReserveSpawn");
        }
    }

    IEnumerator ReserveSpawn()
    {
        _reserveCount++;

        yield return new WaitForSeconds(Random.Range(0, _spawnTime));
        GameObject obj = Managers.Game.Spawn(Define.WorldObject.Monster, "Knight");

        NavMeshAgent nma = obj.GetOrAddComponent<NavMeshAgent>();
        Vector3 randPos;
        while(true) // randPos๊ฐ€ ๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๊ธธ์ด ๋‚˜์˜ฌ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณต
        {
            Vector3 randDir = Random.insideUnitSphere * Random.Range(0, _spawnRadius); // ๋žœ๋ค์œผ๋กœ ๋ฝ‘ํžŒ ๋ฐฉํ–ฅ๋ฒกํ„ฐ๊ฐ€ randDir์— ๋‹ด๊ธด๋‹ค. (insideUnitCircle์€ 2D)
            randDir.y = 0; // ๋•…์„ ๋œท๊ณ  Spawn ๋˜์ง€ ์•Š๋„๋ก
            randPos = _spawnPos + randDir;

            // randPos๊ฐ€ ๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๊ธธ์ธ์ง€ ์ฒดํฌ
            NavMeshPath path = new NavMeshPath();
            if (nma.CalculatePath(randPos, path)) // ๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๊ธธ์ด๋ฉด True, ๊ฐˆ ์ˆ˜ ์—†๋Š” ๊ธธ์ด๋ฉด False๋ฅผ ๋ฐ˜ํ™˜
                break;
        }
        obj.transform.position = randPos;

        _reserveCount--;
    }
}โ€‹

 

GameManagerEx ์ˆ˜์ •
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManagerEx : MonoBehaviour
{
    // ...
    
    public Action<int> OnSpawnEvent; // int๋Š” ์ฆ๊ฐ€/๊ฐ์†Œ ์ˆ˜

    // ...

    public GameObject Spawn(Define.WorldObject type, string path, Transform parent = null)
    {
        // ...

        switch (type)
        {
            case Define.WorldObject.Monster:
                // ...
                if (OnSpawnEvent != null)
                    OnSpawnEvent.Invoke(1);
                break;
            // ...
        }

        // ...
    }

    // ...
    
    public void Despawn(GameObject go) 
    {
        // ...

        switch (type)
        {
            case Define.WorldObject.Monster:
                {
                    if (_monsters.Contains(go)) {
                        _monsters.Remove(go);
                        if (OnSpawnEvent != null)
                            OnSpawnEvent.Invoke(-1);
                    } 
                }
                break;
            // ...
        }

        // ...
    }
}

 

GameScene ์ˆ˜์ •
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameScene : BaseScene
{
    protected override void Init() {
        // ...

        //Managers.Game.Spawn(Define.WorldObject.Monster, "Knight");
        GameObject go = new GameObject { name = "SpawningPool" };
        SpawningPool pool = go.GetOrAddComponent<SpawningPool>();
        pool.SetkeepMonsterCount(5);
    }

    // ...
}โ€‹

 

+ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ (  )

  - Unity๋Š” ๋‚œ์ˆ˜ ์ƒ์„ฑ๊ณผ ๊ด€๋ จ๋œ ํ•จ์ˆ˜๋“ค์ด ์žˆ๋Š” ์ง‘ํ•ฉ์ธ Random ์ด๋ผ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

   ~> ๊ฐ€์žฅ ๋Œ€ํ‘œ์ ์ธ ๊ธฐ๋Šฅ์ด Random.Range(min, max) ์ด๋ฉฐ ์ด๋Š” min๊ณผ max ๋ฒ”์œ„ ๋‚ด์—์„œ ๋žœ๋คํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

        (์ด๋•Œ max์˜ Type์ด int์ผ ๊ฒฝ์šฐ max์˜ ๊ฐ’์€ ํฌํ•จ๋˜์ง€ X)

   ~> Random.insideUnitSphere์€ ๋ฐ˜๊ฒฝ 1์„ ๊ฐ–๋Š” ๊ตฌ ์•ˆ์˜ ์ž„์˜์˜ ์œ„์น˜(Vector3)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ”„๋กœํผํ‹ฐ์ด๋‹ค.

   ~> Random. insideUnitCircle ์€ ๋ฐ˜๊ฒฝ 1์„ ๊ฐ–๋Š” ๊ตฌ ์•ˆ์˜ ์ž„์˜์˜ ์œ„์น˜(Vector2)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ”„๋กœํผํ‹ฐ์ด๋‹ค.

 

 

728x90

'๐Ÿ“‚ Unity Engine Study > ๐Ÿ“„ Unity ์ธํ”„๋Ÿฐ ๊ฐ•์˜' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

์ธํ”„๋Ÿฐ ๊ฐ•์˜ - [์‹ค์ „ ๊ฒŒ์ž„ ์ฝ”๋“œ ๋ฆฌ๋ทฐ] ์œ ๋‹ˆํ‹ฐ ์บ์ฃผ์–ผ ๊ฒŒ์ž„ (์—˜๋ฆฌ์ŠคํŒก) (์„น์…˜ 0~1)  (0) 2024.04.21
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part4: ๊ฒŒ์ž„ ์„œ๋ฒ„ (์„น์…˜ 3 ~ 5)  (0) 2024.02.18
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part4: ๊ฒŒ์ž„ ์„œ๋ฒ„ (์„น์…˜ 0 ~ 2)  (0) 2024.02.12
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 7 ~ 12)  (0) 2024.01.24
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 0 ~ 6)  (0) 2024.01.04
  1. [ ์„น์…˜ 13. ๋ฏธ๋‹ˆ RPG ]
'๐Ÿ“‚ Unity Engine Study/๐Ÿ“„ Unity ์ธํ”„๋Ÿฐ ๊ฐ•์˜' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part4: ๊ฒŒ์ž„ ์„œ๋ฒ„ (์„น์…˜ 3 ~ 5)
  • ์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part4: ๊ฒŒ์ž„ ์„œ๋ฒ„ (์„น์…˜ 0 ~ 2)
  • ์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 7 ~ 12)
  • ์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 0 ~ 6)
YeonSu02
YeonSu02
Email : rkddustn2519@naver.com
IsLiife2Email : rkddustn2519@naver.com
  • YeonSu02
    IsLiife2
    YeonSu02
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ
      • ๐Ÿ“‚ Computer Science
      • ๐Ÿ“‚ Unity Engine Study
        • ๐Ÿ“„ Unity ์ธํ”„๋Ÿฐ ๊ฐ•์˜
        • ๐Ÿ“„ Unity ์œ ํŠœ๋ธŒ ๊ฐ•์˜
        • ๐Ÿ“„ Unity ์ฐธ๊ณ 
        • ๐Ÿ’ป Game Development
      • ๐Ÿ“‚ Quality Assurance Study
        • ๐Ÿ”ฅ ์—˜๋ฆฌ์Šค SW QAํŠธ๋ž™
        • ๐Ÿ“„ QA ๊ณต๋ถ€
        • ๐Ÿ“š QA ์ฑ… ๋ฆฌ๋ทฐ
      • ๐Ÿ“‚ Program Language Study
        • ๐Ÿ“„ C# ๊ณต๋ถ€
        • ๐Ÿ“„ ํŒŒ์ด์ฌ ๊ณต๋ถ€
        • ๐Ÿ“„ JavaScript ๊ณต๋ถ€
        • ๐Ÿ“„ TypeScript ๊ณต๋ถ€
      • ๐Ÿ“‚ Additional Study
        • ๐Ÿ“„ Git
        • ๐Ÿ“„ Docker
        • ๐Ÿ“„ Jenkins
        • ๐Ÿ“„ Firebase
        • ๐Ÿ“„ Economy
        • ๐Ÿ“„ License
      • ๐Ÿ“‚ Experience
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
  • ๋งํฌ

    • GitHub
  • ์ธ๊ธฐ ๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
YeonSu02
์ธํ”„๋Ÿฐ ๊ฐ•์˜ - Part3: ์œ ๋‹ˆํ‹ฐ ์—”์ง„ ๋‚ด์šฉ ์ •๋ฆฌ (์„น์…˜ 13)

๊ฐœ์ธ์ •๋ณด

  • ํ‹ฐ์Šคํ† ๋ฆฌ ํ™ˆ
  • ํฌ๋Ÿผ
  • ๋กœ๊ทธ์ธ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”

๋‹จ์ถ•ํ‚ค

๋‚ด ๋ธ”๋กœ๊ทธ

๋‚ด ๋ธ”๋กœ๊ทธ - ๊ด€๋ฆฌ์ž ํ™ˆ ์ „ํ™˜
Q
Q
์ƒˆ ๊ธ€ ์“ฐ๊ธฐ
W
W

๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€

๊ธ€ ์ˆ˜์ • (๊ถŒํ•œ ์žˆ๋Š” ๊ฒฝ์šฐ)
E
E
๋Œ“๊ธ€ ์˜์—ญ์œผ๋กœ ์ด๋™
C
C

๋ชจ๋“  ์˜์—ญ

์ด ํŽ˜์ด์ง€์˜ URL ๋ณต์‚ฌ
S
S
๋งจ ์œ„๋กœ ์ด๋™
T
T
ํ‹ฐ์Šคํ† ๋ฆฌ ํ™ˆ ์ด๋™
H
H
๋‹จ์ถ•ํ‚ค ์•ˆ๋‚ด
Shift + /
โ‡ง + /

* ๋‹จ์ถ•ํ‚ค๋Š” ํ•œ๊ธ€/์˜๋ฌธ ๋Œ€์†Œ๋ฌธ์ž๋กœ ์ด์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ํ‹ฐ์Šคํ† ๋ฆฌ ๊ธฐ๋ณธ ๋„๋ฉ”์ธ์—์„œ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.