UDN
Search public documentation:

CodingStandardKR
English Translation
日本語訳
中国翻译

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 홈 > 언리얼스크립트 > 코딩 표준

코딩 표준


개요


에픽에는 코딩에 대해 몇가지 간단한 표준과 규칙이 있습니다. 이 가운데 대부분은 UnrealScript와 관련된 것들이지만, C++ 코딩에도 적용됩니다. 이 문서는 토론이나 진행중인 작업을 위한 것이 아니라, 에픽의 현재 코딩 표준을 반영하는 것이며, 라이선시의 편의를 위해 제공됩니다. 단 엔진에 이 표준이 완전히 반영되지는 않는다는 점에 유의해 주십시오. 저희 코드는 수십만 줄에 달하며, 코드가 새로 추가되거나 옛 코드가 리팩토링될 때마다 이를 업데이트 하고 있습니다.

프로그래머들에게 있어서, 코딩 규칙은 매우 중요합니다. 그 이유 중의 몇 가지 는 다음과 같습니다:

  • 하나의 소프트웨어가 그 수명을 지속하는 동안 들어가는 경비 가운데 80%는 유지 보수비입니다.
  • 원저자가 그 소프트웨어의 수명이 다할 때까지 관리하는 일은 거의 없습니다.
  • 코딩 규칙은 그 소프트웨어를 한층 읽기 쉽도록 해주므로, 엔지니어들은 새 코드를 보다 빨리 그리고 철저하게 이해할 수 있습니다. 저희는 이 프로젝트가 지속되는 동안 틀림없이 새 엔지니어 및 수습사원들을 채용하게 될 것이며, 아마도 엔진에 새로 변경한 사항들을 다음 프로젝트에도 계속 사용하게 될 것입니다.
  • 만일 저희가 mod 커뮤니티 개발자들께 소스 코드를 공개하기로 결정한다면, 이해하기 쉬운 것이기를 바랍니다.
  • 사실 이 규칙 가운데 상당수는 컴파일러간의 호환을 위해 필요한 것이기도 합니다.

클래스 구성: 클래스 이름은 명사입니다


  • 클래스 코멘트 블록
  • 클래스 선언
  • 변수 선언
  • C++ 에 한하여: Static 및 const 변수
  • Public 변수
  • Protected 변수
  • Private 변수
  • cpptext
  • Constructor 및 Destructor
  • BeginPlay( ), Destroyed( ) 등을 포함
  • 기능성에 따라 그룹지어진 메서드와 스테이트(state,상태)
  • 기능성에 따라 그룹지어진, 스테이트-없는(어떤 스테이트와도 연결되어 있지 않은) 유틸리티 메서드
  • 스테이트-없는 메서드 중 한 스테이트에 들어가고 나가는 것과 관련된 것은 반드시 그 스테이트의 바로 앞에 와야 함
  • 만일 스테이트와 연결된 메서드나 자동 스테이트를 위한 스테이트가 있다면, 반드시 이것이 가장 먼저 정의되는 스테이트여야 함
  • defaultproperties

스테이트 구성 : 스테이트 이름은 형용사입니다


  • 스테이트 코멘트 블록 (메서드 코멘트와 유사)
  • 메서드
  • Begin: 코드

메서드 구성: 메서드의 이름은 동사입니다


  • 메서드 코멘트 블록
  • 로컬 변수

변수: C++에 알맞은 타입을 사용!


  • UBOOL - boolean (4 바이트). BOOL은 컴파일되지 않음.
  • TCHAR - character (TCHAR 크기를 추정하지 말 것)
  • BYTE - unsigned byte (1 바이트)
  • SBYTE - signed byte (1 바이트)
  • WORD - unsigned "short" (2 바이트)
  • SWORD - signed "short" (2 바이트)
  • UINT - unsigned int (4 바이트)
  • INT - signed int (4 바이트)
  • QWORD - unsigned "quad word" (8 바이트)
  • SQWORD - signed "quad word" (8 바이트)
  • FLOAT - single precision floating point (4 바이트)
  • DOUBLE - double precision floating point (8 바이트)
  • PTRINT - 포인터를 가질 수 있는 integer (PTRINT 크기를 추정하지 말 것)

코멘트


코멘트는 의사를 전달하는 수단입니다; 의사 전달은 지극히 중요 합니다. 코멘트에 대해서는 다음 몇가지 사항을 명심해야 합니다 (Kernighan & Pike의 The Practice of Programming 에서):

  • 그 자체로서 잘 설명되는 코드를 쓴다:
