C#/수업 내용

Vertical2DShooting

HSH12345 2023. 2. 3. 13:17

GameEnums

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

public class GameEnums
{
    public enum eGunType
    {
        Deafualt,
        Mulitple
    }
}

 

 - 게임에 필요한 정보를 Enum형식으로 저장

 

 

App

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

public class App : MonoBehaviour
{
    private string version = "0.0.2";

    private void Awake()
    {
        Object.DontDestroyOnLoad(this.gameObject);
    }

    private void Start()
    {
        AsyncOperation oper =  SceneManager.LoadSceneAsync("Title");
        oper.completed += (obj) => {

            TitleMain titleMain = GameObject.FindObjectOfType<TitleMain>();
            titleMain.Init(this.version);
        };        
    }
}

 - 처음 시작시 비동기적으로 다른 씬을 불러오고 대리자를 통해 TitleMain 스크립트를 읽고(FindObjectOfType) 해당 클래스 내의 Init메서드를 호출한다. 버전정보를 Init메서드의 인자로 입력하여 다음 씬에 버전 정보를 전달한다.

 

 - 해당 스크립트는 다른 씬에서도 유지된다.

 

 

TitleMain

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

public class TitleMain : MonoBehaviour
{
    public Text txtVersion;

    public void Init(string version)
    {
        this.txtVersion.text = version;
    }

    public void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            SceneManager.LoadScene("Lobby");
        }
    }
}

 - App 클래스로부터 전달받은 문자열(버전정보)를 UI.Text형식(컴포넌트)의 변수의 text에 접근하여 저장한다. 

 

 - 마우스를 클릭을 인식하여 Lobby씬으로 이동한다.

 

 

LobbyMain

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

public class LobbyMain : MonoBehaviour
{
    public Button btnStart;
    public Button btnGun1;  //기본 
    public Button btnGun2;  //멀티 
    public Text textSelectedGun;

    private GameEnums.eGunType selectedGunType;

    public GameEnums.eGunType SelectedGunType
    {
        set {
            this.selectedGunType = value;
            this.textSelectedGun.text = string.Format("{0}를 선택했습니다.", this.selectedGunType);
        }
    }

    private AsyncOperation oper;
    void Start()
    {
        this.SelectedGunType = GameEnums.eGunType.Default;

        this.btnStart.onClick.AddListener(() => {
            Debug.Log("게임 시작");
            //선택된 건의 타입 출력 
            Debug.Log(this.selectedGunType);
            
            this.oper =  SceneManager.LoadSceneAsync("Game");
            oper.completed += (obj) =>
            {
                GameMain gameMain = GameObject.FindObjectOfType<GameMain>();
                gameMain.Init(this.selectedGunType);
            };

        });

        this.btnGun1.onClick.AddListener(() => {
            Debug.Log("기본 건을 선택 했습니다.");
            this.SelectedGunType = GameEnums.eGunType.Default;
        });

        this.btnGun2.onClick.AddListener(() => {
            Debug.Log("멀티 건을 선택 했습니다.");
            this.SelectedGunType = GameEnums.eGunType.Multiple;
        });
    }
}

 - UI의 버튼 게임오브젝트를 컴포넌트에서 입력받고 변수에 저장하여 Start메서드에서 기능을 구현한다. OnClick 이벤트가 발생하면 원하는 기능을 실행하기 위해 Addlistener 이벤트를 사용한다. 해당 이벤트의 인자로 람다식을 입력하면 Button형식의 UI를 활용하여 버튼이 클릭될 때 원하는 기능이 실행되도록 할 수 있다.

 

 - AsyncOperation 형식의 변수를 사용하여 Game씬 GameMain 클래스의 Init에 접근하고 해당 인자로 GameEnum클래스의 selectedGunType변수를 입력한다.

 

 - 해당 변수의 값을 정의하기 위해 프로퍼티를 사용하는데 특정 버튼을 클릭하면 프로퍼티에서 입력받은 값을 set해준다.  변수에 입력받은 이넘타입의 종류를 입력하고 UI의 텍스트의 값으로 입력된다.

 

 

GameMain

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

public class GameMain : MonoBehaviour
{
    public GameObject[] gunPrefabs;
    public GameObject explosionPrefab;
    public PlayerController player;
    public Enemy[] enemies;
    public Text txtScore;

    private int score;  //total

