본문 바로가기
KDT/유니티 기초

23/08/09 SimpleRPG (+ 이펙트 효과)

by 잰쟁 2023. 8. 9.
728x90

MainScene

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

namespace Test2
{
    public class Test_PlayerAttackSceneMain : MonoBehaviour
    {
        [SerializeField]
        private Button btnAttack;
        [SerializeField]
        private HeroController heroController;
        [SerializeField]
        private MonsterController monsterController;
        [SerializeField]
        private GameObject hitFxPrefab;

        void Start()
        {
            Debug.LogFormat("btnAttack: {0}", btnAttack);  //Button 컴포넌트의 인스턴스

            //이벤트 등록
            this.monsterController.onHit = () =>
            {

                Debug.Log("이펙트 생성");

                Vector3 offset = new Vector3(0, 0.5f, 0);  //이펙트 위치 지정
                Vector3 tpos = this.monsterController.transform.position + offset;

                Debug.LogFormat("생성위치: {0}", tpos);
                //프리팹 인스턴스(복사본) 생성
                GameObject fxGo = Instantiate(this.hitFxPrefab);
                //위치설정
                fxGo.transform.position = tpos;
                //파티클 실행
                fxGo.GetComponent<ParticleSystem>().Play();

            };

            this.btnAttack.onClick.AddListener(() =>
            {

                //사거리 체크
                //두 점(A,B)과의 거리, 두 오브젝트의 radius의 합
                //B-A : C(방향벡터), magnitude (벡터의 길이)
                //MonsterController 컴포넌트가 붙어있는 게임오브젝의 Transform컴포넌트의 Position속성 (Vector3)
                Vector3 b = monsterController.transform.position;
                Vector3 a = heroController.transform.position;
                Vector3 c = b - a;  //정규화되지 않은 방향벡터 (방향과 크기), 크기 : 길이

                //--------------> (방향 ->, 크기 ------------ )

                float distance = c.magnitude; //거리(크기)
                Debug.LogFormat("distance : {0}", distance);
                Debug.DrawLine(a, b, Color.red, 5f);  //영웅과 몬스터의 길이를 출력

                //단위벡터 (길이가 1인 벡터)
                //Vector3 normal = c.normalized; //방향
                //시작위치, 방향, 얼마동안 보여줄건가(초), 색상, 타입 (채움)
                //DrawArrow.ForDebug(a, normal, 5f, Color.red, ArrowType.Solid);
                //DrawArrow.ForDebug(a, c, 5f, Color.red, ArrowType.Solid);
                //Vector3. Distance

                //두 컴포넌트의 radius (반지름)의 합
                float radius = this.heroController.Radius + this.monsterController.Radius;
                Debug.LogFormat("radius: {0}", radius);
                Debug.LogFormat("<color=yellow>distance: {0},radius: {1}</color>", distance, radius);
                Debug.LogFormat("IsWithRange: <color=lime>{0}</color>", this.IsWithinRange(distance, radius));

                if (this.IsWithinRange(distance, radius))
                {
                    //HeroController에게 공격을 명령
                    this.heroController.Attack(this.monsterController);
                }

            });
        }
        //사거리 체크
        private bool IsWithinRange(float distance, float radius)
        {
            //distance > radius   //false
            return radius >= distance;  //true
        }

        // Update is called once per frame
        void Update()
        {

        }
    }
}

HeroController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Test2
{
    public class HeroController : MonoBehaviour
    {
        public enum eState
        {
            Idle, Attack
        }

        [SerializeField]
        private float radius = 1f;
        private Animator anim;
        //private eState state = eState.Idle;
        private eState state;   //초기화 0(Idle)
        private float impactTime = 0.399f;
        public MonsterController target;

        private Coroutine attackRoutine;

        public float Radius
        {
            get
            {
                return this.radius;
            }
        }

        void Start()
        {
            //내가(HeroController) 컴포넌트가 붙어있는 게임오브젝트에 붙어있는 Animator컴포넌스 가져옴
            this.anim = this.GetComponent<Animator>();
        }

        // Update is called once per frame
        void Update()
        {

        }

        public void Attack(MonsterController target)
        {
            this.target = target;
            //공격 애니메이션을 1회 실행
            //애니메이터 컴포넌트가 필요
            this.PlayAnimation(eState.Attack);
        }

        //enum을 변수로 받아 애니메이션 실행
        private void PlayAnimation(eState state)
        {
            //this.state : prev, state: current
            //중복막기
            if(this.state != state)
            {
                Debug.LogFormat("{0} -> {1}", this.state, state);
                this.state = state;
                this.anim.SetInteger("State", (int)state);  //정수 -> enum

                switch (state)
                {
                    case eState.Attack:
                        //이미 코루틴이 실행중이라면 중지
                        if(this.attackRoutine != null)
                        {
                            this.StopCoroutine(this.attackRoutine);
                        }
                        //코루틴 함수는 StartCoroutine으로 실행해야 함
                        this.attackRoutine = this.StartCoroutine(this.WaitForCompleteAttackAnimation());
                        break;
                }

            }
            else
            {
                Debug.LogFormat("{0}는 현재와 동일한 상태입니다.", state);
            }
            
        }
        //코루틴 함수
        //IEumerator 반환타입을 갖는 1개 이상의 yield return을 포함하는 함수
        //Update와 동일하게 동작 가능
        private IEnumerator WaitForCompleteAttackAnimation()
        {
            yield return null; //다음의 시작(1프레임 건너뜀, next frame)

            Debug.Log("공격 애니메이션이 끝날때까지 기다림");
            //layerIndex : 0
            AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
            bool isAttackState = animStateInfo.IsName("Attack01");
            Debug.LogFormat("isAttackState: {0}", isAttackState);
            if (isAttackState)
            {
                Debug.LogFormat("animStateInfo.length: {0}", animStateInfo.length);
            }
            else
            {
                Debug.LogFormat("Attack State가 아닙니다.");
            }

            //0.11초 이후에 임펙트 효과 넣기
            yield return new WaitForSeconds(impactTime);  //Impact
            Debug.Log("<color=red>Impact!!!</color>");

            //대상에게 피해를 입힘
            this.target.HitDamage();

            //0.833초를 대기
            //yield return new WaitForSeconds(0.833f);
            yield return new WaitForSeconds(animStateInfo.length - impactTime); //0.48초만큼 기다림

            //Idle 애니메이션을 함
            //this.anim.SetInteger("State",0);
            this.PlayAnimation(eState.Idle);
        }

        //기즈모 생성
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.yellow;
            GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
        }
    }
}

