게임 엔진에서 하드 코딩을 피하는 방법


22

내 질문은 코딩 질문이 아닙니다. 일반적으로 모든 게임 엔진 디자인에 적용됩니다.

하드 코딩을 어떻게 피합니까?

이 질문은 생각보다 훨씬 깊습니다. 예를 들어, 작동에 필요한 파일을로드하는 게임을 실행 load specificfile.wad하려면 엔진 코드 와 같은 말을 어떻게 피 합니까? 또한 파일이로드 될 때 어떻게 말하지 load aspecificmap in specificfile.wad않습니까?

이 질문은 거의 모든 엔진 설계에 적용되며 가능한 적은 엔진을 하드 코딩해야합니다. 그것을 달성하는 가장 좋은 방법은 무엇입니까?

답변:


42

데이터 기반 코딩

언급 한 모든 것은 데이터에 지정할 수있는 것입니다. 왜로드 aspecificmap합니까? 게임 구성에 따르면 플레이어가 새 게임을 시작할 때 첫 번째 수준이거나 방금로드 한 플레이어의 저장 파일에있는 현재 저장 지점의 이름이기 때문입니다.

어떻게 찾 aspecificmap습니까? 맵 ID와 디스크상의 리소스를 나열하는 데이터 파일에 있기 때문입니다.

하드 코딩을 피하기 위해 합법적으로 어렵거나 불가능한 "소규모"리소스 세트 만 있으면됩니다. 약간의 작업으로, 이것은 하나의 하드 코딩 된 기본 자산 이름 등 으로 제한 될 수 있습니다 main.wad. 이 파일은 명령 행 인수를 게임 aka에 전달하여 런타임시 잠재적으로 변경 될 수 있습니다 game.exe -wad mymain.wad.

데이터 중심 코드 작성은 몇 가지 다른 원칙에 의존합니다. 예를 들어, 시스템이나 모듈이 특정 자원을 요구하지 않고 대신 해당 종속성을 반전시킬 수 있습니다. 즉, 초기화 코드에 DebugDrawer로드 하지 마십시오 debug.font. 대신 DebugDrawer초기화 코드에서 리소스 핸들을 가져 오십시오. 이 핸들은 메인 게임 구성 파일에서로드 될 수 있습니다.

코드베이스의 구체적인 예로, 리소스 데이터베이스에서로드되는 "전역 데이터"개체가 있습니다 (기본적으로 ./resources폴더이지만 명령 줄 인수로 오버로드 될 수 있음). 이 전역 데이터의 리소스 데이터베이스 ID는 코드베이스에서 유일하게 필요한 하드 코딩 된 리소스 이름입니다 (프로그래머가 게으 르기 때문에 다른 경우도 있지만 일반적으로 결국 수정 / 제거합니다). 이 글로벌 데이터 오브젝트는 구성 데이터를 제공하는 것이 유일한 목적인 구성 요소로 가득합니다. 구성 요소 중 하나는 여러 가지 구성 항목 중에서 모든 기본 UI 리소스 (글꼴, Flash 파일, 아이콘, 현지화 데이터 등)에 대한 리소스 핸들을 포함하는 UI Global Data 구성 요소입니다. UI 개발자가 기본 UI 자산의 이름을 /ui/mainmenu.swf에서/ui/lobby.swf그들은 단지 그 전역 데이터 참조를 업데이트합니다. 엔진 코드를 전혀 변경할 필요가 없습니다.

이 전역 데이터를 모든 용도로 사용합니다. 모든 재생 가능한 캐릭터, 모든 레벨, UI, 오디오, 핵심 자산, 네트워크 구성, 모든 것. ( 모든 것이 아니라 다른 것들도 수정해야 할 버그입니다.)

이 접근법에는 다른 많은 장점이 있습니다. 우선, 자원 포장 및 번들링이 전체 프로세스에 필수적입니다. 엔진의 하드 코딩 경로는 또한 동일한 경로가 게임 자산을 패키지화하는 스크립트 나 도구에 하드 코딩되어야한다는 의미입니다. 그러면 해당 경로가 동기화되지 않을 수 있습니다. 그 대신 단일 코어 자산 및 참조 체인에 의존하여 단일 명령으로 자산 번들을 구축하고 bundle.exe -root config.data -out main.wad필요한 모든 자산이 포함됨을 알 수 있습니다. 또한 번 들러는 리소스 참조를 따르기 때문에 필요한 자산 포함 하고 프로젝트 수명 동안 필연적으로 누적되는 모든 남은 보풀을 건너 뛸 것입니다. 가지 치기를위한 보풀).

