ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (4) 적 유닛 제작 #1
    Galaxy Ball/2. 싱글플레이 - 스토리모드 2024. 5. 20. 12:12

    이번엔 스테이지 안에서 쓰일 적 유닛을 만들어보도록 하겠다

     

    적 유닛의 디자인은 이미 예전에 구상해두었고, 사실상 모든 유닛이 거의 비슷한 구조라

    가장 맨처음 기본유닛의 구현만 성공한다면 나머지 구현은 어렵지 않아보인다

     

     

    우선 기본 구조이다.

    1. 몸통이 될 구체(Center)

    2. 발사할 총구(Gun)

    3. 총알이 발사될 빈 오브젝트(Muzzle)로 구성되어 있다

     

    이제 구현할 내용들을 정리해보자

     

    1. 몸통에 내구도(HP)가 보이고, 몸통과 총알이 충돌시 내구도가 1씩 줄어든다. 내구도가 0이되면 파괴

    2. 총구는 몸통과 함께 회전한다. 총구와 총알이 충돌시 내구도가 줄어들지 않는다

    3. 빈 오브젝트에서 총알이 발사된다. 총알의 방향은 총구의 방향과 같다

     

    이제 한번 하나씩 구현해나가보겠다

     

     

    코드를 작성하기전에 컴포넌트부터 간단하게 보자 각각 RigidBody2D와 충돌판정을 위해 BoxCollider2D를 붙여주었다

    그리고 Center에는 내구도와 충돌판정, 그리고 회전을 구현할 Enemy1Center 스크립트를 부착해주었다

    두번째는 날아갈 총알을 생성할 Muzzle이다. 총알을 발사할 Enemy1Fire 스크립트를 부착해주었다

     

    총알로 쓰일 Bullet이다. 이것 역시 기본 콜라이더와 총알의 내구도, 총알이 날아가는것을 구현할 스크립트를 부착했다

     

    이제 코드를 한번 짜보자. 사실 기본적인 틀인 기존에 짜두었던 시스템과 크게 다르지 않기에 

    이번에도 미리 짜둔 코드들을 활용해보도록 하겠다

     

     

    1. 몸통에 내구도(HP)가 보이고, 몸통과 총알이 충돌시 내구도가 1씩 줄어든다. 내구도가 0이되면 파괴

    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class Enemy1center : MonoBehaviour
    {
        Rigidbody2D rigid;
        public float increase = 4f;
        public bool hasExpanded = false;
        private int randomNumber;
        private TextMeshPro textMesh;
    
        private void Start()
        {
            rigid = GetComponent<Rigidbody2D>();
            GameObject textObject = new GameObject("TextMeshPro");
            textObject.transform.parent = transform;
            textMesh = textObject.AddComponent<TextMeshPro>();
            randomNumber = Random.Range(1, 6);
            textMesh.text = randomNumber.ToString();
            textMesh.fontSize = 6;
            textMesh.alignment = TextAlignmentOptions.Center;
            textMesh.autoSizeTextContainer = true;
            textMesh.rectTransform.localPosition = Vector3.zero;
            textMesh.sortingOrder = 3;
    
            StartCoroutine(RotateObject());
        }
        private void OnCollisionEnter2D(Collision2D coll)
        {
            if (coll.gameObject.tag == "P1ball" || coll.gameObject.tag == "P2ball" || coll.gameObject.tag == "P1Item" || coll.gameObject.tag == "P2Item")
            {
                if (randomNumber > 0)
                {
                    randomNumber--;
                    textMesh.text = randomNumber.ToString();
                }
                if (randomNumber <= 0)
                {
                    Destroy(transform.parent.gameObject); // 부모 오브젝트 삭제
                }
            }
        }
    
        private IEnumerator RotateObject()
        {
            while (true)
            {
                float targetAngle = Random.Range(-60f, 60f);
                float currentAngle = transform.eulerAngles.z;
                float rotationTime = 1f; // 회전하는 데 걸리는 시간
                float elapsedTime = 0f;
    
                while (elapsedTime < rotationTime)
                {
                    elapsedTime += Time.deltaTime;
                    float angle = Mathf.LerpAngle(currentAngle, targetAngle, elapsedTime / rotationTime);
                    transform.eulerAngles = new Vector3(0, 0, angle);
                    yield return null;
                }
    
                yield return new WaitForSeconds(Random.Range(3f, 5f));
            }
        }
    }

     

     

    우선 초반에 내구도 시스템은 BallController 스크립트에서 그대로 가져왔다


        private IEnumerator RotateObject()
        {
            while (true)
            {
                float targetAngle = Random.Range(-60f, 60f);
                float currentAngle = transform.eulerAngles.z;
                float rotationTime = 1f; // 회전하는 데 걸리는 시간
                float elapsedTime = 0f;

                while (elapsedTime < rotationTime)
                {
                    elapsedTime += Time.deltaTime;
                    float angle = Mathf.LerpAngle(currentAngle, targetAngle, elapsedTime / rotationTime);
                    transform.eulerAngles = new Vector3(0, 0, angle);
                    yield return null;
                }

                yield return new WaitForSeconds(Random.Range(3f, 5f));
            }
    }

     

    새로 추가된건 오브젝트의 회전을 구현하는 코루틴 하나. 각도는 -60~60도 간격안에서 회전하도록 설계했고

    끊임없이 회전만 하는것이 아닌 특정 각도를 정하고 그 각도까지 부드럽게 회전한뒤에 3~5초 정도를 대기한뒤

    다시 코루틴이 돌아 새로운 각도를 정하고 그 위치까지 부드럽게 회전하는것

     

    이것을 반복하도록 while(true) 로 걸어두었다

     

    2. 총구는 몸통과 함께 회전한다. 총구와 총알이 충돌시 내구도가 줄어들지 않는다

    3. 빈 오브젝트에서 총알이 발사된다. 총알의 방향은 총구의 방향과 같다

     

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UIElements;
    
    public class Enemy1Fire : MonoBehaviour
    {
        public GameObject enemyBulletPrefab;
        public static float ShotPower;
        public static Vector3 ShotDirection;
        void Start()
        {
            StartCoroutine(SpawnBullets());
        }
        private IEnumerator SpawnBullets()
        {
            while (true)
            {
                float waitTime = Random.Range(4f, 7f);
                yield return new WaitForSeconds(waitTime);
                Instantiate(enemyBulletPrefab, transform.position, Quaternion.identity);
            }
        }
      
    }

     

    총알을 생성할 빈 오브젝트에 부착될 스크립트

    이번 역시 쭉 반복될 코루틴안에 4~7초 사이에 총알을 하나 생성하도록 만들었다

     

    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class EnemyBulletControl : MonoBehaviour
    {
        Rigidbody2D rigid;
        Vector2 lastVelocity;
        float deceleration = 2f;
        public float increase = 4f;
        private bool iscolliding = false;
        public bool hasExpanded = false;
        private bool isStopped = false;
        private int randomNumber;
        private TextMeshPro textMesh;
        private bool hasBeenReleased = false;
        private float rotationAngle = 0f; // 회전 각도를 저장할 변수
    
        public int BallMinHP = 1;
        public int BallMaxHP = 6;
        //public AudioSource HitSound;
        //public AudioSource SwellSound;
    
        private void Start()
        {
            rigid = GetComponent<Rigidbody2D>();
            GameObject textObject = new GameObject("TextMeshPro");
            textObject.transform.parent = transform;
            textMesh = textObject.AddComponent<TextMeshPro>();
            randomNumber = Random.Range(BallMinHP, BallMaxHP);
            textMesh.text = randomNumber.ToString();
            textMesh.fontSize = 2;
            textMesh.alignment = TextAlignmentOptions.Center;
            textMesh.autoSizeTextContainer = true;
            textMesh.rectTransform.localPosition = Vector3.zero;
            textMesh.sortingOrder = 1;
            LaunchBall();
        }
    
        private void Update()
        {
            Move();
            expand();
        }
    
        public void SetRotationAngle(float angle)
        {
            rotationAngle = angle;
        }
    
        void Move()
        {
            if (rigid == null || isStopped) return;
    
            lastVelocity = rigid.velocity;
            rigid.velocity -= rigid.velocity.normalized * deceleration * Time.deltaTime;
    
            if (rigid.velocity.magnitude <= 0.01f && hasExpanded)
            {
                isStopped = true;
                StartCoroutine(DestroyRigidbodyDelayed());
            }
        }
    
        void expand()
        {
            if (rigid == null || iscolliding) return;
            if (rigid.velocity.magnitude > 0.1f) return;
            if (Input.GetMouseButton(0)) return;
    
            //if (!hasExpanded)
            //{
            //    SwellSound.Play();
            //}
            transform.localScale += Vector3.one * increase * Time.deltaTime;
            hasExpanded = true;
        }
    
        private void OnCollisionEnter2D(Collision2D coll)
        {
            //if (!hasExpanded)
            //{
            //    HitSound.Play();
            //}
            if ((coll.gameObject.tag == "P1ball" || coll.gameObject.tag == "P2ball" || coll.gameObject.tag == "P1Item" || coll.gameObject.tag == "P2Item" || coll.gameObject.tag == "EnemyBall") && rigid == null)
            {
                if (randomNumber > 0)
                {
                    randomNumber--;
                    textMesh.text = randomNumber.ToString();
                }
                if (randomNumber <= 0)
                {
                    Destroy(gameObject);
                }
            }
            if (coll.contacts != null && coll.contacts.Length > 0)
            {
                Vector2 dir = Vector2.Reflect(lastVelocity.normalized, coll.contacts[0].normal);
                if (rigid != null)
                    rigid.velocity = dir * Mathf.Max(lastVelocity.magnitude, 0f); // 벡터 값이 음수가 되지 않게 함
            }
            this.iscolliding = true;
        }
    
        private void OnCollisionExit2D(Collision2D collision)
        {
            this.iscolliding = false;
        }
    
        IEnumerator DestroyRigidbodyDelayed()
        {
            yield return new WaitForSeconds(0.8f);
            if (rigid != null)
                Destroy(rigid);
        }
    
        public void LaunchBall()
        {
            if (rigid != null && !hasBeenReleased)
            {
                float ShotPower = Random.Range(4f, 8f);
                Vector2 launchDirection = Quaternion.Euler(0, 0, rotationAngle) * Vector2.up;
                rigid.velocity = launchDirection * ShotPower;
                hasBeenReleased = true;
            }
        }
    }

     

    총알에 부착될 스크립트. 위에서 총알을 생성한다면 생성된 총알은 Center의 각도에 맞춰 날아간다

    나머지 총의 내구도나 팽창, 반사 같은 부분들은 전부 BallController에서 가져왔다

     

     

     

    근데 뭔가 이상하다..발사되는게 총구의 방향에 맞춰 발사되는것이 아닌 무조건 위방향으로 발사되는것 같다

    다만 발사 되자마자 총구에 부딫혀 반사되어 애매하게 보일뿐. 

     

     

    ....

     

    그리고 이걸 해결하는데만 1시간이 걸렸다.. 무슨 짓을 해도 총구의 방향대로 발사되는것이 아닌

    한쪽 방향으로만 발사되다 결국 해답을 찾았다

     

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Enemy1Fire : MonoBehaviour
    {
        public GameObject enemyBulletPrefab;
    
        public void SpawnBullet()
        {
            GameObject bullet = Instantiate(enemyBulletPrefab, transform.position, Quaternion.identity);
            Vector2 shotDirection = -transform.up;
            float shotPower = Random.Range(4f, 8f);
            Rigidbody2D bulletRigidbody = bullet.GetComponent<Rigidbody2D>();
            bulletRigidbody.velocity = shotDirection * shotPower;
        }
    }

     

    정답은 Center의 회전값을 받아 발사하는게 아닌 총알을 생성하는 빈 오브젝트였다

    어느방향으로 회전하든 빈 오브젝트의 Y축값을 마이너스로 반전시켜 발사하면 해결되는 문제였다

     

    그렇기에 총알을 생성하는 빈 오브젝트에서 생성뿐만 아니라 힘을 주어 발사하는 역할까지 맡게 되었다

    그렇기에 EnemyBulletControl에서는 내구도와 반사, 점점 줄어드는 속도등 총알의 자체 데이터들을 맡게 되었다

     

     

     

    하아..깔끔하게 나가는거 편안하다

     

    이 외에도 총알이 연속 두번 발사되는등의 오류나 한번 회전하는데 5초, 회전을 멈춘뒤 발사하는데 1초 대기등

    수정사항이 몇가지 더 있었지만 설명은 생략하도록 하겠다

     

     

     

    using System.Collections;
    using TMPro;
    using UnityEngine;
    
    public class Enemy1center : MonoBehaviour
    {
        Rigidbody2D rigid;
        public float increase = 4f;
        public bool hasExpanded = false;
        private int randomNumber;
        private TextMeshPro textMesh;
        public bool isStop;
        private Enemy1Fire enemy1Fire;
    
        private void Start()
        {
            rigid = GetComponent<Rigidbody2D>();
            GameObject textObject = new GameObject("TextMeshPro");
            textObject.transform.parent = transform;
            textMesh = textObject.AddComponent<TextMeshPro>();
            randomNumber = Random.Range(1, 6);
            textMesh.text = randomNumber.ToString();
            textMesh.fontSize = 6;
            textMesh.alignment = TextAlignmentOptions.Center;
            textMesh.autoSizeTextContainer = true;
            textMesh.rectTransform.localPosition = Vector3.zero;
            textMesh.sortingOrder = 3;
    
            enemy1Fire = GetComponentInChildren<Enemy1Fire>(); // Enemy1Fire 컴포넌트 참조
    
            StartCoroutine(RotateObject());
        }
    
        private void OnCollisionEnter2D(Collision2D coll)
        {
            if (coll.gameObject.tag == "P1ball" || coll.gameObject.tag == "P2ball" || coll.gameObject.tag == "P1Item" || coll.gameObject.tag == "P2Item")
            {
                if (randomNumber > 0)
                {
                    randomNumber--;
                    textMesh.text = randomNumber.ToString();
                }
                if (randomNumber <= 0)
                {
                    Destroy(transform.parent.gameObject); // 부모 오브젝트 삭제
                }
            }
        }
    
        private IEnumerator RotateObject()
        {
            while (true)
            {
                // 5초 동안 정지
                yield return new WaitForSeconds(5f);
    
                // 회전할 각도 설정
                float targetAngle = Random.Range(-70f, 70f);
                float currentAngle = transform.eulerAngles.z;
                float rotationTime = 1f; // 회전하는 데 걸리는 시간
                float elapsedTime = 0f;
    
                // 회전하기
                while (elapsedTime < rotationTime)
                {
                    elapsedTime += Time.deltaTime;
                    float angle = Mathf.LerpAngle(currentAngle, targetAngle, elapsedTime / rotationTime);
                    transform.eulerAngles = new Vector3(0, 0, angle);
                    yield return null;
                }
    
                // 회전이 끝난 후 1초 뒤에 총알 발사
                yield return new WaitForSeconds(1f);
    
                if (enemy1Fire != null)
                {
                    enemy1Fire.SpawnBullet();
                }
            }
        }
    }
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Enemy1Fire : MonoBehaviour
    {
        public GameObject enemyBulletPrefab;
    
        public void SpawnBullet()
        {
            GameObject bullet = Instantiate(enemyBulletPrefab, transform.position, Quaternion.identity);
            Vector2 shotDirection = -transform.up;
            float shotPower = Random.Range(4f, 8f);
            Rigidbody2D bulletRigidbody = bullet.GetComponent<Rigidbody2D>();
            bulletRigidbody.velocity = shotDirection * shotPower;
        }
    }
    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class EnemyBulletControl : MonoBehaviour
    {
        Rigidbody2D rigid;
        Vector2 lastVelocity;
        float deceleration = 2f;
        public float increase = 4f;
        private bool iscolliding = false;
        public bool hasExpanded = false;
        private bool isStopped = false;
        private int randomNumber;
        private TextMeshPro textMesh;
        private bool hasBeenReleased = false;
        private float rotationAngle = 0f; // 회전 각도를 저장할 변수
    
        public int BallMinHP = 1;
        public int BallMaxHP = 6;
        //public AudioSource HitSound;
        //public AudioSource SwellSound;
    
        private void Start()
        {
            rigid = GetComponent<Rigidbody2D>();
            GameObject textObject = new GameObject("TextMeshPro");
            textObject.transform.parent = transform;
            textMesh = textObject.AddComponent<TextMeshPro>();
            randomNumber = Random.Range(BallMinHP, BallMaxHP);
            textMesh.text = randomNumber.ToString();
            textMesh.fontSize = 2;
            textMesh.alignment = TextAlignmentOptions.Center;
            textMesh.autoSizeTextContainer = true;
            textMesh.rectTransform.localPosition = Vector3.zero;
            textMesh.sortingOrder = 1;
        }
    
        private void Update()
        {
            Move();
            expand();
        }
    
        public void SetRotationAngle(float angle)
        {
            rotationAngle = angle;
        }
    
        void Move()
        {
            if (rigid == null || isStopped) return;
    
            lastVelocity = rigid.velocity;
            rigid.velocity -= rigid.velocity.normalized * deceleration * Time.deltaTime;
    
            if (rigid.velocity.magnitude <= 0.01f && hasExpanded)
            {
                isStopped = true;
                StartCoroutine(DestroyRigidbodyDelayed());
            }
        }
    
        void expand()
        {
            if (rigid == null || iscolliding) return;
            if (rigid.velocity.magnitude > 0.1f) return;
            if (Input.GetMouseButton(0)) return;
    
            //if (!hasExpanded)
            //{
            //    SwellSound.Play();
            //}
            transform.localScale += Vector3.one * increase * Time.deltaTime;
            hasExpanded = true;
        }
    
        private void OnCollisionEnter2D(Collision2D coll)
        {
            //if (!hasExpanded)
            //{
            //    HitSound.Play();
            //}
            if ((coll.gameObject.tag == "P1ball" || coll.gameObject.tag == "P2ball" || coll.gameObject.tag == "P1Item" || coll.gameObject.tag == "P2Item" || coll.gameObject.tag == "EnemyBall") && rigid == null)
            {
                if (randomNumber > 0)
                {
                    randomNumber--;
                    textMesh.text = randomNumber.ToString();
                }
                if (randomNumber <= 0)
                {
                    Destroy(gameObject);
                }
            }
            if (coll.contacts != null && coll.contacts.Length > 0)
            {
                Vector2 dir = Vector2.Reflect(lastVelocity.normalized, coll.contacts[0].normal);
                if (rigid != null)
                    rigid.velocity = dir * Mathf.Max(lastVelocity.magnitude, 0f); // 벡터 값이 음수가 되지 않게 함
            }
            this.iscolliding = true;
        }
    
        private void OnCollisionExit2D(Collision2D collision)
        {
            this.iscolliding = false;
        }
    
        IEnumerator DestroyRigidbodyDelayed()
        {
            yield return new WaitForSeconds(0.8f);
            if (rigid != null)
                Destroy(rigid);
        }
    }

     

     

    아 마지막으로 EnemyBall이라는 새로운 속성의 오브젝트가 생겼으니 다른 구체들과의 충돌판정도 나도록 해주자

Designed by Tistory.