몬스터애니메이션 컨트롤러 초기

몬스터 추적범위와 스태이트 변경

몬스터애니메이션컨트롤러 Hit

몬스터 추적 후 어택

몬스터 피격 애니메이션

몬스터 피격 시 출혈

플레이어 사망시 몬스터 춤

적 사망시 죽는 애니메이션

6(1).적 사망시 죽는 애니메이션.mp4
3.94MB

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;

public class PlayerCtrl : MonoBehaviour
{
    private Transform tr;
    public float moveSpeed = 10.0f;
    public float turnSpeed = 80.0f;
    private Animation anim;
    private readonly float initHp = 100f;
    public float currentHp;

    public delegate void PlayerDieHandler(); //대리자 정의

    public static event PlayerDieHandler OnPlayerDie; //이벤트 변수 정의

    private void Awake()
    {
        //제일먼저 호출되는 함수
        //스크리브가 비활성화되있어도 호출되는 함수

    }

    private void OnEnable()
    {
        //두번째로 호출되는 함수
        //스크립트가 활성화 될때마다 호출되는 함수
    }

    IEnumerator Start()
    {
        this.currentHp = this.initHp;
        // 세번째로 호출되는 함수 
        //Update함수가 호출되기 전에 호출되는 함수 
        //코루틴으로 호출될수 있는 함수 
        //예) 
        //IEnumerator Start(){}

        tr = GetComponent<Transform>();
        this.anim = GetComponent<Animation>();


        //이름으로 실행 하거나 
        this.anim.Play("Idle");


        //이름으로 가져와서 플레이 하거나 
        //this.anim.clip = this.anim.GetClip("Idle");
        //this.anim.Play();

        this.turnSpeed = 0f;

        yield return new WaitForSeconds(0.3f);

        this.turnSpeed = 80f;

    }

    private void FixedUpdate()
    {
        //일정간격으로 호출되는 함수 (기본값은 0.02초)
        //물리엔진의 계산 주기와 일치

    }

    void Update()
    {
        //프레임마다 호출되는 함수
        //호출 간격이 불규칙하다
        //화면의 렌더링 주기와 일치 함
        float h = Input.GetAxis("Horizontal"); // -1.0 ~ 0 ~ 1
        float v = Input.GetAxis("Vertical"); // -1.0 ~ 0 ~ 1
        float r = Input.GetAxis("Mouse X");

        //Debug.Log("h=" + h);
        //Debug.Log("v=" + v);
        Debug.Log("r=" + r);

        //this.tr.Translate(Vector3.forward * 1 * 0.01f , Space.Self); //로컬좌표 기준으로 이동
        //this.tr.Translate(Vector3.forward * 1 * Time.deltaTime, Space.World); //월드좌표 기준으로 이동

        //매 프레임마다 1유닛씩 이동 => 0.02초
        //this.tr.Translate(Vector3.forward * 1 * Time.deltaTime);


        //매 초마다 1유닛씩 이동
        //프레임 레이트가 서로다른 기기에서도 개발자가 지정한 일정한 속도로 이동하기 위함
        //this.tr.Translate(Vector3.forward * v * Time.deltaTime * moveSpeed); //방향 * 속도 * 시간 => forward = (0,0,1) // v => -1~ 1 사이의 값 => v를 사용해서 앞뿐만이 아닌 뒤로도 이동가능

        //전후좌우 이동방향 벡터 계산
        Vector3 moveDir = (Vector3.forward * v) + (Vector3.right * h); // right = (1,0,0)

        //방향 * 속도 * 시간 
        this.tr.Translate(moveDir.normalized * moveSpeed * Time.deltaTime);

        //Vector3.up 축으로 turnSpeed만큼
        this.tr.Rotate(Vector3.up * r * this.turnSpeed);

        //캐릭터 애니메이션 실행
        PlayerAnim(h, v);


    }

    private void LateUpdate()
    {
        //Update함수가 종료된 후 호출 되는 함수
    }