나쁨 좋음
t = s + l - b; TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;

  • 당연한 것을 장황하게 설명하지 않는다; 코멘트를 하지 말거나 뭔가 도움이 될만한 것을 쓴다:
나쁨 좋음
// iLeaves 증분 // 찻잎이 하나 더 늘어남
Leaves++; Leaves++;

  • 잘못된 코드를 코멘트화 하지 않는다- 다시 쓸 것!
나쁨 좋음
// 총 잎의 수는 큰 잎들과  
// 작은 잎들의 합계에서 그 둘에  
// 모두 해당하는 것의 수를 뺀 것  
t = s + l - b; TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;

  • 코드와 모순되게 하지 않는다:
나쁨 좋음
// iLeaves를 절대 증분하지 말 것! //찻잎이 하나 더 늘어남
Leaves++; Leaves++;

저희의 문서기록 스타일은 C# 프로젝트인 것(은 해당 부분 참고)을 제외하고는 Javadoc 에 기초를 두고 있습니다. 궁극적으로는 저희도 자동 생성 문서에 착수할 것입니다.

다음의 예는 클래스, 상태, 메서드 그리고 변수에 대한 코멘트의 형식을 보여줍니다. 코멘트는 코드를 보충해야 한다는 점을 잊지 마십시오. 코드는 구현을 기록하고 코멘트는 그 의도를 기록합니다. 코드의 의도를 변경한 경우에는 반드시 코멘트를 업데이트하도록 하십시오.

class Tea extends WarmBeverage
   native, perobjectconfig, transient;
/**
 * 이 클래스는 깔끔한 일을 많이 합니다.
 * 다른 클래스랑 같이 깔끔한 일을 더 많이 합니다.
 */

/** 중국의 차 값을 저장. */
var float Price;


/**
 * state Brewing
 * 이 스테이트는 컵에 차가 우러난(Brewing) 상태를 표현.
 * entered: 디폴트 스테이트에서 AddBoilingWater 가 호출.
 * exited: Pour 에서 나오거나, 시간이 너무 경과.
 * (시간 초과시 GettingBitter로 이동)
 */
state Brewing
{

/**
 * Steep은 차를 우려내는데 쓰인 물의 양과 온도를 사용해서
 *차 맛의 델타값을 계산.
 *
 * @param    VolumeOfWater – 차를 우리는데 사용되는 물의 양.
 *           밀리미터 단위의 양수 (체크되지 않음!)
 *
 * @param    TemperatureOfWater – 물의 절대온도(oK).
 *           273 < temperatureOfWater < 380
 *
 * @param    NewPotency – 우려내기가 시작된 후 차의 농도;
 *           0.97에서  1.04 사이이어야 함.
 *
 * @return    차맛의 강도 변화를 반환함.
 *           단위(TTU) per minute.
 *
 */

function float Steep (float VolumeOfWater, float TemperatureOfWater,
                      out float NewPotency)
{
}
}

클래스 코멘트에 포함되는 것은?

  • 이 클래스에서 해결하는 문제에 대한 설명. 이 클래스가 왜 만들어졌는지?
스테이트 코멘트에 포함되는 것은?
  • 이 스테이트에서 해결하는 문제에 대한 설명. 이 오브젝트가 왜 이러한 스테이트를 가지고 있는지. 위 클래스의 순서를 유심히 보십시오. 스테이트 내에 있지 않은 스테이트 관련 함수는 스테이트 코멘트의 바로 앞에 와야 합니다.
함수 코멘트에서 각 부분의 의미는?
  • Brewing::Steep은 Steep 메서드가 Brewing 스테이트 내에 있다는 것을 뜻합니다. 이는 C++ 의 유효범위 해결 연산자를 언리얼스크립트 코멘트에서 사용한 것입니다.
  • 그 다음은 이 함수의 목적입니다. 여기서는 _이 함수에서 해결하는 문제_를 기록합니다. 위에서 말씀드린 대로 코멘트는 _의도_를 기록하고 코드는 구현을 기록합니다.
  • inputs에는 이 메서드로의 입력 파라미터 목록을 기록합니다. 각 파라미터에는 측정 단위, 예상 값의 범위, "불가능한" 값, 그리고 현상태/오류 코드의 의미가 포함되어야 합니다.
  • inputs/outputs (여기서는 필요하지 않아서 포함되지 않음) 에는 이 함수에 의해 변경되는 값을 제공하는 파라미터를 기록합니다. 파라미터를 입력과 출력 모두 로 기록하십시오.
  • outputs 에는 (호출자에 의해 제공된 값이 무시되는) 출력 전용 파라미터들의 목록을 기록합니다. 반환값의 의미는 물론 오류/실패가 발생할 경우 그 값이 어떤 영향을 받는지도 포함되어야 합니다.
  • returns에는 출력 변수에 대한 기록과 같은 방법으로 예상 반환값을 기록합니다.

