나도코딩 - 유니티 무료 강의 (Crash Course) 따라하기

출처 : 

유니티 게임 개발 무료 강의

https://www.clien.net/service/board/lecture/18055864?po=0&sk=title&sv=%EC%9C%A0%EB%8B%88%ED%8B%B0&groupCd=&pt=0

강사 : 

나도코딩

https://www.youtube.com/@nadocoding


유니티 무료 강의 (Crash Course) - 5시간 만에 게임 만드는 법 배우기 (2023. 4. 29)

https://www.youtube.com/watch?v=rJE6bhVUNhk&t=3s


강의에서 사용한 Editor 버전이 2021.3.23f1 이고, 

현재 최신 Editor 버전은 6000.0.42f1 이라서 해보면서 변경점 정리해 둠

https://github.com/sgchoi5/Unity_Study_Projects/blob/main/Trash%20Fight.zip


Unzip 후에 Unity Hub 에서 Add Project 에서 Add from repository 를 이용해서 import



차이점들


1) Project 생성

  2D Core 대신에 Universal 2D Core 선택함

   기존에 없던 옵션: Connect to Unity Cloud 는 끔



2) Main Camera 의 Background Color 설정 위치가 Inspector 에서 바뀜

   Environment 아래에 보자



3) Game View 에서 Display 에서 하나 추가



4) Scripts 폴더에서 Create => C# Script 는 이제 없어지고,

   MonoBehaviour Script 선택하면 됨



   * Scripting 이라는 메뉴도 추가되어 있음


5) Pixel Per Unit 관련 설정이 Inspector 창에서 보이지 않으면 Project Window 에서 선택해서 봐야함



6) Enter Play Mode Options 는 Unity6 에서는 이미 선택되어 있음

   Edit - Project Settings


7) 강의 화면과 다르게 Main Camera 의 외곽선이 보이지 않으면

View Option 을 보고 Gizmos 를 켜주면 된다




8) Enemy.cs 에서 충돌처리 코드 문제



Unity 6.0 사용시에 The name 'other' does not exist in the current context 라는 에러가 나오면, other 대신에 collision 으로 바꿔주면 된다. OnTriggerEnter2D() 의 parameter name 이other 에서 collision 으로 바뀜

Gemini 2.0 Flash 에 물어보면, 아래와 같이 알려줌

In Unity 6.0 (and newer), the parameter name for the collider you're colliding with is now collision (or you can rename it to something else, but it's recommended to stick with collision for clarity).


9) GameManager 추가해도 Icon 이 톱니바퀴로 안 바뀜


10)  GameManager.cs 에서 FindObjectOfType<Player>()  사용시에 deprecate warning 발생하는데, 아래와 같이 FindFirstObjectByType<Player>() 로 바꿔주면 됨.

        if (coin % 30 == 0) { // 30, 60, 90, ...
            Player player = FindFirstObjectByType<Player>();


Deprecate warning 내용

'Object.FindObjectOfType<T>()' is obsolete: 'Object.FindObjectOfType has been deprecated. Use Object.FindFirstObjectByType instead or if finding any instance is acceptable the faster Object.FindAnyObjectByType'


11) Text 설정시 Wrapping 모드를 Disable 로 바꾸는 것이 있는데, 6.0 에서 바뀐 이름은 Text Wrapping Mode 이고, No Wrap 으로 설정하면 됨


Tip1. GameObjects 의 "reset" 은 Inspector 에서 Transform 오른쪽에 있는 ... 선택하면 나온다. Ctrl + Z 로도 안 되면 reset 해라


Tip2. 게임 이미지 리소스 검색

Google 검색에서

  character sprite site:opengameart.org

  recycle site:opengameart.org


Tip3. 중간에 나오는 Pixel Per Unit 에 대한 이해가 필요하면 아래 링크 참조

https://data-pandora.tistory.com/entry/Unity-Unity6-%ED%94%BD%EC%85%80-%EC%95%84%ED%8A%B8-2D-%EB%9D%BC%EC%9D%B4%ED%8C%85-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-1


Tip4. Visual Studio Code 에서 주석처리 Ctrl + K + C    

                               주석해제 Ctrl + K + U


Background.cs

public class Background : MonoBehaviour
{
    private float moveSpeed = 3f;
   
    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.down * moveSpeed * Time.deltaTime;
        if (transform.position.y < -10) {
            transform.position += new Vector3(0, 20f, 0);
        }
    }
}

Coin.cs

public class Coin : MonoBehaviour
{
    private float minY = -7f;

    void Start()
    {
        Jump();
    }

    // Update is called once per frame
    void Update()
    {
        if (transform.position.y < minY) {
            Destroy(gameObject);
        }
    }

    void Jump() {
        Rigidbody2D rigidBody = GetComponent<Rigidbody2D> ();
        float randomJumpForce = Random.Range(4f, 8f);
        Vector2 jumpVelocity = Vector2.up * randomJumpForce;
        jumpVelocity.x = Random.Range(-2f, 2f);
        rigidBody.AddForce(jumpVelocity, ForceMode2D.Impulse);
    }
}

Enermy.cs

public class Enemy : MonoBehaviour
{
    [SerializeField]
    private GameObject coin;

    [SerializeField]
    private float moveSpeed = 10f;

    private float minY = -7f;
    [SerializeField]
    private float hp = 1f;

