ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Unity 스크립트 기초 - 클래스
    기타/Unity 2022. 1. 10. 03:46

    주의! 해당 포스트는 정제되지 않았습니다.

    해당 주제에 대한 공부를 진행하며 동시에 정리한 글이기 때문에 잘못된 부분이 매우 많습니다.

    (잘못된 부분에 대한 지적은 댓글로 감사히 받겠습니다)


    클래스 (Class)

    유니티에서 스크립트 작성을 위해 사용하는 C#은 객체 지향 언어이다. 객체 지향 언어를 간단히 설명하면, 객체(Object)를 중심으로 하여 서로 상호작용하도록 구성하는 방식으로 프로그래밍하게 만들어진 언어라 할 수 있다.

     

    이때 각 객체(Object)는 각각 자신만의 데이터와 함수를 가질 수 있다. 그리고 이러한 객체가 어떠한 데이터와 함수를 가지는지를 정의하는 것을 클래스라 한다.

    이러한 클래스는 각 객체의 데이터에 해당하는 멤버 변수와, 함수에 해당하는 메서드로 구성된다. 이렇게 각 클래스를 정의하면 이를 이용해 실체를 가지는 객체를 생성할 수 있다.

     

    유니티에서는 이러한 C# 언어로 작성한 스크립트를 게임 내의 오브젝트에 연결해 제어하는 방식을 사용한다.

    본래의 C#에서 클래스를 사용하는 방식은 클래스를 먼저 선언하고, 그다음 생성자를 이용해 해당 클래스로 객체(Object)를 생성하는 방식이다.

    하지만 유니티에서는 먼저 유니티 프로그램 내에서 오브젝트를 생성해두고, 후에 클래스 단위로 정의된 스크립트 파일을 원하는 오브젝트에 연결해 사용하는 식이다.

    서로 순서가 반대인 느낌이 들지만, 객체가 연결된 클래스의 멤버 변수와 메서드를 가진다는 점에서는 차이가 없다.


    유니티 스크립트

    유니티에서 스크립트를 생성하여 이를 열어보면 아래와 같이 기본 형태가 갖춰져 있다.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class UnityTestScript : MonoBehaviour
    {
        // Start is called before the first frame update
        void Start()
        {
            
        }
    
        // Update is called once per frame
        void Update()
        {
            
        }
    }

     

    위의 코드를 살펴보면 UnityTestScript라는 클래스가 생성되어 있는데, 이는 유니티에서 생성한 스크립트 파일의 이름과 같다. 실제로 위의 스크립트 파일의 이름은 UnityTestScript.sc이다.

     

    만약 유니티 스크립트 파일의 이름과 클래스의 이름이 다르다면, 해당 스크립트 파일을 유니티 게임 오브젝트에 연결해도 스크립트 파일 내에서 이름이 같은 클래스를 찾지 못해 오브젝트에 제대로 적용되지 못해 오류가 나므로 주의해야 한다.

     

    위의 스크립트 파일을 생성하면서 동시에 자동으로 생성된 클래스를 살펴보면, 기본적으로 Start()와 Update()라는 메서드가 있다. 이는 MonoBehaviour의 이벤트 함수로 아직은 자세히 다루지 않는다.

    우선은 Start() 메서드는 해당 스크립트가 적용된 오브젝트가 생성될 때 1회 실행되며, Update() 메서드는 매 프레임마다 실행되는 메서드라고만 알고 있으면 된다.


    클래스 선언

    클래스는 접근 제한자, class키워드, 클래스명을 아래와 같은 방식으로 나열해 선언한다.

    <접근 제한자> class <클래스명> {멤버 변수 또는 메서드 등}
    using UnityEngine;
    
    public class MyFirstClass
    {
        private int attack = 100;
        public int defence;
        
        public void showDefence()
        {
            Debug.Log(this.defence);
        }
    }

    위의 코드는 MyFirstClass라는 클래스를 선언한 형태이다.

    해당 클래스는 attack과 defence라는 변수명을 가지는 멤버 변수를 가지고 있고, showDefence라는 메서드를 가지고 있다. 해당 메서드는 Debug.Log라는 메서드를 이용해 현재 오브젝트의 defence값을 유니티 Console창에 보여주는 역할을 한다.

     

    먼저 클래스의 생성과 클래스 안의 모든 멤버에 사용된 public이나 private 같은 수식어에 대한 설명이다.

    접근 제한자

    접근 제한자는 클래스 내의 멤버에 접근하는 것을 제한하는 역할을 한다.

    주로 사용하는 접근 제한자의 종류와 접근 가능한 클래스 범위는 아래와 같다.

    • public : 클래스 외부에서도 접근 가능
    • protected : 같은 클래스와 해당 클래스의 자식 클래스에서만 접근 가능
    • private : 클래스 내부에서만 접근 가능

    (접근 제한자에 대한 내용은 아래에서 다시 자세하게 설명하겠음)

    멤버

    멤버는 클래스의 데이터와 동작을 나타낸다.

    멤버의 종류는 많지만 우선은 간단히 사용하는 멤버의 종류에 대해 알아보면 아래와 같다.

    생성자 ClassName(){...} 클래스가 생성될 때 실행되는 메서드
    (클래스명과 같은 이름을 사용)
    소멸자 ~ClassName(){...} 클래스가 소멸될 때 실행되는 메서드
    (클래스명 앞에 ~기호를 붙인 이름을 사용)
    상수 const int constantValue 값이 변하지 않는 상수
    멤버 변수 int variableValue 값이 변하는 변수
    메서드 void method(){...} 특정 행동을 나타내는 메서드

    객체의 생성과 접근

    클래스는 객체(Object)를 생성하기 위한 일종의 틀과 같은 것이다. 따라서 위와 같은 방식으로 클래스를 선언했다면, 이를 이용해 객체를 생성할 수 있다.

     

    이때 객체를 생성하는 방식은 new라는 키워드를 이용하는 것이며, 자세한 방법은 아래와 같다.

    [접근 제한자] <클래스명> <생성할 객체명> = new <생성자>
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class MyFirstClass
    {
        private int attack = 100;
        public int defence;
        
        public void showDefence()
        {
            Debug.Log(this.defence);
        }
    }
    
    public class UnityTestScript : MonoBehaviour
    {
        void Start()
        {
            MyFirstClass instance = new MyFirstClass();
            instance.defence = 100;
            instance.showDefence();
        }
        
        void Update()
        {
    
        }
    }

    위의 코드는 위에서 보여준 유니티 스크립트 코드와 클래스 선언에서 사용한 코드를 합한 형태이다.

    약간의 차이가 있다면, UnityTestScript 클래스의 Start() 메서드 안에 새로운 코드 3줄이 생성되었다.

     

    먼저 새로 추가된 코드 중 첫째 줄은 new라는 키워드를 이용하여 새로운 객체를 생성하는 부분이다.

    MyFirstClass instance = new MyFirstClass();

    위에서 MyFirstClass라는 클래스를 선언하였기에 이를 이용해서 객체를 생성한다.

     

    클래스명은 먼저 선언한 클래스 명인 MyFirstClass를 사용하였고, 이를 이용해 생성할 객체의 이름은 instance로 정하였다. 이후 new 키워드와 생성자를 이용해 객체를 생성할 수 있다.

    생성자

    위에서 설명한 내용 중 클래스명과 객체명은 원하는 대로 정하면 되는 것이고, new 키워드는 정해진대로 사용하면 되는 것이기에 크게 문제가 없다. 단, 그 뒤에 사용되는 생성자라는 부분은 아직 배우지 않았다.

     

    위의 코드를 살펴보면 생성자에 해당되는 부분이 클래스명과 일치하는 메서드 형식이라는 것을 알 수 있다.

    또한 앞에서 설명한 클래스 선언 부분을 보면, 클래스 내에서 선언할 수 있는 멤버의 종류에 생성자가 있는 것을 알 수 있다. 이러한 생성자는 클래스명과 같은 이름을 사용하여 지정하고, 클래스가 생성될 때 동작을 관리한다.

     

    중요한 건 위의 코드에서 임의로 만든 MyFirstClass에는 생성자를 따로 지정하지 않았다. 하지만 그럼에도 MyFirstClass()라는 메서드를 이용해 해당 클래스의 객체를 만들 수 있는 이유는, 개발자가 따로 생성자를 만들지 않아도 컴파일러에서 자동으로 기본 생성자(default constructor)를 생성해주기 때문이다.

     

    만약 아래와 같이 직접 생성자를 미리 지정해 둔다면, 기본 생성자는 생성되지 않는다.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class MyFirstClass
    {
        public MyFirstClass(int attack)
        {
            this.attack = attack;
        }
        private int attack = 100;
        public int defence;
    
        public void showDefence()
        {
            Debug.Log(this.defence);
        }
    }
    
    public class UnityTestScript : MonoBehaviour
    {
        void Start()
        {
            MyFirstClass instance = new MyFirstClass(20);
            instance.defence = 100;
            instance.showDefence();
        }
    
        void Update()
        {
    
        }
    }
    [기존] MyFirstClass instance = new MyFirstClass();
    [변경] MyFirstClass instance = new MyFirstClass(20);

    MyFirstClass 클래스 안에 클래스명과 같은 이름을 가지는 생성자를 만들었다. 해당 생성자는 객체가 생성될 때 자동으로 실행되는 메서드이며, 일반적인 메서드와 마찬가지로 인수를 받을 수 있다. 단, 반환 값을 가지지는 않는다.

     

    이렇게 하나의 인수를 가지는 생성자를 직접 만들어두었기 때문에, 이를 이용해 객체를 생성할 때도 생성자에서 요구하는 개수의 인수를 넣어줘야 한다.

    멤버 접근

    이제 클래스를 이용해 객체를 생성하는 과정은 끝났다. 이렇게 생성한 객체는 해당 객체의 멤버에 접근을 하는 방식으로 사용된다. 유니티 클래스 내의 Start() 메서드에서 객체 생성 부분을 제외한 두 줄이 멤버 접근에 관한 부분이다.

    instance.defence = 100;
    instance.showDefence();

    해당 코드를 살펴보면 객체명에 .(온점)을 찍은 뒤 접근하고 싶은 멤버(멤버 변수 or 메서드)의 이름을 붙이는 형태로 해당 객체의 멤버에 접근한다.

     

    이때 해당 객체의 클래스에서 선언된 접근 제한자에 의해 멤버에 접근할 수 있는 권한이 달라지게 된다.

    접근 권한자는 위에서 언급했듯이 아래와 같이 크게 3가지 종류가 있다.

    • public : 클래스 외부에서도 접근 가능
    • protected : 같은 클래스와 해당 클래스의 자식 클래스에서만 접근 가능
    • private : 클래스 내부에서만 접근 가능

    여기서 public으로 선언된 멤버에 한해서만 외부에서 접근하여 사용이 가능하다.

    위의 코드에서 멤버 접근 예시로 사용한 instance.defence와 instance.showDefence()의 경우, 해당 객체를 생성하는 데에 사용한 클래스인 MyFirstClass에서 defence멤버와 showDefence메서드에 대해 찾아보면 public으로 선언된 것을 확인할 수 있다.

    public class MyFirstClass
    {
        public MyFirstClass(int attack)
        {
            this.attack = attack;
        }
        private int attack = 100;
        public int defence;
    
        public void showDefence()
        {
            Debug.Log(this.defence);
        }
    }

    따라서 private으로 선언된 멤버 변수인 attack에 대해서는 외부에서 접근할 수 없다.

    만약 강제로 접근하려 시도한다면 아래의 사진과 같은 경고문을 받게 된다.

    정적 멤버 (Static)

    위에서 임의로 만든 클래스인 MyFirstClass에는 여러 멤버가 있지만, 그중에서 메서드인 showDefence()를 살펴보면, Debug.Log라는 메서드를 이용해 자신의 defence수치를 표현하는 역할을 한다.

        public void showDefence()
        {
            Debug.Log(this.defence);
        }

    이때 Debug.Log라는 것은 Unity에서 자체적으로 제공하는 기능으로, C#의 기본적인 기능은 아니다.

    이는 유니티에서 자체적으로 Debug라는 클래스를 만들어두고, 해당 클래스에서 제공하는 메서드인 Log를 이용하는 방식이다. 그래서 객체의 멤버에 접근하는 방식과 똑같은 방식을 사용하고 있는 것이다. (온점을 사용하는 방식)

     

    하지만 위의 내용을 제대로 숙지했다면 뭔가 이상한 부분이 하나 있다.

    Debug라는 것은 Unity에서 미리 만들어 둔 클래스인데, 그렇다면 해당 클래스의 생성자를 이용해 먼저 객체를 생성하고, 그다음에 해당 객체를 이용해 객체명.Log()의 형태로 사용하는 것이 맞다.

     

    하지만 위의 코드에서는 Debug.Log의 형식으로 클래스를 이용해 메서드를 바로 직접적으로 호출했다.

    이는 Debug라는 클래스에 선언된 Log메서드가 static으로 선언되었기 때문이다.

    public static void Log(object message);

    이렇게 static을 이용해 선언된 멤버의 경우, 생성자에 의해 객체가 생성되는 순간에 메모리를 할당받는 인스턴스 멤버(일반적인 경우)와 달리 프로그램이 시작될 때부터 종료될 때까지 메모리에 올라가 있는다. 즉, 해당 멤버는 프로그램 전체에 걸쳐 항상 같은 메모리를 사용하게 된다.

     

    따라서 static을 이용한 멤버 변수의 경우, 서로 다른 객체라도 같은 클래스를 공유한다면, 해당 멤버 변수에 대한 값은 항상 유지된다. (같은 메모리를 사용하므로)

    따라서 게임 전반에 걸쳐 값을 계속 기억하고 조정해야 하는 경우 사용하는 경우가 많다.

     

    또한 static을 이용한 메서드의 경우에도, 객체가 메모리를 할당받기 전에 이미 클래스에 종속되어 메모리를 부여받은 상태이다. 따라서 객체(instance)를 생성하여 메서드를 호출하는 방식이 아닌, 클래스 자체를 이용하여 메서드를 호출하는 방식을 사용한다. 다만, static 메서드의 경우 해당 클래스의 인스턴스 객체 멤버를 참조할 수는 없다. (시기상 인스턴스 객체 멤버가 메모리를 할당받기 전에 호출되므로)


    클래스의 상속

    지금까지 살펴본 클래스에 대한 내용 중에서 유니티에서 C# 스크립트를 생성할 때 자동으로 생성되는 유니티의 컴포넌트 클래스를 잘 보면 배우지 않은 특이한 부분이 있다.

    public class UnityTestScript : MonoBehaviour
    {
        void Start() {...}
        void Update() {...}
    }

    클래스 명 뒤에 콜론과 MonoBehaviour라는 알 수 없는 형태가 붙어있다.

    이는 클래스의 상속을 나타내는 표현이다.

     

    클래스의 상속이란, 특정 클래스의 멤버를 그대로 사용하면서 추가적인 기능을 붙여 새로운 클래스를 만드는 기법이다. 즉, 위의 코드에서 UnityTestScript는 MonoBehaviour라는 클래스의 모든 기능을 상속받은 상태이다.

     

    Monobehaviour은 기본적으로 유니티에서 준비해둔 클래스로, 유니티 게임 오브젝트를 위한 컴포넌트 클래스라면 기본적으로 이 클래스를 상속받는다.

    해당 스크립트에 있는 Start() 메서드나 Update() 메서드를 사용할 수 있는 이유도 Monobehaviour 클래스를 상속받았기 때문이다.

     

    Monobehaviour에 대한 자세한 내용은 아래의 유니티 공식 Documentaion에서 확인할 수 있다.

     

    Unity - 스크립팅 API: MonoBehaviour

    Javascript를 사용하는 경우에, 자동으로 MonoBehaviour를 상속받습니다. C# 또는 Boo언어를 사용하는 경우에는 명시적으로 MonoBehaviour를 상속받아야 합니다. Note: MonoBehaviour를 비활성화 하는 체크박스(에

    docs.unity3d.com

     

    댓글