Unity 검색

예측할 수 없는 즐거움: 무작위화가 게임 설계에 주는 이점

2022년 4월 11일 게임 | 10 분 소요
lowpoly image of two people standing on grid outlined plane
lowpoly image of two people standing on grid outlined plane
다루는 주제
공유

Is this article helpful for you?

Thank you for your feedback!

플레이어가 게임을 계속 재미있게 플레이할 수 있도록 무작위 요소를 추가하는 방법을 알아보세요. 이 글은 시스템 설계에 대한 Christo Nobbs의 시리즈 중 두 번째 글이며, Unity 게임 디자이너 플레이북에도 소개되었습니다. 이 전자책을 읽고 Unity에서 게임플레이를 프로토타이핑, 제작, 테스트하는 방법을 자세히 살펴보세요.

이전 블로그에서 Christo는 디자이너가 자신의 게임에서 흥미롭고 예측 불가능한 게임플레이를 구현하는 시스템을 어떻게 만들 수 있는지 설명했습니다. 이 글에서는 예시를 들어 무작위화를 설정하는 방법을 자세히 설명합니다.

Image with light purple background with the GamePlay & Design Ebook cover
무작위화 및 다른 관련 주제에 대해서는 무료 전자책인 Unity 게임 디자이너 플레이북에서 확인할 수 있습니다.

플레이어에게 시각적인 힌트 남기기

비주얼 프롬프트를 사용하여 플레이어가 게임 월드의 시스템을 더 탐색하도록 유도하고, 플레이어를 위한 특별한 경험을 고안할 수 있습니다. 앞서 게시한 생태계를 만드는 시스템: 새로운 게임 디자인에서는 모든 요소가 나무로 제작되고 구축되며, 예측이 불가능한 화재 전파 시스템을 구현할 수 있는 샌드박스 공간에 대해 간략하게 이야기한 바 있습니다. 플레이어가 도끼로 나무를 벨 수 있다고 가정하면서, 이 예시를 들어 이야기하겠습니다.

플레이어가 서 있는 랜드스케이프가 평평하다고 가정해 보세요. 플레이어는 나무를 찍어 넘길 때 나무가 쓰러질 방향을 알 수 없습니다. 만약 나무 중 일부가 '고목'이라면, 다시 말해서 죽었지만 간신히 서 있었던 나무라면 어떨까요? 언제라도 쓰러질 수 있겠지요. 게임에서 이처럼 예측 불가능한 요소는 플레이어를 놀라게 하고 플레이 환경을 더 역동적으로 만듭니다. 

이 월드에서는 쓰러지는 나무가 위험하다는 사실을 플레이어가 알아채고 방심하지 않도록, 시각적인 힌트를 얼마든지 추가할 수 있습니다. 이러한 힌트는 플레이어가 위험한 고목과 건강하고 덜 위협적인 나무를 구분하는 데 도움이 됩니다. 플레이어는 위험을 따져 보는 방법을 선택해야 할 것입니다. 이미 쓰러져 있는 나무에서 장작을 찾고, 죽은 나무가 쓰러지면서 그로 인해 다치는 일이 없도록 하는 것이 더 현명할까요?

디자이너는 별안간 나무가 쓰러지고, 불이 붙고, 그로 인해 혼란스러운 상황이 이어지는 등의 체계적인 연쇄 반응이 발생하는 위치를 매핑하고, 플레이어가 그러한 이벤트를 발생시키도록 게임에 해당 반응을 설계해야 합니다.

Unity.Engine.Random으로 예상치 못한 요소 생성하기

Unity의 Random 스크립팅 클래스는 게임에서 무작위 데이터를 생성하기 위한 방법을 제공하는 정적 클래스입니다. 이 클래스는 .NET Framework 클래스인 System.Random과 이름이 같으며 비슷한 용도로 사용되지만, 몇 가지 주요 방식에서 차이가 있습니다. 예를 들면 System.Random보다 20%~40% 빠릅니다. 

다음은 Random 클래스로 사용할 수 있는 정적 프로퍼티와 메서드입니다. 

정적 프로퍼티

  • insideUnitCircle: 반지름이 1.0인 원의 내부 또는 표면에 있는 임의의 점을 반환(읽기 전용)
  • insideUnitSphere: 반지름이 1.0인 구의 내부 또는 표면에 있는 임의의 점을 반환(읽기 전용)
  • onUnitSphere: 반지름이 1.0인 구의 표면에 있는 임의의 점을 반환(읽기 전용)
  • Rotation: 임의의 회전을 반환(읽기 전용)
  • rotationUniform: 임의의 회전을 균일 분포로 반환(읽기 전용)
  • state: 난수 생성기의 전체 내부 상태를 가져오거나 설정
  • value: [0.0..1.0] 내에서(범위 포함) 임의의 플로트를 반환(읽기 전용)

