지금까지 콘솔을 실행했을 때 무언가를 입력하고 엔터를 치면 다음 단계로 넘어가는 방식이었지만, 실제로 게임 같은 걸 해 보면 방향키나 WASD를 누르면 바로 반영이 되었을 것이다.
C# 콘솔을 공부하고 있는 지금 단계에도 그런 입력과 동시에 실행시킬 수 있는 기능이 있다.
이 ReadKey 기능을 이용하여 간단하게 플레이어의 움직임을 구현해보자.
static void PlayerMovement()
{
while (true)
{
switch (Console.ReadKey(true).Key)
{
case ConsoleKey.W:
case ConsoleKey.UpArrow:
Console.WriteLine("위로 이동!");
break;
case ConsoleKey.A:
case ConsoleKey.LeftArrow:
Console.WriteLine("왼쪽 이동!");
break;
case ConsoleKey.S:
case ConsoleKey.DownArrow:
Console.WriteLine("아래 이동!");
break;
case ConsoleKey.D:
case ConsoleKey.RightArrow:
Console.WriteLine("오른쪽 이동!");
break;
}
}
}
static void Main(string[] args)
{
PlayerMovement();
}
여기에서 ReadKey의 괄호 안에 true 나 false를 넣을 수 있는데, true를 넣으면 입력된 키가 무엇인지 출력되지 않고, false를 넣으면 입력된 키가 무엇인지 출력된다.
* 해당 결과는 false를 넣었을 때 출력되는 값인데, wasd는 출력되는 반면 방향키는 표시되지 않는다는 재밌는 사실도 알아냈다.
예를 들어 이런 함수를 만들어 보고자 한다.
-입력값1 과 입력값2를 받고, 입력값1을 입력값2로 나눈 몫과 나머지를 출력하는 함수
다만, 앞선 상편에서 반환값이 있는 함수는 return 구문을 지나가는 순간 함수를 빠져나간다고 했다.
그러면 결과를 두 개 이상 출력할 수 있는 방법은 없는 걸까?
이를 해결할 수 있는 방법이 out를 활용하는 것이다.
우선 위의 상황을 가정한 함수를 작성해 보자.
static int Divide(int left, int right)
{
return left / right; // 몫
return left % right; // 나머지
}
이와 같이 식을 작성했을 때, 비주얼 스튜디오에서는 아래와 같이 표시된다.
아래에 있는 식이 실행되지 않을 거란 경고가 뜨는 것이다. 이를 해결하기 위해서는, out (변수)를 추가하고 return 보다 위에 나머지를 구하는 식을 넣으면 된다.
static int Divide(int left, int right, out int remain)
{
remain = left % right; // 나머지
return left / right; // 몫
}
static void Main(string[] args)
{
int result;
int remain;
result = Divide(11, 3, out remain);
Console.WriteLine("11 / 3 은 몫 {0}, 나머지 {1}", result, remain);
}
결과 출력은 다음과 같이 나온다.
* 생각해 보면, 이전에 배웠던 TryParse도 같은 원리로 작동한다는 걸 알 수 있다.
int.TryParse(Console.ReadLine(), out number);
결과는 성공 여부인 bool 값으로 출력하고, 추가로 성공 여부에 따라 number에 반환값을 넣어주는 것이다.
이와 같이 추가적인 반환값이 필요할 때 Out을 활용할 수 있다.
아래와 같이 코드를 작성하고 실행시켜 보자.
static void Function()
{
Console.WriteLine("1");
Console.WriteLine("2");
Console.WriteLine("3");
}
static void Main(string[] args)
{
Function();
Function();
Function();
}
이 코드를 실행하면 아래와 같이 실행될 것을 알 것이다.
다만 여기에서, 저번에 배웠던 디버깅과는 다른 기능을 배워보고자 한다.
요전번 [(3-2) - 디버거] 에서 배운 기능이 F11이었다.
F11은 한 단계씩 코드 실행이라고 적혀 있다.
그러면 그 옆의 버튼이 무엇인지 살펴보자.
F10을 누르면 프로시저 단위의 실행이 가능하다고 되어 있다. 이건 F11과 무슨 차이인지 눌러보자.
(F10을 두 번만 누른 화면이다)
이와 같은 기능은 문제가 없는 함수는 F10으로 빠르게 과정을 넘겨버리고, 문제가 있는 함수로 도달하는 방식으로 사용할 수 있다.
이와 같은 디버깅 기능을 알아보면서 우리는 함수가 어떻게 작동하는지 알게 되었다.
함수를 입력하면 그 함수로 이동해서 기능을 실행하는 방식인데, 이때 실행시키던 함수의 정보를 저장하기 위해 사용하는 곳을 호출 스택이라고 한다.
위에 사용한 코드를 예시로 함수 호출 스택이 어떻게 진행되는지 생각해 봤다.
두 변수에 저장된 숫자를 바꿔주는 함수에 관해 생각해보자.
이때 유의할 점은, 우선 바꿀 값 중에 하나는 임시로 다른 변수에다가 저장해 놓고 변수를 바꾸는 방식으로 작성해야 한다. (필자는 해당 과정이 필요한 이유에 관한 부분을 한 번에 이해했기 때문에 그 설명은 생략했다)
static void Swap(int left, int right)
{
int temp = left;
left = right;
right = temp;
}
static void Main(string[] args)
{
int a = 10;
int b = 20;
Swap(a, b);
Console.WriteLine("a : {0}, b : {1}", a, b);
}
해당 코드를 실행해서 도출하길 원하는 결과는 "a : 20, b : 10"일 것이다. 하지만 결과를 보면 아래와 같이 나온다.
왜 원했던 대로 숫자의 위치가 바뀌지 않았을까? 이에 관해서는 잘 생각해 보면 이유를 알 수 있다.
일단 지금 Swap로 작성한 함수가 void로 되어 있으니, 반환값이 없을 것이다.
즉 함수는 그 자체로만 한 번 실행되고 종료한 것이다.
그러면 반환값을 주면 해결할 수 있지 않을까? 이렇게 생각하고 쥐어비틀기 식으로 코드 수정을 해봤다.
static int Swap(int left, int right, out int finalRight)
{
finalRight = left;
return right;
}
static void Main(string[] args)
{
int a = 10;
int b = 20;
a = Swap(a, b, out b);
Console.WriteLine("a : {0}, b : {1}", a, b);
}
이를 출력하면 원하는 대로 출력되긴 한다.
하지만 이런 방법을 사용하기에는 헷갈리기도 하고, 까딱 실수하면 원하는 대로 결과가 나오지 않을 거라는 문제점이 있었다.
(필자도 return 값이랑 out값에 각각 뭘 넣어야하는지 금방 떠올리지 못했다.)
이렇게 복잡하게 해결하는 대신에 ref(Reference-참조) 를 사용한다.
ref를 사용하여 똑같은 기능을 만들면 다음과 같이 작성할 수 있다.
static void Swap(ref int left, ref int right)
{
int temp = left;
left = right;
right = temp;
}
static void Main(string[] args)
{
int a = 10;
int b = 20;
Swap(ref a, ref b);
Console.WriteLine("a : {0}, b : {1}", a, b);
}
출력하면 아래와 같이 나온다.
이와 같이 두 함수의 실행에서 차이가 나는 것은,
첫 번째 void로만 작성한 함수가 값에 의한 호출(Call by Value),
두 번째 ref를 붙이고 작성한 함수가 참조에 의한 호출(Call by Reference)이기 때문이다.
call by value와 call by reference는 다음과 같은 차이를 보인다.
1) call by value
함수의 매개변수에 변수가 복사되어 들어간다.
함수 내에서 생긴 변화는 다른 함수에 영향을 주지 못한다.
매개변수를 담은 공간과 실제 변수의 공간은 다른 메모리 주소를 가진다.
2) call by reference
함수의 매개변수에 변수의 주소가 들어간다.
함수 내에서 생긴 변화가 다른 함수에 영향을 줄 수 있다.
매개변수를 담은 공간과 실제 변수의 공간은 같은 메모리 주소를 가진다.
이와 같은 특징을 활용하면 아래와 같이 생각해 볼 수도 있다.
- 함수로 도출된 값을 일회용으로만 쓸 건지? >> call by value
- 함수로 도출된 값을 반영할 건지? >> call by reference
이걸 게임에 도입했을 시 아래와 같은 예시로 활용할 수 있을 거란 생각이 들었다.
call by value : 변동 확률, 예를 들어 가챠 게임의 천장(몇 뽑 이상 시 특정 캐릭터 확정 획득 등)
call by reference : 레벨업에 따른 스텟 상승(영구 반영 요소)
이와 같은 활용 방법을 생각하여 게임에 반영해보도록 하자.