몇가지 특별 코멘트. 사선(/)과 단어 사이에 여백이 없는 것에 유의 하십시요; 이는 관계없는 코드를 검색할 필요 없이 필요한 것만 쉽게 검색할 수 있도록 합니다.
//@debug 릴리스 전에 삭제될 디버그 구문
//@todo 아직 해야 할 일이 더 있음. 설명 추가.

디버그 코드는 대개 엔진에서 삭제됩니다. 이를 남겨둘 필요가 있다면, 코드가 #if 0 이 아니라 if(0) 블록 안에 싸여 있어야 합니다. 그러면 이 디버그 코드는 컴파일되지만, 최적화에서는 제외됩니다. 코멘트에서 이것이 디버그 코드임을 설명하고, 여러분의 이름 및 이 디버그 코드의 용도를 포함시키십시오.

제3자의 코드 변경:

엔진에서 사용하는 (wxWindows, FaceFX, Novodex 등의) 라이브러리에 코드를 변경할 때마다 반드시, //@UE3 코멘트에 변경 내용과 함께 이유를 설명하도록 하십시오. 이렇게 하면 변경 내용을 해당 라이브러리의 새 버전에 통합하는 것이 쉬워지고, 라이선시가 쉽게 찾아볼 수도 있게 됩니다.

C# 부가 표준


C# 에서 코멘트를 할 때는

/** */
포맷을 사용하지 말고, 코드 도움말 파일을 올바르게 생성하는
///
포맷을 사용하십시오. 아래의 예를 참조하십시요:

namespace UnrealLogging
{
   /// <summary>
   /// 로그되는 메시지의 수준을 나타냄
   /// </summary>
   public enum LogLevel { LOG_Error, LOG_Warning, LOG_Debug };

   /// <summary>
   /// 범용 로깅 인터페이스. 이 인터페이스는 어떤 것이 됐든
   /// 그 타겟 환경에 대한 로깅을 지원하기 위해 구현될 것임.
   /// </summary>
   public interface ILogger
   {
      /// <summary>
      /// 구현된 로깅 지원에 메시지 작성
      /// </summary>
      /// <param name="MsgLevel">작성중인 메시지의 엄격함</param>
      /// <param name="StrMsg">출력할 메시지</param>
      void Log(LogLevel MsgLevel,string StrMsg);

      /// <summary>
      /// 로깅에 적용할 필터링 설정. 이보다
      /// 높은 수준의 메시지는 로그되지 않음.
      /// </summary>
      /// <param name="MaxLevel">작성되는 최대 로그 수준</param>
      void SetFilter(LogLevel MaxLevel);
   }
}

필수 프로젝트 세팅

XML 문서 파일을 유효화 하고, 이를 Perforce에서 최신으로 유지하십시오.

어떠한 코드 기록도 에러로 취급하지 않도록하는 옵션을 유효화 하십시오.

명명 규칙


변수, 함수, 스테이트 그리고 클래스 이름은 뜻이 분명하고, 모호하지 않고, 설명적이어야 합니다. 이름의 범위가 클 수록 잘 설명된 이름의 중요성도 높아집니다. 지나친 약어의 사용을 피하십시오.

변수의 의미에 대한 코멘트를 달 수 있도록, 변수는 한 번에 하나씩만 선언하십시오. 이는 JavaDocs 스타일에서도 요구되는 사항입니다. 변수의 앞에 한 줄 또는 여러 줄의 코멘트를 사용할 수 있으며, 변수를 그룹지을 때 선택적으로 빈 줄을 남겨둘 수 있습니다.

변수의 이름에는 다음의 대문자 사용 형식을사용해야 합니다: ThisIsAGoodVariableName. "thisIsABadName" (일명 camel case) 또는 "this_is_a_bad_name" (언더스코어)을 사용하지 마십시오.

