Unity 개인 공부

Unity 개인 공부(2025-01-19)

kimjunki-8 2025. 1. 19. 23:47
player player = new player();

player.id = 0;
player.name = "응애";
player.strength = 52.2f;
player.weapon = "검";
player.talk();
player.HasWeapon();
클래스
자, 여기서 클래스는 Python의 클래스와 다릅니다. Python에서는 비슷한 기능을 가진 함수들의 모음이라면, 
여기서는 하나의 사물(오브젝트)와 대응하는 로직입니다.
일단 클래스가 어떻게 생겼는지는 위에 한번 나와 있습니다.
public class NewMonoBehaviourScript : MonoBehaviour
일단, 파이썬과 비슷하게 class를 사용해 클래스를 선언합니다.

자 참고로 시작하기전 알아야 할 사항이 있습니다.
Python과 다르게
Unity에서 클래스를 새로 만들 때 새로운 파일에 클래스를 정의하는 것이 좋습니다.

1. Unity의 C# 스크립트 관리 방식
Unity에서는 각 C# 스크립트 파일이 하나의 클래스를 정의하도록 권장합니다. 이는 Unity 에디터가 스크립트 파일을 컴파일하고 이를 객체에 연결할 때 유용하기 때문입니다. 하나의 파일에 여러 클래스를 넣는 것보다는 각 클래스를 별도의 파일에 넣으면 Unity가 이를 더 잘 처리할 수 있습니다.
클래스와 파일 이름 일치: Unity는 보통 파일 이름과 클래스 이름이 일치해야 합니다. 파일명과 클래스명이 다르면, Unity에서 해당 스크립트를 찾을 수 없게 되어 오류가 발생할 수 있습니다.

2. 가독성 및 유지보수성 향상
한 파일, 하나의 책임: 하나의 클래스는 보통 하나의 책임을 지고 있습니다. 여러 클래스를 한 파일에 넣으면, 각 클래스의 역할과 책임이 명확해지지 않고 가독성이 떨어질 수 있습니다.
클래스가 많아지면 파일 내에서 클래스들이 뒤섞여 관리하기 어려워지기 때문에, 각 클래스를 별도의 파일로 분리하면 코드를 이해하고 관리하는 데 도움이 됩니다.

그래서 actor라는 새로운 스크립트를 만들어봅시다.

public class actor
{
    int id;     
    string name;
    string weapon;
    float strength;
    int level;

    string talk() 
    {
        return "대화를 걸었습니다.";
    }

    string HasWeapon() {
        return weapon;
    }

    void Levelup() {
        level = level + 1;
    }
}

먼저 이렇게 만들었습니다. 이렇게 만든 이유가 어차피 이것을 다른데서 가져와서 사용할 것이기 때문입니다.
자, 이렇게되면, 어떻게 쓰냐 python은 인스턴스를 만들다고 하면 C#도 비슷합니다.

아 참고로 말하지만, class에서 
public class actor : MonoBehaviour처럼 MonoBehaviour를 상속받은 클래스는 new 키워드로 직접 인스턴스를 생성할 수 없으며, 반드시 게임 오브젝트에 추가하여 컴포넌트로 사용해야 합니다.

MonoBehaviour를 상속하지 않은 일반 클래스는 new 키워드를 사용하여 객체를 생성하고 사용할 수 있습니다.

actor(클래스 이름) actor(넣을 변수 이름) = new actor(클래스 이름)();

이렇게하면 하나의 클래스를 변수에 넣어서 쓸 수 있는데, 이것을 인스턴스(파이썬에도 알죠?)라고 합니다.

그리고 이제 저희가 아는 Python의 기능처럼, 각 클래스에 있는 함수들에게 접근하기 위해 .을 씁니다.
그런데 지금은 아마 나오는게 없을 것입니다. 왜냐하면 이것은 접근자의 문제이기 때문입니다.