이 모든 것의 까다로운 코너 사례는 스크립트에 있습니다. 엔진을 데이터 중심으로 만드는 것은 개념적으로 쉽지만 스크립트가 데이터로 간주되어 리소스 경로를 무차별 적으로 사용하는 것이 허용되는 매우 많은 프로젝트 (AAA에 취미)를 보았습니다. 하지마 Lua 파일에 리소스가 필요하고 함수를 호출하는 textures.lua("/path/to/texture.png")경우 자산 파이프 라인은 스크립트가 /path/to/texture.png올바르게 작동해야하고 텍스처가 사용되지 않고 불필요하다고 생각할 수 있다는 것을 알면서 많은 문제를 겪게 됩니다. 스크립트는 다른 코드와 같이 처리해야합니다. 리소스 나 테이블을 포함하여 필요한 모든 데이터는 엔진과 리소스 파이프 라인이 종속성을 검사 할 수있는 구성 항목에 지정해야합니다. "로드 스크립트 foo.lua" 라고 표시된 데이터는 "foo.lua예를 들어 스크립트가 적을 무작위로 스폰하는 경우 가능한 적의 목록을 해당 구성 파일에서 스크립트로 전달합니다. 엔진은 적을 레벨로 미리로드 할 수 있습니다 ( 가능한 스폰의 전체 목록을 알고 있기 때문에 리소스 파이프 라인은 모든 적을 게임에 번들로 묶는 것을 알고 있습니다 (구성 데이터에 의해 명확하게 참조되기 때문에) 스크립트가 경로 이름의 문자열을 생성하고 load함수를 호출하는 경우 엔진과 리소스 파이프 라인은 스크립트가로드 할 자산을 구체적으로 알 수있는 방법이 있습니다.


좋은 답변, 매우 실용적이며 사람들이 이것을 구현할 때 발생하는 함정과 오류를 설명합니다! +1
whn

+1. 구성 데이터를 포함하는 리소스를 가리키는 패턴을 따르는 것이 모딩을 활성화하려는 경우에도 매우 유용합니다. 게임을 수정하는 것은 훨씬 더 어렵고 위험하므로 자신 만의 데이터 파일을 만들지 않고 원본 데이터 파일을 변경해야합니다. 우선 순위가 정의 된 여러 파일을 가리킬 수 있다면 더욱 좋습니다.
Jeutnarg

12

일반적인 기능에서 하드 코딩을 피하는 것과 같은 방법입니다.

매개 변수를 전달하고 정보를 구성 파일에 보관합니다.

이러한 상황에서 엔진 작성과 클래스 작성간에 소프트웨어 엔지니어링에는 전혀 차이가 없습니다.

MgrAssets
public:
  errorCode loadAssetFromDisk( filePath )
  errorCode getMap( mapName, map& )

private:
  maps[name, map]

그런 다음 클라이언트 코드 는 자산 파일의 위치와 포함 된 맵을 알려주는 정보가 들어 있는 "마스터"구성 파일 ( 파일 은 하드 코드되거나 명령 행 인수로 전달됨)을 읽습니다 .

거기에서 모든 것은 "마스터"구성 파일에 의해 구동됩니다.


1
그렇습니다. 맞춤형 논리를 가져 오는 일종의 메커니즘이 있습니다. 사용자가 엔진의 핵심 기능을 확장하기 위해 등의 C #, 파이썬 같은 언어를 포함시켜 수 있습니다 정의 기능
qCring

3

나는 다른 답변을 좋아하기 때문에 약간 반대가 될 것입니다. ;)

데이터에 대한 지식을 엔진에 코딩하는 것을 피할 수 없습니다. 정보의 출처에 관계없이 엔진은 정보를 찾아야합니다. 그러나 실제 정보 자체를 엔진에 인코딩하지 않아도됩니다.

"순수한"데이터 중심 접근 방식을 사용하면 초기 구성을로드하는 데 필요한 명령 줄 매개 변수를 사용하여 실행 파일을 시작할 수 있지만 해당 정보를 해석하는 방법을 알기 위해서는 엔진을 코딩해야합니다. 구성 파일은 JSON 경우 예, 당신은 엔진을 찾기 위해 알고 있어야합니다 하드 코드로는, 예를 찾아 변수를 필요로 "intro_movies"하고 "level_list"등등.