모든 boolean에는 참/거짓을 묻는 질문이 있어야 합니다. bool 을 반환하는 모든 함수들도 마찬가지입니다. 모든 boolean 변수에는접두사 b 가 붙어야 합니다.

/** 킬로그램 단위의 차 무게*/
var float TeaWeight;

/** 찻잎의 수*/
var int TeaNumber;

/** TRUE는 차에서 냄새가 난다는 것을 나타냄*/
var bool bDoesTeaStink;

/** 사람이 읽을 수 없는 차의 FName*/
var name TeaName;

/** 사람이 읽을 수 있는 차의 이름*/
var String TeaName;

구조체 타입은 변수의 끝에 클래스의 이름을 덧붙입니다:

/** 어느 클래스의 차를 사용할 것인지 */
var class<Tea> TeaClass;

/** 차를 따르는 소리 */
var Sound TeaSound;

/** 차의 그림 */
var Texture TeaTexture;

C++ 코드에서는 기본으로 모든 클래스 변수들을 private으로 하십시오. 스크립트와의 상호작용이 요구되는 경우, 즉 XxxClasses.h로부터 생성된 경우에만 변수를 public 으로 하십시오..

procedure (반환값을 가지지 않는 함수)의 이름에는 강한 동사에 이어 목적어를 사용해야 합니다. 메서드의 목적어가 그 메서드가 속해있는 오브젝트 자체인 경우는 예외입니다; 이 경우에는 문맥상으로 목적어가 무엇인지 알 수 있습니다. 사용을 피해야할 이름 중에는 "Handle" 및 "Process"로 시작하는 것들이 포함됩니다; 이 동사들은 애매모호합니다.

Tea SomeT;
SteepTea(SomeT);    // 처리되는 오브젝트 타입의 메서드 이름
SomeT.Pour();       // Tea에서 메서드를 호출; 동사만으로도 충분함

값을 반환하는 함수에는 반드시 반환값에 대한 설명이 있어야 합니다; 이름에서 그 함수가 어떤 값을 반환할 것인지를 분명히 해야 합니다. 이는 특히 boolean 함수에서 중요합니다. 다음에 예로든 두 메서드를 비교해 보십시요:

function bool CheckTea(Tea t) {...} // TRUE가 무엇을 뜻하는지?
function bool IsTeaFresh(Tea t) {...} // 이름에서 TRUE=차가 신선함을 밝힘

스테이트 및 클래스 이름은 대문자로 시작하고 이름의 중간에도 대문자를 사용하여 읽기 쉽도록 해야 합니다. 클래스의 이름은 명사이어야 하며, 스테이트 이름은 현재의 상태를 나타내는 것이어야 합니다 (형용사 등). 스테이트는 절대로 클래스 없이는 사용되지 않으므로, 항상 내재되어 있는 오브젝트가 있습니다.

전달한 변수들은 모두 사용하십시오. 값을 사용하지 않을 경우에는 이를 파라미터 목록에서 제거하거나, 파라미터 타입을 계속 가지고 있되 이를 코멘트 처리하십시요:

void Update(FLOAT DeltaTime, UBOOL /*bForceUpdate*/);

Unreal Script: 참조로 전달된 파라미터 이름에는 접두사 out_ 이 붙어야 합니다:

function PassRefs (out float out_wt, float ht, out int out_x)

Const

C++에서 const를 사용할 수 있다면 꼭 사용하십시오. 특히 함수의 파라미터나 클래스의 메서드에는 const를 사용하는 것이 좋습니다. const는 컴파일러 directive 이기도 하지만 문서화에 도움이 되기도 합니다.

Virtual

부모 클래스의 virtual 함수를 오버라이드하는 파생 클래스에서 virtual 함수를 선언할 때는 반드시 키워드 virtual을 사용해야 합니다. C++ 의 표준에 의하면 'virtual' 의 행위는 상속된 것이기 때문에 이것은 선택적인 것이지만, virtual 함수 선언이 모두 virtual 키워드를 사용하면 코드가 한결 분명해집니다.

실행 블록


중괄호 { }

중괄호 사용에 대한 논쟁은 쓸데없는 일입니다. 에픽에는 오래동안 유지해온, 중괄호를 새 줄에 넣는 이용 패턴이 있습니다. 계속해서 이 패턴을 사용하시기 바랍니다.