정적 메서드

  • ColorHSV: HSV 및 알파 범위에서 임의의 색상을 생성
  • InitState: 시드로 난수 생성기 상태를 초기화
  • Range: [minInclusive..maxInclusive] 내에서(범위 포함) 임의의 플로트를 반환

이전 블로그에서는 디자인 레버의 역할과 ScriptableObject를 사용하여 해당 값을 저장하는 방법에 대해 알아보았습니다. Unity의 Random.Range를 사용하여 이러한 값을 적절하게 설계된 범위로 교체할 수 있습니다. 이 범위는 [minInclusive..maxInclusive] 내에서(범위 포함) 임의의 플로트를 반환합니다. minInclusive 및 maxInclusive를 모두 포함하여 이 사이에 주어진 플로트 값이 무작위 표본 천만 개당 한 번씩 나타날 것입니다.

이 방식을 사용하면 결과에 대해 설정된 범위에서 값을 얻을 수 있습니다. 여러 범위를 테스트해서 게임플레이 목표에 맞는 범위를 찾아야 하지만, 반드시 설정했던 정의된 범위에 맞게 설계해야 합니다.

Image of a white robot watching tree branch fires in a forest
게임 디자이너 시리즈의 첫 번째 블로그에서 모듈 시스템에 대해 알아보세요.

한 치 앞도 내다볼 수 없는 숲속의 모험

무작위 요소를 활용하면 몰입도를 높일 수 있습니다. 예를 들어, 각 나무의 HP가 100으로 고정되어 있고 도끼로 한 번 찍을 때마다 나무의 HP가 25 포인트 깎인다고 하겠습니다. 이 과제는 곧 예측이 가능하게 되고, 따라서 지루해지겠죠. 나무의 HP를 76~100으로 설정하더라도, 어떤 나무든 도끼로 네 번 찍으면 쓰러질 것입니다. 하지만 범위를 줄여 보면, 예를 들어 75~76으로 범위를 좁히면 3~4번 찍어야 나무가 쓰러지기 때문에 게임플레이 결과가 더 다양해집니다.

이 시나리오를 더 흥미롭게 만드는 또 다른 방법은 HP 바 대신 명확한 시각적 신호로 HP의 변화를 표시하는 것입니다. 그렇게 하면 플레이어는 게임플레이를 진행하면서 도끼로 대략 몇 번 찍으면 나무가 쓰러지는지 알 수 있습니다. 시각적 신호를 활용하면 원하는 게임플레이에 맞게 균형을 맞추고 조정할 수 있는 제한적인 예측 불가능성을 추가할 수 있습니다. 고정 값 대신 Random 클래스를 사용해서 지루한 과제를 즐거운 놀이로 바꿔 보세요.

이 예시에 덧붙이자면, 도끼를 한 번 휘두를 때마다 HP가 15~25 범위에서 임의의 값만큼 줄어들도록 지정하는 것도 가능합니다. 그러면 나무를 자르는 데 얼마나 많은 도끼질이 필요할지 플레이어가 쉽게 예측할 수 없습니다. 나무가 언제 쓰러지게 될지 가늠하려면 시각적 신호, 즉 나무에서 날아오는 덩어리들의 크기나 나무 몸통에 생긴 틈, 떨어지는 나뭇가지, 음향 효과 등과 같은 여러 단서에 더 의존해야 할 것입니다.

플레이어는 각 나무가 쓰러지는 시점을 정확히 알 수는 없겠지만, 시간이 지나며 점점 더 많은 나무를 베어 넘기면서 학습을 통한 추측에 따라 결과적으로 생존 가능성을 높일 수 있습니다.

Screenshot of Random Hit Damage settings in the Inspector
Unity 게임 디자이너 플레이북을 읽고 인스펙터(Inspector)에서 레버를 만들고 필드를 시각화하는 방법에 대한 예시를 찾아 보세요. 이 이미지에서 Damage Intensity는 최종 Damage Value에 영향을 미치는 임의의 값이며 Range 속성과 함께 표시됩니다.

무작위화의 목표는 플레이어가 위험을 계산하고 결과를 관리해야 하는 예측 불가능한 도전을 선사하는 것입니다.

Random 클래스 사용법에 대한 몇 가지 예를 더 살펴보겠습니다.

카드 게임에서의 무작위화 가중치 설정

