K-digital traning/유니티 심화

[주말과제] HeroShooter stage1까지

내꺼블로그 2023. 8. 28. 00:43

1.코드

 

-Tutorial

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class TutorialScene : MonoBehaviour
{
    [SerializeField]
    private GameObject portalPrefab;
    [SerializeField]
    private GameObject nextPortalPrefab;
    [SerializeField]
    private GameObject playerPrefab;
    [SerializeField]
    private FloatingJoystick joystick;
    [SerializeField]
    private GameObject doorGo;
    [SerializeField]
    private Image dim;

    private Player player;
    private Portal portal;
    private Door door;
    private bool finish;

    // Start is called before the first frame update
    void Start()
    {

        GameObject portalGo = Instantiate(portalPrefab);
        portalGo.transform.position = new Vector3(-3.5f, 0.05f, 10);
        portal = portalGo.GetComponent<Portal>();
        GameObject playerGo = Instantiate(playerPrefab);
        player = playerGo.GetComponent<Player>();
        door = this.doorGo.GetComponent<Door>();
        portal.onArrive = () =>
        {
            Debug.Log("플레이어 도착");
            door.OpenTheDoor();
            GameObject nextPortalGo = Instantiate(nextPortalPrefab);
            nextPortalGo.transform.position = doorGo.transform.position;
            nextPortalGo.transform.Translate(Vector3.up * 1.8f);
        };
        door.onArrive = () =>
        {
            Debug.Log("next stage");
            this.finish = true;
            player.StopAct();
            StartCoroutine(FadeOut());
        };
    }

    // Update is called once per frame
    void Update()
    {
        if (this.finish)
            return;
        float h = joystick.Direction.x;
        float v = joystick.Direction.y;

        this.player.Motion(h, v);
    }

    private IEnumerator FadeOut()
    {
        yield return new WaitForSeconds(0.2f);

        for (float i = 0f; i < 1f; i += 0.02f)
        {
            Color color = dim.color;
            color.a = i;
            dim.color = color;
            yield return null;
        }
        SceneManager.LoadScene("Stage1");
        Debug.Log("next");
    }
}

 

 

 

 

-Portal

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

public class Portal : MonoBehaviour
{
    private bool isArrive;
    public System.Action onArrive;
    // Start is called before the first frame update
    //void Start()
    //{

    //}

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

    //}

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player")&&!this.isArrive)
        {
            isArrive = true;
            this.onArrive();
            StartCoroutine(CoDestroy());
        }
    }

    private IEnumerator CoDestroy()
    {
        yield return new WaitForSeconds(0.2f);
        Destroy(this.gameObject);
    }
}

 

 

 

 

 

-Door

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

public class Door : MonoBehaviour
{
    [SerializeField]
    private Transform left;
    [SerializeField]
    private Transform right;

    private bool isOpen;
    public System.Action onArrive;
    //// Start is called before the first frame update
    //void Start()
    //{
        
    //}

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

    public void OpenTheDoor()
    {
        this.GetComponent<BoxCollider>().isTrigger = true;
        this.GetComponent<BoxCollider>().center = new Vector3(-2f, 0f, 0f);
        StartCoroutine(CoOpenTheDoor());
    }

    private IEnumerator CoOpenTheDoor()
    {
        Quaternion leftDoor = Quaternion.Euler(0f, -100f, 0f);
        Quaternion rightDoor = Quaternion.Euler(0f, 100f, 0f);
        while (true)
        {
            this.left.localRotation = Quaternion.Slerp(this.left.localRotation, leftDoor, 3f*Time.deltaTime);
            this.right.localRotation = Quaternion.Slerp(this.right.localRotation, rightDoor, 3f*Time.deltaTime);
            if (this.left.localRotation == leftDoor)
                break;
            yield return null;
        }
        isOpen = true;
    }

    private void OnTriggerEnter(Collider other)
    {
        if (isOpen&&other.CompareTag("Player"))
        {
            this.onArrive();
        }
    }
}

 

 

 

-Player

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;

public class Player : MonoBehaviour
{
    public enum eState
    {
        Idle, Run, Shoot
    }

    [SerializeField]
    private float moveSpeed = 1f;

    [SerializeField]
    private float turnSpeed = 80f;

    [SerializeField]
    private float radius = 20f;

    [SerializeField]
    private GameObject gunPos;