if - else

if-else 구문의 각 실행 블록은 중괄호 안에 있어야 합니다. 이는 편집에서의 실수를 방지하기 위해서입니다 - 중괄호가 사용되지 않으면 누군가가 무심히 if 블록에 한 줄의 코드를 추가할 수도 있습니다. 이 줄은 if 표현에 의해 제어되지 않을 것이고, 좋지 않은 결과를 낳을 것입니다. 더 나쁜 것은 조건부로 컴파일된 항목으로 인해 if/else 구문의 조건 비교가 중단되어 버리는 것입니다. 그러므로 항상 중괄호를 사용하십시오.

if (bHaveUnrealLicense)
{
   InsertYourGameHere();
}
else
{
   CallMarkRein();
}

다중 if 구문은 각 else if가 첫 번째 if와 같은 양만큼 들여쓰기 하여 같은 행의 위치에서 시작되도록 하여야 합니다; 이렇게 하면 읽는 이들이 구조를 분명히 파악하게 됩니다:

if (TannicAcid < 10)
{
   log("Low Acid");
}
else if (TannicAcid < 100)
{
   log("Medium Acid");
}
else
{
   log("High Acid");
}

코드를 실행 블록별로 들여쓰기 하십시오. 언리얼 스크립트 클래스의 멤버 함수들은 들여쓰기를 하지 않아도 됩니다. 클래스들은 블록 안에 싸여있지 않기 때문입니다. 코드를 들여쓰기 할 때는 스페이스바를 사용하지 말고 탭을 사용하십시오. 이렇게 하면 누구나 자기가 좋아하는 탭 레벨에서 코드를 볼 수 있습니다.

Switch 구문

빈 case(똑같은 코드를 가진 여러 개의 case)를 제외하고, switch의 case 구문은 한 case가 다음 case로 넘어간다는 것을 명백하게 표시해 주어야 합니다. 각각의 case에 break를 포함시키거나 코멘트를 붙이십시오. 그밖의 코드 제어-이동 (return, continue) 명령어를 사용하는 것도 괜찮습니다.

항상 default case 를 두고, 누군가 default 뒤에 case 를 새로 추가시키는 경우에 대비해서 break 를 포함시키십시오.

switch (condition)
{
   case 1:
      --code--;
      // 통과
   case 2:
      --code--;
      break;
   case 3:
      --code--;
      return;
   case 4:
   case 5:
      --code--;
      break;
   default:
      break;
}

defaultproperties

디폴트 프로퍼티는 순서대로 나열해야 합니다: 먼저 상속된 변수, 그 다음이 클래스 변수입니다. config 및 localized 변수는 더이상 UE3의 defaultproperties 안에 정의할 수 없다는 점 기억하시기 바랍니다.

defaultproperties
{
   // Object로부터 상속된 변수
   bGraphicsHacks=TRUE

   // WarmBeverage로부터 상속된 변수
   bThirsty=FALSE

   // 클래스 변수
   bTeaDrinker=TRUE
}

Boolean 표현식


C++ 에서, boolean 표현식에서는 언제나 TRUE 또는 FALSE를 값으로 사용하십시오. true 또는 false는 사용하지 마십시오. 이상한 컴파일러/정의 문제를 일으킵니다. 0 또는 1은 의미가 분명하지 않으니 사용하지 마십시오.

UnrealScript 에서는 (소문자) true false 를 사용해야 합니다.

코드를 쉽게 읽을 수 있도록 주저없이 local bool 도 몇 만들어 보시기 바랍니다. 아래 두 번째 예제에서 보면 어느 조건 하에서 DoSomething() 이 호출되는지 변수 이름을 통해 아주 쉽게 알 수 있습니다.

if ((Blah.BlahP.WindowExists.Etc && stuff) &&
    !(Player exist && Gamestarted && player still has pawn &&
    IsTuesday())))
{
   DoSomething();
}

위는 아래와 같이 쓰는 것이 좋습니다.

local bool bLegalWindow;
local bool bPlayerDead;

bLegalWindow = (Blah.BlahP.WindowExists.Etc && stuff);
bPlayerDead = (Player exist && Gamestarted &&
               player still has pawn && IsTuesday());

if ( bLegalWindow && !bPlayerDead )
{
   DoSomething();
}

주목: 표현식은 연산자 이후에 끊으며(연산자가 이전 줄을 종료하며), 다음 줄은 짝이 맞는 괄호에 정렬되어 있습니다.