    public void SetMoveSpeed(float moveSpeed) {
        this.moveSpeed = moveSpeed;
    }

    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.down * moveSpeed * Time.deltaTime;
        if (transform.position.y < minY) {
            Destroy(gameObject);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "Weapon") {
            Weapon weapon = collision.gameObject.GetComponent<Weapon>();
            hp -= weapon.damage;
            if (hp <= 0) {
                if (gameObject.tag == "Boss") {
                    GameManager.instance.SetGameOver();                    
                }
                Destroy(gameObject);
                Instantiate(coin, transform.position, Quaternion.identity);
            }
            Destroy(collision.gameObject);
        }
    }
}

EnemySpawner.cs

public class EnemySpawner : MonoBehaviour
{
    [SerializeField]
    private GameObject[] enemies;

    [SerializeField]
    private GameObject boss;

    private float[] arrPosX = { -2.2f, -1.1f, 0f, 1.1f, 2.2f };

    [SerializeField]
    private float spawnInterval = 1.5f;

    void Start()
    {
        StartEnemyRoutine();
    }

    void StartEnemyRoutine() {
        StartCoroutine("EnemyRoutine");
    }

    public void StopEnemyRoutine() {
        StopCoroutine("EnemyRoutine");
    }

    IEnumerator EnemyRoutine() {
        yield return new WaitForSeconds(3f);

        float moveSpeed = 5f;
        int spawnCount = 0;
        int enemyIndex = 0;
        while (true) {
            foreach (float posX in arrPosX) {
                SpawnEnemy(posX, enemyIndex, moveSpeed);
            }

            spawnCount += 1;
            if (spawnCount % 10 == 0) { // 10, 20, 30, ...
                enemyIndex += 1;
                moveSpeed += 2f;
            }

            if (enemyIndex >= enemies.Length) {
                SpawnBoss();
                enemyIndex = 0;
                moveSpeed = 5f;
            }

            yield return new WaitForSeconds(spawnInterval);
        }
    }

    void SpawnEnemy(float posX, int index, float moveSpeed) {
        Vector3 spawnPos = new Vector3(posX, transform.position.y, transform.position.z);

        if (Random.Range(0, 5) == 0) {
            index += 1;
        }

        if (index >= enemies.Length) {
            index = enemies.Length - 1;
        }

        GameObject enemyObject = Instantiate(enemies[index], spawnPos, Quaternion.identity);
        Enemy enemy = enemyObject.GetComponent<Enemy>();
        enemy.SetMoveSpeed(moveSpeed);
    }

    void SpawnBoss() {
        Instantiate(boss, transform.position, Quaternion.identity);
    }
}

GameManager.cs

public class GameManager : MonoBehaviour
{
    public static GameManager instance = null;

    [SerializeField]
    private TextMeshProUGUI text;

    [SerializeField]
    private GameObject gameOverPanel;

    private int coin = 0;

    [HideInInspector]
    public bool isGameOver = false;

    void Awake()
    {
        if (instance == null) {
            instance = this;
        }
    }

    public void IncreaseCoin() {
        coin += 1;
        text.SetText(coin.ToString());

        if (coin % 30 == 0) { // 30, 60, 90, ...
            Player player = FindFirstObjectByType<Player>();
            if (player != null) {
                player.Upgrade();
            }
        }
    }

    public void SetGameOver() {
        isGameOver = true;

        EnemySpawner enemySpawner = FindFirstObjectByType<EnemySpawner>();
        if (enemySpawner != null) {
            enemySpawner.StopEnemyRoutine();
        }

        Invoke("ShowGmaeOverPanel", 1f);
    }

    void ShowGmaeOverPanel() {
        gameOverPanel.SetActive(true);
    }

    public void PlayAgain() {
        SceneManager.LoadScene("SampleScene");
    }
}

Player.cs

public class Player : MonoBehaviour
{
    [SerializeField]
    private float moveSpeed = 0;

    [SerializeField]
    private GameObject[] weapons;
    private int weaponIndex = 0;

    [SerializeField]
    private Transform shootTransform;

    [SerializeField]
    private float shootInterval = 0.05f;
    private float lastShotTime = 0f;

    // Update is called once per frame
    void Update()
    {
        Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        float toX = Mathf.Clamp(mousePos.x, -2.34f, 2.35f);
        transform.position = new Vector3(toX, transform.position.y, transform.position.z);

        if (GameManager.instance.isGameOver == false) {
            Shoot();
        }
    }

    void Shoot() {
        if (Time.time - lastShotTime > shootInterval) {
            Instantiate(weapons[weaponIndex], shootTransform.position, Quaternion.identity);
            lastShotTime = Time.time;
        }
    }

    private void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "Boss") {
            GameManager.instance.SetGameOver();
            Destroy(gameObject);
        } else if (collision.gameObject.tag == "Coin") {
            GameManager.instance.IncreaseCoin();
            Destroy(collision.gameObject);
        }
    }

    public void Upgrade() {
        weaponIndex += 1;
        if (weaponIndex >= weapons.Length) {
            weaponIndex = weapons.Length - 1;
        }
    }
}

Weapon.cs

public class Weapon : MonoBehaviour
{
    [SerializeField]
    private float moveSpeed = 10f;
    public float damage = 1f;

    void Start()
    {
        Destroy(gameObject, 1f);
    }

    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.up * moveSpeed * Time.deltaTime;
    }
}




댓글

이 블로그의 인기 게시물

Unity Tip 2 : Visual Studio Code 로 변경

노마드 코더 (Nomad Coders) 강의 따라하기 - KIMCHI-Run 게임