    //Awake - Init - Start - Update 
    public void Init(GameEnums.eGunType selectedGunType)
    {
        foreach (Enemy enemy in this.enemies)
        {
            enemy.onExplode = (fxPos) =>
            {
                score += enemy.score;
                this.txtScore.text = score.ToString();

                //이펙트 생성 
                GameObject go = Instantiate(this.explosionPrefab);
                go.transform.position = fxPos;
            };
        }

        this.player.onGetPower = (power) => {

            if (this.player.gun.type == Gun.eType.Single)
            {
                this.player.DettachGun();
                //멀티건 생성 -> AttachGun
                Gun gun = this.CreateGun(GameEnums.eGunType.Multiple);
                this.player.AttachGun(gun);
            }
            else 
            {
                score += 200;   //스코어 누적 
                this.txtScore.text = score.ToString();  //UI 갱신 
            }

        };


        //총을 만든다 
        Gun gun = this.CreateGun(selectedGunType);

        //플레이어에게 총을 지급 한다 
        this.player.Init(gun);
    }

    private Gun CreateGun(GameEnums.eGunType gunType)
    {
        int index = (int)gunType;
        GameObject prefab = this.gunPrefabs[index];
        GameObject go = Instantiate(prefab);
        Gun gun = go.GetComponent<Gun>();
        return gun;
    }
}

 - LobbyMain클래스에 의해 씬과 Init메서드가 호출되며 시작한다. Init메서드는 호출될 때 인자로 입력받은 enum타입의 무기 정보를 매개변수로 저장하고 컴포넌트를 통해 저장한 Enemy타입의 배열 내부의 갯수만큼 반복하며 Enemy 클래스의 onExplode(Action) 대리자를 초기화한다. 람다식으로 구현하며 onExplode 대리자는 호출될 때 Vector3형식의 값을 입력받는데 이 값을 통해 익명메서드가 실행될 되면서 생성되는 인스턴스의 위치를 지정한다. 또한 onExplode가 실행될 때마다 해당 타입의 인스턴스가 가진 스코어 값이 현재 클래스의 score 누적되며 문자열로 변환되어 Text 형식의 UI에 저장된다.(실행될 때 마다 값이 변하므로 계속 변하는 값을 출력함)

 

 - PlayerController 클래스의 onGetPower 대리자를 정의한다. 매개변수로 입력 받은 정수형 값을 PlayerController 클래스의 현재 gun.type의 enum 타입과 같지 않다면 입력받은 정수 값을 score 변수에 누적하고 Text형식의 UI에 score의 값을 문자열로 변환해 입력한다. 반대로 gun type이 같다면 Player 클래스의 DettachGun 메서드를 호출하고 현재 클래스의 CreateGun을 호출하여 매개변수로 enum타입의 값을 입력하여 반환받은 값을 Gun타입의 변수에 저장한다. 그 값을 Player 클래스의 AttachGun메서드를 호출하며 매개변수로 입력한다.

 

 - 처음 Init메서드가 이전 씬에서 호출 될 때 입력받은 enum형식의 guntype을 매개변수로 현재 클래스의 CreateGun 메서드를 호출하고 Gun타입의 변수에 저장하고 이 변수를 매개변수로 PlayerController 클래스의 Init 메서드를 호출한다.

 

 - CreateGun 메서드는 Gun 타입의 값을 반환하며 매개변수로 입력받은 enum 타입 값을 정수형으로 명시적 변환하여 변수에 저장하고 해당 정수 값으로 게임오브젝트 벼열에 저장되어 있는 프리팹들의 인덱스 값을 지정해 그 값을 저장한다. 그 후 그 값을 매개변수로 Instantiate 메서드를 호출하여 게임오브젝트를 인스턴스화 하고 각각의 그 인스턴스의 컴포넌트를 확인하여 Gun타입으로 변환한다.(Gun 타입을 반환하는 이유는 입력받은 값을 PlayerController 클래스의 Init 메서드에서에서 gun.gameObject.transform.SetParent(this.transform); 코드로 게임오브젝트로 변환하기 때문이다.) 

 

 

PlayerController

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

public class PlayerController : MonoBehaviour
{
    public GameObject playerBulletPrefab;
    public GameObject boomPrefab;
    public Gun gun;

    public float speed = 1f;
    public System.Action<Power> onGetPower;