마찬가지로 함수로 뽑아내기 어려울 만큼 "너무 단순한" 코드란 것은 없습니다. 만약 bLegalWindow 변수같은 내용이 여러 함수에서 쓰인다고 한다면, (파라미터를 쉽게 사용할 수 있다는 전제 하에) 자체 함수로 뽑아내는 것이 여러모로 편리할 것입니다.

일반적인 스타일 문제:


  • 하나의 함수가 여러 개의 값을 반환하는 것을 피하십시오. 코드가 지나치게 어려워지지 않는 한, 하나의 return 구문만을 사용하십시오. 그러면 코드 관리는 물론, 실행 경로의 추적이나 디버깅 변경 등이 훨씬 수월해 집니다.

  • debugf/warnf/appErrorf 함수에 절대 구조체를 전달하지 마십시오. %s로 프린트할 수 있도록 FName과 FString 에는 *를 사용하십시오.

  • 의존관계 거리를 최소화하십시오. 코드가 일정한 값을 가지고 있는 변수에 의존할 경우에는 그 변수의 값을 사용하기 직전에 설정하십시오. 변수를 실행 블록의 첫머리에서 초기화하고 코드가 100줄이 넘어갈 때까지 사용하지 않으면, 다른 사람이 그 의존관계를 의식하지 못한채 우연히 그 값을 변경할 가능성이 큽니다. 값을 설정하고 그 다음 줄에서 이를 사용하면, 그 변수가 왜 그런 방식으로 초기화되었는지 그리고 어디에 쓰일 것인지가 분명해집니다. Berkeley의 한 연구는 변수가 이를 이용하는 코드보다 7줄 이상 떨어져서 선언되면 정확하지 않게 사용될 확률이 매우 높다는 것을 (> 80%) 나타냈습니다.

  • 함수의 길이. 함수는 60줄 이상이 되지 말아야 합니다. 이것은 임의의 숫자이지만, 이 정도면 함수 전체가 한 장의 프린트 용지에 들어가게 됩니다. 함수가 이보다 더 길다면 리팩터링을 고려해 보십시오. 함수를 작게 만들고 인라인 함수를 사용하면 호출의 과부하를 피할 수 있습니다.

  • 컴파일러의 경고를 처리하십시오. 컴파일러의 경고 메시지는 뭔가가 잘못되었음을 뜻합니다. 컴파일러가 경고한 내용을 찾아 수정하십시오. 이를 처리할 수 없을 경우에는 최후의 수단으로 #pragma를 사용하여 그 경고를 억제하십시오.

  • FLOAT 가 INT 로 묵시적 형변환되는 것을 절대 허용치 마십시오. 이는 연산이 느리며, 모든 컴파일러에서 컴파일되지 않습니다. 대신에 항상 appTrunc() 함수를 사용해서 INT로 변환하십시오. 이렇게 하면 컴파일러간의 호환이 보장됨과 동시에 코드의 생성이 더 빨라집니다.