혹시 class 앞에 있던 public을 보셨나요? 이것이 그 개념입니다.
여기서 우리가 위에 클래스를 가져와서 변수에 넣을 수 있었던 이유가 바로 저 public이 있기 때문입니다.
즉, 저 public이라는 접근자 때문입니다.

자, 이제 본론으로 가보면, 저희가 정의한 actor 클래스에서 각 속성 int id, string name에는 사실 안 보이지만, 앞에는 private이라는 접근자가 생략이 되어 있습니다.
우리가 python에서 그냥 a = 10할 때, 이 값은 int라는 것을 숨기는 것과 비슷합니다.
그래서 이것들을 다른 곳에서 접근할 수 있도록 public으로 바꿔주는 것이 좋습니다.
public -> 외부 클래스에 공개로 설정하는 접근자.

public class actor
{
    public int id;     
    public string name;
    public string weapon;
    public float strength;
    public int level;

    public string talk() 
    {
        return "대화를 걸었습니다.";
    }

    public string HasWeapon() {
        return weapon;
    }

    public void Levelup() {
        level = level + 1;
    }
}

이렇게 말입니다.

그러면 이제 가져와서 쓸 수도 있습니다.

actor player = new actor();

player.id = 0;
player.name = "응애";
player.strength = 52.2f;
player.weapon = "검";
Debug.Log(player.talk());
Debug.Log(player.HasWeapon());

 

이렇게 말입니다.

그러면 저 MonoBehaviour은 뭘까요?
저건 아까 말했듯이 저건 상속입니다.
즉, python으로 치자면, 미리 만들어 놓은 프레임을 가져와서 거기서 오버라잇 하는 느낌입니다.

여기서 한번 player라는 스크립트를 만들어 봅시다.
참고로 상속을 받는게 있으면 using UnityEngine;이 필요합니다.

using UnityEngine;

public class player : actor
{
    public string move() {
        return "응애, 움직이다.";
    }
}

이렇게 한번 만들어 봤습니다.

자, 그러면 이걸 어떻게 쓸까, 한다면,

player player = new player();

player.id = 0;
player.name = "응애";
player.strength = 52.2f;
player.weapon = "검";
player.talk();
player.HasWeapon();

이렇게 기존에 actor 클래스의 이름을 player로 바꿨습니다.
참고로 이렇게 해도 전혀 문제가 없습니다. 왜냐하면 actor가 가진 모든 요소들을 player가 모두 물려받았기 때문에, 쓸 수 있기 때문입니다.

이렇게 설명하면 더 쉬울것입니다.
1. actor ->
다른 요소들을 만들 수 있는 각종 요소들의 뼈대(id, name)
2. player ->
그 뼈대를 상속받아 그 뼈대를 기준으로 각 새롭게 값을 넣음 id = 1, name = "각지"
참고로 상속받아서 쓰기 때문에, actor에 있는 값은 전혀 바뀌지 않습니다.

더 쉽게 풀어서 설명하면 붕어빵 기계가 될 수 있습니다.
붕어빵을 만들 때, 그 틀은 뭘 하든 바뀌지 않습니다. 기존 틀에 새로운 여러가지 요소를 넣어서(팥빵, 크림) 새로운 붕어빵(player)을 만들 수 있다는 느낌입니다. 즉, 기존 틀은 고유하지만, 그것을 가지고 와서 어떻게 쓸지에 대한 것은 player의 몫입니다.(즉, actor는 부모 클래스, player는 자식 클래스가 됩니다.) 그리고 player는 actor의 요소를 그대로 물려받지만, 또 물려받으면서 값을 넣거나, 추가, 변형, 심지어 뭐 새로운 함수 추가까지 다 할 수 있습니다.

짧게 추가 설명을 붙이자면
MonoBehaviour는 유니티 게임 오브젝트 클래스 입니다.

이제 기초가 모두 끝났습니다.


유니티 라이프사이클 첫번째 초기화 영역
이제 유니티 게임오브젝트의 흐름을 알아보겠습니다.