    public void Init(Gun gun)
    {
        this.gun = gun;
        this.gun.gameObject.transform.SetParent(this.transform);
        this.gun.transform.localPosition = Vector3.zero;    //new Vector3(0, 0, 0);
    }

    void Update()
    {
        float h = Input.GetAxisRaw("Horizontal"); 
        float v = Input.GetAxisRaw("Vertical");
        Vector3 dir = new Vector3(h, v, 0);
        this.transform.Translate(dir.normalized * this.speed * Time.deltaTime);

        if (Input.GetKeyDown(KeyCode.Space))    //스페이스바를 누르면 
        {
            this.Shoot();   //총알을 발사 
        }

        if (Input.GetKeyDown(KeyCode.B))
        {
            this.Boom();
        }
    }

    private void Boom()
    {
        GameObject go = Instantiate(this.boomPrefab);
        go.GetComponent<Boom>().Init();
        go.transform.position = Vector3.zero;
    }

    private void Shoot()
    {
        //내가 가지고있는 Gun 이 총알을 발사 
        this.gun.Shoot();
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Power") {
            Debug.Log("아이템 획득");

            Power power = collision.gameObject.GetComponent<Power>();

            //멀티건으로 변경 
            this.onGetPower(power);

            Destroy(collision.gameObject);  //아이템 제거 

        }
    }

    public void AttachGun(Gun gun)
    {
        this.gun = gun;
        this.gun.transform.SetParent(this.transform);
    }

    public void DettachGun()
    {
        //자식으로 붙은 Gun게임 오브젝트를 제거 
        Destroy(this.gun.gameObject);
        Debug.LogFormat("gun: {0}", this.gun);
        this.gun = null;
    }
}

 - Init 메서드는 GameMain 클래스에서 호출 된 후 Gun 타입의 매개변수를 Gun타입 멤버변수에 저장하고 그 값의 게임오브젝트의 위치를 현재 클래스의 위치를 부모로 지정해준다. 그리고 해당 변수의 로컬 위치정보에 0을 입력하여 부모오브젝트 내부에 속하도록 한다. (gun 변수는 GameMain 클래스에서 go변수(Instantate된 게임오브젝트)의 컴포넌트이다.)

 

 - Update 메서드에서 float 형식의 h와 v 변수에 각각Input.GetAxisRaw("Horizontal"); 메서드와 Input.GetAxisRaw("Vertical");를 통해  키보드의 가로축과 세로축의 값을 저장한 후 Vector3 형식의 변수에 저장한다(방향벡터). 해당 변수를 현재 게임오브젝트의 Translate 메서드의 매개변수로 사용하는데 이 때 Vector3(방향벡터) 값을 normalized 하여 대각선의 속도를 고정한다. 또한 속도와 시간을 곱해 프레임이 변하더라도 속도가 같도록 한다.

 

 - 스페이스바를 입력받으면 현재 클래스의 Shoot 메서드를, B키를 입력받으면 Boom 메서드를 호출한다.

 

- collision이 트리거 안으로 들어올 때 해당 collision의 태그가 Power라면 해당 콜리전의 Power컴포넌트를 변수에 저장하고 onGetPower의 매개변수로 사용하여 대리자 메서드를 호출한다. 그 후 해당 collision의 게임오브젝트를 파괴한다.

 

- Gun타입의 매개변수를 입력받아 Gun 타입멤버 변수에 저장하고 해당 변수의 부모를 현재 클래스로 지정한다.(SetParent 메서드를 사용할 때 transform은 GameObject 에 접근한다.)

 

- 현재 클래스의 Gun타입 변수의 게임오브젝트를 파괴하고 변수 값을 null로 지정한다.

 

 

MultiGun

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

public class MultiGun : Gun
{
    //https://docs.unity3d.com/ScriptReference/TooltipAttribute.html
    [Tooltip("right fire point")]
    public Transform firePoint2;    //right 

    public override void Shoot()
    {
        //base.Shoot();
        //총알을 생성 
        GameObject go1 = Instantiate(this.bulletPrefab);    //left
        GameObject go2 = Instantiate(this.bulletPrefab);    //right
        go1.transform.position = this.firePoint.position;   
        go2.transform.position = this.firePoint2.position;
    }
}

 - Tooltip을 활용하여 컴포넌트에 주석을 입력할 수 있다.

 

 - Gun 메서드를 상속받아 Shoot 메서드를 오버라이드 하여 사용하고 있다.

 

 