MonsterController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;


namespace Test2 
{
    public class MonsterController : MonoBehaviour
    {
        [SerializeField]
        private float radius = 1f;
        private Animator anim;

        //대리자
        public Action onHit;

        public float Radius
        {
            get
            { return this.radius; }
        }

        void Start()
        {
            this.anim = GetComponent<Animator>();
        }

        // Update is called once per frame
        void Update()
        {

        }

        //공격받는 메서드
        public void HitDamage()
        {
            Debug.Log("피해 애니메이션을 실행합니다.");

            //대리자 호출
            this.onHit();

            this.anim.SetInteger("State",2);
        }

        //기즈모 생성
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.yellow;
            GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
        }
    }
}

ParticleController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParticleDestroyer : MonoBehaviour
{
    private ParticleSystem ps;
    //private float elapsedTime = 0;

    // Start is called before the first frame update
    void Start()
    {
        this.ps = this.GetComponent<ParticleSystem>();
        //코루틴 실행
        this.StartCoroutine(this.CoWaitForPlayAfterDestory());
    }

    //IEnumerator반환하는 함수, 1개 이상의 yield return을 포함하는 함수
    private IEnumerator CoWaitForPlayAfterDestory()
    {
        //this.ps.main.duration 기다렸다가 (프레임 경과)
        yield return new WaitForSeconds(this.ps.main.duration);
        //파괴
        Destroy(this.gameObject);
    }

    //void Update()
    //{
    //    this.elapsedTime += Time.deltaTime;
    //    if(this.elapsedTime >= this.ps.main.duration)
    //    {
    //        Destroy(this.gameObject);
    //    }
    //}  ==> 이렇게 칠 것을 CoRutine을 이용하여 처리
}

Gizmos

using UnityEngine;

public class GizmosExtensions
{
    private GizmosExtensions() { }

    /// <summary>
    /// Draws a wire arc.
    /// </summary>
    /// <param name="position"></param>
    /// <param name="dir">The direction from which the anglesRange is taken into account</param>
    /// <param name="anglesRange">The angle range, in degrees.</param>
    /// <param name="radius"></param>
    /// <param name="maxSteps">How many steps to use to draw the arc.</param>
    public static void DrawWireArc(Vector3 position, Vector3 dir, float anglesRange, float radius, float maxSteps = 20)
    {
        var srcAngles = GetAnglesFromDir(position, dir);
        var initialPos = position;
        var posA = initialPos;
        var stepAngles = anglesRange / maxSteps;
        var angle = srcAngles - anglesRange / 2;
        for (var i = 0; i <= maxSteps; i++)
        {
            var rad = Mathf.Deg2Rad * angle;
            var posB = initialPos;
            posB += new Vector3(radius * Mathf.Cos(rad), 0, radius * Mathf.Sin(rad));

            Gizmos.DrawLine(posA, posB);

            angle += stepAngles;
            posA = posB;
        }
        Gizmos.DrawLine(posA, initialPos);
    }

    static float GetAnglesFromDir(Vector3 position, Vector3 dir)
    {
        var forwardLimitPos = position + dir;
        var srcAngles = Mathf.Rad2Deg * Mathf.Atan2(forwardLimitPos.z - position.z, forwardLimitPos.x - position.x);

        return srcAngles;
    }
}

DrawArrow

using UnityEngine;
using System.Collections;
#if UNITY_EDITOR
using UnityEditor;
#endif
// adapted from http://wiki.unity3d.com/index.php/DrawArrow 


public enum ArrowType
{
    Default,
    Thin,
    Double,
    Triple,
    Solid,
    Fat,
    ThreeD,
}