유니티 생명주기에서 가장 첫번째로 실행되는 구간은 바로 초기화입니다.
그 구간에서도 가장 먼저 실행되는 함수가 Awake()입니다. 그리고 딱 한번만 실행됩니다.

조금 더 살펴보겠습니다.
저희가 처음 스크립트 파일을 만들었을 때는 Awake()함수는 보이지 않습니다.
Unity에서 Awake() 함수는 MonoBehaviour 클래스의 기본 제공 메서드 중 하나로, 특정 상황에서만 실행되기 때문에 기본으로 포함되지 않을 수 있습니다.

근데 왜 보이지 않을까요?
Unity가 자동으로 생성하는 스크립트 템플릿에는 보통 Start()와 Update() 메서드만 포함되어 있습니다. 이는 두 메서드가 가장 자주 사용되기 때문입니다. Awake()는 기본 템플릿에 포함되지 않지만, 필요할 때 직접 작성할 수 있습니다. Unity는 Awake()를 스크립트에 추가하면 자동으로 호출합니다.

Awake()의 역할
Awake()는 Unity에서 스크립트가 활성화될 때 가장 먼저 호출되는 메서드로, 초기화 작업에 주로 사용됩니다.

특징:
객체가 활성화될 때 한 번만 호출됩니다.
Start()보다 먼저 실행됩니다.
씬이 로드될 때 또는 객체가 활성화될 때 호출됩니다.
다른 스크립트 및 컴포넌트를 초기화하거나, GameObject를 찾는 데 유용합니다.


Awake()를 추가하고 싶다면

using UnityEngine;

public class MyScript : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("Awake 실행");
    }

    void Start()
    {
        Debug.Log("Start 실행");
    }

    void Update()
    {
        Debug.Log("Update 실행");
    }
}​

 

이렇게 추가하면 됩니다. 
Unity는 모든 Awake()를 먼저 호출한 후, Start()를 호출합니다.

Awake()는 기본 템플릿에 포함되지 않는 이유는 다음과 같습니다:
Awake()는 초기화 작업이 필요할 때만 사용됩니다.
템플릿을 단순하게 유지하기 위해 자주 사용되는 메서드만 포함합니다.

그 다음은 저희가 봐왔던 Start 함수 입니다.
Start -> 업데이트 시작 직전, 최초 실행. 즉, update 함수로 들어가기 전에 실행되는 함수.

using UnityEngine;

public class MyScript : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("Awake 실행");
    }

    void Start()
    {
        Debug.Log("Start 실행");
    }

여기까지 
초기화 영역이라고 합니다.


유니티 라이프사이클 두번째 물리연산 영역
두번째는 물리연산 영역입니다.
여기서 사용되는 첫번째 함수는 void FixedUpdate()입니다.
FixedUpdate 함수는 먼저, Unity 엔진에서 물리 연산을 하기 전에 실행되는 함수
그러면 Update와의 차이는 뭘까요
먼저 Unity에서 Update()와 FixedUpdate()는 둘 다 특정 작업을 반복적으로 실행하기 위한 메서드지만, 업데이트 주기와 용도에서 큰 차이가 있습니다. 물리 연산이 중요한 상황에서는 이 차이를 이해하는 것이 매우 중요합니다.

Update()
호출 주기:
매 프레임마다 호출됩니다.
프레임 속도(프레임 레이트, FPS)에 따라 호출 횟수가 달라질 수 있습니다.
그리고 1초에 60 프레임으로 업데이트 됩니다.

사용 목적:
게임 로직(예: 입력 처리, UI 업데이트)에 주로 사용됩니다.
시간에 따라 애니메이션을 업데이트하거나, 플레이어 입력(키보드, 마우스, 터치)을 처리합니다.

특징:
프레임 속도가 변하면 호출 간 간격도 변합니다.
일정한 간격으로 실행되지 않을 수 있으므로 물리 연산에 적합하지 않습니다.
void Update()
{
    float move = Input.GetAxis("Horizontal");
    transform.Translate(Vector3.right * move * Time.deltaTime);
}