    private void PlayerAnim(float h, float v)
    {
        //키보드 입력값을 기준으로 동작할 애니메이션 실행
        if (v >= 0.1f)
        {
            //0.25f초간 이전 애니메이션과 실행할 애니메이션을 보간 
            this.anim.CrossFade("RunF", 0.25f); //전진 애니메이션 실행 
        }
        else if (v <= -0.1f)
        {
            this.anim.CrossFade("RunB", 0.25f);//후진 
        }
        else if (h >= 0.1f)
        {
            this.anim.CrossFade("RunR", 0.25f);//오른쪽 
        }
        else if (h <= -0.1f)
        {
            this.anim.CrossFade("RunL", 0.25f);//왼쪽 
        }
        else
        {
            this.anim.CrossFade("Idle", 0.25f);//기본 
        }

    }


    private void OnTriggerEnter(Collider other)
    {
        if(this.currentHp >= 0f && other.CompareTag("Punch"))
        {
            this.currentHp -= 10f;
            Debug.Log($"Player hp = {currentHp / initHp}");

            if (this.currentHp <= 0f)
            {
                this.PlayerDie();
            }

        }
    }
    private void PlayerDie()
    {
        Debug.Log("Player Die!");

        ////Monster 태그를 가진 오브젝트들을 검색
        //GameObject[] monsters = GameObject.FindGameObjectsWithTag("Monster");

        ////모든 몬스터의 OnplayerDie함수실행

        //foreach(GameObject monster in monsters)
        //{
        //    monster.SendMessage("OnPlayerDie", SendMessageOptions.RequireReceiver);
        //}
        PlayerCtrl.OnPlayerDie(); //ㄷ대리차 호출(이벤트 발생 Dispatch)

    }


}

 

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

public class BulletCtrl : MonoBehaviour
{
    public float damage = 20.0f;
    public float force = 1500f;
    private Rigidbody rb;

    void Start()
    {
        this.rb = this.GetComponent<Rigidbody>();
        this.rb.AddForce(this.transform.forward * force);

        Destroy(gameObject, 3f);
    }

}

 

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

public class FollowCam : MonoBehaviour
{
    public Transform targetTr;
    private Transform camTr;

    //대상으로 부터 떨어질 거리 
    [Range(2.0f, 20.0f)]
    public float distance = 10.0f;

    //Y축으로 이동할 높이 
    [Range(0.0f, 10.0f)]
    public float height = 2.0f;

    //반응속도 
    public float damping = 10f;

    //SmoothDamp에 사용할 속도 변수
    private Vector3 velocity = Vector3.zero;

    //카메라 LookAt OffSet
    public float targetOffset = 2.0f;


    void Start()
    {
        this.camTr = GetComponent<Transform>();     //Transform 객체 캐싱 
    }

    private void LateUpdate()
    {
        //추적할 대상의 뒤쪽 distance만큼 이동 
        //높이를 height만큼 이동 
        Vector3 pos = this.targetTr.position +
            (-targetTr.forward * distance) +
            (Vector3.up * height);

        //구면 선형 보간 함수를 사용해 부드럽게 위치를 변경 
        //시작위치, 목표 위치, 시간 
        //this.camTr.position = Vector3.Slerp(this.camTr.position, pos, Time.deltaTime * damping);

        //시작위치, 목표위치, 현재속도, 목표 위치까지 도달할 시간
        this.camTr.position = Vector3.SmoothDamp(this.camTr.position, pos, ref velocity, damping);


        //Camera를 피벗 좌표를 향해 회전 
        this.camTr.LookAt(this.targetTr.position + (targetTr.up * targetOffset));

    }
}

 

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

public class FireCtrl : MonoBehaviour
{
    public GameObject bulletPrefab;
    public Transform firePoint;
    public AudioClip fireSfx;
    private AudioSource audioSource;

    private MeshRenderer muzzleFlash;
    private Coroutine muzzleFlashCoroutine;

    private void Start()
    {
        audioSource = GetComponent<AudioSource>();
        muzzleFlash = this.firePoint.GetComponentInChildren<MeshRenderer>();
        muzzleFlash.enabled = false;
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            this.Fire();
        }
    }

    private void Fire()
    {
        Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);

        audioSource.PlayOneShot(fireSfx, 1f);

        if (muzzleFlashCoroutine != null) StopCoroutine(muzzleFlashCoroutine);

        this.muzzleFlashCoroutine = StartCoroutine(this.ShowMuzzleFlash());
    }

    IEnumerator ShowMuzzleFlash()
    {
        Vector2 offset = new Vector2(Random.Range(0, 2), Random.Range(0, 2)) * 0.5f;
        muzzleFlash.material.mainTextureOffset = offset;



        float angle = Random.Range(0, 360);
        muzzleFlash.transform.localRotation = Quaternion.Euler(0, 0, angle);


        float scale = Random.Range(1, 2);
        muzzleFlash.transform.localScale = Vector3.one * scale;


        muzzleFlash.enabled = true; //보여주기 

        yield return new WaitForSeconds(0.2f);  //0.2초후 

        muzzleFlash.enabled = false;    //안보여주기 
    }
}

 

