개발/유니티

[유니티] 유니티 learn #8

kimchangmin02 2025. 8. 28. 10:29

적이 충돌한 게임 오브젝트RubyController 스크립트를 가지고 있는지 확인하면 됩니다. 만약 가지고 있다면 메인 캐릭터이므로 데미지를 가할 수 있습니다.

void OnCollisionEnter2D(Collision2D other)
{
    RubyController player = other.gameObject.GetComponent<RubyController>();

    if (player != null)
    {
        player.ChangeHealth(-1);
    }
}

아, 그니깐, 만약에, 충돌한 적이, 루비가 아니면

루비 스크립트를 가지고 있지않을거고,

결국 null이되니깐, 조건문 통과못하는거네

 

 

 

 

 

 

이렇게되면, 레벨의 갯수만큼, Boolean형 자료형이 늘어나는데,

아니면, 리스트를 만들어서, 젤 처음에는, 모두 false만 담는 방법도 있을것같고

아니면, c#도 switch문이 있나

아니면, 레벨이 늘어나는 정도가  규칙적이라면, 10초 지날때마다, 호출하게 해도될것같기도 

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

public class GameManager : MonoBehaviour
{   

    private int level;
    float timer;
    public TextMeshProUGUI levelText;
    public int enterLevel2;
    public int enterLevel3;

    Boolean ifEnteredLv2 = false;

    Boolean ifEnteredLv3 = false;
    // Start is called before the first frame update

    public int getLevel()
    {
        return level;
    }
    void Start()
    {
        timer = 0;
        levelText.text = "Lv1";
    }

    // Update is called once per frame
    void Update()
    {
        timer += Time.deltaTime;

        if (timer >= enterLevel2 && !ifEnteredLv2)
        {   
            level += 1;
            //Debug.Log("lv2");
            levelText.text = "Lv"+level;
        }
        else if (timer >= enterLevel3 &&ifEnteredLv3)
        {
            level += 1;
            levelText.text = "Lv"+level;
            //Debug.Log("lv3");
        }
    }
}

 

 

 

1. switch 문 사용 (특정 레벨 진입 시점 고정)

C#에도 switch 문이 있습니다. 각 레벨 진입 시점이 고정되어 있다면 switch 문으로 관리할 수 있습니다.

C#
 
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class GameManager : MonoBehaviour
{   
    private int level = 1; // 시작 레벨은 1로 초기화
    private float timer;
    public TextMeshProUGUI levelText;

    // 각 레벨로 진입하는 시간을 배열로 관리
    public float[] levelEnterTimes; 

    // 현재 레벨 진입 여부를 확인하기 위한 인덱스
    private int currentLevelIndex = 0; 

    public int getLevel()
    {
        return level;
    }

    void Start()
    {
        timer = 0;
        levelText.text = "Lv" + level;
    }

    void Update()
    {
        timer += Time.deltaTime;

        // levelEnterTimes 배열의 범위를 벗어나지 않도록 확인
        if (currentLevelIndex < levelEnterTimes.Length)
        {
            if (timer >= levelEnterTimes[currentLevelIndex])
            {
                level++;
                levelText.text = "Lv" + level;
                currentLevelIndex++; // 다음 레벨 진입 조건을 확인하도록 인덱스 증가
                Debug.Log("Lv" + level + " Entered!");
            }
        }
    }
}

Inspector 설정 예시:

levelEnterTimes 배열의 크기를 2로 설정하고,

  • Element 0: 10 (10초 후 Lv2 진입)
  • Element 1: 20 (20초 후 Lv3 진입)

이렇게 설정하면 됩니다.

2. 규칙적인 레벨 증가 (예: 일정 시간마다)

만약 레벨이 10초, 20초, 30초... 와 같이 규칙적으로 증가한다면, levelEnterTimes 배열을 직접 설정하는 대신, levelIncreaseInterval과 같은 변수를 사용할 수 있습니다.