그러나 "잘 구성된"엔진은 구성 데이터와 참조하는 데이터를 바꾸는 것만으로도 다양한 게임에서 작동 할 수 있습니다.

따라서 Mantra는 최소한의 노력으로 변경 작업을 수행 할 수 있도록 하드 코딩을 피하는 것이 아닙니다.

전적으로 지원하는 데이터 파일 접근 방식과 대비하기 위해 데이터를 엔진으로 컴파일하는 것이 좋습니다. 그렇게하는 "비용"이 더 낮 으면 실질적인 피해는 없습니다. 당신이 그것에 대한 유일한 사람이라면 나중에 파일 처리를 연기하고 반드시 자신을 망칠 필요는 없습니다. 처음 몇 개의 게임 프로젝트에는 게임 자체에 하드 코딩 된 큰 데이터 테이블 (예 : 무기 목록 및 다양한 데이터)이있었습니다.

struct Weapon
{
    enum IconID icon;
    enum ModelID model;
    int damage;
    int rateOfFire;
    // etc...
};

const struct Weapon g_weapons[] =
{
    { ICON_PISTOL, MODEL_PISTOL, 5, 6 },
    { ICON_RIFLE, MODEL_RIFLE, 10, 20 },
    // etc...
};

따라서이 데이터를 참조하기 쉬운 곳에 놓고 필요에 따라 쉽게 편집 할 수 있습니다. 이상적인 것은 이런 것들을 일종의 구성 파일에 넣는 것이지만 구문 분석 및 번역과 모든 재즈를 수행해야하며 구조 간 참조를 연결하면 실제로 원하지 않는 추가 고통이 될 수 있습니다 다루다.


json을 파싱하는 것은 그리 어렵지 않습니다. 관련된 유일한 "비용"은 학습입니다. (특히, 적절한 모듈 또는 라이브러리를 사용하는 법을 배우십시오. Go는 예를 들어 훌륭한 JSON 지원을 제공합니다.)
Wildcard

굉장히 어렵지는 않지만 단지 학습 그 이상으로해야합니다. 예를 들어 기술적으로 JSON을 구문 분석하는 방법을 알고 다른 많은 파일 형식에 대한 파서를 작성했지만 타사 솔루션을 찾아 설치하거나 종속성을 확인하고 빌드하는 방법을 알아야합니다. 하지 않는 것보다 더 많은 시간이 걸립니다.
dash-tom-bang

4
모든 것은 그 일을하지 않는 것보다 더 많은 시간이 걸립니다. 그러나 필요한 도구는 이미 작성되었습니다. 게임을 작성하기 위해 컴파일러 설계 하거나 기계 코드를 사용하지 않아도되는 것처럼작업중인 플랫폼 의 언어 배워야합니다 . 따라서 JSON 파서를 사용하는 법도 배우십시오.
와일드 카드

당신의 주장이 무엇인지 잘 모르겠습니다. 이 답변에서 저는 YAGNI를 옹호합니다. 당신이 도움이되지 않는 무언가를하는 데 시간을 낭비하거나 낭비 할 필요가 없다면 그렇게하지 마십시오. 그것에 시간을 보내고 싶다면 훌륭합니다. 나중에 시간을 보내야 할 수도 있지만 그렇지 않을 수도 있지만 실제로 게임을 만드는 작업에서 방해가 될 수 있습니다. 게임 개발은 사소한 일입니다. 게임을 만드는 데 필요한 모든 단일 작업은 간단합니다. 대부분의 게임에는 백만 개의 간단한 작업이 있으며 책임있는 개발자는 해당 목표에 가장 빨리 도달하는 게임을 선택합니다.
dash-tom-bang

2
사실, 나는 당신의 대답을 찬성했습니다. 실제 논쟁 은 없습니다 . JSON 을 구문 분석하는 것이 어렵지 않다는 것을 알고 싶었 습니다. 다시 읽어 보면, 나는 대부분 스 니펫에 응답하고 있다고 생각 하지만 "그런 다음 구문 분석과 번역 및 모든 재즈를 수행해야합니다." 그러나 저는 개인 프로젝트 게임 및 YAGNI에 동의합니다. :)
와일드 카드
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.