AI와 대결하는 카드 게임에서 AI가 오로지 플레이어의 행동을 기반으로 플레이한다고 가정해 보세요. 이 이벤트는 매번 동일한 결과를 반환하므로 무작위화 요소 없이 빠르게 예측 가능한 게임이 될 것입니다.

확률을 50%로 설정하는 것도 무작위화라고 하기에는 너무 단순해서 플레이어가 금세 파악하게 됩니다. 대신 플레이어의 행동을 기반으로 무작위 요소를 레이어로 추가해 보세요. 이렇게 하면 카드 풀에 있는 두 장의 카드 중에서 선택하거나 기존 풀에서 다수의 카드 중 특정 카드를 선택하는지 여부보다 역동적인 플레이를 할 수 있는 복잡한 시스템이 만들어집니다.

적이 다른 카드보다 특정 카드를 선호하도록 함으로써 카드 테이블의 난이도를 높일 수 있습니다. 예를 들어 카드마다 주어진 가치 또는 공격 전 카드 구성의 완성도에 따라 적이 어떤 결정을 내릴지 예측할 수 있습니다. 보스 카드는 다른 파워 카드와 함께 플레이하면 대미지를 배가할 수 있으므로, 적은 특정 종류의 파워 카드가 생길 때까지 기다리며 플레이어의 게임을 더 어렵게 만듭니다. 보스 카드가 특정한 다른 파워 카드와 함께 플레이될 확률을 높이거나 낮춤으로써 그러한 구성에 '가중치'를 추가할 수 있습니다.

Image of Heartstone card game
블리자드 엔터테인먼트의 하스스톤은 지속적으로 큰 인기를 누리고 있는 카드 게임입니다. 2014년에 출시되었으며 Unity로 제작되었습니다.

펄린 노이즈

게임에 다양한 형태로 무작위화를 활용할 수 있습니다. 펄린(Perlin) 노이즈가 좋은 예로, 자연스러운 특성을 가지고 있으며 시드에서 그래디언트 노이즈를 생성합니다. 시네머신에 사용해서 3인칭 팔로우 카메라에 더 자연스러운 카메라 효과를 만들어 보세요.

Screenshot of the Perlin noise algorithm in Cinemachine
시네머신에 펄린 노이즈 알고리즘을 활용해서 더 자연스러운 느낌의 카메라 움직임을 만드세요.

펄린 노이즈를 사용해 보려면 Mathf.PerlinNoise 사용법에 대한 기술 자료와 함께 에셋 스토어에서 Starter Assets – Third-person Character Controller 또는 Gaia 팩을 확인하세요.

Image of the Perlin noise sampled texture (blurred black and white spots)
펄린 노이즈 샘플 텍스처를 살펴보세요. 노이즈가 각 지점에서 완전히 임의의 값을 갖는 대신, 패턴 전체에 걸쳐 점점 증가 및 감소하는 값을 갖는 '파동'으로 구성됩니다. 이러한 노이즈는 텍스처 효과 및 애니메이션, 터레인 하이트맵 생성 등의 기본 요소로 사용할 수 있습니다.

AI 에이전트 공격

한 인터뷰에서 Bungie Studios의 Halo 2 리드 엔지니어인 Chris Butcher는 게임에 사용된 AI에 대해 다음과 같이 말했습니다. “예측할 수 없는 무언가를 만드는 것이 목표는 아닙니다. 여러분이 원하는 것은 플레이어가 특정 입력을 제공할 수 있을 정도의 일관성을 지닌 인공 지능이죠. 플레이어는 자신의 행동에 AI가 특정한 방식으로 반응할 것이라 기대할 수 있습니다.”

Image of a green robot standing in the middle of a lowpoly ground outside of cubed buildings
위 에셋은 에셋 스토어의 Starter Assets – Third-person Character Controller 팩에서 사용할 수 있습니다.

그러한 점에서, AI 에이전트를 어떻게 설정하면 게임을 지속적으로 예측 불가능하면서도 활기 있게 유지할 수 있을까요?

이를 실험하는 한 가지 방법은 AI 에이전트를 주어진 지점으로 옮길 수 있는 툴인 A* Pathfinding Project Pro 같은 에셋 스토어의 AI 툴과 Starter Assets를 함께 사용하는 것입니다.

AI 에이전트가 플레이어를 향해 이동하면 플레이어는 공격을 받을 것이라고 예상할 수 있습니다. 하지만 대신 대화를 시작하면 어떻게 될까요? 더 활기찬 느낌을 주기 위해 주변을 돌아다니며 공간에 어우러지는 NPC를 더 추가하는 것은 어떨까요? 이러한 NPC가 차례대로 하나씩 지점을 선택하도록 하거나, 나아가 Random 클래스를 사용하는 규칙 세트를 기반으로 논리적인 지점을 선택하도록 할 수도 있습니다.