C++ 의 네임스페이스


  • 원한다면 네임스페이스를 사용해 클래스, 함수, 변수를 정리해도 됩니다. 심지어 전체 서브시스템을 한 네임스페이스에 래핑하고 싶을 수도 있지만, 보통 언리얼 코드에서는 그리 하지 않습니다.

  • 언리얼 코드는 현재 글로벌 네임스페이스로 래핑되어있지 않습니다. 특히나 제3자 코드를 끌어올 때는 글로벌 스코프에서의 충돌을 조심해야 합니다.

  • "using" 선언(declaration)을 글로벌 스코프에, .cpp 파일에도 넣지 마십시오. (저희 "유니티" 빌드 시스템에 문제를 일으킵니다.)

  • 다른 네임스페이스 안이나 함수 바디 안에는 "using" 선언을 넣어도 괜찮습니다.

  • 네임스페이스 안에 "using"을 넣으면, 동일한 트랜슬레이션 유닛에 그 네임스페이스가 또다시 나타날 것입니다. 일관성만 유지한다면 괜찮기는 할 것입니다.

  • 위의 규칙을 따르기만 한다면야 헤더 파일에 안전하게 "using"을 사용할 수는 있습니다.

  • 주의할 점은 앞서(forward)-선언된 타입은 그 원래 각각의 네임스페이스 안에 선언해 줘야 합니다. 안그러면 링크 에러가 날 것입니다.

  • 한 네임스페이스 안에 클래스/타입을 많이 선언하면, 다른 글로벌-스코프 클래스에서는 그 타입을 사용하기가 까다로워 질 수 있습니다. (즉 펑션 시그너처가 클래스 선언에 나타날 때는 명시적 네임스페이스를 사용해야 할 것입니다.)

  • 스코프 속으로의 네임스페이스 안에 있는 특정 변수만을 앨리어싱하도록 (Foo:FBar 식으로) "using"을 사용할 수 있습니다만, 보통 언리얼 코드에서는 그리 하지 않습니다.

  • 종종 네임스페이스에 enum 선언을 래핑하는 것도 유용합니다(C# 스타일 스코핑). 아래 예제를 참고하십시오.

네임스페이스 예제 (스코핑된 enum)

/**
 * C# 스타일 enum 스코핑을 이뤄내고자 네임스페이스 안에 열거형 정의
 */
namespace EColorChannel
{
    /** 이 enum 에 대한 실제 타입으로 EColorChannel::Type 을 선언 */
    enum Type
    {
        /** Red 컬러 채널 */
        Red,

        /** Green 컬러 채널 */
        Green,

        /** Blue 컬러 채널 */
        Blue
    };
}


/**
 * 컬러 채널이 주어지면 그 컬러에 대한 이름 문자열을 반환
 *
 * @param   InColorChannel   이름을 반환할 대상이 되는 컬러 채널
 *
 * @return  이 컬러 채널의 이름
 */
FString GetNameForColorChannel( const EColorChannel::Type InColorChannel )
{
    FString Name;

    switch( InColorChannel )
    {
        case EColorChannel::Red:
            Name = TEXT( "Red" );
            break;

        case EColorChannel::Green:
            Name = TEXT( "Green" );
            break;

        case EColorChannel::Blue:
            Name = TEXT( "Blue" );
            break;
    }

    return Name;
}

예제:


/**
 * 이 클래스는 여러가지 파형 활동을 취급합니다. 주어진 시간에
 * 주어진 게임패드에서 재생되는 파형 데이터를 관리합니다.
 * 플레이어 콘트롤러는 주어진 게임패드에서 파형을 시작/멈춤/일시 정지할 때
 * 이 클래스를 호출합니다. UXboxViewport는 이 클래스를 호출해서
 * 현재의 진동 상태에 대한 정보를 얻어 이를 게임패드에 적용합니다.
 * 뷰포트는 이를 위해 파형의 샘플 데이터에서 정의된 함수를 평가합니다.<BR>
 *
 * 저작권:    Copyright (c) 2005<BR>
 * 회사:      Epic Games Inc.<BR>
 */
class WaveformManager extends Object within PlayerController
   native
   transient
   poolable(1,0);

/**
 * 플레이어가 게임패드의 진동을 무효화했는지 아닌지(TCR C5-3).
 * 이 코멘트는 길어서 많은 줄이 필요함.
 */
var bool bAllowsForceFeedback;

/** 현재 재생중인 웨이브폼*/
var ForceFeedbackWaveform FFWaveform;

/** 플레이어의 콘트롤러에 의해 정지되었는지 아닌지 */
var bool bIsPaused;

/** 재생되는 현재 웨이브폼 샘플 */
var int CurrentSample;

/** 이 웨이브폼이 시작된 이래 경과된 시간의 총계 */
var float ElapsedTime;

/** 모든 웨이브폼을 (사용자의 설정에 따라) 확대 또는 축소할 수 있는 양 */
var float ScaleAllWaveformsBy;

/**
 * 게임패드에서 재생할 파형을 설정함
 *
 * @param Waveform 재생할 파형 데이터
 */
simulated final function PlayWaveform(ForceFeedbackWaveform Waveform)
{
   // 현재의 샘플과 시간을 0으로 초기화하고, 포즈되어 있다면 언포즈
   CurrentSample = 0;
   ElapsedTime = 0.0;
   bIsPaused = FALSE;
   // 웨이브폼이 사용가능한지 확인
   if (Waveform != None && Waveform.Samples.Length > 0 &&
      bAllowsForceFeedback )
   {
      // 재생할 웨이브폼 설정
      FFWaveform = Waveform;
   }
   else
   {
      FFWaveform = None;
   }
}

/**
 * 웨이브폼을 null화시켜 중지시킴
 */
simulated final function StopWaveform()
{
   // 현재 웨이브폼 제거
   FFWaveform = None;
}

/**
 * 게임패드에서 웨이브폼 재생 포즈/언포즈
 *
 * @param bPause TRUE 는 포즈, FALSE 는 재개
 */
simulated final function PauseWaveform(optional bool bPause)
{
   // 게임패드에 포즈 스테인트 설정
   bIsPaused = bPause;
}

defaultproperties
{
   bAllowsForceFeedback=TRUE
   ScaleAllWaveformsBy=1.0
}

C++ 함수


/**
 * 각 하위 카메라 효과에 대해 포스트 렌더링을 실행, 그 결과를 축적함.
 * 이렇게 함으로써 또 하나의 전체 화면 패스에 이미시브 결과가 추가되는 것을
 * 막을 수 있으며, 이미시브 상에 2 패스의 블러를 무료로 얻을 수 있게 됨
 *
 * @param Viewport   그림을 그려넣을 뷰포트
 * @param RI      그림 그리기에 사용되는 렌더 인터페이스
 */
void UUCScreenEmissiveWithBloom::PostRender(UViewport* Viewport,FRenderInterface* RI)
{
   guard(UUCScreenEmissiveWithBloom::PostRender);

   checkSlow( EmissiveCamEffect && BloomCamEffect );

   // 이미시브 포스트 렌더가 그 결과를
   // 백 버퍼 위에 그리지 않는지 확인
   EmissiveCamEffect->DisplayOutput = 0;
   EmissiveCamEffect->PostRender( Viewport, RI );

   // 사용할 블룸 카메라 이펙트에 대한
   // 이미시브 카메라 이펙트에서 결과를 설정
   BloomCamEffect->EmissiveResultTarget =
EmissiveCamEffect->GetEmissiveResultTarget();
   BloomCamEffect->PostRender( Viewport, RI );

   unguard;
}


/**
 * 링크  0에 첨부된  int변수를 증가시키고,
 * 이를 링크 1에 첨부된 int변수와 비교,
 * 비교 결과에 따라 충격을 가함.
 */
void USeqCond_Increment::Activated()
{
   check(VariableLinks.Num() >= 2 && "Decrement requires at least 2 variables");

   // 두 int 변수를 집어옴
   INT Value1 = 0;
   INT Value2 = 0;
   TArray<INT*> IntValues1;
   GetIntVars( IntValues1, TEXT("Counter") );
   TArray<INT*> IntValues2;
   GetIntVars( IntValues2, TEXT("Comparison") );
   // 모든 카운터 변수 증가
   for (INT VarIdx = 0; VarIdx < IntValues1.Num(); VarIdx++)
   {
      *(IntValues1(VarIdx)) += IncrementAmount;
      Value1 += *(IntValues1(VarIdx));
   }
   // get the actual values by adding up all linked variables
   for (INT VarIdx = 0; VarIdx < IntValues2.Num(); VarIdx++)
   {
      Value2 += *(IntValues2(VarIdx));
   }
   // 값을 비교하여 출력 충격을 설정
   OutputLinks(Value1 <= Value2 ? 0 : 1).bHasImpulse = 1;
}

C++ 클래스


/**
 * 모든 템플릿 배열의 기초 클래스. 동적 배열의 할당을 처리하며,
 * 비효율적 성장 패턴으로 인해 다량의 데이터가 복사되는 것을 방지하기 위해
 * 얼마만큼의 "슬랙"(slack) 메모리를 할당해야 할지 결정하는 알고리즘을 사용함.
 */
class FArray
{
public:
   /**
    * 배열 블록에의 접근을 허용
    *
    * @return 배열을 구성하는 메모리 블록에 대한 포인터
    */
   void* GetData()
   {
      return Data;
   }

   /**
    * 지정된 인덱스가 배열의 범주 내에 있는지 확인함
    *
    * @return 그 인덱스가 적법한 것이면  TRUE, 아니면 FALSE
    */
   UBOOL IsValidIndex( INT i ) const
   {
      return i>=0 && i<ArrayNum;
   }

   // ...

protected:
   /** 배열을 나타내는 메모리 블록에 대한 포인터 */
   void*    Data;
   /** 현재 배열이 가지고 있는 요소의 수 */
   INT      ArrayNum;
   /** realloc 없이 배열이 가질 수 있는 요소의 수 */
   INT      ArrayMax;
};