FixedUpdate()
호출 주기:
Unity의 물리 연산 시스템(Physics Engine)에 따라 고정된 간격(기본값: 0.02초, 즉 50fps)으로 호출됩니다.
Time.fixedDeltaTime으로 호출 간격을 확인하거나 조정할 수 있습니다.
그리고 1초에 50 프레임으로 업데이트 됩니다.

사용 목적:
물리 연산과 관련된 작업(예: Rigidbody 조작, 힘 적용, 속도 업데이트)에 사용됩니다.
일정한 간격으로 호출되므로 물리 연산에서 예측 가능한 결과를 보장합니다.

특징:
프레임 속도에 영향을 받지 않으며, 항상 일정한 간격으로 실행됩니다.
물리 엔진(Physics)의 업데이트와 동기화됩니다.
높은 프레임 속도(예: 120 FPS)나 낮은 프레임 속도(예: 30 FPS)에서도 같은 결과를 보장합니다.

void FixedUpdate()
{
    float move = Input.GetAxis("Horizontal");
    Rigidbody rb = GetComponent<Rigidbody>();
    rb.AddForce(Vector3.right * move);
}​

 


유니티 라이프사이클 세번째 게임로직 영역
사실 물리와 게임로직은 프레임이라는 개념에 둘 다 들어갑니다.
이렇게, 물리는 FixedUpdate 함수가, 게임 로직은 Update 함수가 서로의 영역에 있다고 보시면 됩니다.
즉, 게임 로직에는 이제 Update함수를 써야한다는 뜻입니다.

여기서 잠시 물리와 게임 로직에 대해 조금 자세히 알아보겠습니다
1. 물리(Physical)란?
"세상에서 물건들이 움직이는 규칙"을 담당합니다.
예를 들어:
공을 던지면, 공은 하늘로 올라갔다가 중력에 의해 땅으로 떨어집니다.
물건을 밀면, 물건은 힘에 따라 앞으로 가다가 멈춥니다.
자동차가 부딪히면, 충돌로 인해 방향이 바뀌거나 멈추게 됩니다.
이런 계산들은 Unity의 물리 시스템에서 처리됩니다. 이를 담당하는 것이 바로 FixedUpdate()입니다.
주로 힘, 속도, 중력, 충돌 같은 것을 계산할 때 사용됩니다.

2. 게임 로직(Game Logic)이란?
"게임 속에서 일어나는 규칙과 행동"을 말합니다.
예를 들어:
사용자가 키보드를 누르면, 캐릭터가 움직입니다.
몬스터를 공격하면 체력이 줄어듭니다.
점수가 100점에 도달하면 "승리 화면"이 뜹니다.
이런 행동과 규칙들은 Unity에서 Update()를 통해 처리됩니다. 주로 키 입력, 점수 계산, 화면 업데이트 같은 것을 처리할 때 사용됩니다.

이렇게 업데이트 함수들이 모두 끝난 후 실행되는 함수는 바로 LateUpdate입니다.
LateUpdate: 모든 업데이트 끝난 후 실행되는 함수(참고로 update와 같이 1초에 60프레임 입니다)
LateUpdate()는 Unity에서 모든 업데이트(Update()) 작업이 끝난 후 실행되는 메서드입니다. 주로 게임 로직과 관련된 후처리 작업에 사용됩니다. 특히 주로 다른 스크립트나 컴포넌트의 업데이트 작업이 모두 끝난 뒤, 최종적으로 후처리가 필요할 때 사용됩니다.

사용 방법:
카메라 이동/추적
Update()에서 캐릭터가 움직인 후, 카메라가 캐릭터를 따라가는 작업을 LateUpdate()에서 처리합니다.
이렇게 하면 카메라가 항상 최신 위치를 기준으로 따라갈 수 있습니다.

애니메이션 후처리
애니메이션이 Update에서 처리된 뒤, 그 결과를 기반으로 추가 작업을 수행할 때 사용합니다.

