[기획] C 몰라도 OK? 비주얼 스크립팅, 얼마나 쉬울까

기획기사 | 윤서호 기자 | 댓글: 18개 |



"아, 나도 게임을 만들고 싶다"

게이머라면 누구나 한 번쯤은 이런 생각을 해봤을 겁니다. 물론 그런 생각을 할 시간에 한 톨이라도 레벨 혹은 실력을 올리는 게 더 경제적이라고 할지 모르죠, 그렇지만 호기심과 창의성은 태어나면서부터 갖고 있는 인간의 충동이자 본능이고, 지금까지 인류가 발전해온 원동력이기도 하죠.

무언가 거창한 말을 하긴 했지만, 결론은 아마 대다수가 아는 그대로일 겁니다. 현실과 이상의 괴리라고 하죠. 게임을 할 때는 생각 없이 재미있게 즐길 수 있지만, 만드는 건 또 다른 차원의 이야기니까요. 마치 먹는 걸 좋아해도 좋아해도 요리를 잘하거나 혹은 좋아하는 것과는 별개라고 해야 할까요?

더군다나 게임 개발 과정은 장벽 하나가 더 막고 있습니다. 중간에 기계를 한 차례 거쳐야 한다는 점이죠. 그렇기 때문에 기계와의 대화 수단, 즉 프로그램 언어를 알 필요가 있습니다. 사람 간의 커뮤니케이션과 달리, 기계는 딱 말하는 그대로만 알아듣는 융통성 0, 철벽 그 자체죠. 바디랭귀지나, "~인 것 같아" 같은 모호한 표현이 한 톨이라도 섞이면 못 알아듣죠. 그랬다간 Null Reference, 혹은 에러를 띄우기 일쑤입니다. 위치를 말할 때도 마찬가지죠. 단순히 어디 옆이라고 하면 못 알아듣고, 기준점을 명확히 좌표로 제시한 뒤 그 지점으로부터 얼마나 떨어져있다는 것을 수치로, 혹은 수치값을 대입해서 계산할 수 있는 형태로 줘야 합니다.



▲ 기계와의 소통(?)을 위해선 피할 수 없는 과정이죠

칼같이 정확해야 하고, 수학적으로 표현해야 하는 기계의 언어체계에 익숙하지 않아서 개발 중에 좌절하는 지망생도 많았습니다. 그렇지만 인간의 호기심과 창작욕은 이 난관에서 또다른 방향으로 빛나게 됐죠. "프로그램 언어 몰라도 개발할 수 있는 방법은 없을까?" 이 질문에 답하기 위해 나온 것 중 하나가 이른바 비주얼 스크립팅입니다.

말 그대로 스크립트를 시각화한 이 비주얼 스크립팅은 딱딱한 프로그램 언어로 스크립트를 짜는 대신에 유닛과 노드로 로직이 어떻게 돌아가는지 직접 보고 설계할 수 있게 하는 기술입니다. 언리얼의 블루프린트, 유니티에서 최근에 도입된 볼트가 이를 활용한 대표적인 사례죠. 엔진 구조 자체가 C++(언리얼), C#(유니티)을 기반으로 한 만큼 그 이해도가 있으면 더 잘 쓸 수 있지만, 모르더라도 유닛을 만들고 노드를 연결하는 것만으로도 게임의 코어를 구축할 수 있게끔 했습니다.



▲ 비주얼 스크립팅이란 말 그대로 숫자와 문자로만 써있던 내용을 그래프 형태로 시각화한 것입니다

물론, 이것만 있으면 프로그램 언어를 몰라도 원하는 게임을 만들어갈 수 있을까? 하는 의문이 들기 마련입니다. 사람들이 원하는 건 기본 예제가 아니라, 한 단계 더 나아간 무언가도 뚝딱 만들 수 있나 여부를 확인하는 것이니까요. 지금 만들고 있는 작품이 여러 한계에 부딪혀서 진전이 없는 가운데, 과연 비주얼 스크립팅으로 이런 샘플을 어떻게 만들어갈 수 있을지 직접 확인해봤습니다.


어, 점프 같은 게 이렇게 간단하게 됨?
확실히 기본 동작 만들기는 쉽다

'당신은 게임을 만들어보겠다는 일념으로 야심차게 게임 엔진을 다운로드 받았습니다. 그리고 실행해서 프로젝트를 만든 그 다음, 무엇을 해야 할지 고민하기 시작합니다. 예제로 본 공굴리기 정도는 눈감고도 할 수 있겠다 싶어서 등한시했지만 후회되기 시작하죠. 공을 어찌저찌 만들었는데, 어떻게 해야지 이 공이 움직일 수 있을까요?'