    private Vector3 dir;
    private Animator anim;
    private eState state;
    private Coroutine moveCoroutine;
    private Coroutine attackCoroutine;
    private bool isFinish;
    private int monsterLayer;
    private int environmentLayer;
    private Monster target;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = this.GetComponent<Animator>();
        this.monsterLayer = LayerMask.NameToLayer("MONSTER");
        this.environmentLayer = LayerMask.NameToLayer("ENVIRONMENT");
    }

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





        }*/

    public void Motion(float h, float v)
    {
        if (isFinish) return;
        //방향
        dir = Vector3.forward * v + Vector3.right * h;
        //Debug.LogFormat("dir: {0}", dir);
        this.DetectMonster();
        //회전
        if (dir != Vector3.zero)
        {
            this.Move();
        }
        else
        {
            if(target==null)
                PlayAnimation(eState.Idle);
            else
            {
                //Debug.Log("공격");
                this.Attack();
            }
        }
    }

    private void Move()
    {
        this.PlayAnimation(eState.Run);
        float angle = Mathf.Rad2Deg * Mathf.Atan2(dir.x, dir.z);
        this.transform.localRotation = Quaternion.AngleAxis(angle, Vector3.up);
        if (moveCoroutine != null)
            StopCoroutine(moveCoroutine);
        moveCoroutine = StartCoroutine(CoMove());
    }

    private IEnumerator CoMove()
    {
        while (true)
        {
            this.transform.Translate(dir.normalized * this.moveSpeed * Time.deltaTime, Space.World);
            yield return null;
        }
    }


    //몬스터 감지 메서드
    private void DetectMonster()
    {
        Collider[] monster = Physics.OverlapSphere(this.transform.position, radius, 1 << monsterLayer);
        //foreach(Collider collider in monster)
        //{
        //    Debug.LogFormat("monster: {0}", collider.name);
        //}
        if (monster.Length != 0)
        {
            float minDis = 1000000f;
            foreach (Collider coll in monster)
            {
                if (coll.GetComponent<Monster>().isDie)
                    continue;
                float dis = Vector3.Distance(this.transform.position, coll.transform.position);
                if (minDis > dis)
                {
                    minDis = dis;
                    this.target = coll.GetComponent<Monster>();
                }
            }
            //Debug.LogFormat("<color=red>minDis: {0}, targetPosition: {1}</color>", minDis, target.transform.position);
            Vector3 targetDir = this.target.transform.position - this.transform.position;
            Ray ray = new Ray(this.transform.position + Vector3.up, targetDir);
            Debug.DrawRay(ray.origin, ray.direction*1000f, Color.red);
            RaycastHit hit;
            LayerMask layerMask = 1 << monsterLayer | 1 << environmentLayer;
            if (Physics.Raycast(ray.origin, ray.direction, out hit, 1000f, layerMask))
            {
                if (hit.collider.CompareTag("Monster"))
                {
                    //Debug.Log(this.target.name);
                }
                else
                    this.target = null;
            }
            else
                this.target = null;
        }
        //Debug.LogFormat("<color=yellow>target: {0}</color>", target);
    }

    //몬스터 공격
    private void Attack()
    {
        //타겟을 조준
        this.transform.LookAt(this.target.transform.position);

        //애니메이션   
        if(this.attackCoroutine==null)
        this.attackCoroutine = this.StartCoroutine(CoAttack());
    }

    private IEnumerator CoAttack()
    {
        this.PlayAnimation(eState.Shoot);
        yield return new WaitForSeconds(0.05f);
        Debug.Log("Attack");
        BulletGenerator bulletGenerator = this.gunPos.GetComponentInChildren<BulletGenerator>();
        bulletGenerator.Generator();
        yield return new WaitForSeconds(0.6f);
        this.attackCoroutine = null;
    }

    private void PlayAnimation(eState state)
    {
        this.state = state;
        this.anim.SetInteger("State", (int)this.state);
        if (this.state == eState.Shoot)
            this.anim.Play("Shoot", -1, 0);

    }

    public void StopAct()
    {
        isFinish = true;
        this.dir = Vector3.zero;
        PlayAnimation(eState.Idle);
    }
}

 

 

-Stage1

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

public class Stage1 : MonoBehaviour
{
    [SerializeField]
    private FloatingJoystick joystick;

    [SerializeField]
    private GameObject portalPrefab;

    [SerializeField]
    private MonsterGenerator monsterGenerator;
    private List<Monster> monsterList;

    [SerializeField]
    private Image dim;

    [SerializeField]
    private GameObject doorGo;

    [SerializeField]
    private GameObject playerPrefab;
    private Player player;

