ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 게임개발 2일차 (함수, struct, class, 추상화, 다형성, 상속)
    게임 개발 2025. 5. 24. 12:07


    c#의 함수 인자 전달에는 값 전달, ref 전달, out 전달이 있다.

    값 전달 코드를 적어보자.

    void Square (int n) { n = 5; return;}
    void Start(){
        int x = 3;
        Debug.Log(x);
        Square(x);
        Debug.Log(x);}

    간단한 x=3으로 설정하고 출력한 후 값을 5로 설정하는 함수에 들어갔다가 다시 출력하는 코드다.

    이렇게하면 3, 3이 출력된다. 3이라는 값이 전달되고 5로 바뀌었지만 복사본이 바뀐거기 때문에 원본은 그대로기 때문이다.

     

    ref 전달을 보자. void Square(ref int n)과 Square(ref x)로 바꿔보자.

    결과는 3, 5다. ref 전달은 변수가 저장되어 있는 장소와 값을 전달하기 때문에 원본이 바뀔 수 있기 때문이다.

     

    out 전달은 ref와 사뭇 다르다. void Square(out int n)과 Square(out x)로 바꿔보자.

    out은 ref와 달리 변수가 저장되어 있는 장소만 전달하고 안에 들어있는 값을 무시한다. 그래서 이번 코드에서는 결과는 3, 5로 동일하게 나오지만 만약 다른 코드에서 n을 그대로 사용하려고 하면 n이 초기화되어 있지 않기 때문에 오류가 뜨게 된다.

     

    함수에는 인자에 default값을 지정할 수 있다. 예를 들어 void Square(int x=3) 은 x값을 전달받으면 그걸 쓰고, 생략되어 있으면 3을 쓰라는 의미이다. 

     

    크기가 정해지지 않은 문자열들을 받고 싶을 때는 params string[] a 처럼 사용하면 된다. 그러면 "asd", "sdf" 를 문자열 배열로 받아온다. 다만 크기가 정해지지 않았기 때문에 어디가 끝인지 몰라 Square( params string[] a, int n ) 이렇게 사용하면 안된다. 문자열을 어디까지만 받고 새로 int n을 받아야 할지를 모르기 때문이다. 그래서 Square( int n, params string[] a ) 이렇게 맨 뒤에 써야 한다.

     

    함부로 원본을 바꾸는 건 좋지 않기 때문에 불필요한 곳에는 ref, out 전달을 자제하고 값 전달을 쓰자.


    enum 열거형으로 새로운 자료형을 만들 수 있다.

    enum Attack{ Arrow, Bullet, Missile=5 }

    void Start(){
        Attack x = Attack.Bullet;
        switch (x){
            case Attack.Arrow: Debug.Log("화살"); break;
            case Attack.Bullet: Debug.Log("총알"); break;
            case Attack.Missile: Debug.Log("미사일"); break;

        }
    }

    출력은 총알이 된다. 기본적으로 enum은 순서대로 0,1,2,... 이렇게 저장한다.( Missile=5처럼 지정할 수도 있다) (int) x 를 출력해보면 1로 나온다. 내부적으로는 정수형으로 저장하고 있기 때문이다. 굳이 이렇게 하는 이유는 개발자가 알아보기가 쉽기 때문이다.

    참고로 (int) x 이렇게 (변환이 가능한) 변수를 다른 변수형으로 변환하는 것을 타입캐스팅이라고 한다.

     

    enum은 내부적으로 단순한 정수들의 모임이기 때문에 new나 ()를 붙일 필요 없이 선언하면 된다.


    struct라는 여러가지 변수를 모아놓은 자료형을 만들 수 있다.
    struct HumanData{
        public string name;
        public int age;
        public float height;
    }
    void Start(){
        HumanData James = new HumanData();
        James.name = "제임스";
        James.age = 30;
        James.height = 180.5f;
        HumanData[] players = new HumanData[5];
        players[0] = new HumanData();
        players[1] = new HumanData();
    }

    이렇게 사람데이터라는 새로운 자료형을 만들고 사람 각각을 변수로 보고 여러가지 정보를 저장하게 만들 수 있다.

    C#에서는 구조체(struct)나 클래스(class) 내부의 변수는 기본적으로 private이기 때문에 public을 변수 앞에 적어줘야 외부에서도 사용할 수 있다.


    c#은 객체지향 언어이다. 객체지향이란 세상이 객체들의 상호작용으로 이루어진다고 본다는 것에서 시작한다.

    각 객체를 class라고 하고 안에 변수들을 저장한다. 예를 들어 캐릭터 클래스라면 hp mp 등이 되겠다.
    그러면 struct랑 class는 똑같은 것이 아닌가? 차이가 있다. class는 단순 변수만 아니라 하는 행동까지 지정할 수 있다.

    게임 캐릭터라면 스킬사용하기(mp-) 힐받기(hp+)등이 있겠다.

    클래스가 가지고 있는 변수를 멤버 변수(속성,프로퍼티라고도 함), 행동을 메소드라고 한다.

    어떤 캐릭터라는 클래스 틀에서 찍어낸 변수 각각을 객체 또는 인스턴스라고 한다.

    캐릭터 클래스의 코드이다. 여기서 public Character(string characterName, int initialHP) 부분은 클래스 이름하고도 같고 리턴값도 없는데 뭘까? 바로 생성자라고 한다. 객체가 생성될 때 멤버변수초기화를 위해서 실행 될 때 자동으로 한번 호출된다.

    실행될 때 생성자를 적어놨으면 그것으로 실행되고 만약 안적어놨으면 public Character(){   }  라는 빈 기본생성자를 만들어준다.

    이렇게 캐릭터 푸드 클래스를 만들고 메인함수에서 인스턴스를 생성 후에 때리고 힐하고 때리고 밥을 먹였다.

    결과는 true false. 처음에는 피가 8로 살았지만 그 다음에는 100을 맞고 죽은 이후 밥을 줘서 그대로 죽었다.


    이미 만든 비슷한 클래스가 있는데 새로운 클래스를 만들어야 할 때 우리는 클래스를 상속(확장)할 수 있다. 클래스의 특징을 이어받은 후에 약간을 수정하는 방법인데, 코드를 재사용할 수 있어서 편리하고 용량도 아낄 수 있게 된다. 상속은 부모클래스에서 자식클래스로 이루어진다.

    Wizard : Character 이렇게 상속을 할 수 있다. 그림으로 보면 아래와 같다.

     

     

    Wizard로 만들었지만 Wizard는 Character을 상속받은 클래스이기 때문에 SJ는 Character냐?라는 질문에 True를 반환한다.


    변수의 유효범위(scope)에 대해서 알아보자.

    for(~){ int x= 0}

    int x=2

    이 코드는 오류다. 바깥에서 x를 정의했는데 안에서도 한번 더 정의하기 때문이다.

     

    void(~){int x=0}

    void(~){int x=2}

    이 코드는 돌아간다. 서로 다른 함수에서 같은 이름의 변수는 아예 다른 변수 취급이다.

     

    class(){

       int x=0;

       void(){int x=3)

    }

    이 코드도 잘 돌아간다. 멤버변수 x와 함수안의 x는 다른 취급이다. 만약 멤버변수 x를 다루고 싶으면 this.x를 다뤄야 한다.

     

    캡슐화라는 개념이 있다. 캡슐화란 보일건 보이도록하고 안보여야할건 안보이게 하는 것이다.

    public private protected가 있다. 이를 이용해서 캡슐화를 구현한다.

    public은 어디서나 보이고, private은 외부에서 안보이고, protected는 외부에서는 자식클래스들에게만 보여주겠다는 의미이다.

    character 에서 변수를 정한다고 할 때 파란색이 private, 하늘색이 protected, 노란색이 public이다.

    캡슐화를 이용하면 보기도 편하고 잘못된 값이 들어가는 것을 방지할 수 있다.


    클래스의 멤버변수에 getter setter를 설정할 수 있다.

    public int hp{

       get; set;

    }

    이 변수을 향한 접근을 제어할 수 있게 된다. 예를 들어 set 없이 get만 넣으면  james.hp = 10 과 같은 set은 불가능하고

    Debug.Log(james.hp) 같은 get은 가능해진다.

    이런 식으로 한 곳에 몰아서 처리할 수 있게 되고 캡슐화에 도움이 된다. hp는 protected지만 hp를 다루는 get set은 public HP로 지정해서 하는 방식이다.


    인자의 자료형, 인자의 수 등으로 구별해서 같은 이름으로 여러 개의 메소드를 만들 수 있다. 이를 오버로드(과적)라고 한다.

    위를 예로 들면 Attack(10)은 10의 데미지로 공격, Attack(철수)는 철수를 공격, Attack(10, 20)은 10을 딜해서 20으로 만듬

    만약 힐을 하는 메소드가 있는데 위자드는 20을 힐하고 기본 캐릭터는 10을 힐 한다고하면 오버라이드 기능을 이용해 볼 수 있다. 자식이 부모의 같은 이름의 메소드를 덮어쓰는 기능이다. 오버라이드될 부모의 메소드에 virtual을 넣고 오버라이드할 자식의 메소드에 override를 입력하고 wizard의 인스턴스인 SJ로 SJ.Heal을 한다면 부모에 10힐을 한다는 Heal메소드가 있어도 20힐을 하게 된다.

    base.Heal은 만약 부모의 Heal메소드를 이용하고 싶을 때 사용하는 코드다.

    추상메소드와 추상클래스라는 개념이 있다. Fly를 부모에 선언만해놓고 구현은 하지 않는다. 그리고 자식메소드들이 Fly를 오버라이드해서 실제 구현을 하는 방식이다. Fly를 추상메소드라고 하고 Animal을 추상클래스라고 한다. 추상메소드와 추상클래스에는 Abstract를 붙여야 한다. 이를 이용하면 좀더 깔끔하게 객체를 정리할 수 있다. 참고로 자식에는 override를 입력해야 함.(부모에 virtual을 추가로 입력하지는 않음)

     

    부모클래스는 하나이하여야 하지만 인터페이스는 여러개 가질수 있다.

    인터페이스는

    그림과 같이 마치 함수만 가지는 마치 추상클래스 비스무리한 것이다. 구현이 안된 함수로만 이루어져야 한다.

    만약 TransFormer가 Car를 부모클래스로 가지고 Human과 Monster를 인터페이스로 받는다면

    public class TransFormer : Car, Human, Monster 이렇게 적고 만약 Car가 추상클래스였다면 추상메소드를 구현하고, 인터페이스에 들어있는 함수들을 다 구현해야 한다.

Designed by Tistory.