위 예제처럼 기초지식이 전무한 상태에서 게임 개발을 시작하진 않겠지만, 어쨌거나 초보들이 가장 처음에 겪는 진입장벽은 위 예제와 사실 다를 바가 없습니다. 기본적인 건 금방 만들겠거니, 하고 시작했는데 의외로 그 기본조차도 구현하지 못해서 씨름하다가 결국 지쳐서 포기하는 일도 태반이죠.



▲ 막상 예제를 다운로드 받아본 뒤에, 이걸 어떻게 움직이지? 여기서부터 문제가 발생합니다

한 가지 미리 말씀드리자면, '비주얼 스크립팅'은 모든 걸 다 쉽게 해결해주는 도깨비방망이가 아닙니다. 조금 더 구조를 쉽게 파악하면서 뼈대를 만들 수 있게 해주는 도구죠. 어쨌든 그 기본동작을 수행하게 만들려면, 그에 맞는 준비는 따로 필요합니다.

잠시 게임이라는 틀에서 벗어나 현실적으로 생각해보면, 물체를 움직이기 위해선 힘이 필요합니다. 그 힘이 물체의 어느 방향에서 작용하느냐에 따라 물체가 움직이는 방향이 정해지죠. 우리가 해야 할 일은 그 물체에 어떤 방향에서 힘을 어느 정도 주느냐를 컴퓨터에게 말해주는 겁니다. 그러기 위해서는 프로그램 언어를 써야죠. 엔진에서 어느 정도 처리해주니 어셈블리까지는 아니더라도, C++ 혹은 C# 정도까지는 가줘야 합니다.

썩 좋은 코드는 아니지만 제가 만든 걸 예로 들자면, 이렇게 만들 수 있겠죠.




프로그래머라면 구멍이 숭숭 뚫린 코드라는 걸 단번에 눈치채겠지만, 어쨌든 이 코드를 쓰면 캐릭터가 점프하기는 합니다. 그런데 프로그래밍을 전혀 모르는 사람이라면 이게 어떻게 해서 캐릭터를 점프하게 만드는 문장인지 알기가 어렵죠. 익숙한 영단어를 대강 훑어서 뜻은 파악할 수 있지만, 인간의 문법과는 다른 양식으로 써있기 때문이죠. 더군다나 용어를 아예 모르면 한 줄도 쓸 수 없다는 단점도 있습니다.

반면 비주얼 스크립팅, 특히 볼트에서는 우클릭이나, 혹은 유닛에서 노드를 연결하려고 할 때 해당 항목에 생성 가능한 목록이 나옵니다. 이를 훑어보고 필요한 것을 대강 찾아서 생성하고, 연결하는 식으로 만들어낼 수 있죠.



▲ 뭘 해야 할지 모르겠다면, 일단 우클릭부터 하면 단서들이 나오기 시작합니다



▲ 코드를 일부분이라도 알고 있으면 그걸 검색해서 바로 원하는 걸 찾을 수도 있죠

뿐만 아니라 프로그래밍 초심자들이 이해하기 어려운, 다른 함수에서 레퍼런스해서 호출한다는 개념을 거의 사용하지 않기 때문에 비교적 이해하기가 쉽습니다. 그러면서도 ctrl+드래그로 각 영역별로 노드를 묶어서 이동시킬 수 있기 때문에 관리하기도 편하고요. 이 점은 사실 양날의 검이긴 하지만, 단순한 동작을 만들 때는 장점으로 작용합니다.

어쨌거나 비주얼 스크립팅으로 구현하자면, 이런 식입니다. 우선 버튼 입력(On Button Input)을 찾아서 유닛을 만듭니다. 버튼 이름은 인풋 시스템 매니저에 기록된 그대로 쓰거나, 혹은 온 버튼 인풋에 있는 키보드, 마우스, 게임패드 옵션에서 원하는 키를 찾아서 설정하면 되죠. 그 옆에는 오브젝트에 2D 강체 속성을 부여하고, 그 강체에 힘을 가하는 명령어인 Rigidbody2D.AddForce를 찾아서 유닛을 만듭니다. 좌표는 통상적으로 (x, y, z)의 순서인데 2D라 (x, y)만 사용하죠. 또 점프는 수직 운동이니까 수평 방향인 x축은 0으로 하고 수직 방향인 y축으로 어느 정도의 힘을 줄 것인지만 적어주면 됩니다.