using UnityEngine;
using System.Collections;
using UnityEditor;


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

 

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

public class RemoveBullet : MonoBehaviour
{
    public GameObject sparkEffectPrefab;

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.collider.CompareTag("Bullet"))
        {
            //첫번째 충돌 지점의 정보 추출 
            ContactPoint contactPoint = collision.contacts[0];

            DrawArrow.ForDebug(contactPoint.point, -contactPoint.normal, 10, Color.red);

            //충돌한 총알의 법선 벡터를 쿼터니언으로 변환 
            Quaternion rot = Quaternion.LookRotation(-contactPoint.normal);

            Instantiate(this.sparkEffectPrefab, contactPoint.point, rot);

            Destroy(collision.gameObject);
        }
    }
}

 

using UnityEngine;

public class BarrelCtrl : MonoBehaviour
{
    private int hitCount = 0;
    private Transform trans;
    private Rigidbody rb;
    public GameObject explosionPrefab;
    public Texture[] textures;
    private new MeshRenderer renderer;

    //폭발 반경 
    public float radius = 10f;


    void Start()
    {
        this.trans = this.GetComponent<Transform>();
        this.rb = this.GetComponent<Rigidbody>();

        //자식에 있는 랜더러 컴포넌트를 찾아옴 
        this.renderer = this.GetComponentInChildren<MeshRenderer>();
        //난수 발생 
        int index = Random.Range(0, this.textures.Length);
        //텍스쳐 변경 
        this.renderer.material.mainTexture = textures[index];
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.collider.CompareTag("Bullet"))
        {
            if (++hitCount == 3)
            {
                ExplodeBarrel();
            }
        }
    }

    private void ExplodeBarrel()
    {
        //폭파 이펙트를 생성 
        GameObject exp = Instantiate(explosionPrefab, this.trans.position, Quaternion.identity);
        //이펙트 5초 있다가 제거 
        Destroy(exp, 5.0f);

        //힘을 위로 가함 
        //this.rb.mass = 1.0f;    //질량을 줄여주고 
        //this.rb.AddForce(Vector3.up * 1500);    //위로 힘을 가한다 

        //간접 폭발력 전달 
        IndirectDamage(this.trans.position);

        //3초후 드럼통 제거 
        Destroy(this.gameObject, 3);
    }

    private void IndirectDamage(Vector3 pos)
    {
        //주변에 있는 드럼통들을 모두 추출 
        Collider[] colls = Physics.OverlapSphere(pos, this.radius, 1 << 3);

        foreach (var coll in colls)
        {
            var rb = coll.GetComponent<Rigidbody>();
            rb.mass = 1.0f;
            rb.constraints = RigidbodyConstraints.None;
            rb.AddExplosionForce(1500, pos, radius, 1200);
        }
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(this.transform.position, this.radius);
    }


}

 

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

public class MonsterCtrl : MonoBehaviour
{
    public enum State
    {
        IDLE, TRACE, ATTACK, DIE //ctrl+shift+u : 대문자로 변경
    }

    public State state = State.IDLE;
    public float traceDist = 10f; //추적 사정거리
    public float attackDist = 2.0f;//공격 사거리
    public bool isDie = false;//몬스터 사망여부
    private int hp = 100; //몬스터 체력
    
    private NavMeshAgent agent;
    private Transform playerTrans;
    private Animator anim;
    private GameObject bloodEffectPrefab;

    private readonly int hashTrace = Animator.StringToHash("IsTrace");
    private readonly int hashAttack = Animator.StringToHash("IsAttack");
    private readonly int hashHit = Animator.StringToHash("Hit");
    private readonly int hashPlayerDie = Animator.StringToHash("PlayerDie");
    private readonly int hashSpeed = Animator.StringToHash("Speed");
    private readonly int hashDie = Animator.StringToHash("Die");


    private void OnEnable()
    {
        PlayerCtrl.OnPlayerDie += this.OnPlayerDie;
    }