C#
 
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class GameManager : MonoBehaviour
{   
    private int level = 1; // 시작 레벨은 1로 초기화
    private float timer;
    public TextMeshProUGUI levelText;

    public float levelIncreaseInterval = 10f; // 10초마다 레벨 증가

    private float nextLevelUpTime;

    public int getLevel()
    {
        return level;
    }

    void Start()
    {
        timer = 0;
        levelText.text = "Lv" + level;
        nextLevelUpTime = levelIncreaseInterval; // 첫 번째 레벨업 시간 설정
    }

    void Update()
    {
        timer += Time.deltaTime;

        if (timer >= nextLevelUpTime)
        {
            level++;
            levelText.text = "Lv" + level;
            nextLevelUpTime += levelIncreaseInterval; // 다음 레벨업 시간 갱신
            Debug.Log("Lv" + level + " Entered!");
        }
    }
}

 

 

 

 

 

 

 

1. transform.position = position; (직접적인 위치 변경)

  • 물리 엔진 관여 없음: 이 방식은 GameObject의 Transform 컴포넌트에 직접 접근하여 위치를 변경합니다. Rigidbody2D가 GameObject에 있더라도, 이 명령은 물리 엔진의 계산을 무시하고 오브젝트의 위치를 강제로 설정합니다.
  • 충돌 처리: 만약 이 방식으로 오브젝트를 움직이다가 다른 콜라이더와 겹치게 되면, 물리 엔진은 겹쳐진 상태를 감지하지 못하거나, 다음 물리 업데이트에서 갑작스럽게 충돌을 해결하려 할 수 있습니다 (텔레포트된 것으로 간주). 이로 인해 예상치 못한 물리 반응(버그, 진동)이 발생할 수 있습니다.
  • 사용 시점:
    • 물리적인 상호작용이 전혀 필요 없는 오브젝트 (예: UI 요소, 배경 오브젝트, 순수하게 그래픽적인 이동).
    • 오브젝트를 순간이동시키거나, 물리 법칙을 완전히 무시한 움직임이 필요할 때.
    • Rigidbody2D가 없는 오브젝트를 움직일 때.
  • 예시: 질문의 moveUpAndDown 함수는 transform.position = position;을 사용하여 오브젝트를 물리 엔진과 무관하게 상하로 움직입니다. 이 오브젝트가 다른 물리 오브젝트와 충돌해야 한다면 문제가 발생할 수 있습니다.

2. rigidbody2D.MovePosition(position); (물리 엔진을 통한 위치 변경)

  • 물리 엔진 관여: 이 방식은 Rigidbody2D 컴포넌트를 통해 오브젝트의 위치를 변경합니다. 즉, 물리 엔진에게 "이 오브젝트를 이 위치로 움직여달라"고 요청하는 것입니다. 물리 엔진은 이 요청을 기반으로 충돌 감지 및 해결을 고려하면서 오브젝트를 움직입니다.
  • 충돌 처리: MovePosition은 이동 중에 발생할 수 있는 충돌을 미리 감지하고, 해당 충돌에 맞춰 적절하게 오브젝트의 위치를 조정합니다. 이는 FixedUpdate 함수 내에서 호출하는 것이 일반적이며, 물리 계산 주기와 동기화됩니다.
  • 사용 시점:
    • Rigidbody2D를 가지고 있고, 다른 물리 오브젝트와 정확하고 부드러운 물리 상호작용이 필요한 오브젝트 (예: 플레이어 캐릭터, 움직이는 플랫폼, 물리 퍼즐 오브젝트).
    • 일관된 물리 시뮬레이션이 중요한 경우.
  • 예시: 플레이어 캐릭터가 땅을 밟고 움직이거나, 움직이는 플랫폼이 플레이어를 밀어내야 할 때 MovePosition을 사용하면 물리적으로 자연스러운 움직임과 상호작용을 얻을 수 있습니다.

 

 

 

 

 

 

네, 맞습니다! public bool vertical;  Boolean goingUp = true; 둘 다 bool (Boolean) 타입의 변수를 선언하는 유효한 방법입니다.

하지만 C#에서는 일반적으로 bool 키워드를 사용하는 것이 관례입니다.