활과 화살로 플레이어를 쏘는 AI 에이전트가 있다고 가정해 보겠습니다. AI 에이전트는 최대 발사 범위가 안타깝게도 10m이므로, 캐릭터와 일정 거리를 유지해야 합니다. AI는 플레이어 앞에서 10m 떨어진 위치에서 화살을 쏩니다. 특히 내비메시(NavMesh)에서 같은 위치를 놓고 싸우는 또 하나의 슈터를 추가한다면, 이는 흥미롭거나 이상적인 설정이라고 할 수 없습니다.

게임의 시나리오를 더 흥미롭게 구성하기 위해 적이 플레이어에게 접근해야 하는 영역을 선택하세요. Random.insideUnitCircle을 사용하고, vector2 결과를 X축 및 Z축에 대한 vector3로 전달한 후 양 축에 RandomRange를 활용하여 플레이어 주변의 최소 및 최대 반경이 있는 영역을 구합니다.

Screenshot of script used for an AI agent selecting an attack point near the main character
메인 캐릭터 주변의 공격 지점을 선택하는 AI 에이전트에 사용되는 스크립트
screenshot of design levers in the Inspector
디자인 레버를 사용하면 인스펙터에서 AI 동작을 미세 조정할 수 있습니다.

이 AI 에이전트는 플레이어에게 가깝게 접근하는 대신, 플레이어 주변의 허용 가능한 범위 내에서 위치를 선택해 발사해야 합니다. 최소한의 코딩으로 게임을 더 흥미롭게 만들고 싶다면 이를 모든 AI 에이전트에 적용해 플레이어를 여러 각도에서 공격하도록 하세요. 특정 거리만큼 떨어져 있어야만 에이전트가 공격할 수 있음을 알기 때문에 플레이어는 AI의 행동을 어느 정도 예측할 수 있지만, 어떤 각도에서 공격해 올지 예측할 수는 없습니다.

image of a lowpoly space with a white crosshair over a grey and green circle
빨간 점은 적 에이전트가 이동할 위치를 의미합니다.

AI 에이전트가 근접 공격을 할 수 있도록 하여 이 예시를 확장할 수 있습니다. 동일하게 플레이어로부터 임의의 지점을 사용하되 범위의 반경을 줄여 보세요. 그렇게 하면 AI 에이전트는 공격할 때 공격 풀에서 선택할 수 있습니다.

플레이어는 근접전 대형의 공격을 받을 수 있다는 것을 알고 있습니다. 하지만 어느 방향에서 공격해 올까요? 위에서 아래로 공격해 올까요? 아니면 왼쪽에서 오른쪽으로 크게 휘두르는 공격을 가할까요? 무슨 일이 일어날지 예측하려면 움직임을 통해 힌트를 얻어야 할 것입니다.

플레이어를 동시에 공격하려는 적 NPC가 여럿이라면 이 시나리오는 복잡해질 수 있습니다. 하지만, 예를 들어 유비소프트의 Far Cry 2에서 볼 수 있듯이, 플레이어가 모든 적으로부터 동시에 공격을 받는 것은 아닙니다. 서로 다른 적들이 서로 다른 시간에 무작위로 공격해 올 수 있죠. Game Maker's Toolkit의 이 동영상에서 이 예시와 기타 AI 시나리오에 대해 자세히 알아볼 수 있습니다.

현실 세계에서 수행한 작업의 결과를 게임에 현실적으로 모사하려는 경우, 복잡하고 다면적인 방정식이나 잘 훈련된 ML 에이전트가 필요할 것입니다. 제대로 설계하지 않으면 모든 것이 어색해 보일 수 있고, 구현하는 데 많은 시간이 소요되고 기술적 오버헤드가 발생할 수 있으며, 상황을 파악하면서 균형을 유지하고 제어하는 것이 여전히 어려울 수 있습니다.

하지만 Unity의 Random 클래스를 사용하면, 게임 디자이너는 몰입도를 높여 한층 더 생생하고 실감나는 씬을 더 빠르게 구현할 수 있습니다. 물론 무작위화가 항상 바람직한 것은 아니며, 예측 가능성은 여전히 중요합니다. 그러나 무작위 요소를 적절한 위치에 배치하면, 가장 알맞은 때에 플레이어로 하여금 게임을 계속 플레이하고 싶게 만드는 특별한 경험을 선사할 수 있습니다.

2022년 4월 11일 게임 | 10 분 소요

Is this article helpful for you?

Thank you for your feedback!

다루는 주제
관련 게시물