    void Start()
    {
        //플레이어를 찾아아서 이동한다.
        this.agent = GetComponent<NavMeshAgent>();

        //프리팹을 로드한다 
        this.bloodEffectPrefab = Resources.Load<GameObject>("BloodSprayEffect");

        this.playerTrans = GameObject.FindWithTag("Player").transform;

        this.anim = GetComponent<Animator>();

        

        //이동한다
        this.agent.SetDestination(this.playerTrans.position);

        //몬스터의 상태를 체크 하는 코루틴
        this.StartCoroutine(CheckMonsterState());

        //상태에 따라 몬스터의 행동을 수행하는 코루틴
        this.StartCoroutine(this.MonsterAction());
    }

    IEnumerator MonsterAction()
    {
        while(!this.isDie)
        {
            switch (this.state)
            {
                case State.IDLE:
                    agent.isStopped = true; //추적정지
                    this.anim.SetBool(hashTrace, false);
                    break;
                case State.TRACE:
                    agent.SetDestination(this.playerTrans.position);
                    agent.isStopped = false; //추적을 재개
                    this.anim.SetBool(hashTrace, true);
                    this.anim.SetBool(hashAttack, false);
                    break;
                case State.ATTACK:
                    this.anim.SetBool(hashAttack, true);
                    break;
                case State.DIE:
                    this.isDie = true;
                    agent.isStopped = true;
                    anim.SetTrigger(hashDie);
                    GetComponent<CapsuleCollider>().enabled = false;
                    break;
            
            }


            yield return new WaitForSeconds(0.3f);
        }
    }

    IEnumerator CheckMonsterState()
    {
        while(!this.isDie)
        {
            yield return new WaitForSeconds(0.3f);

            //몬스터 상태가 죽었을 때 코루틴을 즉시 종료
            if(state == State.DIE)
            {
                yield break; //코루틴을 즉시 종료
            }

            //거리 측정
            float distance = Vector3.Distance(this.transform.position, playerTrans.position);

            //공격 사거리 측정
            if(distance <= attackDist)
            {
                state = State.ATTACK;
            }
            else if(distance <= traceDist) //시야에 들어오면
            {
                state = State.TRACE;
            }
            else //공격 사거리에도 안들어오고 시야에도 없다
            {
                state = State.IDLE;
            }
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.collider.CompareTag("Bullet"))
        {
            Destroy(collision.gameObject);
            anim.SetTrigger(this.hashHit);

            //충돌 지점을 가져온다.
            ContactPoint cp = collision.GetContact(0);
            Vector3 pos = cp.point;

            //충돌지점의 법선벡터(Noraml Vector)의 반대 방향으로 가져온다.
            //피격시 피가 튀기는 방향을 조절한다.
            Quaternion rot = Quaternion.LookRotation(-cp.normal);

            //이펙트를 생성
            this.ShowBloodEffect(pos, rot);

            hp -= 10;
            if(hp <= 0)
            {
                state = State.DIE;
            }
        }
    }


    void ShowBloodEffect(Vector3 pos, Quaternion rot)
    {
        //프리팹의 인스턴스화 게임인스턴스, 위치, 회전, 부모
        GameObject blood = Instantiate(this.bloodEffectPrefab, pos, rot, this.transform);

        Destroy(blood, 1.0f);
    }

    private void OnDrawGizmos()
    {
        if(state == State.TRACE)
        {
            Gizmos.color = Color.blue;
            Gizmos.DrawWireSphere(transform.position, traceDist);
        }
        if (state == State.ATTACK)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(transform.position, attackDist);
        }
    }

    public void OnPlayerDie()
    {
        this.StopAllCoroutines();

        agent.isStopped = true;

        anim.SetFloat(hashSpeed, Random.Range(0.8f, 1.2f));

        anim.SetTrigger(hashPlayerDie);
    }

    private void OnDisable()
    {
        //이벤트 리스너를 제거
        PlayerCtrl.OnPlayerDie -= this.OnPlayerDie;
    }


}

'산대특 > 게임 클라이언트 프로그래밍' 카테고리의 다른 글

Occlusion Culling  (0) 2024.03.18
Sprite Shooter - Destroy Barrel and Layer  (0) 2024.03.13
Zombie - IK  (0) 2024.03.07
Zombie - Line Render  (0) 2024.03.07
Tank - Rigidbody.MovePosition  (0) 2024.03.06

+ Recent posts