public static class DrawArrow
{
    public static void ForGizmo(Vector3 pos, Vector3 direction, Color? color = null, bool doubled = false, float arrowHeadLength = 0.2f, float arrowHeadAngle = 20.0f)
    {
        Gizmos.color = color ?? Color.white;

        //arrow shaft
        Gizmos.DrawRay(pos, direction);

        if (direction != Vector3.zero)
        {
            //arrow head
            Vector3 right = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 + arrowHeadAngle, 0) * new Vector3(0, 0, 1);
            Vector3 left = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 - arrowHeadAngle, 0) * new Vector3(0, 0, 1);
            Gizmos.DrawRay(pos + direction, right * arrowHeadLength);
            Gizmos.DrawRay(pos + direction, left * arrowHeadLength);
        }
    }

    public static void ForDebug(Vector3 pos, Vector3 direction, float duration = 0.5f, Color? color = null, ArrowType type = ArrowType.Default, float arrowHeadLength = 0.2f, float arrowHeadAngle = 30.0f, bool sceneCamFollows = false)
    {
        Color actualColor = color ?? Color.white;
        duration = duration / Time.timeScale;

        float width = 0.01f;

        Vector3 directlyRight = Vector3.zero;
        Vector3 directlyLeft = Vector3.zero;
        Vector3 directlyBack = Vector3.zero;
        Vector3 headRight = Vector3.zero;
        Vector3 headLeft = Vector3.zero;

        if (direction != Vector3.zero)
        {
            directlyRight = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 + 90, 0) * new Vector3(0, 0, 1);
            directlyLeft = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 - 90, 0) * new Vector3(0, 0, 1);
            directlyBack = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180, 0) * new Vector3(0, 0, 1);
            headRight = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 + arrowHeadAngle, 0) * new Vector3(0, 0, 1);
            headLeft = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 - arrowHeadAngle, 0) * new Vector3(0, 0, 1);
        }

        //draw arrow head
        Debug.DrawRay(pos + direction, headRight * arrowHeadLength, actualColor, duration);
        Debug.DrawRay(pos + direction, headLeft * arrowHeadLength, actualColor, duration);

        switch (type)
        {
            case ArrowType.Default:
                Debug.DrawRay(pos, direction, actualColor, duration); //draw center line
                break;
            case ArrowType.Double:
                Debug.DrawRay(pos + directlyRight * width, direction * (1 - width), actualColor, duration); //draw line slightly to right
                Debug.DrawRay(pos + directlyLeft * width, direction * (1 - width), actualColor, duration); //draw line slightly to left

                //draw second arrow head
                Debug.DrawRay(pos + directlyBack * width + direction, headRight * arrowHeadLength, actualColor, duration);
                Debug.DrawRay(pos + directlyBack * width + direction, headLeft * arrowHeadLength, actualColor, duration);

                break;
            case ArrowType.Triple:
                Debug.DrawRay(pos, direction, actualColor, duration); //draw center line
                Debug.DrawRay(pos + directlyRight * width, direction * (1 - width), actualColor, duration); //draw line slightly to right
                Debug.DrawRay(pos + directlyLeft * width, direction * (1 - width), actualColor, duration); //draw line slightly to left
                break;
            case ArrowType.Fat:
                break;
            case ArrowType.Solid:
                int increments = 20;
                for (int i = 0; i < increments; i++)
                {
                    float displacement = Mathf.Lerp(-width, +width, i / (float)increments);
                    //draw arrow body
                    Debug.DrawRay(pos + directlyRight * displacement, direction, actualColor, duration); //draw line slightly to right
                    Debug.DrawRay(pos + directlyLeft * displacement, direction, actualColor, duration); //draw line slightly to left
                                                                                                        //draw arrow head
                    Debug.DrawRay((pos + direction) + directlyRight * displacement, headRight * arrowHeadLength, actualColor, duration);
                    Debug.DrawRay((pos + direction) + directlyRight * displacement, headLeft * arrowHeadLength, actualColor, duration);
                }
                break;
            case ArrowType.Thin:
                Debug.DrawRay(pos, direction, actualColor, duration); //draw center line
                break;
            case ArrowType.ThreeD:
                break;
        }

        /*#if UNITY_EDITOR
            //snap the Scene view camera to a spot where it is looking directly at this arrow.
                if (sceneCamFollows)
                    SceneViewCameraFollower.activateAt(pos + direction, duration, "_arrow");
        #endif*/
    }

    public static void randomStar(Vector3 center, Color color)
    {
        //special: refuse to draw at 0,0.
        if (center == Vector3.zero) return;
        for (int i = 0; i < 2; i++)
            DrawArrow.ForGizmo(center, UnityEngine.Random.onUnitSphere * 1, color, false, 0.1f, 30.0f);
    }

    public static void comparePositions(Transform t1, Transform t2)
    {
        //direct from one to the other:
        ForDebug(t1.position, t2.position - t1.position);

        //direction
        //Vector3 moveDirection = (t2.position-t1.position).normalized;
    }
}