bool vs Boolean

  1. bool (소문자):
    • C# 언어에서 제공하는 키워드입니다.
    • System.Boolean 타입의 별칭(alias) 입니다.
    • 대부분의 C# 코드에서 부울 값을 선언할 때 사용되는 표준이자 권장 방식입니다.
    • 예시: public bool isMoving;, private bool hasCollided = false;
  2. Boolean (대문자):
    • .NET Framework System 네임스페이스에 정의된 클래스/구조체(struct) 이름입니다.
    • bool 키워드는 컴파일러가 내부적으로 System.Boolean으로 처리합니다.
    • 기술적으로 사용할 수는 있지만, C#에서는 기본 타입의 키워드(예: int 대신 Int32, string 대신 String)를 사용하는 것이 더 흔하고 선호됩니다.

왜 둘 다 되는가?

C# 컴파일러가 bool 키워드를 만나면, 내부적으로 System.Boolean 타입으로 변환하여 처리하기 때문입니다. 마치 int 키워드가 System.Int32의 별칭인 것과 같습니다.

C#
 
int myInteger = 10;          // C# 키워드
System.Int32 myOtherInteger = 20; // .NET Framework 타입
// 둘 다 동일한 Int32 타입의 정수형 변수를 선언하는 것입니다.

결론 및 권장 사항

  • 둘 다 문법적으로는 유효하며 동일하게 작동합니다.
  • 하지만 C# 코드를 작성할 때는 bool (소문자) 키워드를 사용하는 것이 좋습니다. 이는 C#의 관례이며, 코드를 더 읽기 쉽고 표준적으로 만듭니다.

따라서, 질문하신 코드에서는 Boolean goingUp = true; 대신 bool goingUp = true;로 작성하는 것이 더 일반적이고 권장되는 방식입니다.

C#
 
public class MyScript : MonoBehaviour
{
    public bool vertical; // 권장
    // public Boolean vertical2; // 작동하지만 덜 일반적

    bool goingUp = true; // 권장
    // Boolean goingUp2 = true; // 작동하지만 덜 일반적

    void Start()
    {
        if (vertical)
        {
            Debug.Log("Vertical is true.");
        }

        if (goingUp)
        {
            Debug.Log("Going up is true.");
        }
    }
}

 

 

 

 

 

 

 

 

 

 

 

  • Collision2D other (사고 현장 보고서):
    • OnCollisionEnter2D(Collision2D other)에서 other는 사고 현장에서 경찰이 작성한 '사고 현장 보고서' 같은 겁니다.
    • 이 보고서에는 사고에 대한 모든 정보가 담겨 있어요: "어떤 차가 부딪혔는지", "어디가 긁혔는지", "충돌 시 속도는 어땠는지" 등등.
    • 보고서 자체는 종이 뭉치일 뿐, 차가 아닙니다.
    • 당신이 이 보고서를 보면서 "이 보고서에 바퀴가 달려있나?"라고 묻는다면 이상하겠죠? 보고서에는 바퀴가 없으니까요.
    • 마찬가지로, Collision2D other (보고서) 자체에는 GetComponent라는 기능이 없습니다. GetComponent는 "이 게임 오브젝트에 이런 부품(컴포넌트)이 있니?"라고 묻는 기능인데, 보고서는 게임 오브젝트가 아니기 때문이죠.
  • other.gameObject (사고 차량):
    • 보고서( Collision2D other ) 안에는 "충돌한 상대방의 차량(GameObject) 정보"가 명확하게 적혀 있습니다. 이게 바로 other.gameObject 입니다.
    • 이제 당신은 보고서에 적힌 '사고 차량'을 보고 "이 차에 루비 컨트롤러(RubyController)라는 부품이 달려있니?"라고 물어볼 수 있습니다.
    • 이렇게 물어보는 것이 other.gameObject.GetComponent<RubyController>() 입니다. 이것은 자연스럽고 올바른 질문이죠.

그래서 핵심 차이는:

  1. Collision2D other 일 때:
    • other.GetComponent<RubyController>() (안 됨): "사고 현장 보고서에 루비 컨트롤러 부품이 달려있니?" 라고 묻는 것과 같습니다. 보고서에는 그런 부품이 없습니다. 그래서 오류가 납니다.
    • other.gameObject.GetComponent<RubyController>() (됨): "사고 현장 보고서에 적힌 차량에 루비 컨트롤러 부품이 달려있니?" 라고 묻는 것과 같습니다. 이것은 정확하고 올바른 질문입니다.

 

 

 

 

 

 

 