▲ 살짝 다르긴 하지만, 비슷한 효과를 내기엔 이 정도면 충분합니다




캐릭터를 좌우로 움직이는 것도 비슷한 방식입니다. 버튼을 입력하면 움직이게 만든다는 기본기는 동일하니까요. 버튼 대신에 축을 기점으로 움직이는 방식을 주로 활용하니, 버튼 대신 GetAxis로 하고 수직 이동이 아닌 수평 이동이라 축 이름은 Horizontal로 설정했습니다. 그 다음에는 Rigidbody2D에, 수평 이동에서 흔히 사용하는 velocity 유닛을 뒀죠. 이동할 때마다 새로운 x, y 좌표를 갖게 되니 벡터2를 새로 설정했습니다.

이동방향이 바뀌면서 캐릭터가 바뀌는 것도 코딩으로 처리하면 함수를 따로 만들고 인용하는 방식을 사용했습니다. 제가 만들고 있는 게임 역시도 Flip()이라는 함수를 따로 만들고 여기에 조건식을 따로 넣어서 각 조건에 해당할 때마다 이미지가 좌우 바뀌게끔 했죠.



▲ 2D 게임은 움직일 때 이미지가 좌우로 바뀌는 것까지 고려해야 해서 조금 복잡합니다






▲ 만약 이 부분이 없다면?



▲ 이렇게 움직이게 될 겁니다

일단 엔진에서 이미지가 좌우로 바뀌게 하려면 스케일의 x축에 -1을 곱해서 값을 전환하는 방식을 씁니다. 함수의 대칭이동과 유사한 원리죠. 그리고 이를 대입해줄 시기는 좌표에서 음수 방향, 즉 왼쪽으로 갈 때입니다. 그래서 속도값이 음수일 때 로컬스케일의 x축에 -1을 곱하도록 유닛을 중간에 넣어서 수식을 만들어나갑니다.

원래 비주얼 스크립팅을 사용해서 만든 것도 아니고, 더 복잡한 로직이 내장된 애셋을 강제로 끌어다가 쓴 것이라 다소 어색하긴 합니다. 하지만 문자열로만 구성된 코드에 비해서 보기도 쉽고, 일일이 타이핑하지 않고 로직을 만든 뒤 연결하는 것만으로도 기본적인 동작은 금방 만들 수 있죠. 물론 정교하게 만들기 위해서는 그만한 노력이 필요하죠. 기계는 말 그대로 시키는 것만 하니, 일일이 다 지시하고 설정해줘야 원하는 결과물을 출력할 수 있으니까요.


거미줄처럼 엉켜버린 로직, 어떻게 풀어야 할까
구조에 대한 이해가 없으면 복잡한 건 만들기 어렵다

"근데 이거 왜 똑같이 안 만들었어요?"

눈썰미가 좋은 분이라면 아마 금방 눈치채셨을 겁니다. 제가 실제로 사용하고 있는 코드와 비주얼 스크립팅의 내용이 동일하지 않다는 점을 말이죠. 사실대로 말하자면, 제가 사용한 코드는 다 제가 만든 게 아니고 유니티 예제로 나온 것들과 여러 곳에서 예제로 든 것들을 따와서 제 나름대로 개조한 것들입니다. 그리고 개발할 때마다 "이거 만들어보고 싶다"라고 해서 어찌저찌 구현한 것들을 덕지덕지 붙이다보니, 말 그대로 스파게티 코드가 따로 없죠.

▲ 원래 만들던 프로젝트는 좀 더 복잡합니다


ChaerimController 스크립트 원문