UI와 관련된 작업
화면에 표시되는 정보(예: 체력 바, 점수 등)를 오브젝트의 최신 상태를 기준으로 업데이트할 때 유용합니다.

유니티 라이프사이클 네번째 게임로직 해체
Unity에서 말하는 "해체"는 객체(Object)가 파괴(destroyed)되거나, 더 이상 필요하지 않을 때 처리되는 과정을 의미합니다. 해체 단계에서는 주로 정리 작업을 수행합니다.

해체란
게임 오브젝트(GameObject) 또는 스크립트가 삭제되기 직전에 발생하는 작업입니다.
Unity에서 OnDestroy() 메서드를 사용하여 해체 시 실행할 작업을 정의할 수 있습니다.


OnDestroy()란?
오브젝트가 파괴될 때 호출되는 메서드입니다.오브젝트가 Destroy() 함수로 파괴되거나, 씬(Scene)이 닫힐 때 호출됩니다.

void OnDestroy()
{
    Debug.Log("이 오브젝트가 해체되었습니다!");
}​

왜 해체가 중요한이유
리소스 관리
네트워크 연결, 파일 핸들, 메모리 등 외부 자원을 정리해야 할 때.
예: 오브젝트가 삭제될 때 네트워크 연결을 끊거나, 파일을 닫아야 할 경우.

메모리 누수 방지
삭제되는 오브젝트가 다른 시스템이나 스크립트와 연결되어 있다면, 해체 단계에서 연결을 끊지 않으면 메모리 누수가 발생할 수 있습니다.

정리 작업
삭제 전에 로그를 남기거나, 특정 상태를 저장해야 할 때 사용합니다.

일단 여기서 실행시킨 후 오브젝트를 삭제하면 이 오브젝트가 해체되었습니다! 가 뜹니다.


그런데 여기서 오브젝트 활성화 비활성화의 개념이 나옵니다.
1. 활성화와 비활성화란?
활성화(Active):
게임 오브젝트나 컴포넌트가 화면에 나타나거나 동작을 수행할 수 있는 상태입니다.
예: 캐릭터가 움직이고, 카메라가 화면을 보여줌.

비활성화(Inactive):
게임 오브젝트나 컴포넌트가 화면에 보이지 않고, 동작하지 않는 상태입니다.
예: 적 캐릭터가 맵에서 사라지고, 관련 스크립트도 멈춤.

2. 오브젝트 활성화/비활성화의 특징
게임 오브젝트:
게임 오브젝트 전체를 활성화/비활성화할 수 있습니다.
gameObject.SetActive(true) → 활성화
gameObject.SetActive(false) → 비활성화
컴포넌트:
특정 컴포넌트(예: 스크립트, 콜라이더)만 활성화/비활성화할 수도 있습니다.
component.enabled = true → 활성화
component.enabled = false → 비활성화

일단 활성화는 초기화화 물리 사이에 있습니다

using UnityEngine;

public class Lifecycle : MonoBehaviour
{
    private void Awake() 
    {
        Debug.Log("hi");
    }

    private void OnEnable() {
        
    }
    
    private void Start() {
        Debug.Log("Hello");
    }​

 

여기서 OnEnable은 게임 오브젝트가 활성화 되었을 때 쓰이며, 여기서 최초 1회 실행이 아니라, 켜고 끄고 할 때마다 실행이 됩니다. 그렇기 때문에 Awake보다는 늦게, Start보다는 빠르게(그러니까 그 중간) 실행이 되야합니다.

여기서 이제 비활성화가 나옵니다. 
비활성화는 게임로직과 해체 사이에 존재합니다.

Disable은 다음 시간에

 

'Unity 개인 공부' 카테고리의 다른 글

Unity 개인 공부(2025-01-22)  (2) 2025.01.22
Unity 개인 공부(2025-01-21)  (0) 2025.01.21
Unity 개인 공부(2025-01-20)  (0) 2025.01.20
Unity 개인 공부(2025-01-18)  (0) 2025.01.18
Unity 개인 공부(2025-01-17)  (0) 2025.01.17