    // Start is called before the first frame update
    void Start()
    {
        
        GameObject playerGo = Instantiate(playerPrefab);
        this.player = playerGo.GetComponent<Player>();

        Door door = doorGo.GetComponent<Door>();

        GameObject monsterGo1 = monsterGenerator.Generator(0);
        GameObject monsterGo2 = monsterGenerator.Generator(0);
        monsterList = new List<Monster>
        {
            monsterGo1.GetComponent<Monster>(),
            monsterGo2.GetComponent<Monster>()
        };

        monsterGo1.transform.position = new Vector3(-4f, 0f, 13f);
        monsterGo2.transform.position = new Vector3(3f, 0f, 11f);

        foreach(Monster monster in monsterList)
        {
            monster.onDie = () =>
            {
                Debug.LogFormat("{0} is Die", monster.transform.name);
                Destroy(monster.gameObject);
                monsterList.Remove(monster);
                if (monsterList.Count <= 0)
                {
                    Debug.Log(monsterList.Count);
                    door.OpenTheDoor();
                    GameObject portalGo = Instantiate(portalPrefab);
                    portalGo.transform.position = doorGo.transform.position;
                    portalGo.transform.Translate(Vector3.up * 1.8f);
                }
            };
        }

        StartCoroutine(FadeIn());
    }

    // Update is called once per frame
    void Update()
    {
        float h = joystick.Direction.x;
        float v = joystick.Direction.y;

        this.player.Motion(h, v);
    }

    private IEnumerator FadeIn()
    {
        Debug.Log("start");
        float alpha = dim.color.a;
        for(float i = alpha; i > 0; i -= 0.02f)
        {
            Color color = dim.color;
            color.a = i;
            dim.color = color;
            yield return null;
        }
        yield return new WaitForSeconds(0.2f);
    }
}

 

 

-BulletGenerator

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

public class BulletGenerator : MonoBehaviour
{
    [SerializeField]
    private GameObject bulletPrefab;
    // Start is called before the first frame update
    /*void Start()
    {
        
    }

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

    public void Generator()
    {
        GameObject bullet = Instantiate(bulletPrefab,this.transform);
    }
}

 

 

 

 

-Bullet

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

public class Bullet : MonoBehaviour
{
    private float damage = 10f;
    // Start is called before the first frame update
    void Start()
    {
        this.transform.SetParent(null);
    }

    // Update is called once per frame
    void Update()
    {
        this.transform.Translate(Vector3.forward * Time.deltaTime * 5f);
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Monster"))
        {
            Debug.Log("<color=blue>Attack</color>");
            Monster monster = other.GetComponent<Monster>();
            monster.HitDamage(this.damage);
        }

        Destroy(this.gameObject);
    }
}

 

 

 

 

-MonsterGenerator

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

public class MonsterGenerator : MonoBehaviour
{
    [SerializeField]
    private List<GameObject> monsterPrefabs;
    // Start is called before the first frame update
    //void Start()
    //{
        
    //}

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

    public GameObject Generator(int idx)
    {
        GameObject monster = Instantiate(monsterPrefabs[idx]);
        return monster;
    }
}

 

 

 

 

-Monster

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

public class Monster : MonoBehaviour
{
    public enum eState
    {
        Idle, HitDamage, Die
    }

    private float hp;
    private float curHp;
    public bool isDie;
    public System.Action onDie;
    private Animator anim;
    private eState state;

    // Start is called before the first frame update
    void Start()
    {
        this.hp = 100f;
        this.curHp = hp;
        this.anim=this.transform.GetComponent<Animator>();
        this.PlayAnimation(this.state);
    }

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

     }*/

    public void HitDamage(float damage)
    {
        this.curHp -= damage;
        this.anim.SetTrigger("Hit");
        Debug.LogFormat("monster Hp: {0}", this.curHp);
        if (curHp <= 0)
        {
            this.Die();
        }
    }

    private void Die()
    {
        Debug.Log("monster Die");
        this.isDie = true;
        StartCoroutine(CoDie());
    }

    private IEnumerator CoDie()
    {
        this.PlayAnimation(eState.Die);
        yield return new WaitForSeconds(0.8f);
        this.onDie();
    }

    private void PlayAnimation(eState state)
    {
        this.state = state;
        this.anim.SetInteger("State", (int)this.state);
    }
}

 

 

 

 

결과

 

 

 

 

 

 

-아쉬운 부분

(1)UI 구현을 못했다.

(2)총 방향이 어색하다.(플레이어가 타겟을 바라보도록 설정하였으나 총의 위치가 플레이어와는 달라 날아가는 총알의 방향이 타겟에 묘하게 맞지 않음)

(3)애니메이션 타이밍이 어설프다

(4)나무들에 콜라이더를 통째로 박아버릴걸 그랬다.(나무 기둥 사이에 틈으로 총알이 발사되는 경우 발생 -> 간혹 총알이 나무에 부딪히기도 함... 요건 좀 해결하자 제발,,)

 

 

결론 : 공부좀 합시다,,