public class ChaerimController : MonoBehaviour
{
[HideInInspector] public bool facingRight = true;
[HideInInspector] public bool jump = false;

public float moveForce = 365;
public float maxSpeed = 5;
public float jumpForce = 1000f;
public Transform groundCheck;
public int attackDamage = 20;
public int crushDamage = 50;
public float jumpForceBegins = 1000f;
public Transform crushRange;

public Transform Blade;
public GameObject bullet;
public Transform targetingPoint;
public float bulletForce;

public bool shoot = false;
public float chargingTime = 0;
public float chargeReady = 1.5f;

public int jumpCount = 0;

private bool grounded = false;
private Animator anim;
private Rigidbody2D rb2d;

public GameObject slider;
public Slider powerGauge;

void Awake()
{
anim = GetComponent(); // 애니메이터 불러 와
rb2d = GetComponent(); // 리지드바디2D 호출
slider.SetActive(false);
}

void Update()
{
grounded = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Ground"))
|| Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Trap"))
|| Physics2D.Linecast(transform.position, crushRange.position, 1 << LayerMask.NameToLayer("Trap"))
|| Physics2D.Linecast(transform.position, crushRange.position, 1 << LayerMask.NameToLayer("Ground"))
|| Physics2D.Linecast(transform.position, crushRange.position, 1 << LayerMask.NameToLayer("Block"));

if (Input.GetButtonDown("Jump"))
{
if (jumpCount <= 2)
{
jump = true;
rb2d.velocity = Vector2.zero;
rb2d.AddForce(new Vector2(0f, jumpForce));
jumpCount += 1;


}

if (jumpCount > 2)
{
jump = false;
jumpForce = 0;
}
}

float h = Input.GetAxis("Horizontal");
bool fire = Input.GetButton("Fire1");
bool fire2 = Input.GetButtonDown("Fire2");
bool charging = Input.GetKey(KeyCode.LeftShift);

anim.SetFloat("Charging", chargingTime);
anim.SetFloat("Speed", Mathf.Abs(h));

if (Input.GetKey(KeyCode.LeftShift))
{
slider.SetActive(true);
chargingTime += Time.deltaTime;
powerGauge.value = chargingTime / chargeReady;

if (fire && chargingTime < chargeReady)
{
chargingTime = 0;
}
}

if (fire2)
{
chargingTime = 0;
slider.SetActive(false);
}

if (charging == false)
{
chargingTime = 0;
slider.SetActive(false);
}

if (Mathf.Abs(h * rb2d.velocity.x) < maxSpeed)
rb2d.AddForce(Vector2.right * h * moveForce);

if (Mathf.Abs(rb2d.velocity.x) > maxSpeed)
rb2d.velocity = new Vector2(Mathf.Sign(rb2d.velocity.x) * maxSpeed, rb2d.velocity.y);

if (h > 0 && !facingRight)
Flip();

else if (h < 0 && facingRight)
Flip();

anim.SetBool("Jump", jump);
anim.SetBool("Fire", fire);
anim.SetBool("Fire2", fire2);

if (grounded)
{
anim.SetTrigger("Grounded");
jumpCount = 1;
jumpForce = jumpForceBegins;
jump = false;
}
}

void Flip()
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}

void Shot()
{
shoot = false;
GameObject bulletClone;

Vector2 direction = targetingPoint.transform.position - gameObject.transform.position;
direction.Normalize();

bulletClone = Instantiate(bullet, Blade.transform.position, Blade.transform.rotation) as GameObject;
bulletClone.GetComponent().AddForce(direction * bulletForce);

chargingTime = 0;
slider.SetActive(false);
}
}



그런 건 차치하더라도, 비주얼 스크립팅도 하다보면 비슷한 문제에 봉착하게 됩니다. 각종 단어와 용어, 부호, 기호를 유닛과 노드로 간단하게 치환했다고 하지만, 말 그대로 치환한 것이지 총량 자체가 바뀐 게 아니니까요. 복잡한 동작을 만들수록 필요로 하는 유닛과 노드도 많아지고, 그걸 어떻게 연결해야 할까 고민해야 합니다.

더군다나 거의 모든 요소를 시각화해서 유닛으로 드러내는 형태다보니, 조금만 수식이 복잡해져도 유닛과 노드 수가 상당히 많아서 정리하기가 까다롭다는 것도 비주얼 스크립팅의 문제죠. 앞서 캐릭터를 움직이는 수식이 코드로 짠 것과 다소 다른데, 그렇게 짠 이유는 if 조건문을 유닛과 노드로 일일이 다 만들거나 연결하지 못해서 임시방편으로 만든 것이기 때문입니다.

때로는 수식으로 간단하게 짤 수 있는 것도, 유닛과 노드로 일일이 다 구축해야 해서 분량이 방대해지는 것도 또다른 문제죠. 덧셈식이나 곱셈도 기호를 쓰는 걸로 끝나는 게 아니라 일일이 Add, Multiply 유닛을 만들고 거기에 숫자를 유닛으로 만들어서 연결하는 방식이죠. 그러다보니 유닛 수가 많아질수록 연결고리가 더 복잡해질 수밖에 없죠.



▲ 이걸 쓰는데



▲ 이만큼의 연결고리가 필요합니다