1. OnCollisionEnter2D (쾅! 문에 부딪혔을 때)

  • 비유: 당신이 문을 향해 걸어가다가 "쾅!" 하고 문에 직접 몸으로 부딪혔을 때입니다.
    • 문이 '충돌'을 감지하고 "누가 나한테 부딪혔네!" 하고 반응합니다.
  • 특징:
    • 물리적 상호작용: 실제로 닿아서 서로 밀거나 튕겨 나갈 수 있습니다. 문에 부딪히면 당신이 멈추거나 뒤로 밀려날 수 있듯이요.
    • 사용 예시:
      • 플레이어가 벽에 부딪혀 멈출 때.
      • 총알이 적에게 맞아 데미지를 줄 때.
      • 두 오브젝트가 서로 힘을 주고받아야 할 때.
    • 정보: 누가 부딪혔는지에 대한 **'사고 보고서(Collision2D)'**를 줍니다. 이 보고서 안에는 부딪힌 상대방(other.gameObject), 부딪힌 위치, 충격량 등 아주 자세한 정보가 들어있어요.

2. OnTriggerEnter2D (스윽~ 문을 통과했을 때)

  • 비유: 당신이 문으로 걸어가다가 문에 닿기 직전, 문 옆에 설치된 '자동문 센서'를 통과했을 때입니다.
    • 문은 당신의 존재를 감지하고 "어떤 사람이 여기를 지나가네!" 하고 반응합니다. 하지만 당신은 문에 부딪히지 않고 그냥 통과할 수 있습니다. 문이 열리든 말든, 당신은 계속 직진합니다.
  • 특징:
    • 비물리적 상호작용 (감지 목적): 실제로 닿는 것(충돌)은 아니지만, 특정 영역 안으로 들어왔음을 감지할 때 사용합니다. 서로 밀거나 튕겨 나가지 않습니다.
    • 사용 예시:
      • 플레이어가 동전 아이템을 주울 때 (동전을 통과하면 주워짐).
      • 플레이어가 특정 구역에 진입했을 때 (퀘스트 시작, 지역 감지).
      • 자동문이 플레이어를 감지하고 열릴 때.
    • 정보: 누가 통과했는지에 대한 **'상대방의 외형 정보(Collider2D)'**를 줍니다. 여기에는 상대방의 게임 오브젝트(other.gameObject)에 대한 정보가 들어있습니다.

가장 쉬운 차이점 요약:

  • OnCollisionEnter2D:
    • "쾅!"하고 부딪혀서 서로 영향을 줄 때 (물리적 충돌).
    • "부딪혔다!" 라는 자세한 사고 보고서(Collision2D other)를 받습니다.
  • OnTriggerEnter2D:
    • "스윽~"하고 통과해서 무언가를 감지할 때 (영역 진입 감지).
    • "통과했다!" 라는 상대방의 외형 정보(Collider2D other)를 받습니다.

콜라이더 설정:

이 두 가지를 사용하려면, 오브젝트에 Collider2D 컴포넌트가 있어야 합니다. 그리고 인스펙터에서 Is Trigger 체크박스를 어떻게 설정하느냐에 따라 달라집니다.

  • Is Trigger 체크 해제: OnCollisionEnter2D가 작동합니다. (실제 부딪힘)
  • Is Trigger 체크: OnTriggerEnter2D가 작동합니다. (영역 통과 감지)

 

 

 

 

 

 

 

왜 안되지 ㅠ

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

public class Enemy : MonoBehaviour
{

    public int speed = 1;
    float maxX;
    float minX;
    float maxY;
    float minY;

    public int damage = 1;

    Boolean goingUp = true;
    Rigidbody2D rigidbody2D;
    float timer;

    public bool vertical;
    bool goLeft;
    

    public float changeTime = 3.0f;