Gun

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

public class Gun : MonoBehaviour
{
    public enum eType
    { 
        Single, Multiple 
    }

    public eType type;
    public Transform firePoint;
    public GameObject bulletPrefab;

    public virtual void Shoot()
    {
        //총알을 생성 
        GameObject go = Instantiate(this.bulletPrefab);
        //위치 설정 
        go.transform.position = this.firePoint.position;
    }
}

 - enum 타입의 값을 활용하여 GameMain 클래스의 onGetPower 대리자의 내부 기능의 조건을 지정하는 데 사용한다.

 

 

PlayerBullet

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

public class PlayerBullet : MonoBehaviour
{
    public float speed;

    void Update()
    {
        this.transform.Translate(this.transform.up * this.speed * Time.deltaTime);

        if (this.transform.position.y >= 5.73f)
        {
            //제거 
            Destroy(this.gameObject);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        //T A Component of the matching type, otherwise null if no Component is found.
        //https://docs.unity3d.com/ScriptReference/Component.GetComponent.html
        Enemy enemy = collision.gameObject.GetComponent<Enemy>();
        if (enemy != null)
        {
            Debug.LogFormat("Enemy Type: {0}", enemy.type);
            Destroy(this.gameObject);
            enemy.Explode();
        }
    }
}

 - transform.up 으로 좌표상 y가 +되도록 움직인다.

 

 - collision의 게임오브젝트에 접근하여 Enemy 컴포넌트를 확인하고 널이 아니라면 해당 게임오브젝트를 파괴한다.(널이라는 뜻은 해당 컴포넌트에 접근하였으나 Enemy타입의 값이 아니라는 뜻)

 

Enemy

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

public class Enemy : MonoBehaviour
{
    public enum eType
    { 
        A, B 
    }

    public int score;
    public eType type;
    public Action<Vector3> onExplode;

    public void Explode()
    {
        this.onExplode(this.transform.position);

        Destroy(this.gameObject);
    }

}

 - enum타입을 활용하여 적 종류를 구분하고 컴포넌트를 활용하여 각각의 점수와 enum 타입을 지정한다.

 

 - Action 대리자로 Explode 메서드를 구현하는데 현재 생성된 인스턴스의 위치 정보를 onExplode 대리자 메서드의 매개벼변수로 입력하여 위치정보를 GameMain 클래스로 전달한다.

 

 

Effect

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

public class Effect : MonoBehaviour
{
    private Animator anim;

    private void Start()
    {
        this.anim = this.GetComponent<Animator>();
        AnimatorClipInfo[] arr =  this.anim.GetCurrentAnimatorClipInfo(0);
        //Debug.Log(arr.Length);
        //Debug.Log(arr[0].clip);
        //Debug.Log(arr[0].clip.name);
        //Debug.Log(arr[0].clip.length);

        StartCoroutine(this.WaitForAnimation(arr[0].clip.length));
    }

    private IEnumerator WaitForAnimation(float length)
    {
        yield return new WaitForSeconds(length);

        Destroy(this.gameObject);
    }

}

 -  AnimatorClipIfo 타입의 배열을 활용하여 Animator의 정보를 입력받고 그 길이를 매개변수로 코루틴을 실행한다. 코루틴은 그 값을 시간으로 변환한 만큼의 시간 후에 프레임이 넘어가고 (애니메이션이 끝나고) 게임 오브젝트가 파괴된다.

 

Boom

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

public class Boom : MonoBehaviour
{
    public float duration = 2f;

    public void Init()
    {
        StartCoroutine(this.WaitForBoom());
    }

    private IEnumerator WaitForBoom()
    {
        yield return new WaitForSeconds(this.duration);
        Destroy(this.gameObject);
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        Debug.Log(collision.gameObject.name);
        if (collision.tag == "Enemy")
        {
            collision.GetComponent<Enemy>().Explode();
        }
    }
}

 - Boom 클래스의 게임오브젝트와 충돌한 collision의 태그가 Enenmy라면 해당 collision의 컴포넌트에 접근해서 Explode메서드를 호출한다. 해당 메서드는 onExplode Action 대리자 메서드로 구현되며 해당 값이 GamaMain 클래스로 저장된다.