그나마 고리를 파악해서 수정하기는 쉽다는 이점도 있고, 오류가 난 유닛은 빨간색으로 바뀌기 때문에 어떤 문제가 발생했는지 원인 분석도 쉽습니다. 하지만 하다보면 거미줄처럼 얼기설기 얽히는 건 마찬가지고, 그걸 어떻게 풀어서 써내려갈지 구조 자체를 이해할 필요가 있다는 기본 자체는 변하지 않았죠.



▲ 그래도 어느 부분에서 오류가 났나 확실하게 볼 수 있습니다


"게임이 이렇게 돌아가는구나"를 보여주는 도구, 비주얼 스크립팅
코드 이해도를 검증해보고, 간단하게 게임도 만들어보고

장인은 도구를 따지지 않는다는 말처럼, 결국 게임 개발 역시도 도구보다는 개발자의 실력, 이해도가 완성도를 좌우합니다. 앞서 보신 것처럼, 간편하게 해줄 수 있는 도구가 있어도 이해도가 떨어지면 결과물은 썩 좋지 못하죠.

그렇다고 해서 도구의 발전을 폄훼하는 일은 바람직하지 않습니다. 도구가 발전하면서 작업과정이 효율적으로 바뀌거나, 혹은 혹은 선택의 폭이 한 층 더 넓어지곤 했으니까요. 비주얼 스크립팅은 그중 후자라고 보면 되겠습니다. 코딩을 전혀 들어보지 못해서 막막해하던 사람에게 접근해볼 수 있는 길을 만들어준 셈이니까요.



▲ 코드를 시각화시킨 만큼, 이를 컴파일하는데 시간이 추가로 소모되는 것도 단점 중 하나죠

물론 비주얼 스크립팅 역시도 근간이 코드로 되어있는 만큼, 그 로직에서 자유롭지는 않습니다. 앞서 보셨던 것처럼 수식과 기초적인 언어 정도는 알아둬야 하죠. 또 하나, 어설프게 프로그래밍을 배웠다가 비주얼 스크립팅으로 넘어가면 Get과 Set 등 기본적인 것도 일일이 구분해서 유닛을 만들어야 하는 것에 다소 당황스럽기도 하죠. 일부 수식은 유닛과 노드가 비교적 많이 필요해서, 코드로는 간단하게 짤 것도 덕지덕지 붙여서 만들어야 하는 것도 있습니다.

하지만 통상의 프로그래밍과 달리, 비주얼 스크립팅은 개념을 몰라도 어떤 구조인지는 눈으로 보고 파악할 수 있다는 이점이 있습니다. 일반적인 스크립트는 문자로 되어있지만, 비주얼 스크립팅의 그래프는 다양한 유닛과 노드, 그리고 색상으로 시각화했기 때문이죠. 말 그대로 단순 글자만 써진 문서와 그래프의 가시성 차이라고 할까요.



▲ 논리의 흐름뿐만 아니라, 어떤 내용이 어디에 작용하는지도 바로 눈으로 확인할 수 있습니다

물론 안에 담긴 내용이나, 혹은 그 표현 방식 자체가 인간의 언어 체계와 다소 다르다보니 간단하게 표현했다고 해도 이해하는 것 자체가 쉽지만은 않습니다. 다만 접근 자체가 조금 더 쉽다는 점, 그리고 어떤 논리로 돌아가는지 눈으로 직접 확인하면서 설계할 수 있다는 점에서 비주얼 스크립팅은 확실히 매력있는 도구라 할 수 있습니다. 코딩을 어정쩡하게 배운 입장에서 살펴보면, 그간 기본기가 얼마나 부실했나도 재확인할 수 있는 계기이기도 하고요.

2020년도 어느 덧 마무리되고, 2021년이 다가오고 있습니다. 새해에는 항상, 오래 가지는 않더라도 한 가지 다짐은 꼭 하고는 합니다. 그 중에 게임 개발 과정을 배워보는 것이 여러 해 동안 묵혀둔 숙원이었던 분도 있을 겁니다. 그러다가 꼭 딱딱하고 어려워보이는 프로그램 언어 때문에, 아예 손을 댈 엄두조차 못내는 분도 있을 것이고요.

첫 술에 배부르지 않고 무조건 쉽다고 할 수는 없지만, 차근차근 논리부터 이해할 수 있는 비주얼 스크립팅으로 접근해보는 것도 하나의 방법이라 하겠습니다. 실제 교육 현장에서도 활용하고 있고, 또 간단한 무언가는 몇 번 훑어보는 것만으로도 충분히 만들 수 있으니, 비주얼 스크립팅 한 번 접해보시는 것은 어떨까요?

댓글

새로고침
새로고침

기사 목록

1 2 3 4 5