    // Start is called before the first frame update
    void Start()
    {
        //Camera gameCamera = Camera.main;
        //Vector3 rightEdge = gameCamera.ScreenToWorldPoint(new Vector3(Screen.width, 0, 10));
        //Vector3 topEdge = gameCamera.ScreenToWorldPoint(new Vector3(0, Screen.height, 10));
        //maxX = rightEdge.x;
        //maxY = topEdge.y;
        maxY = 2.65f;
        minY = -5.3f;
        rigidbody2D = GetComponent<Rigidbody2D>();
        vertical = false;
        maxX = 2.99f;
        minX = -2.91f;
        goLeft = false;

    }

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

    void move()
    {
        if (vertical)
        {   
            moveUpAndDown();
            
        }
        else
        {
            moveRightAndLeft();
        }
    }

    void moveRightAndLeft()
    {
        Vector2 position = rigidbody2D.position;
        if (goLeft)
        {
            if (position.x <= minX)
            {
                goLeft = false;
                return;
            }
            position.x -= speed;
        }
        else
        {
            if (position.x >= maxX)
            {
                goLeft = false;
                return;
            }
            position.x += speed;
        }
        rigidbody2D.MovePosition(position);
    }

    void moveUpAndDown()
    {
        Vector2 position = rigidbody2D.position;
        //Debug.Log("moveUpAndDown()호출");
        if (goingUp)
        {
            //Debug.Log("if (goingUp) true임");

            //Debug.Log("position.y:" + position.y);
            //Debug.Log("maxY:" + maxY);
            //Debug.Log(position.y < maxY);
            if (position.y < maxY)
            {

                position.y += speed * Time.deltaTime;
                //Debug.Log("올라가는중");
            }
            else
            {
                goingUp = false;
                return;
            }

        }
        else
        {
            if (position.y > minY)
            {
                position.y -= speed * Time.deltaTime;
                //Debug.Log("내려가는중 ");
            }
            else
            {
                goingUp = true;
                return;
            }
        }

        rigidbody2D.MovePosition(position);
    }

    // void OnTriggerEnter2D(Collider2D other)
    // {
    //     //Debug.Log("Object that entered the trigger : " + other);
    //     RubyController controller = other.GetComponent<RubyController>();

    //     if (controller != null)
    //     {
    //         controller.ChangeHealth(-1 * damage);
    //     }
    // }

    void OnCollisionEnter2D(Collision2D other)
    {
        RubyController player = other.gameObject.GetComponent<RubyController>();

        if (player != null)
        {
            player.ChangeHealth(-1*damage);
        }
    }




    void calTimeIfVerticalOrHorizontal()
    {
        timer -= Time.deltaTime;

        if (timer < 0)
        {
            timer = changeTime;
            if (vertical == false)
            {
                vertical = true;
            }
            else
            {
                vertical = false;
            }
        }
    }
}

문제점 1: moveRightAndLeft() 에서 Time.deltaTime 누락

moveRightAndLeft() 함수를 보면 position.x -= speed; 또는 position.x += speed; 이렇게 Time.deltaTime을 곱하지 않고 있습니다. Update() 함수는 프레임마다 호출되는데, speed만 더하면 컴퓨터 사양에 따라, 또는 프레임 속도에 따라 이동 거리가 달라져서 움직임이 일정하지 않게 됩니다.

수정 제안:
position.x -= speed * Time.deltaTime;
position.x += speed * Time.deltaTime;

문제점 2: moveUpAndDown() return; 

moveUpAndDown() 함수 안에서 maxY minY에 도달했을 때 return;을 해버립니다. 이렇게 되면 다음 rigidbody2D.MovePosition(position); 코드가 실행되지 않고 함수가 종료됩니다. 즉, 경계에 닿는 순간 오브젝트의 움직임이 멈춰버립니다.

수정 제안:
return; 문을 제거하고 rigidbody2D.MovePosition(position);이 항상 실행되도록 해야 합니다.

문제점 3: Update()에서 rigidbody2D.MovePosition() 호출

물리 관련 코드는 일반적으로 FixedUpdate()에서 호출하는 것이 권장됩니다. FixedUpdate()는 일정한 시간 간격으로 호출되므로, 물리 계산이 더 정확하고 안정적입니다. Update()는 프레임마다 호출되므로 프레임 속도에 따라 변동성이 있습니다.