스터디를 진행하며 실시한 모의 면접을 정리한 내용입니다.
겹치는 내용을 고려하지 않고 면접을 실시하기 때문에 일자 별로 겹치는 내용이 많을 수 있습니다.

1. (C#) reflection이란?

더보기

런타임에 객체의 정보를 파악하기 위해 사용되는 기술입니다. 리플렉션을 활용하면 런타임에 객체, 필드, 메서드 등의 정보를 얻을 수 있습니다. 

2. (C#) 델리게이트란?

더보기

델리게이트란 메서드를 더욱 유연하게 사용할 수 있도록 도와주는 문법입니다. 델리게이트에 함수를 바인딩하여 사용하면 call back을 편하고 안전하게 구현할 수 있으며, 동시에 호출되어야 하는 여러 메서드를 하나로 묶어 관리할 수 있게 됩니다.

3. (유니티) update, fixedupdate, lateupdate에 대해 설명하라.

더보기

Update와 LateUpdate는 매 프레임마다 호출되는 함수입니다. 오브젝트가 매 프레임마다 수행해야 하는 작업이 있다면, Update혹은 LateUpdate 내부에 코드를 작성하여 처리할 수 있습니다. Update와 lateUpdate 모두 매 프레임마다 호출되지만, LateUpdate 함수는 모든 오브젝트의 Update 함수가 호출된 이후에 호출됩니다. 모든 객체에 대해 Update 가 이루어진 이후에 추가적으로 처리해야 할 작업이 있다면, LateUpdate에서 처리할 수 있습니다.

 

FixedUpdate는 프레임마다 호출되는 함수가 아니라 일정 주기마다 호출되는 함수입니다. Update와 LateUpdate는 호출 간격이 들쑥날쑥한 반면, FixedUpdate는 정해진 주기마다 호출되기 때문에 항상 일정한 호출 간격을 보장받습니다. 이러한 특징으로 인해 물리 연산을 주로 FixedUpdate에서 처리합니다.

4. (유니티) prefab 이란?

더보기

씬에서 사용될 게임 오브젝트를 미리 정의하여 파일의 형태로 보관하는 것을 프리팹이라고 합니다. 해당 게임 오브젝트가 보유할 컴포넌트, 트랜스폼 정보 등을 정의하여 파일의 형태로 저장한 뒤, 실제 씬에 오브젝트가 생성될 때, 해당 파일의 값을 참조하여 오브젝트를 생성하게 됩니다. 원본이 변하지 않고 항상 동일한 상태를 유지하기 때문에 생성되는 오브젝트들의 통일성을 보장할 수 있습니다.

5. (C#) 박싱과 언박싱에 대해 설명하라. 이를 대체할 수 있는 문법이 있는가?

더보기

boxing은 데이터를 object타입 혹은 참조 형식으로 변환하는 것을 의미하며, unboxing은 변환된 것을 원래의 상태로 되돌리는 것을 의미합니다. 하나의 클래스가 다양한 자료형에 대응하기 위해선, 모든 자료형이 공통으로 상속받고 있는 object 클래스를 이용해야 합니다. 이를 위해, 데이터를 object 클래스의 참조형으로 변환하여 통일성있게 저장하고, 실제로 사용할 땐 다시 원본의 상태로 되돌려 값을 사용하게 됩니다. 하지만, boxing과 unboxing은 그 횟수가 많아질수록 성능에 큰 부담을 주게 됩니다. 이를 완화하기 위해 generic이라는 문법을 사용할 수 있습니다. boxing, unboxing은 런타임에 실행되는 반면, generic은 대부분의 연산이 컴파일 타임에 처리되기 때문에 성능 부분에서 큰 향상을 기대할 수 있습니다.

6. (C#) LINQ에 대해 설명하라.

더보기

C#언어에 포함되어 있는 쿼리 기능입니다. LINQ는 다량의 데이터를 편하고 안전하게 사용할 수 있도록 도와줍니다. LINQ는 C#에서 제공하는 컬렉션에만 사용가능한 것이 아니라, 프로세스 외부의 데이터베이스나 XML 등에도 편리하게 사용할 수 있습니다.

7. (C#) ref와 out 키워드에 대해 설명하라.

더보기

ref와 out은 모두 참조 전달에 사용되는 키워드입니다. ref의 경우 특정 변수를 참조하도록 하는 키워드입니다. ref로 선언된 변수는 선언과 동시에 가리키는 대상이 정해져야 하며, 가리키는 대상은 반드시 초기화가 되어있어야 합니다.

 

 out 키워드는 참조 전달 중에서도 함수 내부에서 값을 저장해주는 역할을 위해 사용되는 키워드입니다. out의 경우 가리키는 대상이 초기화가 되어 있지 않아도 되지만, 함수 내부에서는 반드시 값을 저장해주어야 합니다.

 

ref를 이용해 out을 모두 대체할 수는 있지만, 구분하여 사용함으로써 변수가 어떻게 사용되는 지를 더욱 명확히 할 수 있습니다.   

8. (C#) interface에 대해 설명하라.

더보기

여러 클래스 간에 공통의 인터페이스를 정의하기 위해 사용되는 문법입니다. 추상 클래스와 유사하지만, 모든 함수가 abstract로 선언되어야 하며 멤버 변수 및 부모 클래스를 가질 수 없다는 차이점이 있습니다. 이러한 차이는 인터페이스가 다중 상속을 통해 사용된다는 특징으로 인해 발생합니다.

 

다중 상속은 다이아몬드 문제 등 여러 문제를 야기할 수 있기 때문에 C#에선 다중상속을 막아놓았지만, 인터페이스는 다중 상속을 통해 구현되는 기능이기 때문에 interface로 선언된 클래스에만 다중 상속이 허락되어 있습니다. 상술했던 추상 클래스와 인터페이스의 차이점은 다중상속으로 인한 문제점을 방지하기 위해 언어 차원에서 제약을 걸어둔 것이라고 할 수 있습니다.

9. (C#) colider와 rigidbody에 대해 설명하라.

더보기

Colider는 충돌을 탐지하기 위해 사용되는 컴포넌트입니다. 각 오브젝트 간의 충돌 검사를 위해선 Colider 컴포넌트가 필수적으로 필요합니다.

 

RigidBody는 대상에 다양한 물리를 적용하기 위해 사용되는 컴포넌트입니다. 중력, 마찰력 등의 물리법칙을 간편하게 오브젝트에 적용할 수 있게 됩니다. 

 

만약, 주먹에 맞은 대상이 후방으로 밀려나는 효과를 구현하고 싶다면, Colider를 사용해 주먹과 대상의 충돌을 검사한 뒤, RigidBody를 통해 일정 힘을 가함으로써 대상을 밀려나도록 할 수 있습니다.

10. (유니티) 레이캐스팅이란 무엇인가? 사용한다면 어디에 사용할 수 있는지 예시가 있는가?

더보기

레이캐스팅이란,  특정 지점에서 광선을 발사하여 경로에 존재하는 오브젝트를 검출하는 충돌 검사 기법입니다. 3D 공간에 있는 물체를 클릭하는 기능을 구현하고 싶을 때, 2D 공간에 투영되어 있는 마우스 좌표를 3D 월드 좌표로 변환한 뒤 레이캐스팅을 사용하여 경로에 있는 물체를 탐지하여 상호작용이 발생하도록 구현할 수 있습니다. 뿐만 아니라, 총을 발사하는 것을 레이캐스팅을 사용해 구현할 수도 있으며 전방의 벽을 탐지하기 위해서도 사용할 수 있습니다. 

11. (유니티) 모노비헤이비어란?

더보기

객체와 유니티 엔진을 결합하기 위해 상속해야 하는 클래스입니다. 모노비헤이비어 클래스를 상속받음으로써 클래스와 에디터를 연동할 수 있으며, 객체의 생명주기가 유니티 엔진에 의해 관리되도록 할 수 있습니다. 또한, 클래스 내에서 유니티 엔진의 프레임워크를 사용할 수 있게 되면서 편리하고 유연한 개발이 가능해집니다.

12. (C#) 코루틴이란 무엇인가?

더보기

함수를 중지하거나 재개하는 기능을 제공해주는 문법입니다. 코루틴에 의해 중지된 함수를 재개하는 것은 일반적인 함수 호출에 비해 빠르기 때문에, 코루틴 함수 내부에 반복문으로 기능을 정의한 후 이를 중지하고 재개하는 방식으로 활용한다면 성능의 향상을 기대할 수 있게 됩니다. 또한, 입력, 응답 등을 기다리는 대기 시간이 발생할 때 코루틴 함수를 재개하여 다른 작업을 처리하도록 한다면 작업 사이의 비는 시간을 최소화하여 성능을 극대화할 수 있게 됩니다.

13. (C#) const와 readonly 키워드에 대해 설명하라. 

더보기

두 키워드 모두 값의 수정을 막는 키워드입니다. const의 경우 대상을 아예 상수화하기 때문에 컴파일 단계에서 값이 정해져야 합니다. 그렇기 때문에 const로 선언된 변수는 선언과 동시에 초기화가 이루어져야 합니다. 런타임에 생성자가 호출된 이후 값이 정해지는 커스텀 클래스의 경우 const 키워드를 사용할 수 없으며, 멤버 변수로 선언된 const 변수는 모든 인스턴스가 같은 값을 공유하게 된다는 특징이 있습니다.

 

반면, readonly 키워드의 경우 힙영역에 동적으로 메모리를 할당하여 값을 저장하며, 생성자에 한해 런타임에 값이 변경되는 것이 허락됩니다. 이러한 특징으로 인해, 모든 자료형에 대해 readonly 키워드를 사용할 수 있으며 인스턴스 별로 다른 값을 보유할 수 있게 됩니다. 하지만, 런타임에 힙영역을 참조하는 이유로 const 변수에 비해 낮은 성능을 보유하게 된다는 단점이 있습니다. 

14. (C#) event란 무엇인가?

더보기

델리게이트에 제약이 추가된 특수한 형태입니다. 발행 구독 패턴을 사용해 특정 상황에 실행되어야 하는 여러 함수들을 관리할 때 유용하게 사용할 수 있습니다.

 

event는 일반적인 델리게이트와 달리 대입 연산이 불가능하고 증감연산만 가능하며, event를 소유한 객체 외부에서 바인딩된 함수를 호출할 수 없다는 특징이 있습니다.

15. (유니티) mono와 il2cpp에 대해 설명하라.

더보기

유니티에서 제공하는 스크립팅 백엔드입니다. 모노의 경우 런타임에 코드를 기계어로 변환하기 때문에 컴파일 타임이 il2cpp에 비해 빠르지만, 런타임 퍼포먼스가 il2cpp에 비해 저조하다는 단점이 있습니다.

 

반면, il2cpp는 빌드 과정에서 기계어 번역을 마치기 때문에 컴파일 타임이 느리다는 단점이 있지만, 모노에 비해 런타임 퍼포먼스가 우수하다는 장점이 있습니다.

 

프로젝트에 있어 효율성, 개발 속도 등에서 이점을 챙기고자 한다면 모노를 사용하는 것이 유리하며 고성능 퍼포먼스를 노리고자 한다면 il2cpp를 사용하는 것이 좋습니다.

16. (C#) 기본 자료형(int, float)등에 대해 null을 사용할 수 있는가?

더보기

일반적으로는 null을 대입할 수 없지만, Nullable 객체를 활용하면 null을 대입할 수 있게 됩니다. 

C#에는 값을 참조형으로 사용하기 위한 두가지의 키워드를 제공해준다.

바로, ref와 out이다.

더보기

참조 전달이란, 값을 그대로 전달하는 것이 아니라 값이 저장된 메모리 영역이 어디인지를 전달하는 것이다.

 

값을 그대로 전달하게 되면, 함수 내부에서 그 값을 사용하기 위해 복사가 발생하게 된다. 하지만, 메모리 영역의 위만 전달하게 된다면 함수 내부에선 값을 복사하지 않고도 위치를 참조해 변수에 직접 접근할 수 있게 된다.

 

이해가 안된다면, call by value와 call by reference에 대해 검색해보자.

두 키워드는 무슨 차이가 있을까? 사실 근본적으로는 거의 비슷하다. 역할도 기능도 방식도 그렇다.

하지만, 차이점은 몇 가지 존재한다.

 

1. ref 키워드가 사용된 변수는 가리키는 대상이 초기화가 되어있어야 하지만, out은 초기화가 되어 있지 않아도 된다.

void Func()
{
    int A = 3;
    int B;
    
    //오류X
    Add_5(ref A);
    
    //오류 발생
    Add_5(ref B); 
}

void Add_5(ref int input)
{
    input += 5;
}

Add_5 함수에 값을 참조전달하기 위해 ref키워드를 사용하게 되면 B의 경우엔 오류가 발생한다.

이유는 대상이 초기화가 되어 있지 않기 때문이다.

하지만, Add_5의 파라미터를 out int 로 변경하여 사용한다면 또 다른 에러가 발생할 것이다. 이유는 아래 조건 때문이다.

 

2. out 키워드가 사용된 변수는 함수 내부에서 사용되기 전에 초기화가 반드시 이루어져야 한다.

void Func()
{
    int A = 3;
    int B;

    //오류X
    Set_5(out A);

    //오류X
    Set_5(out B);
}

void Add_5(out int input)
{
    //오류 발생
    input += 5;
}

void Set_5(out int input)
{
    input = 5;
}

위의 함수를 보면, Add_5함수는 값에 5를 더해주는 형식으로 사용하고 있다.

그런데, 사용할 변수의 값이 초기화가 안되어있기 때문에 값을 더하는 연산은 의도하지 않은 결과를 일으킬 수 있다.

이러한 위험성을 제거하기 위해 out키워드를 사용해 전달된 값은 반드시 내부에서 초기화를 해주어야 한다.

 

Set_5 함수의 경우 내부에서 값을 초기화해주고 있기 때문에, 에러가 발생하지 않는다.

이처럼 out키워드를 사용하여 참조 전달을 할 때엔, 대상을 초기화하지 않은 상태로 전달해도 되지만 그 내부에서는 우선적으로 초기화가 이루어져야만 한다.

 

3. out 키워드는 함수의 파라미터에만 사용이 가능하지만, ref는 그 외에도 사용할 수 있다.

void Func()
{
    //오류X
    int A = 3;
    ref int B = ref A;

    //오류 발생
    int C = 5;
    out int D = out C; 
}

이런걸 쓸 일이야 거의 없겠지만, 위처럼 ref는 같은 스코프 내에서 변수를 선언해서 사용할 수도 있다.

또한, C# 버전에 따라 멤버 변수에도 사용할 수가 있다.

 

반면, out키워드는 위와 같은 사용이 불가능하다.

 

ref와 out을 구분하는 이유

위의 차이를 생각해보면 사실 out키워드를 사용할 필요는 딱히 없어보인다.

ref 키워드로 out 키워드를 완전히 대체할 수 있기 때문이다.

 

그럼에도 불구하고 out 키워드가 존재하는 이유는 뭘까?

바로 그 의미를 확실하게 하기 위함이다.

 

참조 전달(call by reference)를 사용하는 이유는 크게 2가지가 있다.

 

1. 외부 변수의 값을 사용하기 위해 (읽기)

2. 외부 변수의 값을 변경하기 위해 (쓰기)

 

1번의 경우 간단하다. 외부에 있는 어떠한 변수의 값을 함수 내부에서 사용하고 싶은데 값형으로 전달하게 되면 복사가 발생하기 때문에 참조형을 통해 불필요한 복사를 막기 위해 참조 전달을 사용하는 것이다.

 

예를 들어 원소가 10만개인 배열을 사용할 때, 값형으로 전달하게 되면 10만번의 복사가 발생하지만, 참조전달을 하게 되면 이러한 복사가 발생하지 않기 떄문이다.

 

즉 성능 측면에서 큰 이득이 있기 때문이다.

 

2번의 경우는 어떨까? 이 역시도 1번과 동일하게 복사를 줄이고 성능을 향상시키기 위함이 크다.

 

만약 함수 내부에서 연산 결과 값을 배열에 저장하여 10만개의 원소를 보유하게 되었다고 했을 때, 이 배열을 값형으로 반환하게 되면 저장하는 과정에서 복사가 발생하게 될 것이다. 반면, 빈 배열을 참조형으로 전달한 뒤에 함수 내부에서 원소를 삽입하게 되면 불필요한 복사가 발생하지 않게 될 것이다.

 

결국 참조 전달은 성능의 향상을 위해 사용하는 것이 가장 큰 목적인 것이다.

그런데 위에서 말했듯이, ref 키워드만 있어도 두 이유를 모두 만족시킬 수 있다.

 

하지만, 코드를 좀 쳐본 사람이라면 알겠지만 프로그래밍에 있어서 가독성은 아주아주 중요한 역할을 한다. 이 변수가 어떻게 사용될 지 함수의 선언만 보고도 어느정도 유추할 수 있게 설계하는 것은 아주 중요한 일이다. out 키워드는 이를 확실히 하기 위해 사용된다.

 

함수의 파라미터가 out으로 선언되어 있다면, 우리는 그 키워드만 보고서 "아, 변수를 넣어주면 함수 내부에서 그 안에 값을 넣어주겠구나" 라는 예측이 가능하다는 것이다.

 

ref키워드만 모두 사용하게 된다면, 이를 구분할 수가 없을 것이다.

 

즉, 변수가 어떻게 사용되는지 그 의미를 확실하게 알려주기 위해 사용되는 키워드인 것이다.

하지만, out 키워드를 사용했는데 내부에서 값을 저장해주지 않는다면? 

 

외부에서 그 함수를 호출해 변수를 전달한 사람은 변수에 올바른 값이 저장되어 있을 것이라고 생각했겠지만, 당연히 알 수 없는 값이 들어있을 것이고 이는 의도치 않은 오류를 야기시킬 것이다.

 

즉, 가독성을 높이려다가 안정성을 엄청나게 해치는 꼴이 되어버릴 수 있다는 것이다. 이러한 실수를 방지하기 위해, out키워드를 사용해 전달한 변수는 함수 내부에서 반드시 초기화가 이루어지도록 강제되고 있다. 언어 차원에서 프로그래머의 실수를 막아주는 것이다.

 

즉, out은 함수 내부에서 값을 저장해주는 상황에 사용하는 키워드이며 ref는 내부에서 값을 사용할 때 사용카는 키워드라고 생각하면 좋다. (물론 ref를 사용해도 내부에서 값을 저장할 수 있다. 하지만, out과 그 역할을 구분하여 사용하는 편이 좋다.)

'C# > C#' 카테고리의 다른 글

C# - LINQ (Language Integrated Query)  (0) 2024.09.02
C# - const, readonly  (0) 2024.08.10
C# - is, as 연산자  (0) 2024.08.07
C# - 클래스, 접근 제한 지정자, 프로퍼티  (0) 2024.07.27
C# - C#의 자료형  (0) 2024.07.24

C#에는 값을 상수화하는 방법으로 2개의 키워드를 제공해준다.

바로 const와 readonly이다.

(상수화 : 변수에 저장되어 있는 값을 바꿀 수 없도록 하는 것)

 

그렇다면, 두 키워드에는 무슨 차이가 있을까?

가장 큰 차이는 const로 선언된 변수는 컴파일 타임에 값이 결정되고, readonly는 런타임에 결정된다는 것이다.

 

const 변수는 컴파일 타임에 값이 정해져야 하기 때문에 선언을 할 때 반드시 값을 초기화해야 한다.

//1
const int A;
A = 3;

//2
const int A = 3;

A라는 변수에 3이라는 값을 할당하는 방법은 위처럼 2가지가 있지만, const 변수에게 있어 1번 방식은 허용되지 않는다.

왜냐하면 컴파일 타임에 값을 정할 수 없기 때문이다.

 

이러한 이유로 const가 붙은 필드(멤버 변수)를 보유한 클래스의 경우, 모든 인스턴스가 같은 값을 공유하게 된다.

그렇다면 생각해보자. 모든 인스턴스가 같은 값을 공유할 것이 확실하다면, 굳이 인스턴스별로 메모리를 계속 할당할 필요가 있을까? 당연히 그럴 필요가 없다.

 

그래서 const 변수는 static이랑 동일하게 작동하게 된다. 클래스 단위로 1개만 생성되며, 데이터 영역에 위치하게 된다.

 

const가 컴파일 타임에 값을 결정해야 한다는 이유로 발생하는 특징이 하나 더 있다.

바로 커스텀 클래스에는 const를 사용할 수 없다는 것이다. 

왜냐하면, 클래스의 경우 생성자가 호출되어야만 그 값이 확정되기 때문에 컴파일 타임에는 정확한 값을 결정할 수가 없기 때문이다.

 

이번엔 readonly에 대해 알아보자.

 

readonly는 const와 다르게 생성자에서 한 번 초기화하는 것이 가능하다. 생성자가 아닌 곳에서는 불가능하다.

이러한 특성 때문에, 생성자 파라미터를 활용하면 인스턴스 별로 다른 값을 가지게 하는 것이 가능하다.

 

하지만, 생성자에서만 초기화 된다는 이유 때문에 한 가지 문제가 생긴다.

예를 들어, 아래 코드를 보자.

public void Function()
{
    readonly int A;
    A = 3;
}

우리는 상수화된 값을 지역 내에서 선언하고 사용하고 싶을 수도 있다.

그런데 readonly는 생성자에서만 값을 초기화하는 것이 가능하다고 했다.

 

그럼 위의 코드는 작동할까? 당연히 작동하지 않는다.

readonly는 이러한 이유로 지역변수로 사용할 수 없고 필드(멤버 변수)로만 사용이 가능하다.

 

readonly는 위에서 언급했던 것처럼 생성자에서 초기화되기 때문에 각 인스턴스가 다른 값을 가질 수도 있다.

이런 이유로 const처럼 static으로 선언되지 않고 일반 변수처럼 선언된다.

인스턴스가 100개라면 100개의 변수가 생성되는 것이다.

 

그렇기 때문에 const보단 더 유연하게 사용할 수 있겠지만, 메모리 사용량은 더 크다는 단점이 생긴다.

또한, readonly의 경우 힙영역에 생성되기 때문에 const변수보다 읽기 연산이 더 느릴 수 있다. 

 

하지만, readonly의 경우 const와 다르게 커스텀 클래스에도 사용이 가능하기 때문에 const보다는 더욱 광범위하게 사용이 가능하다.

 

두 키워드의 차이를 표로 한 눈에 알아보자.

const readonly
컴파일 타임에 값이 결정되어야 한다. 런타임에 값이 결정되어도 된다.
선언과 초기화가 함께 이루어져야 한다. 선언과 초기화를 분리할 수 있다. (초기화는 생성자에서 가능)
커스텀 클래스에는 사용이 불가능하다. 모든 자료형에 대해 사용이 가능하다.
static변수와 같이 프로세스 전체에 1개만 생성된다. 인스턴스의 개수만큼 변수가 생성된다.
데이터 영역에 위치한다. 힙 영역에 위치한다.
필드와 지역 변수에 모두 사용할 수 있다. 필드에만 사용이 가능하다.

 

'C# > C#' 카테고리의 다른 글

C# - LINQ (Language Integrated Query)  (0) 2024.09.02
C# - ref, out  (0) 2024.08.13
C# - is, as 연산자  (0) 2024.08.07
C# - 클래스, 접근 제한 지정자, 프로퍼티  (0) 2024.07.27
C# - C#의 자료형  (0) 2024.07.24

코드를 작성하다 보면, 상속 관계를 상당히 많이 사용하게 된다.

is와 as는 두 클래스간의 상속 관계를 알려줌으로써 더욱 안전하게 상속 관계를 사용할 수 있게 해주는 키워드이다.

 

먼저, is연산자는 인스턴스가 특정 클래스로 캐스팅이 될 수 있는가를 true, false로 알려주는 키워드이다.

using System;
using Test;

namespace Test
{
    public class A
    {

    }

    public class B : A
    {

    }

    public class C
    {

    }
}

class MainClass
{
    public static void Main()
    {
        A newA = new A();
        B newB = new B();
        C newC = new C();

        bool AisB = newA is B;
        bool BisA = newB is A;

        bool AisC = newA is C;
        bool BisC = newB is C;
        bool CisA = newC is A;
        bool CisB = newC is B;

        Console.WriteLine(AisA);
        Console.WriteLine(AisB);
        Console.WriteLine(BisA);
        Console.WriteLine(BisB);

        Console.WriteLine();

        Console.WriteLine(AisC);
        Console.WriteLine(BisC);
        Console.WriteLine(CisA);
        Console.WriteLine(CisB);
        
        return 0;
    }
}

 

위의 코드를 보자.

 

B는 A를 상속받고 있기 때문에, 당연히 newB는 A로 업캐스팅이 가능하다.

하지만, A는 B의 부모클래스이기 때문에 처음부터 A로 생성된 인스턴스는 B로 다운캐스팅이 불가능하다.

C는 A,B와 어떠한 상속관계도 아니므로 당연히 서로 캐스팅이 불가능하다.

 

위의 결과를 보면 아래와 같다.

 

B는 A로 업캐스팅이 가능하기 때문에 newB is A 만이 true로 표시된 것을 볼 수 있다.

이처럼 인스턴스가 특정 클래스로 캐스팅이 가능한지 여부를 반환해주는 것이 is 연산자이다.

 

as연산자는 is연산자의 기능과 더불어 실제 캐스팅까지 수행해주는 연산자이다. 아래 코드를 보자.

using System;
using Test;

namespace Test
{
    public class A
    {

    }

    public class B : A
    {

    }
}

class MainClass
{
    public static void Main()
    {
        A newA = new A();
        B newB = new B();

        B CastedB = newA as B;
        A CastedA = newB as A;

        if(CastedB == null)
        {
            Console.WriteLine("CastedB is NULL");
            Console.WriteLine();
        }
        else
        {
            Console.WriteLine("CastedB is not NULL");
            Console.WriteLine();
        }

        if (CastedA == null)
        {
            Console.WriteLine("CastedA is NULL");
            Console.WriteLine();
        }
        else
        {
            Console.WriteLine("CastedA is not NULL");
            Console.WriteLine();
        }
    }
}

 

실행해보면 아래와 같다.

CastedB는 null을 가리키고 있으며, castedA는 null이 아닌 대상을 가리키고 있다.

 

즉, 캐스팅이 가능하다면 캐스팅된 대상을 반환해주며, 캐스팅이 안된다면 null을 반환하는 것이다.

is연산자처럼 캐스팅 여부를 파악할 수 있을 뿐더러 실제 캐스팅까지 실행해주는 연산자인 것이다.

 

'C# > C#' 카테고리의 다른 글

C# - ref, out  (0) 2024.08.13
C# - const, readonly  (0) 2024.08.10
C# - 클래스, 접근 제한 지정자, 프로퍼티  (0) 2024.07.27
C# - C#의 자료형  (0) 2024.07.24
C# - 클래스와 콘솔 출력  (2) 2024.07.24

유니티에서 씬에 사용될 게임 오브젝트를 C# 스크립트 파일로 생성해보면, MonoBehaviour이라는 클래스를 상속받은 채로 클래스가 생성되어 있는 것을 볼 수 있다.

 

이러한 MonoBehaviour 클래스는 왜 존재하는 것이며 왜 상속받고 있는 것일까?

 

MonoBehaviour

유니티 Docs에 따르면, MonoBehaviour클래스의 가장 큰 역할은 에디터와 스크립트 파일을 연결해주는 것이다.

 

C#으로 작성된 클래스를 에디터에서 읽고, 뷰포트에 띄우고, 컴포넌트를 추가하는 등의 작업을 하기 위해선 해당 클래스에 대한 정보를 에디터가 알아야 할 필요가 있는데, 이를 도와주는 것이 MonoBehaviour 클래스인 것이다.

 

즉, 에디터에서 해당 클래스를 어떤 방식으로든 이용하고 싶다면 MonoBehaviour 클래스를 상속받는 것은 선택이 아닌 필수인 것이다.

 

MonoBehaviour의 역할은 물론 그것 하나 뿐만은 아니다. 아래의 사진을 보자.

해당 사진은 유니티에서 제공하는 유니티 엔진 스크립트의 생명주기를 설명하는 사진이다. 유니티 엔진이 매 프레임 어떻게 동작하고 어떤 함수들을 어떤 순서대로 호출하는지를 보여주는 것이다.

 

우리가 작성한 클래스가 위의 생명주기에 맞춰서 동작하기 위해선 그냥 클래스를 냅다 만든다고 되는 것이 아니다. 유니티에서 제공하는 프레임워크를 벗어나서 마음대로 클래스를 만들게 되면, 이는 위의 생명주기를 벗어나 독자적으로 활동하게 된다. 그렇게 되면, 유니티 엔진에서 제공하는 다양한 기능을 활용할 수 없을 뿐더러 안정성도 해치는 일이 된다.

 

하지만, MonoBehaviour를 상속받게 되면 해당 클래스는 유니티 엔진 스크립트의 생명 주기와 결합이 된다. 위의 사진의 사이클 안에서 작동하게 되는 것이다. 즉, MonoBehaviour 클래스는 유니티 엔진의 프레임워크를 강제하는 역할도 하는 것이다.

 

MonoBehaviour 클래스를 상속받는다면 발생하는 이점이 이것 뿐만은 아니다. 위에서 말했던 것처럼 MonoBehaviour를 상속받게 되면, 유니티 엔진의 프레임워크를 강제받게 된다. 즉, 우리는 유니티 엔진에서 안내하는 방식으로 클래스를 설계하고 구성해야 한다는 것이다. 

 

이 것은 어떻게 보면 불편하고 제약적인 행동으로 보일 수도 있지만, 이를 통해서 우리는 엔진의 구체적인 기능을 이해하지 않아도 클래스를 설계할 수 있게 된다. MonoBehaviour로부터 제공받는 Start(), Update() 등의 함수가 엔진 내부에서 정확히 어떻게 동작하는 지를 이해할 필요 없이 어떠한 역할을 하는지만 이해한다면 우리의 의도대로 작동하는 클래스를 설계할 수 있게 된다. 즉, MonoBehaviour를 상속받고 유니티 엔진이 제공하는 프레임워크에 강제받게 됨으로써 우리는 엔진의 구체적인 작동방식을 이해하지 않아도 클래스를 설계할 수 있게 되는 것이다. 

 

정리해보자면 MonoBehaviour의 역할은 크게 3가지로 볼 수 있다.

 

1. C#으로 작성된 클래스와 에디터를 연결해준다.

2. 사용자에게 유니티 엔진의 프레임워크를 강제한다.

3. 엔진을 구체적으로 이해하지 못해도 사용할 수 있도록 도와준다.

'유니티 > 엔진 기초' 카테고리의 다른 글

유니티 기초 - 스크립팅 백엔드 (Mono, il2cpp)  (1) 2024.08.24
스터디를 진행하며 실시한 모의 면접을 정리한 내용입니다.
겹치는 내용을 고려하지 않고 면접을 실시하기 때문에 일자 별로 겹치는 내용이 많을 수 있습니다.

 

1. 얕은 복사와 깊은 복사에 대해 설명하라.

더보기

얕은 복사는 값을 그대로 복사하는 것입니다. 얕은 복사의 경우 연산이 매우 가볍지만 포인터 변수가 가지고 있는 주소값을 그대로 복사하기 때문에 댕글링 포인터 등의 문제가 발생할 수 있다는 문제점이 있습니다. 이러한 위험성을 해결하기 위해 새로운 메모리 영역을 할당하여 포인터 변수가 가리키고 있는 메모리 주소에 저장된 값을 복사하는 것을 깊은 복사라고 합니다.

2. (C#) is와 as 연산자에 대해 설명하라.

더보기

클래스의 캐스팅 성공 여부를 확인하는 연산자입니다. is연산자는 특정 클래스가 다른 클래스로 캐스팅이 가능한지 여부를 파악하여 true, false로 가능 여부를 반환해줍니다.

 

as연산자는 클래스의 캐스팅 여부를 확인하는 동시에 캐스팅이 가능하다면 캐스팅을 함께 진행해줍니다. 즉, as 연산자는 캐스팅 성공시엔 캐스팅된 대상을 반환하며 실패시엔 null을 반환합니다. \

3. (C#) sortedset과 dictionary에 대해 설명하라.  

더보기

두 클래스 모두 C#의 컬렉션에 포함된 클래스입니다. sortedset의 경우 이진 트리 기반의 자료구조입니다. 데이터를 key로만 저장하며 원소가 삽입되거나 삭제될 때마다 정렬 작업을 실행합니다. 

 

반면, dictionary는 해시테이블 기반의 자료구조입니다. 데이터를 해싱된 key와 value를 쌍으로 저장하며, 원소가 삽입되거나 삭제되어도 정렬 작업을 수행하지 않습니다.

 

이러한 특성들로 인해, dictionary가 sortedset에 비해 속도 측면에선 우수한 성능을 보이지만 dictionary는 데이터가 너무 적을 때엔 메모리 낭비가 심할 수 있다는 단점이 있습니다. 

4. (C#) List 자료구조란?

더보기

C#의 컬렉션에 포함된 자료구조이며, generic을 기반으로 반들어진 클래스입니다. List는 일종의 동적 배열이기 때문에, 데이터가 메모리에 연속적으로 위치하고 있으며 배열의 크기가 가변적이라는 특성이 있습니다.

5. (C#) boxing, unboxing이란? 

더보기

박싱이란, 값 형식을 참조 형식으로 변환하는 것을 의미하며 언박싱이란 박싱된 데이터를 원래의 형식으로 돌려놓는 것을 의미합니다. C#의 모든 자료형은 object를 상속받고 있기 때문에, object의 참조 형식을 활용하면 다양한 자료형을 통일성있게 사용이 가능합니다. 하지만, 박싱과 언박싱은 커다란 오버헤드를 유발하는 작업이기 때문에 최적화 측면에선 지양하는 것이 좋습니다.

6. 멀티스레드 프로그래밍의 주의사항은?

더보기

공유되는 자원이 훼손되지 않도록 주의하여 설계해야 합니다. 하나의 프로세스에 속한 여러 스레드는 스택 메모리를 제외한 모든 메모리를 공유하게 됩니다. 여러 스레드가 같은 메모리 영역에 대해 읽고 쓰는 작업을 동시다발적으로 수행하게 된다면, 해당 메모리 영역엔 실제 프로그래머의 의도와 다른 값이 저장될 수 있습니다. 이를 방지하기 위해선 아토믹, 뮤텍스, 세마포어 등을 활용할 수 있습니다. 

7. 직렬화와 역직렬화란? 

더보기

직렬화는 현재 메인 메모리에 저장되어 있는 데이터를 바이트 스트림으로 변환하는 것을 의미하며, 역직렬화는 바이트 스트림으로 저장되어 있는 데이터를 읽어 메인 메모리에 저장하는 것을 의미합니다. 주로 게임의 세이브 데이터나 네트워크 송수신에 사용됩니다.

8. TCP와 UDP에 대해 설명하라.

더보기

TCP와 UDP는 모두 네트워크 통신 프로토콜입니다. TCP는 연결 지향성 프로토콜로, 통신하는 두 대상이 1:1로 연결된 상태로 통신을 하게 됩니다. 3way handshake, 4way handshake를 통해 안전하게 두 대상을 연결하며, 송수신 과정에서 데이터가 훼손되지 않도록 흐름 제어, 혼잡 제어 등의 기법을 추가적으로 활용합니다. 이로 인해, 매우 높은 데이터의 신뢰성을 기대할 수 있지만, 통신 속도가 상대적으로 느리다는 단점이 있습니다.

 

UDP는 비연결 지향성 프로토콜로, 통신하는 대상이 서로 연결되지 않은 상태로 통신하게 됩니다. 이로 인해, 패킷이 수신되는 경로가 일정하지 않아 수신 순서와 송신 순서가 달라질 수 있어 데이터가 훼손될 가능성이 있습니다. 하지만 UDP는 통신에 있어 추가적인 데이터 검증, 보호 절차를 거치지 않기 때문에 매우 빠른 통신 속도를 보인다는 장점이 있습니다.

 

9.  (C++) int 자료형은 몇바이트인가?

더보기

c++ 표준에선 자료형의 크기가 구현체에 따라 달라질 수 있음을 명시하고 있습니다. 즉, int 자료형의 크기는 일정하지 않으며 운영체제 등의 환경에 따라 크기가 달라질 수 있습니다. 그렇기 때문에, 자료형을 사용할 때엔 __int32, __int64등 그 크기가 함께 명시되어 있는 자료형을 사용하는 것이 좋습니다. 

10. (C#) partial 이란?

더보기

하나의 클래스를 여러 파일에서 나누어 정의할 수 있도록 해주는 키워드 입니다. 아주 많은 메서드를 가진 클래스를 하나의 파일에서 모두 정의하는 경우 가독성이 매우 떨어질 수 있기 때문에, 클래스를 여러 조각으로 나누어 정의하고자 할 때 사용하는 키워드입니다.

11. (C#) base 키워드에 대해 설명하라.

더보기

바로 위의 부모 클래스에 접근할 수 있도록 도와주는 키워드 입니다. base 키워드를 사용하면 부모 클래스의 메서드, 프로퍼티 등을 자식 클래스에서 직접적으로 사용할 수 있게 됩니다.

12. (C++) std::function 이란?

더보기

C++에서 함수 포인터, 함수 객체, 람다 함수 등의 callable을 편하고 안전하게 사용할 수 있도록 제공해주는 템플릿 클래스입니다. 일반적인 C스타일의 함수 포인터에 비해 가독성이 좋고 사용성이 매우 편리하고 안전하게 사용할 수 있는 클래스이며, std::bind 함수와 함께 사용하면 파라미터와 함수를 묶어 더욱 편리하고 유연하게 사용할 수 있게 됩니다

13. (C++) NULL과 nullptr의 차이에 대해 설명하라.

더보기

NULL과 nullptr은 모두 값이나 가리키는 대상이 없음을 표현할 때 사용하는 키워드입니다. NULL은 0을 매크로를 사용해 정의한 것으로 정수 0과 완전히 동일합니다. 그렇기 때문에 여러 파라미터에 대해 오버로딩된 함수를 호출하는 경우, 포인터 타입이 아닌 정수값을 파라미터로 받는 함수를 호출할 가능성이 있습니다.

 

이러한 문제를 해결하기 위해 C++에선 포인터임을 명시하는 nullptr이라는 키워드를 제공해줍니다. nullptr은 정수 0이 아닌 포인터 타입으로 간주되기 때문에 포인터 변수를 파라미터로 받는 함수를 정확하게 호출할 수 있게 됩니다.

 

즉, NULL은 값형이지만 nullptr은 참조형이라는 것이 차이라고 할 수 있습니다.

14. (C++ ) 빌드 과정에 대해 설명하라.

더보기

C++ 코드의 빌드는 전처리기, 컴파일, 어셈블리, 링커 이렇게 크게 4개의 단계로 이루어집니다. 전처리기 단계에선 주석을 제거하고 불필요한 공백을 제거한 뒤, 코드에 사용된 매크로를 모두 실제 사용되는 값으로 치환하는 과정을 거칩니다. 또한 포함된 헤더파일의 코드를 모두 복사하여 하나의 파일에 붙여넣어 헤더파일과 C++파일을 하나로 묶게 됩니다.

 

이후에는 컴파일 단계가 실행이 됩니다. 컴파일 단계에선 작성된 코드의 유효성을 검사하여 발생할 수 있는 오류를 사전에 잡아내는 역할을 합니다. 발견된 오류가 없다면 작성된 C++ 코드를 저수준의 어셈블리어로 변환하는 과정을 거치게 됩니다.

 

이후, 어셈블리 단계에선 어셈블리어로 변환된 코드를 실제 하드웨어가 이해할 수 있는 기계어로 변환하여 obj파일을 생성하게 됩니다.

 

마지막으로 실행되는 링커 단계에선 여러 개의 obj 파일과 정적 라이브러리를 하나로 묶어 최종적으로 실행될 수 있는 exe파일을 만들게 됩니다.

15. (C++) string_view란?

더보기

문자열의 불필요한 복사를 줄이기 위해 사용할 수 있는 클래스 입니다. 함수의 파라미터로 std::string을 사용할 경우, 참조형을 사용하더라도 상황에 따라 복사 및 동적 할당이 발생할 수 있습니다. 하지만, string_view는 내부에 const char* 타입의 변수와 문자열의 사이즈 만을 보유하고 있기 때문에 추가적인 동적 할당이 발생하지 않고, 복사 또한 매우 가벼운 연산으로 수행될 수 있습니다. 하지만, 읽기 전용 클래스인 만큼 string에 비해 문자열을 유연하게 사용하는 것이 불가능하므로 적재적소에 사용하는 것이 중요합니다.

16. (C++) this call 이란?

더보기

C++ 함수 호출 규약 중 하나로 멤버 함수를 호출할 때 사용되는 방식입니다. 멤버 함수는 호출될 때, 항상 호출자의 주소를 알고 있어야 하기 때문에, 멤버 함수는 컴파일 과정에서 파라미터의 가장 앞에 클래스의 포인터 형이 추가되고, 이 곳에 호출자의 주소가 대입되는 과정으로 실행됩니다. 함수 내부의 멤버함수 또한, 첫 번째 인자로 받은 포인터를 사용해 호출하는 방식으로 이루어집니다. 이러한 멤버 함수의 호출 방식을 this call이라고 합니다.

17. 프로세스란?

더보기

프로그램이 실행되어 메인 메모리에 위치하게 되면 이를 프로세스라고 부릅니다. 운영체제에서 독립적인 메모리를 할당하는 단위입니다. 

18. (C++) map과 unordered_map의 차이를 설명하라.

더보기

map과 unordered_map은 모두 key와 value를 쌍으로 저장하는 자료구조입니다. map은 완전 이진 트리를 기반으로 설계되어 있으며, 원소가 삽입되거나 삭제될 때마다 정렬을 하며 트리의 균형을 유지합니다. 

 

반면, unordered_map은 해시테이블 기반으로 설계되어 있으며, 원소가 삽입되거나 삭제되더라도 정렬을 수행하지 않습니다.

 

이러한 차이들로 인해, unordered_map을 사용하게 되면 일반적으로 map에 비해 높은 성능을 기대할 수 있지만 커스텀 클래스를 사용할 경우엔 해시 함수를 직접 구현해야 한다는 불편함이 있고, 데이터가 적을 때엔 메모리의 낭비가 심할 수 있다는 단점이 있습니다.

19. (C#) const 와 readonly의 차이에 대해 설명하라.

더보기

const는 컴파일 타임에 값이 상수로 치환되기 때문에, 선언과 동시에 초기화가 되어야 하며 데이터가 스택 영역에 저장됩니다. 반면, readonly는 생성자에서 값을 초기화 할 수 있으며 데이터가 힙 영역에 동적으로 저장됩니다.

 

const는 컴파일 타임에 상수화가 이루어지고 stack 영역에 저장되기 때문에 readonly로 선언된 변수에 비해 성능 부분에서 이점이 있지만, 기본 자료형에 대해서만 사용이 가능하고 값이 수정될 때마다 새로 컴파일을 해야 한다는 불편함이 있습니다.

 

반면 readonly는 런타임에 초기화가 이루어지고 데이터가 힙 메모리에 위치하기 떄문에 성능 상에선 다소 불리함이 있을 수 있지만, 커스텀 클래스에도 사용할 수 있으며 값을 수정하여도 새로 컴파일하지 않는다는 장점이 있습니다.

20.  다형성이란?

더보기

하나의 대상이 다양하게 표현될 수 있는 성질을 의미합니다. 상속관계의 클래스를 업캐스팅, 다운캐스팅 하는 것이 다형성의 대표적인 예시입니다.

21. (C#) sealed 키워드에 대해 설명하라.

더보기

sealed 키워드는 클래스의 파생을 제한하는 키워드입니다. sealed 키워드가 포함되어 정의된 클래스는 다른 클래스의 부모 클래스가 될 수 없으므로 항상 말단에 위치한 클래스임을 보장받습니다.

22. (C++) std::vector에서 at과 []의 차이를 설명하라.

더보기

[]으로 원소에 접근하는 경우, 대상에 빠르게 접근할 수 있지만 범위를 초과한 인덱스를 사용할 경우 예외를 방출하지 않고 프로세스를 종료하기 때문에 안정성이 낮고 디버깅에 불리하다는 단점이 있습니다.

 

반면, at을 사용하는 경우 범위를 초과한 인덱스를 사용할 경우 예외를 방출하기 때문에 디버깅 측면에선 유리하지만 []를 사용해 접근하는 것에 비해 느릴 수 있다는 단점이 있습니다.

23. 컨텍스트 스위칭이란?

더보기

CPU에서 실행중인 프로세스, 스레드가 다른 프로세스, 스레드로 교체되는 것을 의미합니다. 잦은 컨텍스트 스위칭은 성능 저하의 원인이 될 수 있으므로 프로그램을 설계할 때엔 컨텍스트 스위칭을 최소화하는 방향으로 설계해야 합니다.

24. 인터럽트란?

더보기

CPU가 작업하고 있는 것을 잠시 중단하고 먼저 처리해야 할 작업을 수행하는 것을 의미합니다. 주로 실행중인 작업에서 예외가 발생하여 이를 처리해야 할 경우에 인터럽트가 발생합니다.

25. (C++) malloc/free와 new/delete의 차이를 설명하라.

더보기

malloc의 경우엔 메모리만 할당해주고 추가적인 작업을 수행하지 않지만, new는 메모리 할당 뿐만 아니라 메모리 영역에 생성자 호출하여 값을 초기화 하는 작업과 메모리 영역의 포인터를 자료형에 맞게 타입 캐스팅하는 작업까지 이루어집니다.

 

이러한 과정의 차이로 인해, malloc는 반환형이 void*이며 생성자를 호출할 수 없지만 new의 경우 반환형이 자료형에 맞는 포인터 타입이며 생성자를 호출할 수 있습니다. 하지만, malloc은 자료형에 영향을 받지 않는 만큼 메모리 크기를 자유롭게 할당할 수 있지만 new의 경우엔 자료형이 차지하는 메모리 크기의 배수만큼만 할당할 수 있습니다.

26. 람다함수란?

더보기

람다함수란 이름이 없는 함수를 의미합니다. 일반적인 함수는 미리 선언과 정의를 해놓으면 이를 재사용하는 방식으로 이루어지지만 람다 함수는 필요할 때마다 즉석으로 정의하여 사용하는 방식으로 이루어집니다. 

 

람다함수는 스택 메모리에 저장되기 때문에 지역을 벗어나면 메모리에서 사라지지만 일반적인 함수는 프로세스가 끝날 때까지 코드 영역에 위치하기 때문에, 재사용성이 낮은 함수의 경우 람다함수를 사용하는 것이 메모리 측면에선 이점이 있을 수 있습니다.

 

또한, 필요할 때마다 그 자리에 정의하면 되기 때문에 사용성이 좋다는 장점이 있지만, 무분별하게 사용하게 되면 가독성을 심하게 저하할 수 있다는 단점이 있습니다. 

27. 마크 앤 스윕이란?

더보기

가비지 컬렉터 알고리즘의 한 종류입니다. 트리 구조로 연결되어 있는 오브젝트를 순회하여 참조 가능한 오브젝트에 표시를 한 뒤, 다시 모든 메모리를 훑어 표시가 되어 있지 않은 오브젝트를 제거하는 방식으로 이루어집니다. 

28. 배칭과 인스턴싱이란?

더보기

두 기법 모두 드로우 콜을 줄이기 위해 사용할 수 있는 기법입니다. 배칭은 같은 머티리얼을 공유하는 여러 매쉬를 하나로 결합하여 한 번의 드로우 콜로 렌더링을 해결하는 기법입니다.

 

인스턴싱이란 지오메트리 셰이더를 사용하여 동일한 지오메트리를 사용하는 여러 객체를 병렬적으로 렌더링하는 기법입니다. 전쟁 시뮬레이션 게임에서 수많은 병사를 렌더링할 때 인스턴싱 기법을 사용하면 병사의 수 만큼 드로우 콜이 발생하는 것이 아니라 1번의 드로우 콜로 렌더링을 해결할 수 있게 됩니다.

29. 메모리 단편화란?

더보기

메모리가 효율적으로 사용되지 못하는 상황을 의미합니다. 메모리 단편화는 내부 단편화와 외부 단편화로 분류됩니다.

 

내부 단편화란, 필요한 메모리보다 더 많은 메모리를 할당하여 메모리가 낭비되는 상황을 의미합니다. 외부 단편화란, 메모리의 할당과 해제가 반복되면서 할당된 메모리들 사이에 작은 틈이 발생하는 것을 의미합니다. 이러한 경우 실제 메인 메모리에 물리적 공간이 충분함에도 불구하고 연속적으로 존재하지 않기 때문에 메모리를 할당할 수 없는 상황이 발생할 수 있습니다.

30. (C#) generic 이란?

더보기

어떠한 클래스나 메서드 등을 선언할 때, 다양한 자료형에 대응하기 위해선 일반적으로 자료형에 따라 여러 개를 선언하여야 합니다. 이러한 불편함을 해결하기 위해 C#에서 지원해주는 키워드가 generic입니다. generic을 사용하게 되면 하나의 클래스나 메서드가 여러 자료형에 대해 대응할 수 있게 되며 코드의 재사용성이 매우 높아집니다.

이번엔 전투의 일부를 구현하였다.

 

아래는 전투가 이루어지는 레벨의 코드 전체이다.

더보기
using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class FightLevel : GameLevel
    {
        public override void Update()
        {
            Console.Clear();
            
            if(SelectedMonster == null)
            {
                SelectMonster();
            }
            else
            {
                Fight();
            }
        }

        public FightLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;
            _playerLog = null;
            _monsterLog = null;
        }

        public void Fight()
        {
            if(_selectedMonster == null)
            {
                return;
            }

            DevFunctions.WriteLineColored("플레이어 정보", ConsoleColor.Blue);
            Console.WriteLine("HP : {0} / {1}", _parentCore.CurrentPlayer.CurrentHP, _parentCore.CurrentPlayer.MaxHP);
            Console.WriteLine();

            DevFunctions.WriteLineColored("몬스터 정보", ConsoleColor.Red);
            Console.WriteLine("이름 : {0}", _selectedMonster.Name);
            Console.WriteLine("HP : {0} / {1}", _selectedMonster.CurrentHP, _selectedMonster.MaxHP);
            Console.WriteLine();

            if (_selectedMonster.CurrentHP <= 0)
            {
                Win();   
            }
            else if(_parentCore.CurrentPlayer.CurrentHP <= 0)
            {
                Lose();   
            }
            else
            {
                WriteFightLog();
                SelectBehavior();
                MonsterBehavior();
            }
        }

        private void Win()
        {
            Console.WriteLine("전투에서 승리하였습니다!");

            _parentCore.CurrentPlayer.CurrentEXP += _selectedMonster.WinEXP;
            _parentCore.CurrentPlayer.Money += _selectedMonster.WinMoney;

            Console.WriteLine("아무 키나 입력시 메뉴 선택창으로 돌아갑니다.");

            Console.Read();

            _selectedMonster = null;
            _monsterLog = null;
            _playerLog = null;

            _parentCore.CurrentLevel = LevelType.Menu;
        }
        private void Lose()
        {
            Console.WriteLine("전투에서 패배하였습니다..");
            Console.WriteLine("아무 키나 입력시 메뉴 선택창으로 돌아갑니다.");

            Console.Read();

            _selectedMonster = null;
            _monsterLog = null;
            _playerLog = null;

            _parentCore.CurrentPlayer.CurrentHP = 1;
            _parentCore.CurrentLevel = LevelType.Menu;
        }


        private void WriteFightLog()
        {
            DevFunctions.WriteLineColored("나의 행동", ConsoleColor.Blue);
            
            if(_playerLog != null)
            {
                Console.WriteLine("{0}", _playerLog);
                Console.WriteLine();
            }

            DevFunctions.WriteLineColored("적의 행동", ConsoleColor.Red);
            if(_monsterLog != null)
            {
                Console.WriteLine("{0}", _monsterLog);
            }

            Console.WriteLine();
        }

        private void SelectBehavior()
        {
            Console.WriteLine("1. 공격한다.\n2. 아이템을 사용한다.\n3. 도망친다.");

            string input = Console.ReadLine();

            if (DevFunctions.IsNumeric(input) == false)
            {
                return;
            }

            if (input.Length > 1)
            {
                return;
            }

            int toInt = int.Parse(input);

            switch (toInt)
            {
                case 1:
                    PlayerAttack();
                    break;
            }
        }

        private void PlayerAttack()
        {
            int Damage = GameFunctions.GetCalculatedDamage(_parentCore.CurrentPlayer, _selectedMonster);
            bool isCritical = GameFunctions.isCritical(_parentCore.CurrentPlayer);

            string CriticalMsg = "";

            if(isCritical == true) 
            {
                Damage = (int)(Damage * _parentCore.CurrentPlayer.CriticalPower);
                CriticalMsg = "[크리티컬!] ";
            }

            _playerLog = "적에게 공격 : " + CriticalMsg + Damage.ToString() + "의 피해를 입혔습니다.";

            _selectedMonster.CurrentHP -= Damage;
        }
        private void MonsterBehavior()
        {
            if(_selectedMonster.CurrentHP <= 0)
            {
                _monsterLog = "사망하였습니다.";
                return;
            }

            int Damage = GameFunctions.GetCalculatedDamage(_selectedMonster, _parentCore.CurrentPlayer);
            bool isCritical = GameFunctions.isCritical(_selectedMonster);

            string CriticalMsg = "";

            if (isCritical == true)
            {
                Damage = (int)(Damage * _selectedMonster.CriticalPower);
                CriticalMsg = "[크리티컬!] ";
            }

            _monsterLog = "플레이어에게 공격 : " + CriticalMsg + Damage.ToString() + "의 피해를 입혔습니다.";

            _parentCore.CurrentPlayer.CurrentHP -= Damage;
        }

        private void SelectMonster()
        {
            DevFunctions.WriteLineColored("전투하고 싶은 몬스터를 선택하세요.", ConsoleColor.DarkCyan);
            Console.WriteLine();

            Console.WriteLine("1. 고블린 (적정 레벨 : 1)");
            Console.WriteLine("2. 오우거 (적정 레벨 : 5)");
            Console.WriteLine("3. 드래곤 (적정 레벨 : 10)");
            Console.WriteLine("4. 이전으로 돌아간다.");

            string input = Console.ReadLine();

            if (DevFunctions.IsNumeric(input) == false)
            {
                return;
            }

            if (input.Length > 1)
            {
                return;
            }

            int toInt = int.Parse(input);

            switch (toInt)
            {
                case 1:
                    _selectedMonster = new Goblin();
                    break;
                case 4:
                    _parentCore.CurrentLevel = LevelType.Menu;
                    _selectedMonster = null;
                    break;
            }
        }

        public static Monster SelectedMonster
        {
            get { return _selectedMonster; }
            set { _selectedMonster = value; }
        }

        private static Monster _selectedMonster;
        private static string _playerLog;
        private static string _monsterLog;
    }
}

현재는 공격에 대한 기능만 구현이 되어있고, 아이템 사용은 아직 구현하지 않았다.

공격을 실시하면 몬스터의 HP가 깎이고 몬스터도 플레이어에게 즉시 공격을 실시한다.

몬스터의 HP가 0이 되면, Win함수가 호출되고  적의 HP가 0이되면 Lose함수가 호출된다.

 

Win함수가 호출되면, 플레이어는 경험치와 돈을 획득한다.

 

아래는 플레이어 경험치 변수의 set이 호출될 때 작업을 작성한 것이다.

여기서 레벨 업이 진행되도록 하였다.

더보기
public int CurrentEXP
{
    get { return _currentEXP; }
    set 
    {
        _currentEXP = value;

        if (_currentEXP >= _maxEXP)
        {
            LevelUp();
        }
    }
}

private void LevelUp()
{
    _currentEXP -= _maxEXP;
    Console.WriteLine("레벨이 상승하였습니다!");

    MaxEXP = (int)(_maxEXP * 1.1f);

    _level++;

    _attPower += 10;
    _defPower += 2;
}

레벨이 오르면, 경험치 수치가 조정되고 공격력과 방어력이 일정 수치만큼 오르도록 하였다.

최대 경험치 수치도 동시에 증가한다.

 

아래 코드는 데미지를 계산하는 것과 크리티컬 여부를 판단하는 함수이다.

더보기
public class GameFunctions
{
    public static int GetCalculatedDamage(ICreatureStat attacking, ICreatureStat attacked)
    {
        Random random = new Random();

        int MinAtt = (int)(attacking.AttPower * 0.9f);
        int MaxAtt = (int)(attacking.AttPower * 1.1f);

        int Att = random.Next(MinAtt, MaxAtt);
        int Def = attacked.DefPower;

        float DefRate = Def / 100.0f;
        DefRate = 1.0f - DefRate;

        return (int)(Att * DefRate);
    }

    public static bool isCritical(ICreatureStat attacking)
    {
        Random random = new Random();
        int randomValue = random.Next(0, 100);

        if (randomValue <= attacking.CriticalProb_100)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

 

이 함수를 활용하여 최종 데미지를 산출하였다.

 

아래 동영상은 구현된 결과이다.

 

이렇게 공격을 주고받는 기능은 구현하였다.

다음 번엔 아이템을 사용하는 것과 무기의 특수효과를 적용할 예정이다.

 

그리고 추가적으로 인벤토리 아이템을 출력할 때, 아이템을 이름 순으로 정렬하도록 했는데 정렬 방법이 C++과 조금 달랐다. C++은 비교 연산자를 오버로딩하여 사용하지만 C#은 IComparable 인터페이스를 상속받은 뒤 CompareTo라는 함수를 구현해야 정렬이 가능했다. 

 int IComparable<Item>.CompareTo(Item other)
 {
     if(other.Name.CompareTo(Name) > 0)
     {
         return -1;
     }
     else
     {
         return 1;
     }
 }

 그래서 Item 클래스에 요런 함수를 추가로 작성해주었다.

스터디를 진행하며 실시한 모의 면접을 정리한 내용입니다.
겹치는 내용을 고려하지 않고 면접을 실시하기 때문에 일자 별로 겹치는 내용이 많을 수 있습니다.

1. 메모리 풀링이란?

더보기

런타임에 객체를 계속 생성하고 파괴하는 것이 오버헤드를 유발할 뿐만 아니라 외부 단편화의 원인이 될 수 있기 때문에 이를 방지하기 위해 미리 메모리를 크게 할당해놓고 사용하는 기법을 메모리 풀링이라고 합니다. 

2. 외부단편화란?

더보기

메모리의 할당과 해제가 반복되면 할당된 메모리 사이에 틈이 발생하게 됩니다. 이러한 틈이 많아지면 실제 메모리에 공간이 충분함에도 불구하고 메모리를 할당하지 못하거나 비효율적으로 할당해야 하는 상황이 발생할 수 있습니다. 이를 외부단편화라고 합니다.

3. 가상 메모리란?

더보기

HDD, SDD등의 보조 기억장치의 도움을 받아 메인 메모리의 물리적 용량보다 더 큰 범위의 용량을 사용하는 기법을 가상 메모리라고 합니다.  

4. 사용해본 경험이 있는 디자인 패턴있는가?

더보기

싱글톤 패턴을 사용해보았습니다. 플레이어의 스탯 혹은 보유한 아이템 정보 등을 보관하기 위해선 레벨과 무관하게 존재하는 클래스가 필요했고, 이를 전역 클래스로 구현하였습니다. 하지만, 이 클래스의 인스턴스가 여러개 생성되면 데이터가 안정적으로 보관되지 못할 가능성이 있기 때문에 하나의 인스턴스만 존재함을 보장해야했습니다. 이를 위해 싱글톤 패턴을 활용하였습니다.

5. (C++) 캐스팅 종류는 무엇이 있는가?

더보기

static_cast, dynamic_cast, reinterpret_cast, const_cast가 있습니다. static_cast는 컴파일 타임에 타입 캐스팅이 이루어지기 때문에 성능에 큰 영향을 주지 않는다는 장점이 있지만, 타입 캐스팅의 성공 여부를 반환하지 않아 안전하지 않다는 단점이 있습니다. 반면, dynamic_cast는 런타임에 타입 캐스팅이 이루어집니다. 타입 캐스팅이 실패시 nullptr을 반환하기 때문에 안전하게 사용할 수 있다는 장점이 있지만 성능에 영향을 줄 수 있다는 단점이 있습니다. 

reinterpret_cast는 가장 넓은 범위의 캐스팅을 지원하는 키워드입니다. static_cast는 값을 유지하기 위해 자료형에 따라 비트에 저장된 값을 변경하지만 reinterpret_cast는 비트에 저장된 값을 유지하는 방식의 캐스팅입니다. 다양한 형태의 타입 캐스팅을 지원하지만 자료형에 따라 값을 해석하는 것이 달라지기 때문에 안전하지 못하다는 단점이 있습니다. const_cast는 포인터 변수의 상수성을 일시적으로 제거해주는 타입 캐스팅입니다. 상수화 되어있는 포인터 변수가 다른 대상을 가리키게 하고 싶을 때 사용할 수 있습니다.

6. (C#) ref키워드에 대해 설명하라.

더보기

대상을 참조 타입으로 사용하기 위한 키워드입니다. C#에서 객체는 모두 참조 타입으로 사용되지만 구조체와 string을 제외한 기본 자료형 등은 기본적으로 참조 타입이 아니기 때문에 ref 한정자를 통해 참조 타입으로 사용할 수 있습니다.   

7. (C#) class와 struct의 차이는?

더보기

class는 new를 통해서만 생성할 수 있기 때문에 참조 형식으로만 사용할 수 있습니다. 반면, struct는 new 키워드 없이도 생성할 수 있어 값 타입으로 사용할 수 있습니다. struct는 일반적으로 stack영역에 위치하기 때문에 heap 영역에 위치하는 class보다 성능적인 부분에서 우위에 있지만, class와 달리 상속이 불가능하다는 단점이 있습니다. 

8. 동시성과 병렬성이란?

더보기

여러 작업이 동시에 실행되는 것처럼 보이지만 물리적으로는 동시에 실행되지 않는 것을 동시성이라 하며, 물리적으로 동시에 실행되고 있는 것을 병렬성이라고 합니다.

9. 유저영역과 커널영역이란?

더보기

운영체제에서 사용자가 마음껏 접근할 수 있는 영역을 유저영역이라 하며, 함부로 접근할 수 없도록 제한되어 있는 영역을 커널 영역이라고 합니다. 운영체제는 메모리 관리, 하드웨어 상호작용 등 저수준의 작업을 처리하는 역할을 합니다. 사용자가 이를 함부로 수정하게 되면 기기에 큰 문제를 발생시킬 수 있기 때문에 일부 영역에 대해선 접근할 수 없도록 막아두고 있으며, 이 영역을 커널 영역이라고 합니다. 반면, 문제가 발생하더라도 쉽게 해결할 수 있거나 국소적인 문제만 발생할 수 있는 영역에 대해선 자유로운 접근을 허용하고 있으며 이를 유저 영역이라고 합니다.

11. 외부단편화를 해결하는 방법은 무엇이 있나?

더보기

대표적으로 메모리 풀링 기법이 있습니다. 외부 단편화는 잦은 메모리의 할당과 해제로 인해 발생하기 때문에, 미리 메모리 블록을 할당해놓은 뒤 이를 재사용하는 메모리 풀링 기법을 활용하면 외부 단편화 문제를 최소화할 수 있습니다.

12. 네임스페이스란?

더보기

클래스, 구조체 등을 분류하기 위해 사용하는 문법입니다. 네임스페이스 안에 존재하는 클래스, 구조체 등은 네임스페이스를 통해 사용해야 하기 때문에 역할이나 사용처 등을 쉽게 알아볼 수 있다는 장점이 있습니다. 하지만, 매번 네임 스페이스를 통해 사용하는 것이 다소 불편하기 때문에 using 문법을 활용하여 이를 생략할 수도 있습니다. 

13. (C++) 가상 소멸자를 사용하는 이유는?

더보기

자식 클래스의 소멸자가 호출되는 것을 보장하기 위해 사용합니다. 특정 클래스가 부모 클래스로 업캐스팅되어있는 상태에서 소멸하게 되면, 자식 함수의 소멸자가 호출되지 않을 수 있습니다. 소멸자에서 메모리 해제 등의 중요한 작업이 이루어지는 경우 문제가 발생할 수 있기 때문에 소멸자를 가상화하여 반드시 호출되도록 할 수 있습니다.

14. 캐시 메모리란?

더보기

CPU와 메인 메모리의 작업 처리 속도 차이로 인해, CPU에서 메인 메모리에 접근하는 경우 CPU에 병목 현상이 발생할 수 있습니다. 현대의 컴퓨터는 이러한 병목현상을 완화하기 위해, CPU가 메인 메모리에 접근했을 때 일정량의 데이터를 캐시 메모리로 복사한 뒤 CPU가 캐시 메모리의 데이터를 먼저 탐색하도록 설계되어 있습니다. 캐시 메모리는 메인 메모리에 비해 매우 빠른 작업 처리 속도를 보유하고 있기 때문에 높은 성능 향상을 기대할 수 있습니다.

15. 캐시 적중률이란?

더보기

원하는 데이터가 캐시 메모리에서 발견될 확률을 캐시 적중률이라고 합니다. 캐시 메모리에 원하는 데이터가 없을 경우 CPU는 메인 메모리에 접근하여 데이터를 다시 캐시로 옮겨오는 작업을 실행하게 됩니다. 원하는 데이터를 캐시 메모리에서 많이 발견할 수 있다면 CPU가 메인 메모리에 접근하는 횟수가 적어지기 때문에 캐시 적중률이 높을수록 높은 성능을 기대할 수 있습니다.

16. (C++) 접근 제한 지정자란 무엇인가? 종류는 무엇이 있는가?

더보기

접근 제한 지정자란, 외부의 접근을 허용하는 정도를 설정하는 키워드 입니다. 접근 제한 지정자는 public, protected, private 총 3개의 종류가 있습니다. public는 모든 곳에서 접근을 허용하는 키워드이며, protected는 상속 관계에서만 접근을 허용하는 키워드입니다. private는 어느 곳에서도 접근을 허용하지 않는 키워드입니다.

17. 초기화 리스트와 리터럴 초기화의 차이는?

더보기

리터럴 초기화는 변수가 생성되는 동시에 값을 초기화 하지만, 초기화 리스트는 생성자가 호출될 때 초기화를 실행하기 때문에 리터럴 초기화보다 늦은 시기에 실행되게 됩니다.

18. TCP와 UDP의 차이를 설명하라.

더보기

TCP는 연결 지향형 통신 프로토콜입니다. 소켓을 이용해 서로를 1:1로 연결하여 통신하기 때문에 안전하게 통신할 수 있습니다. 하지만, 안정성을 위해 패킷 송수신에 부가적인 작업을 많이 하는 만큼 반응성은 다소 느리다는 단점이 있습니다.

 

UDP는 비연결 지향형 통신입니다. 서로 연결되어 있지 않고 데이터 검증, 보호 등의 작업을 수행하지 않기 때문에 데이터의 신뢰성이 TCP에 비해 낮다는 단점이 있지만 매우 빠르게 통신할 수 있다는 장점이 있습니다.

 

이러한 특징으로 인해 TCP는 파일의 전송 등 데이터의 신뢰성이 중요한 작업에 자주 이용되며, UDP는 실시간 게임이나 동영상 스트리밍처럼 데이터의 신뢰성보다 속도에 중점을 둬야하는 경우 자주 사용하게 됩니다.

19. (C#) abstract와 interface에 대해 설명하라.

더보기

abstract는 대상을 추상화하는 키워드입니다. abstract 키워드에 의해 추상화 된 대상은 독립적으로 존재할 수 없게 되므로 자식 클래스에서 대상을 구체화해야만 정상적으로 사용이 가능합니다.

interface는 추상화된 객체에 몇가지 제약을 더해 외부에서 편하고 안전하고 기능을 사용할 수 있도록 도와주는 키워드입니다. interface는 C#에서 유일하게 다중상속이 허락된 객체이지만, 다중상속의 위험성을 방지하기 위해 부모 클래스는 가지는 것이 불가능하고 멤버 변수를 가질 수 없습니다. 또한, 내부에 선언된 메서드는 모두 자식 클래스에서 재정의되어야만 합니다. 

20. 객체지향의 4가지 특징에 대해 설명하라.

더보기

객체 지향의 4가지 특징은 상속, 캡슐화, 추상화, 다형성입니다. 상속은 한 클래스 내부에서 선언, 정의한 것을 다른 클래스가 물려받는 것을 의미합니다. 이는 객체의 성질을 표현하는 것에도 도움이 되지만 코드의 재사용성을 높이는 것에도 도움이 됩니다.

 

캡슐화는 연관이 있는 특성을 묶어 내부에서 구현한 뒤, 구체적인 구현부는 외부에 공개하지 않고 기능 사용에 관련된 인터페이스만 공개하는 것을 의미합니다. 캡슐화가 잘 된 객체의 경우 외부에서 객체의 성질을 함부로 변형할 수 없지만, 필요에 따라 객체에서 제공하는 기능은 사용할 수 있기 때문에 안정성, 유지보수성 등을 높일 수 있습니다. 

 

추상화는 클래스를 상속받을 대상의 공통적인 속성을 추려내는 것을 의미합니다. 자식 클래스들이 모두 공통적인 속성을 보유하고 있다면, 해당 속성과 관련된 메서드, 멤버 변수 등을 부모 클래스에서 선언한 뒤 자식 클래스에서 이를 정의하는 방향으로 설계할 수 있습니다. 이러한 과정을 거치면 다양한 자식 클래스가 통일성 있게 작동할 수 있습니다.

 

다형성이란 대상이 다양한 모습으로 표현되고 사용될 수 있는 것을 의미합니다. 상속 관계에 있는 클래스가 부모 클래스로도 표현될 수 있고 자식 클래스로도 표현될 수 있는 것이 다형성의 대표적인 예시입니다.

 

이러한 객체지향의 특징을 살리는 방향으로 코드를 설계한다면 안전하고 유지보수가 용이하며 사용이 편리하고 가독성이 높은 코드를 설계할 수 있습니다.

21. LOD와 밉맵에 대해 설명하라.

더보기

LOD는 특정 기준에 따라 버텍스의 수를 조절하여 매쉬의 디테일 수준을 조절하는 기법입니다. 멀리 있는 대상 혹은 작게 그려지는 대상을 디테일하게 렌더링하는 것은 연산의 낭비로 이어질 수 있기 때문에, 거리, 투영면적 등의 기준에 따라 LOD단계를 조절하여 최적화를 달성할 수 있습니다.

 

밉맵은 LOD와 동일한 기술을 텍스쳐에 적용하는 기술입니다. 디테일하게 렌더링 될 필요가 없는 대상의 텍스쳐 해상도를 낮춰 연산 낭비를 줄이는 기법입니다.

 

두 기술 모두 속도 측면에선 최적화에 큰 도움이 되지만, 단계별 리소스를 별도로 저장해야 하기 때문에 메모리를 많이 사용하게 된다는 단점이 있습니다.

22. 렌더링 파이프라인을 요약해서 설명하라.

더보기

렌더링 파이프라인이란, 데이터로만 존재하는 오브젝트를 모니터에 출력하기 위해 거치는 일련의 과정을 의미합니다. 렌더링 파이프라인은 3D공간에 있는 물체에 WVP 행렬을 곱하여 2D 평면에 투영한 뒤, 뷰포트 행렬을 통해 대상이 그려질 픽셀을 건져내고 쉐이더 등을 통해 최종적인 색상을 결정하는 과정으로 진행됩니다.

23. C#의 static 키워드에 대해 설명하라.

더보기

멤버 변수나 메서드가 인스턴스에 종속되지 않고 클래스에 종속되도록 만들어주는 키워드입니다.

 

static으로 생성된 멤버 변수는 프로세스가 생성되고 초기화되는 과정에서 메모리에 저장되기 때문에 인스턴스가 생성되지 않은 시점에서도 대상을 사용할 수 있게 됩니다. static 멤버 변수는 데이터 영역에 위치하는 만큼, 멤버 변수가 인스턴스별로 생성되는 것이 아니라 모든 인스턴스가 하나의 변수를 공유하여 사용하게 됩니다. 여러 인스턴스가 하나의 변수를 공유하는 만큼 static 멤버 변수의 데이터의 안전성을 보장할 수 있는 방향으로 설계해야 합니다.

 

static 메서드의 경우는 함수를 호출하는데 this가 필요하지 않기 때문에 인스턴스가 없어도 독립적으로 사용할 수 있게 됩니다. 다만, 이러한 이유 때문에 함수 내부에서 this가 필요한 작업은 수행할 수 없다는 단점이 있습니다.

24. (C# )컬렉션이란?

더보기

컬렉션이란, 닷넴 프레임워크에서 지원해주는 데이터 관리용 클래스를 의미합니다.

25. (C#) List와 ArrayList의 차이를 설명하라.

더보기

List는 generic을 기반으로 데이터를 보관하는 자료구조이고, ArrayList는 object를 기반으로 데이터를 보관하는 자료구조 입니다.

 

List는 generic을 사용하는 만큼 선언할 때 명시한 자료형에 해당하는 데이터만 저장할 수 있지만, ArrayList는 C#의 모든 객체가 공통으로 상속받고 있는 object 자료형으로 데이터를 저장하기 때문에 모든 자료형의 데이터를 저장할 수 있습니다.

 

List는 자료형이 한정되어 있는 만큼 ArrayList에 비해 활용성이 다소 제한되어 있지만, 그만큼 안정성이 높으며 boxing, unboxing과정을 거치지 않아 성능 부분에서도 우위에 있다는 장점이 있습니다. 반대로 ArrayList는 안정성이 낮고 boxing, unboxing 과정으로 인해 느린 속도를 보인다는 단점이 있지만 활용성이 매우 넓다는 장점이 있습니다. 

26. BFS와 DFS에 대해 설명하라.

더보기

두 알고리즘 모두 그래프를 완전탐색하는 알고리즘입니다. BFS는 너비우선탐색으로 시작 노드와 가까이에 있는 노드를 우선적으로 탐색하는 알고리즘입니다. 반면, DFS는 노드의 자식노드를 재귀적으로 순회하며 탐색하는 알고리즘입니다. BFS는 가까이에 있는 대상을 DFS보다 먼저 탐색할 확률이 높지만, DFS는 반대로 멀리 있는 대상을 BFS보다 먼저 탐색할 확률이 높다는 차이가 있습니다.

27. 메모리 정렬이란? 

더보기

모든 데이터는 차지하는 메모리 크기의 배수에 해당하는 메모리 주소에 위치해야 합니다. 구조체처럼 메모리에 연속적으로 위치해야 하는 데이터들이 메모리 주소에 올바르게 위치하려면 데이터 사이에 패딩 데이터를 추가하여 데이터가 위치하는 메모리 주소를 맞춰주어야 합니다. 이를 메모리 정렬이라고 합니다. 

28. 내부단편화란?

더보기

메모리 풀링 등을 사용하여 미리 할당한 메모리영역 내에서 메모리가 비효율적으로 사용되는 것을 의미합니다. 실제 필요한 메모리 공간에 비해 불필요하게 많은 메모리를 할당하는 경우 내부 단편화가 발생할 수 있습니다.

29. (유니티) ILtoCPP란?

더보기

프로그래머가 유니티 엔진을 사용할 때는 C#을 사용해 코드를 작성하게 됩니다. 닷넴 프레임워크에서 작동되는 C#은 컴파일 타임에 IL이라는 중간언어로 번역되고  IL이 런타임에 기계어로 변환되는 과정을 거치지만, 유니티 엔진은 그러한 과정을 거치지 않고 IL을 다시 C++로 디컴파일 한 뒤 다시 기계어로 컴파일하는 과정을 거칩니다. 이 작업을 통해 실제로 실행되는 게임은 C++ 코드로 실행되며 닷넴 프레임워크에 의해 실행될 때에 비해 높은 성능을 가지게 됩니다. 

30. 내적과 외적에 대해 설명하라.

더보기

내적은 두 벡터의 방향이 얼마나 일치하는지를 구하는 수학적 개념입니다. 두 벡터를 내적하게 되면 두 벡터의 길이와 사잇각의 코사인 값을 곱한 스칼라 값을 얻을 수 있습니다. 두 벡터의 길이를 안다면 두 벡터의 사잇각을 구할 수 있기 때문에, 게임 프로그래밍에선 두 오브젝트의 위치 관계를 구하는데 사용할 수 있습니다.

 

외적은 두 벡터가 속한 평면의 법선 벡터를 구하는 수학적 개념입니다. 두 벡터를 외적하게 되면, 두 벡터에 모두 수직인 벡터를 구할 수 있습니다. 이를 활용하여 두 오브젝트의 위치 관계를 구하는데 사용할 수 있습니다.

 

내적의 경우, 대상이 나의 앞에 있나 뒤에 있나를 구할 수 있지만 왼편에 있는지 오른편에 있는지는 구할 수 없기 때문에 외적을 추가적으로 사용하여 나의 왼편에 있는지 오른편에 있는지를 구할 수 있습니다.

31. 프로세스 스케줄링이란?

더보기

CPU의 코어는 동시에 여러 작업을 수행할 수 없습니다. 하지만, 실제 컴퓨터에선 여러 작업이 동시에 수행되어야 하기 때문에 CPU는 프로세스를 작은 작업으로 쪼갠 뒤 이를 고속으로 번갈아 수행하며 여러 프로세스가 동시에 실행되는 것처럼 보이게 해줍니다. 

 

이처럼 여러 프로세스가 번갈아가며 수행될 때 효율성을 높이기 위해선 프로세스를 어떤 순서로 얼만큼씩 작업하는가를 구체적으로 설정할 필요가 있습니다. 이를 구체적으로 설정하는 작업을 프로세스 스케줄링이라 하며 이는 운영체제에 의해 수행됩니다.

32. 스케줄링의 선점형, 비선점형에 대해 설명하라.

더보기

선점형 스케줄링이란 한 작업이 실행되고 있을 때 다른 작업을 수행하기 위해 현재 작업이 중단될 가능성이 있는 스케줄링을 의미합니다. 

 

비선점형 스케줄링이란 현재 실행중인 작업이 완료되기 전까진 다른 작업을 수행하지 않는 스케줄링을 의미합니다.

33. 소켓 통신이란?

더보기

소켓 통신이란, 소켓을 사용하는 통신 방법을 의미합니다. 소켓을 사용하여 서버와 클라이언트가 특정 포트번호를 통해 양방향으로 통신하는 방식으로, TCP 프로토콜의 통신 방법입니다.

34. 무브 시맨틱이란?

더보기

깊은 복사의 단점인 성능 저하를 최소화하기 위해, 다시는 사용되지 않을 것이 확신한 객체를 복사할 때 얕은 복사를 응용한 이동 연산을 사용하는 최적화 기법입니다.

35. auto 키워드

더보기

자료형을 프로그래머가 명시하지 않고 컴파일러의 추론에 맡기는 키워드 입니다. 자료형이 길고 복잡하거나 프로그래머가 추론하기 힘든 상황에 편리하게 사용할 수 있지만, 자료형을 한 눈에 알아볼 수 없기 때문에 가독성이 저하될 수 있다는 단점이 있습니다.

 

일단 전투를 추가하기 전에 상점 기능을 추가하였다.

private Tuple<string, ConsoleColor> _shopMsg;

먼저, 상점의 상단에 출력할 메세지를 저장할 튜플을 하나 선언하였다.

여기엔 어떤 물품의 구매를 성공했는지, 혹은 소지 금액이 모자라서 실패했는지 등을 알려주는 문자열이 저장된다.

item2가 출력할 색상이다.

 

아래는 상점 코드이다.

더보기
private void SelectShopItem()
{
    DevFunctions.WriteLineColored("구매하고 싶은 물건의 번호를 입력하세요.", ConsoleColor.Cyan);
    Console.WriteLine(); 

    Console.Write("1. 사과 : ");
    DevFunctions.WriteColored("30원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 HP를 50 회복합니다.]", ConsoleColor.Green);

    Console.Write("2. 배 : ");
    DevFunctions.WriteColored("50원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 HP를 100 회복합니다.]", ConsoleColor.Green);

    Console.Write("3. 필살의 영약 : ");
    DevFunctions.WriteColored("200원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 다음 공격의 크리티컬 확률이 50% 증가합니다.]", ConsoleColor.Green);

    Console.WriteLine("4. 구매를 종료합니다.");

    string input = Console.ReadLine();

    if (DevFunctions.IsNumeric(input) == false)
    {
        return;
    }

    if (input.Length > 1)
    {
        return;
    }

    int toInt = int.Parse(input);

    switch (toInt)
    {
        case 1:
            BuyItem(ItemName.사과);
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            _updateFunc = null;
            _selectFunc = new SelectFunc(SelectMenu);
            _shopMsg = null;
            break;
    }
}

물건을 선택하면 BuyItem이라는 함수를 호출한다.

더보기
 private void BuyItem(ItemName itemName)
 {
     if (Item.isCanBuy(_parentCore.CurrentPlayer, itemName) == true)
     {
         SetShopMsg_Buy(itemName);
         Item.BuyItem(_parentCore.CurrentPlayer, itemName);
     }
     else
     {
         SetShopMsg_Failed();
     }
 }
 
 private void SetShopMsg_Buy(ItemName itemName)
{
    _shopMsg = new Tuple<string, ConsoleColor>(itemName.ToString() + "를 구매하였습니다", ConsoleColor.Blue);
}

private void SetShopMsg_Failed()
{
    _shopMsg = new Tuple<string, ConsoleColor>("소지 금액이 모자랍니다.", ConsoleColor.Red);
}

레벨에 함수는 이렇게 설정되어 있고, 내부 코드를 보면 Item 클래스의 static 함수를 사용하고 있는데 코드는 아래와 같다.

더보기
public static bool isCanBuy(Player player, ItemName itemName)
{
    switch(itemName)
    {
        case ItemName.사과:
            return (player.Money >= 30);
    }

    return false;
}

public static void BuyItem(Player player, ItemName itemName)
{
    switch (itemName)
    {
        case ItemName.사과:
            player.Inventory.Add(new Apple());
            player.Money -= 30;
            break;
    }
}

물품을 구매할 수 있는지 확인하는 함수와 실제로 구매하는 함수이다.

 

아래 코드를 통해, 상단에 소지 금액과 함께 메세지가 출력된다. 그 아래엔 위에서 본 선택 메세지가 출력되는 것이다.

더보기
private void WriteShopMsg()
{
    string Str = "소지 금액 : " + _parentCore.CurrentPlayer.Money.ToString();
    DevFunctions.WriteLineColored(Str, ConsoleColor.Yellow);

    if (_shopMsg != null)
    {
        DevFunctions.WriteLineColored(_shopMsg.Item1, _shopMsg.Item2);
    }
}

 

구현된 결과다.

 

또한, 아이템을 구매한 뒤 현재 소지 품목을 출력하는 기능도 추가하였다.

물품을 구매한 뒤 2번 인벤토리를 확인한다를 선택하면 아래와 같이 소지 품목이 출력된다.

 

상점 기능을 추가하였고, 전투 기능을 추가하기 위해 몬스터와 플레이어에 공통의 인터페이스를 하나 상속하였다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public interface ICreatureStat
    {
        int AttPower { get; }
        int DefPower { get; }
    }
}

그냥 공격력, 방어력을 get하는 함수만 있다.

 

이걸 상속하도록 한 이유는 데미지 계산을 하기 위해서인데, 공격자와 피격자가 몬스터인지 플레이어인지 알 필요 없이 공격력과 방어력만으로 계산하기 위해서이다.

public static int GetFinalAttPower(ICreatureStat attacking, ICreatureStat attacked)
{
    int Att = attacking.AttPower;
    int Def = attacked.DefPower;
    
    float DefRate = Def / 100.0f;
    DefRate = 1.0f - DefRate;
    
    return (int)(Att * DefRate);
}

위의 함수를 호출하면, 공격자가 몬스터인지 플레이어인지 알 필요 없이 결과적인 데미지만 얻어낼 수 있다.

이 외에도 필요한 기능이 있으면 인터페이스에 추가하면 될 것 같다.

 

생각해보니 아이템을 구현할 때 enum을 한글로 사용해보았다.

 public enum ItemName
 {
     사과,
     배,
     필살의영약,
 }

그냥 되나 궁금해서 해봤는데 되길래 걍 써봤다 ㅋㅋ

은근 편한 부분이 있긴 한데, 다시는 쓸 일 없을듯

지난 번에는 대충 어떤 식으로 콘솔을 입력하고 머드 게임을 구성하는지 테스트 해보던 단계라 기능이 분리되어 있지 않았다. 그래서 이번엔 클래스를 조금 더 잘게 분리한 뒤 몇 가지 기능을 추가하였다.

 

먼저 GameCore의 코드이다.

더보기
더보기
using C_Study;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace C_Study
{
    public enum LevelType
    {
        Start,
        Setting,
        Menu,
        Play,
    }

    public class GameCore
    {

        public void Start()
        {
            _player = new Player();
            _levelType = LevelType.Start;

            _levels = new Dictionary<LevelType, GameLevel>();

            _levels.Add(LevelType.Start, new StartLevel(this));
            _levels.Add(LevelType.Setting, new SettingLevel(this));
            _levels.Add(LevelType.Menu, new MenuLevel(this));
        }

        public void Update()
        {
            while (_player != null)
            {
                _levels[_levelType].Update();
            }
        }

        public void End()
        {

        }

        public LevelType CurrentLevel
        {
            set { _levelType = value; }
        }

        public Player CurrentPlayer
        {
            get { return _player; }
        }


        private Player _player;
        private LevelType _levelType;

        private Dictionary<LevelType, GameLevel> _levels;
    }
}

지난번엔 GameCore 내부에 모든 기능을 구현해서 업데이트 함수를 돌리고 있었지만, Level관련 인스턴스를 따로 만들어서 Dictionary 자료구조에 삽입하였고 GameCore는 update함수만 호출하도록 하였다.

 

각 레벨은 생성자에서 GameCore를 인자로 받으면, 해당 Core를 자료구조에 보유하도록 하였다.

Level에서 Core의 옵션을 변경하면서 레벨을 왔다갔다 하기 위해서이다.

 

모든 Level클래스는 GameLevel이라는 추상 클래스를 상속받도록 구현하였다.

 

아래는 GameLevel 클래스이다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public abstract class GameLevel
    {
        protected GameLevel() { }

        public abstract void Update();

        protected GameCore _parentCore;

    }
}

아래는 이를 상속받은 클래스들이다.

 

시작 화면을 담당하는 StartLevel.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class StartLevel : GameLevel
    {
        public StartLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;
        }

        public override void Update()
        {
            Console.Clear();

            Console.WriteLine("*************************************************");
            Console.WriteLine("*************************************************");

            Console.Write("*****************");
            DevFunctions.WriteColored("전사의 모험 RPG", ConsoleColor.Yellow);
            Console.WriteLine("*****************");

            Console.Write("******************");
            DevFunctions.WriteColored("제작자 오의현", ConsoleColor.Yellow);
            Console.WriteLine("******************");

            Console.WriteLine("*************************************************");
            Console.WriteLine("*************************************************");
            Console.WriteLine();

            Console.WriteLine("모험을 시작하시겠습니까?");

            DevFunctions.WriteLineColored("1 : YES", ConsoleColor.Blue);
            DevFunctions.WriteLineColored("2 : NO", ConsoleColor.Red);

            string input = Console.ReadLine();

            if (DevFunctions.IsNumeric(input) == false)
            {
                return;
            }

            int toInt = int.Parse(input);

            switch (toInt)
            {
                case 1:
                    _parentCore.CurrentLevel = LevelType.Setting;
                    break;
                case 2:
                    Environment.Exit(0);
                    break;
            }
        }

    }
}

 

캐릭터 선택을 담당하는 SettingLevel

(아직은 무기 선택만 있지만, 나중에는 뭐 여러가지 추가할 수도 있다. 안할수도 있고..)

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class SettingLevel : GameLevel
    {

        public SettingLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;
        }

        public override void Update()
        {
            WeaponSelect();
            WeaponFixOrReselect();
        }

        private void WeaponSelect()
        {
            while (true)
            {
                Console.Clear();

                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("----------------캐릭터 설정 단계-----------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine();

                DevFunctions.WriteLineColored("무기 선택 단계입니다.", ConsoleColor.Cyan);
                Console.WriteLine();

                Console.WriteLine("검은 기본 공격력이 낮지만 크리티컬 확률이 가장 높습니다.");
                Console.WriteLine("공격시 일정 확률로 출혈을 발생시키기도 합니다.");
                Console.WriteLine();

                Console.WriteLine("창은 적당한 공격력과 크리티컬 확률을 보유하고 있습니다.");
                Console.WriteLine("피격시 일정 확률로 적의 공격을 회피합니다.");
                Console.WriteLine();

                Console.WriteLine("망치는 높은 공격력을 보유하고 있지만 크리티컬 확률이 낮습니다.");
                Console.WriteLine("공격시 일정 확률로 적을 기절 상태로 만듭니다.");
                Console.WriteLine();

                Console.WriteLine("어떤 무기를 선택하시겠습니까?");
                DevFunctions.WriteColored("1.오래된 검 ", ConsoleColor.Blue);
                DevFunctions.WriteColored("2.오래된 창 ", ConsoleColor.Red);
                DevFunctions.WriteColored("3.오래된 망치", ConsoleColor.Green);
                Console.WriteLine();

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if (input.Length > 1)
                {
                    continue;
                }

                int toInt = int.Parse(input);

                switch (toInt)
                {
                    case 1:
                        _parentCore.CurrentPlayer.EquipedWeapon = new OldSword();
                        break;
                    case 2:
                        _parentCore.CurrentPlayer.EquipedWeapon = new OldSpear();
                        break;
                    case 3:
                        _parentCore.CurrentPlayer.EquipedWeapon = new OldHammer();
                        break;
                }

                break;
            }
        }

        private void WeaponFixOrReselect()
        {
            while (true)
            {
                Console.Clear();

                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("----------------캐릭터 설정 단계-----------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine("-------------------------------------------------");
                Console.WriteLine();

                Console.Write("선택하신 무기는 ");
                DevFunctions.WriteColored(_parentCore.CurrentPlayer.EquipedWeapon.WPName, ConsoleColor.Cyan);
                Console.Write("입니다.");
                Console.WriteLine();

                Console.WriteLine("선택하신 무기로 게임을 시작하시겠습니까?");
                DevFunctions.WriteLineColored("1. 네", ConsoleColor.Blue);
                DevFunctions.WriteLineColored("2. 다시 선택할래요", ConsoleColor.Red);

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if (input.Length > 1)
                {
                    continue;
                }

                int toInt = int.Parse(input);

                if(toInt != 1 && toInt != 2)
                {
                    continue;
                }

                switch (toInt)
                {
                    case 1:
                        _parentCore.CurrentLevel = LevelType.Menu;
                        break;
                    case 2:
                        _parentCore.CurrentLevel = LevelType.Setting;
                        break;
                }

                break;
            }
        }
    }
}

아래는 인벤토리 보기, 캐릭터 스탯 보기, 전투하기 등의 메뉴를 선택하는 MenuLevel이다.

_parentCore에서 멤버변수를 사용하는 코드가 너무 길어서, 별도의 get를 따로 만들어야 할 듯 하다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class MenuLevel : GameLevel
    {
        private delegate void UpdateFunc();
        private delegate void SelectFunc();

        public MenuLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;

            selectfunc = new SelectFunc(SelectMenu);
        }

        public override void Update()
        {
            Console.Clear();

            if (updatefunc != null)
            {
                updatefunc();
            }

            if (selectfunc != null)
            {
                selectfunc();
            }
        }

        private void WriteStatus()
        {
            DevFunctions.WriteLineColored("착용중인 무기 정보", ConsoleColor.DarkCyan);

            Console.WriteLine("이름 : {0}  ", _parentCore.CurrentPlayer.EquipedWeapon.WPName);
            Console.WriteLine("공격력 : {0}  ", _parentCore.CurrentPlayer.EquipedWeapon.AttPower);
            Console.WriteLine("크리티컬 확률 : {0}%  ", _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb);
            Console.WriteLine();

            DevFunctions.WriteLineColored("플레이어 스탯 정보", ConsoleColor.DarkCyan);
            Console.WriteLine("레벨 : {0}  ", _parentCore.CurrentPlayer.Level);
            Console.WriteLine("잔여 스탯 포인트 : {0}  ", _parentCore.CurrentPlayer.StatPoint);
            Console.WriteLine("공격력 : {0} ({1} + {2})  ", _parentCore.CurrentPlayer.AttPower + _parentCore.CurrentPlayer.EquipedWeapon.AttPower, _parentCore.CurrentPlayer.AttPower, _parentCore.CurrentPlayer.EquipedWeapon.AttPower);
            Console.WriteLine("크리티컬 확률 : {0} ({1} + {2})%  ", _parentCore.CurrentPlayer.CriticalProb + _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb, _parentCore.CurrentPlayer.CriticalProb, _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb);
            Console.WriteLine("크리티컬 데미지 : {0}%  ", _parentCore.CurrentPlayer.CriticalPower);
            Console.Write("무기 숙련도 : {0}  ", _parentCore.CurrentPlayer.WeaponSkilled);
            DevFunctions.WriteLineColored("*무기 숙련도가 높을수록 출혈, 회피, 기절 확률이 증가합니다.", ConsoleColor.DarkRed);
            Console.WriteLine("방어력 : {0}  ", _parentCore.CurrentPlayer.DefPower);
            Console.WriteLine("체력 : {0} / {1}  ", _parentCore.CurrentPlayer.CurrentHP, _parentCore.CurrentPlayer.MaxHP);
            Console.WriteLine("경험치 : {0} / {1}  ", _parentCore.CurrentPlayer.CurrentEXP, _parentCore.CurrentPlayer.MaxEXP);
            Console.WriteLine();
        }

        private void WriteInventory()
        {
            DevFunctions.WriteLineColored("보유 아이템 정보", ConsoleColor.DarkCyan);
            Console.WriteLine("아이템 개수 : {0}  ", _parentCore.CurrentPlayer.Inventory.Count);
            Console.WriteLine();
        }

        private void SelectMenu()
        {
            while (true)
            {
                Console.WriteLine("1. 캐릭터 정보를 확인한다.");
                Console.WriteLine("2. 인벤토리를 확인한다.");
                Console.WriteLine("3. 전투를 시작한다.");
                Console.WriteLine("4. 게임을 종료한다.");

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if (input.Length > 1)
                {
                    continue;
                }

                int toInt = int.Parse(input);

                switch (toInt)
                {
                    case 1:
                        updatefunc = new UpdateFunc(WriteStatus);
                        break;
                    case 2:
                        updatefunc = new UpdateFunc(WriteInventory);
                        break;
                    case 3:
                        updatefunc = null;
                        selectfunc = new SelectFunc(SelectMonster);
                        break;
                }

                break;
            }
        }

        private void SelectMonster()
        {
            while (true)
            {
                DevFunctions.WriteLineColored("전투하고 싶은 몬스터를 선택하세요.", ConsoleColor.DarkCyan);
                Console.WriteLine();

                Console.WriteLine("1. 고블린 (적정 레벨 : 1)");
                Console.WriteLine("2. 오우거 (적정 레벨 : 5)");
                Console.WriteLine("3. 드래곤 (적정 레벨 : 10)");
                Console.WriteLine("4. 이전으로 돌아간다.");

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if (input.Length > 1)
                {
                    continue;
                }

                int toInt = int.Parse(input);

                switch (toInt)
                {
                    case 4:
                        selectfunc = new SelectFunc(SelectMenu);
                        break;
                }
                break;
            }
        }

        private UpdateFunc updatefunc;
        private SelectFunc selectfunc;
    }
}

 

레벨은 일단 여기까지 구현하였고, Player 클래스 내부에도 몇가지 속성을 추가하였다.

아래는 Player 클래스이다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class Player
    {
        public Player()
        {
            _attPower = 10;
            _defPower = 10;

            _maxHP = 100;
            _currentHP = 100;

            _maxEXP = 100;
            _currentEXP = 0;

            _criticalPower = 150.0f;
            _criticalProb = 5.0f;

            _level = 1;
            _statPoint = 0;

            _weaponSkilled = 1;

            _inventory = new List<Item>();
        }

        public Weapon EquipedWeapon
        {
            get { return _equipedWeapon; }
            set { _equipedWeapon = value; }
        }

        public int AttPower
        {
            get { return _attPower; }
        }
        public int DefPower
        {
            get { return _defPower; }
        }

        public int Level
        {
            get { return _level; }
        }

        public int StatPoint
        {
            get { return _statPoint; }
        }

        public int WeaponSkilled
        {
            get { return _weaponSkilled; }
        }

        public int MaxHP
        {
            get { return _maxHP; }
        }

        public int CurrentHP
        {
            get { return _currentHP; }
        }

        public int MaxEXP
        {
            get { return _maxEXP; }
        }

        public int CurrentEXP
        {
            get { return _currentEXP; }
        }


        public float CriticalPower
        {
            get { return _criticalPower; }
        }

        public float CriticalProb
        {
            get { return _criticalProb; }
        }
        public List<Item> Inventory
        {
            get { return _inventory; }
        }

        private Weapon _equipedWeapon;

        private int _attPower;
        private int _defPower;

        private float _criticalPower;
        private float _criticalProb;

        private int _level;
        private int _statPoint;
        private int _weaponSkilled;

        
        private int _maxHP;
        private int _currentHP;

        private int _maxEXP;
        private int _currentEXP;

        List<Item> _inventory;
    }

}

코드를 보면 멤버변수에 인벤토리가 있는데, item 클래스를 만들어서 이를 상속받은 아이템들을 저장할 것이다.

Item클래스는 아래와 같다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class Item
    {
        protected Item()
        {
        }

        public int AttPower
        {
            get { return _attPower; }
            protected set { _attPower = value; }
        }
        public int DefPower
        {
            get { return _defPower; }
            protected set { _defPower = value; }
        }

        public int WeaponSkilled
        {
            get { return _weaponSkilled; }
            protected set { _weaponSkilled = value; }
        }

        public int HealHP
        {
            get { return _healHP; }
            protected set { _healHP = value; }
        }

        public float CriticalPower
        {
            get { return _criticalPower; }
            protected set { _criticalPower = value; }
        }

        public float CriticalProb
        {
            get { return _criticalProb; }
            protected set { _criticalProb = value; }
        }

        public int RemainTurn
        {
            get { return _remainTurn; }
            protected set { _remainTurn = value; }
        }


        private int _attPower;
        private int _defPower;

        private float _criticalPower;
        private float _criticalProb;

        private int _weaponSkilled;

        private int _healHP;
        private int _remainTurn;
    }
    
    public class Apple : Item
    {
        public Apple()
        {
            HealHP = 50;
        }
    }
}

 

 소비 아이템만 만들 것이기 때문에, 이 아이템으로 인해 상승될 수 있는 옵션들을 저장하였다.

 

무기 클래스도 조금 개선하였다.

검 종류에는 출혈 확률을 추가하였고, 창 종류에는 회피 확률, 망치 종류에는 기절 확률을 추가하였다.

더보기
더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;



namespace C_Study
{
    public enum WeaponType
    {
        None,
        Sword,
        Spear,
        Hammer,
    }

    public class Weapon
    {
        protected Weapon()
        {

        }
        public WeaponType WPType
        {
            get { return _wpType;  }
            protected set { _wpType = value; }
        }

        public string WPName
        {
            get { return _wpName; }
            protected set { _wpName = value; }
        }
        public int AttPower
        {
            get { return _attPower; }
            protected set { _attPower = value; }
        }
        public float CriticalProb
        {
            get { return _criticalProb; }
            protected set { _criticalProb = value; }
        }

        private int _attPower;
        private float _criticalProb;

        private string _wpName;
        private WeaponType _wpType;
    }

    public class Sword : Weapon
    {
        protected Sword()
        {
            WPType = WeaponType.Sword;
            CriticalProb = 25.0f;
        }

        protected float BleedingProb
        { 
            get { return _bleedingProb; }
            set { _bleedingProb = value; }  
        }

        private float _bleedingProb;
    }

    public class Spear : Weapon
    {
        protected Spear()
        {
            WPType = WeaponType.Spear;
            CriticalProb = 15.0f;
        }

        protected float EvadingProb
        {
            get { return _evadingProb; }
            set { _evadingProb = value; }
        }

        private float _evadingProb;
    }

    public class Hammer : Weapon
    {
        protected Hammer()
        {
            WPType = WeaponType.Hammer;
            CriticalProb = 5.0f;
        }

        protected float StunProb
        {
            get { return _stunProb; }
            set { _stunProb = value; }
        }

        private float _stunProb;
    }

    public class OldSword : Sword
    {
        public OldSword()
        {
            WPName = "오래된 검";
            AttPower = 10;
            BleedingProb = 5.0f;
        }
    }

    public class OldSpear : Spear
    {
        public OldSpear()
        {
            WPName = "오래된 창";
            AttPower = 15;
            EvadingProb = 10.0f;
        }
    }

    public class OldHammer : Hammer
    {
        public OldHammer()
        {
            WPName = "오래된 망치";
            AttPower = 30;
            StunProb = 5.0f;
        }
    }
}

아래는 지금까지 구현된 것을 영상으로 찍은 것이다.

 

C++의 문법에 점점 익숙해지는중..

'프로젝트 > C# 머드게임' 카테고리의 다른 글

C# 머드 게임 프로젝트 (4)  (0) 2024.08.01
C# 머드 게임 프로젝트 (3)  (0) 2024.07.30
C# 머드 게임 프로젝트 (1)  (0) 2024.07.28

+ Recent posts