재생 시스템을 설계하는 방법


75

그렇다면 어떻게 리플레이 시스템을 설계할까요?

Warcraft 3 또는 Starcraft와 같은 특정 게임에서 이미 게임을 한 후에 다시 볼 수있는 게임에서 알 수 있습니다.

상대적으로 작은 재생 파일이 생깁니다. 그래서 내 질문은 :

  • 데이터를 저장하는 방법? (맞춤 형식?) (작은 파일 크기)
  • 무엇을 구해야합니까?
  • 다른 게임에서 기간을 기록하는 데 사용할 수 있도록 일반적인 방법으로 만드는 방법은 무엇입니까?
  • 앞으로 되감기 가능 (WC3은 내가 기억하는 한 되 ​​감지 못했습니다)

3
아래의 답변은 많은 귀중한 통찰력을 제공하지만, 목표를 달성하는 것이 필수적이므로 게임 / 엔진을 결정 론적으로 향상시키는 것 ( en.wikipedia.org/wiki/Deterministic_algorithm ) 의 중요성을 강조하고 싶었 습니다.
Ari Patrick

2
또한 물리 엔진은 결정 론적이지 않기 때문에 (하보크 주장은 ...) 게임에서 물리를 사용하는 경우 입력 및 타임 스탬프 만 저장하는 솔루션은 매번 다른 결과를 생성합니다.
Samaursa

5
고정 된 타임 스텝을 사용하는 한 대부분의 물리 엔진은 결정 론적입니다. Havok이 아닌 경우 매우 놀랐습니다.

4
결정론은 동일한 입력 = 동일한 출력을 의미합니다. 하나의 플랫폼에 플로트가 있고 다른 플랫폼에 플로팅되거나 (예를 들어) IEEE 부동 소수점 표준 구현을 고의로 비활성화 한 경우 이는 결정적이지 않고 동일한 입력으로 실행되지 않는다는 것을 의미합니다.

3
나입니까, 아니면이 질문에 격주마다 현상금이 있습니까?
공산주의 오리

답변:


39

이 훌륭한 기사는 많은 문제를 다룹니다 : http://www.gamasutra.com/view/feature/2029/developing_your_own_replay_system.php

이 기사에서 언급하고 잘 수행하는 몇 가지 사항 :

  • 당신의 게임은 결정 론적이어야합니다.
  • 첫 번째 프레임에 게임 시스템의 초기 상태를 기록하고 게임 플레이 중 플레이어 입력 만 기록합니다.
  • 비트 수를 낮추기 위해 입력을 양자화합니다. 즉. 비트는 다양한 범위 (예 : [0, 1] 또는 [-1, 1] 범위 내에서 비트 수가 적음)를 나타냅니다. 실제 게임 플레이 중에도 정량화 된 입력을 가져와야합니다.
  • 입력 스트림에 새로운 데이터가 있는지 여부를 확인하려면 단일 비트를 사용하십시오. 일부 스트림은 자주 변경되지 않으므로 입력의 시간적 일관성을 활용합니다.

대부분의 경우 압축률을 더욱 향상시키는 한 가지 방법은 모든 입력 스트림을 분리하고 독립적으로 전체 길이 인코딩하는 것입니다. 런을 8 비트로 인코딩하고 런 자체가 8 프레임을 초과하는 경우 (델타 인코딩 기술이 아닌 경우) 델타 인코딩 기술보다 우선합니다. 레이싱 게임에서이 기술을 사용하여 트랙 주위를 몇 백 바이트까지 경주하면서 2 명의 플레이어로부터 8 분의 입력을 압축했습니다.

이러한 시스템을 재사용 가능하게 만드는 관점에서, 나는 리플레이 시스템이 일반적인 입력 스트림을 다루도록 만들었지 만 게임 특정 로직이 키보드 / 게임 패드 / 마우스 입력을 이러한 스트림에 마샬링 할 수 있도록 후크를 제공했습니다.

빨리 되감기 또는 임의 탐색을 원하면 N 프레임마다 체크 포인트 (전체 게임 상태)를 저장할 수 있습니다. 재생 파일 크기를 최소화하고 상태가 선택한 지점까지 재생되는 동안 플레이어가 기다려야하는 시간이 합리적이어야합니다. 이 문제를 해결하는 한 가지 방법은 이러한 정확한 체크 포인트 위치에서만 무작위로 검색 할 수 있도록하는 것입니다. 되감기는 게임 상태를 해당 프레임 바로 앞의 체크 포인트로 설정 한 다음 현재 프레임에 도달 할 때까지 입력을 재생하는 것입니다. 그러나 N이 너무 크면 몇 프레임마다 적중 할 수 있습니다. 이러한 문제를 해결하는 한 가지 방법은 현재 검사 점 영역에서 캐시 된 프레임을 재생하는 동안 이전 2 개의 검사 점 사이에서 프레임을 비동기 적으로 사전 캐시하는 것입니다.


RNG가 관련된 경우 스트림에 상기 RNG의 결과를 포함 시키십시오
ratchet freak

1
@ratchet freak : PRNG를 결정 론적으로 사용하면 체크 포인트 동안 시드 만 저장하여 얻을 수 있습니다.
NonNumeric

22

놀랍게도 어려운 "키 입력이 재생 가능한지 확인"솔루션 외에도 모든 프레임에서 전체 게임 상태를 기록 할 수 있습니다. 약간의 영리한 압축으로 크게 줄일 수 있습니다. 이것이 Braid가 시간 되감기 코드를 처리하는 방법이며 꽤 잘 작동합니다.

되감기를 위해 어쨌든 체크 포인트가 필요하기 때문에 문제를 복잡하게 만들기 전에 간단한 방법으로 구현하려고 할 수 있습니다.


2
+1 영리한 압축으로 저장해야 할 데이터의 양을 실제로 줄일 수 있습니다 (예 : 현재 객체에 대해 마지막으로 저장 한 상태와 비교하여 변경되지 않은 상태는 저장하지 않음) . 나는 이미 물리학으로 이것을 시도했고 그것은 실제로 잘 작동합니다. 물리가없고 전체 게임을 되감기를 원하지 않는 경우 Joe의 솔루션을 사용하면 가능한 가장 작은 파일이 생성되므로 되감기를 원할 경우 마지막 n초만 저장할 수 있습니다. 게임.
Samaursa

@Samaursa-표준 압축 라이브러리 (예 : gzip)를 사용하는 경우 상태가 변경되었는지 여부를 확인하는 것과 같은 작업을 수동으로 수행 할 필요없이 동일한 (아마도 더 나은) 압축을 얻을 수 있습니다.
저스틴

2
@Kragen : 사실이 아닙니다. 표준 압축 라이브러리는 확실히 좋지만 종종 도메인 별 지식을 활용할 수 없습니다. 비슷한 데이터를 인접하게 배치하고 실제로 변경되지 않은 것을 제거함으로써 약간의 도움을 줄 수 있다면 실질적으로 문제를 해결할 수 있습니다.
ZorbaTHut

1
@ZorbaTHut 이론적으로는 그렇습니다. 그러나 실제로는 그 노력의 가치가 있습니까?
저스틴

4
노력할 가치가 있는지 여부는 전적으로 얼마나 많은 데이터가 있는지에 달려 있습니다. 수백 또는 수천 개의 유닛을 가진 RTS를 가지고 있다면 아마도 중요 할 것입니다. 재생을 Braid와 같은 메모리에 저장해야하는 경우 중요 할 수 있습니다.

21

f[j]입력 이있는 함수 가 x[j]시스템 상태 s[j]를 state로 변경 하는 일련의 상태와 함수로 구성된 것처럼 시스템을 볼 수 있습니다 s[j+1].

s[j+1] = f[j](s[j], x[j])

상태는 전 세계에 대한 설명입니다. 플레이어의 위치, 적의 위치, 점수, 남은 탄약 등 게임의 프레임을 그리는 데 필요한 모든 것.

기능은 세상에 영향을 줄 수있는 모든 것입니다. 프레임 변경, 키 누르기, 네트워크 패킷.

입력은 함수가받는 데이터입니다. 프레임 변경에는 마지막 프레임이 지난 이후 시간이 걸릴 수 있으며, 키 누름에는 실제 누른 키와 Shift 키의 누르기 여부가 포함될 수 있습니다.

이 설명을 위해 다음과 같은 가정을합니다.

가정 1 :

주어진 게임 실행 상태의 양은 기능의 양보다 훨씬 큽니다. 아마도 수십만 개의 상태가 있지만 수십 가지 기능 (프레임 변경, 키 누르기, 네트워크 패킷 등) 만 있습니다. 물론, 입력량은 상태 수에서 1을 뺀 것과 같아야합니다.

가정 2 :

단일 상태를 저장하는 데 드는 공간 비용 (메모리, 디스크)은 함수 및 입력을 저장하는 것보다 훨씬 큽니다.

가정 3 :

상태를 제시하는 시간적 비용 (시간)은 상태에 대한 함수를 계산하는 것보다 비슷하거나 한두 배 정도 길다.

재생 시스템의 요구 사항에 따라 재생 시스템을 구현하는 몇 가지 방법이 있으므로 가장 간단한 방법으로 시작할 수 있습니다. 또한 종이에 기록 된 체스 게임을 사용하여 작은 예를 만들 것입니다.

방법 1 :

저장 s[0]...s[n]합니다. 이것은 매우 간단하고 매우 간단합니다. 가정 2 때문에, 이것의 공간 비용은 상당히 높습니다.

체스의 경우, 이것은 각 움직임에 대한 전체 보드를 그려서 달성됩니다.

방법 2 :

순방향 재생 만 필요한 경우 간단히을 저장 s[0]한 다음 저장 f[0]...f[n-1](이것은 함수의 ID 이름 일 뿐임) 및 x[0]...x[n-1](이러한 각 함수에 대한 입력 내용 임) 을 저장할 수 있습니다 . 재생하려면 간단하게 시작 s[0]하고, 계산

s[1] = f[0](s[0], x[0])
s[2] = f[1](s[1], x[1])

등등...

여기에 작은 주석을 만들고 싶습니다. 다른 논평자들은이 게임이 "결정 론적이어야한다"고 말했다. 퀀텀 컴퓨터에서 게임을 실행하지 않는 한 모든 컴퓨터 프로그램은 결정적인 ¹이기 때문에 Computer Science 101을 다시 사용해야한다고 말하는 사람은 누구나 말입니다. 그것이 컴퓨터를 정말 멋지게 만드는 이유입니다.

그러나 프로그램은 라이브러리에서 CPU의 실제 구현에 이르기까지 외부 프로그램에 의존 할 가능성이 크므로 플랫폼간에 기능이 동일하게 작동하는 것이 매우 어려울 수 있습니다.

의사 난수를 사용하는 경우 생성 된 숫자를 입력의 일부로 x저장하거나 prng 함수의 상태를 상태의 일부로 저장 s하고 해당 구현을 function의 일부로 저장할 수 있습니다 f.

체스의 경우, 이것은 초기 보드 (알려진 보드)를 그리고 각 조각이 어디로 갔는지 설명함으로써 달성됩니다. 그건 그렇고 그들이 실제로하는 방법입니다.

방법 3 :

이제, 당신은 아마도 당신의 리플레이를 추구 할 수 있기를 원할 것입니다. 즉, s[n]임의의을 계산하십시오 n. 방법 2를 사용하면을 계산 s[0]...s[n-1]하기 전에 계산 해야합니다. s[n]가정 2에 따르면 상당히 느릴 수 있습니다.

이를 구현하기 위해 방법 3은 방법 1과 2의 일반화입니다. 방법 2 f[0]...f[n-1]x[0]...x[n-1]마찬가지로 저장 하고 주어진 상수에 s[j]대해 모두 저장하십시오 . 더 쉬운 용어로, 이는 모든 주 중 하나에 책갈피를 저장한다는 것을 의미합니다 . 예를 들어, 위해 , 당신은 저장j % Q == 0QQQ == 100s[0], s[100], s[200]...

계산하기 위해서는 s[n]임의의에 대해 n, 먼저 이전에 저장된로드 s[floor(n/Q)], 다음에서 모든 기능을 계산 floor(n/Q)n. 기껏해야 Q함수를 계산하게 됩니다. 값이 작을 Q수록 계산 속도가 빠르지 만 더 많은 공간을 소비하지만 값이 클수록 Q공간을 덜 소비하지만 계산하는 데 시간이 더 걸립니다.

사용하는 방법 3 Q==1은 방법 1과 동일하지만 사용하는 방법 3 Q==inf은 방법 2와 동일합니다.

체스의 경우, 이것은 모든 움직임과 10 개의 보드마다 하나씩을 그려서 달성 할 수 있습니다 Q==10.

방법 4 :

당신이 재생을 되돌리고 싶은 경우에, 당신은 방법 3의 작은 변화가 있다고 가정 할 수있다 Q==100, 당신은 계산할 s[150]통해 s[90]역으로. 수정되지 않은 방법 3을 사용하면 50 개의 계산을 수행 s[150]한 다음 49 개의 계산을 더 많이 수행 s[149]해야합니다. 그러나 s[149]get 을 이미 계산 했으므로 처음 계산할 때 s[150]캐시를 만든 다음 표시해야 할 때 이미 캐시에 있습니다.s[100]...s[150]s[150]s[149]

당신은 캐시에게 당신이 계산해야 할 때마다 다시 생성해야 s[j]들면, j==(k*Q)-1주어진 어떤을 위해 k. 이번에는 증가 Q하면 크기가 작아 지지만 (캐시 전용) 시간이 길어집니다 (캐시 재 작성 용). Q상태와 함수를 계산하는 데 필요한 크기와 시간을 알고 있으면 최적의 값을 계산할 수 있습니다.

체스의 경우, 이것은 모든 움직임과 10 개의 보드마다 하나씩 ( Q==10) 을 그려서 달성 할 수 있지만, 마지막으로 계산 한 마지막 10 개의 보드에 별도의 종이로 그려야합니다.

방법 5 :

상태가 단순히 너무 많은 공간을 소비하거나 함수가 너무 많은 시간을 소비하는 경우 실제로 (가짜가 아닌) 리버스 재생을 구현하는 솔루션을 만들 수 있습니다. 이렇게하려면 보유한 각 기능에 대해 역 기능을 작성해야합니다. 그러나이를 위해서는 각 기능이 주입되어야합니다. 이것이 가능하다면, f'함수의 역 을 나타 내기 위해 f계산 s[j-1]은 간단합니다.

s[j-1] = f'[j-1](s[j], x[j-1])

여기에서 함수와 입력은 둘 다 j-1아니라 j입니다. 이 동일한 함수와 입력은 계산할 때 사용했던 것과 같습니다.

s[j] = f[j-1](s[j-1], x[j-1])

이러한 함수의 역을 만드는 것은 까다로운 부분입니다. 그러나 일반적으로 게임에서 각 함수 후에 일부 상태 데이터가 손실되므로 일반적으로 할 수 없습니다.

이 메소드는있는 그대로 s[j-1]만 reverse를 계산할 수 있습니다 s[j]. 즉, 뒤로 재생하기로 결정한 지점부터 시작하여 뒤로 재생 만 볼 수 있습니다. 임의 지점에서 뒤로 재생하려면이 방법을 방법 4와 혼합해야합니다.

체스의 경우, 주어진 보드와 이전 이동으로 어떤 조각이 이동했는지 알 수 있지만 어디로 이동했는지 알 수 없으므로 구현할 수 없습니다.

방법 6 :

마지막으로, 모든 기능이 주입이라고 보장 할 수 없다면 약간의 트릭을 만들 수 있습니다. 각 함수가 새로운 상태 만 반환하도록하는 대신 폐기 된 데이터를 다음과 같이 반환하도록 할 수도 있습니다.

s[j+1], r[j] = f[j](s[j], x[j])

r[j]버려진 데이터는 어디에 있습니까 ? 그런 다음 역함수를 만들어 버린 데이터를 다음과 같이 가져옵니다.

s[j] = f'[j](s[j+1], x[j], r[j])

f[j]및 이외에도 각 기능에 대해 x[j]저장해야합니다 r[j]. 다시 한 번 검색하려면 방법 4와 같은 책갈피를 저장해야합니다.

체스의 경우 이것은 방법 2와 동일하지만 방법 2와 달리 각 조각이 어디로 갔는지 만 알려주므로 각 조각의 출처를 저장해야합니다.

이행:

특정 게임에 대해 모든 종류의 기능과 함께 모든 종류의 상태에서 작동하기 때문에 몇 가지 가정을 할 수 있으므로 구현하기가 더 쉽습니다. 실제로 전체 게임 상태에서 방법 6을 구현하면 데이터를 재생할 수있을뿐만 아니라 시간을 거슬러 올라가 특정 시점부터 재생을 재개 할 수 있습니다. 꽤 대단 할 것입니다.

모든 게임 상태를 저장하는 대신 주어진 상태를 그리는 데 필요한 최소값 만 저장하고 고정 된 시간마다이 데이터를 직렬화 할 수 있습니다. 귀하의 상태는 이러한 직렬화이며, 이제 입력이 두 직렬화의 차이가됩니다. 이 작업의 핵심은 세계 상태가 거의 변하지 않으면 직렬화가 거의 바뀌지 않아야한다는 것입니다. 이 차이는 완전히 가역적이기 때문에 책갈피를 사용하여 방법 5를 구현하는 것이 매우 가능합니다.

이벤트 (fps의 조각 또는 스포츠 게임의 점수)가 발생할 때 최근 데이터를 즉시 재생하기 위해 일부 주요 게임 에서이 기능을 구현했습니다.

이 설명이 너무 지루하지 않기를 바랍니다.

¹ 이는 일부 프로그램이 결정적이지 않은 것처럼 작동 함을 의미하지는 않습니다 (예 : MS Windows ^^). 이제 결정 론적 컴퓨터에서 비결정론 적 프로그램을 만들 수 있다면 Fields 메달, Turing 상, 그리고 아마도 Oscar와 Grammy에게도 가치있는 모든 것을 동시에 얻을 수있을 것입니다.


"모든 컴퓨터 프로그램이 결정적입니다"에서 스레딩에 의존하는 프로그램은 고려하지 않습니다. 스레딩은 리소스를로드하거나 렌더 루프를 분리하는 데 주로 사용되지만 이에 대한 예외가 있으며,이 시점에서 결정 성 강화에 대해 엄격하게 규정하지 않으면 더 이상 진정한 결정 성을 요구하지 못할 수 있습니다. 잠금 메커니즘만으로는 충분하지 않습니다. 추가 작업 없이는 변경 가능한 데이터를 공유 할 수 없습니다. 많은 시나리오에서 게임은 자체적으로 엄격함이 필요하지 않지만 재생과 같은 것은 필요합니다.
krdluzni

1
@krdluzni 진정한 랜덤 소스의 스레딩, 병렬 처리 및 난수는 프로그램을 비 결정적이지 않습니다. 스레드 타이밍, 교착 상태, 초기화되지 않은 메모리 및 경쟁 조건은 프로그램이 수행하는 추가 입력입니다. 이러한 입력을 폐기하거나 전혀 고려하지 않는 이유는 (어떤 이유로 든) 정확히 동일한 입력에서 프로그램이 정확히 동일하게 실행된다는 사실에는 영향을 미치지 않습니다. "비결정론 적"은 매우 정확한 컴퓨터 과학 용어이므로 의미가 무엇인지 모르면 사용하지 마십시오.

@oscar (좀 더 간결하고 바쁠 수 있으며 나중에 편집 할 수 있음) : 엄밀하고 이론적 인 의미에서는 스레드 타이밍 등을 입력으로 주장 할 수 있지만 일반적으로이 값은 관찰 할 수 없으므로 실용적인 의미로는 유용하지 않습니다. 프로그램 자체 또는 개발자가 완전히 제어합니다. 또한 결정적이지 않은 프로그램은 비결정론 적 (상태 머신 의미에서) 프로그램과는 상당히 다릅니다. 용어의 의미를 이해합니다. 기존 용어에 과부하가 걸리지 않고 다른 것을 선택했으면합니다.
krdluzni

@krdluzni 스레드 타이밍과 같이 예측할 수없는 요소 (리플레이를 정확하게 계산하는 데 영향을 미치는 경우)와 함께 리플레이 시스템을 설계하는 데있어 요점은 사용자 입력과 마찬가지로 다른 입력 소스와 동일하게 취급하는 것입니다. 프로그램에 대해 불평하는 사람은 전혀 예측할 수없는 사용자 입력이 필요하기 때문에 "비 결정적"이라고 불평하는 사람이 없습니다. 용어는 부정확하고 혼란 스럽습니다. 차라리 "실제로 예측할 수없는"또는 이와 유사한 것을 사용하고 싶습니다. 그리고 불가능하지는 않습니다. VMWare의 재생 디버깅을 확인하십시오.

9

다른 답변이 아직 다루지 않은 한 가지는 수레의 위험입니다. float를 사용하여 완전히 결정적인 응용 프로그램을 만들 수 없습니다.

float를 사용하면 다음과 같은 경우에만 완전히 결정론적인 시스템을 가질 수 있습니다.

  • 정확히 같은 바이너리 사용
  • 정확히 동일한 CPU 사용

플로트의 내부 표현은 CPU마다 다르기 때문에 AMD와 인텔 CPU간에 가장 크게 달라지기 때문입니다. 값이 FPU 레지스터에있는 한 C 측에서보다 정확도가 높으므로 중간 계산은 더 정밀하게 수행됩니다.

이것이 어떻게 AMD 대 인텔 비트에 영향을 미치는지 분명히 알 수 있습니다. 하나는 80 비트 부동 소수점을 사용하고 다른 하나는 64 비트를 사용한다고 가정 해보십시오. 그러나 왜 동일한 바이너리 요구 사항이 있습니까?

내가 말했듯이, 값이 FPU 레지스터에 있는 한 높은 정밀도가 사용 중입니다 . 다시 컴파일 할 때마다 컴파일러 최적화에서 FPU 레지스터의 값을 FPU 레지스터 안팎으로 교환하여 결과가 미묘하게 달라질 수 있습니다.

가능한 가장 낮은 정밀도를 사용하도록 _control87 () / _ controlfp () 플래그를 설정하여이를 도울 수 있습니다 . 그러나 일부 라이브러리는 이것을 만질 수도 있습니다 (적어도 일부 버전의 d3d는 그랬습니다).


3
GCC를 사용하면 -ffloat-store를 사용하여 다른 라이브러리가 제어 플래그를 어지럽 힐 염려없이 레지스터에서 값을 강제로 제거하고 32/64 비트 정밀도로자를 수 있습니다. 분명히 이것은 속도에 부정적인 영향을 줄 것입니다 (그러나 다른 양자화도 마찬가지입니다).

8

난수 생성기의 초기 상태를 저장하십시오. 그런 다음 각 입력 (마우스, 키보드, 네트워크 등)을 저장하고 시간을 표시합니다. 네트워크 게임이 있다면 이미이 모든 것을 갖추고있을 것입니다.

RNG를 재설정하고 입력을 재생하십시오. 그게 다야.

이 방법은 처음부터 최대한 빨리 재생하는 것 외에는 일반적인 해결책이없는 되감기 문제를 해결하지 못합니다. X 초마다 전체 게임 상태를 체크 포인트하여 성능을 향상시킬 수 있습니다. 그러면 그 많은 수의 게임 만 재생하면되지만 전체 게임 상태도 엄청나게 비쌀 수 있습니다.

파일 형식의 세부 사항은 중요하지 않지만 대부분의 엔진에는 네트워킹, 저장 등을 위해 명령 및 상태를 직렬화하는 방법이 있습니다. 그냥 사용하십시오.


4

결정 론적 재생에 반대 투표를했습니다. 1 / N / 초마다 모든 엔티티의 상태를 저장하는 것이 훨씬 간단하고 오류가 적습니다.

재생시 보여주고 싶은 것을 저장하십시오-위치와 제목 만 있으면 통계를 보여주고 싶다면 저장하십시오. 일반적으로 가능한 한 적게 저장하십시오.

인코딩을 조정하십시오. 모든 것에 가능한 한 적은 비트를 사용하십시오. 재생이 충분히 좋아 보인다면 완벽 할 필요는 없습니다. 제목에 플로트를 사용하더라도 바이트로 저장하고 256 개의 가능한 값 (1.4º 정밀도)을 얻을 수 있습니다. 그것은 당신의 특정 문제에 충분하거나 너무 많은 것일 수 있습니다.

델타 인코딩을 사용하십시오. 기업이 순간 이동을하지 않는 한 (경우에 따라 개별 사례를 처리하지 않는 경우), 새로운 포지션과 기존 포지션의 차이로 포지션을 인코딩하십시오. .

되감기를 원하면 N 프레임마다 키 프레임 (전체 데이터, 델타 없음)을 추가하십시오. 이 방법으로 델타 및 기타 값의 정밀도를 떨어 뜨릴 수 있습니다. 주기적으로 "true"값으로 재설정하면 반올림 오류는 그리 문제가되지 않습니다.

마지막으로 모든 것을 압축하십시오 :)


1
이것은 게임 유형에 따라 다릅니다.
Jari Komppa

나는이 진술에 매우 조심할 것이다. 특히 제 3 자 의존성이있는 더 큰 프로젝트의 경우 상태를 저장하는 것이 불가능할 수 있습니다. 입력을 재설정하고 재생하는 동안 항상 입력이 가능합니다.
TomSmartBishop

2

어렵다. 우선 Jari Komppa의 답변을 읽으십시오.

부동 결과가 약간 다르기 때문에 내 컴퓨터에서 재생 한 내용이 컴퓨터에서 작동하지 않을 수 있습니다. 큰 문제입니다.

그러나 그 후에 임의의 숫자가있는 경우 시드 값을 재생에 저장하는 것입니다. 그런 다음 모든 기본 상태를로드하고 난수를 해당 시드로 설정하십시오. 여기에서 현재 키 / 마우스 상태와 그 길이를 간단히 기록 할 수 있습니다. 그런 다음이를 입력으로 사용하여 모든 이벤트를 실행하십시오.

파일을 훨씬 더 많이 넘기려면 메모리를 덤프해야합니다. 모든 유닛이 돈이 있고 시간이 지남에 따라 모든 게임 상태와 같습니다. 그런 다음 원하는 시간 목적지에 도달 할 때까지 렌더링, 사운드 등을 건너 뛰고 모든 것을 재생하지만 빨리 감습니다. 이는 얼마나 빨리 전달되는지에 따라 1 분 또는 5 분마다 발생할 수 있습니다.

요점은-임의의 숫자 다루기-복사 입력 (플레이어 및 원격 플레이어)-파일 주위를 뛰고 덤프하기위한 덤프 상태 ...-플로팅이 멈추지 않는 것


2

이 옵션에 대해 언급 한 사람이 아무도없는 것이 다소 놀랍지 만 게임에 멀티 플레이어 구성 요소가있는 경우 이미이 기능에 필요한 많은 노력을했을 것입니다. 결국, 멀티 플레이어 란 무엇입니까?하지만 자신의 컴퓨터에서 (약간) 다른 시간에 다른 사람의 움직임을 다시 재생하려는 시도입니까?

또한 대역폭 친화적 인 네트워크 코드로 작업하고 있다고 가정하면 파일 크기가 작을수록 부작용으로 인한 이점을 얻을 수 있습니다.

여러 가지면에서 "매우 결정적"옵션과 "모든 것을 기록 유지"옵션을 결합합니다. 당신은 여전히 ​​결정론이 필요합니다. 만약 당신의 리플레이가 본질적으로 당신이 원래 플레이했던 것과 똑같은 방식으로 게임을 다시하는 봇이라면, 임의의 결과를 가질 수있는 행동은 동일한 결과를 가져야합니다.

데이터 형식은 네트워크 트래픽 덤프만큼 간단 할 수 있지만 조금 정리해도 상처를 입지 않을 것이라고 생각합니다 (결국 다시 재생 지연에 대해 걱정할 필요가 없습니다). 다른 사람들이 언급 한 체크 포인트 메커니즘을 사용하여 게임의 일부만 다시 재생할 수 있습니다. 일반적으로 멀티 플레이어 게임은 항상 너무 자주 게임 업데이트의 전체 상태를 보내므로 이미이 작업을 수행했을 수도 있습니다.


0

가장 작은 재생 파일을 얻으려면 게임이 결정 론적이어야합니다. 일반적으로 난수 생성기를보고 게임 논리에서 사용되는 위치를 확인합니다.

GUI, 파티클 이펙트, 사운드와 같은 게임 로직 RNG와 그 밖의 모든 RNG가 필요할 것입니다. 이 작업을 완료하면 게임 로직 RNG의 초기 상태를 기록한 다음 모든 프레임의 모든 플레이어의 게임 명령을 기록해야합니다.

많은 게임의 경우 입력과 명령이 입력되는 게임 로직 사이에 추상화 레벨이 있습니다. 예를 들어 컨트롤러의 A 버튼을 누르면 "점프"디지털 명령이 true로 설정되고 게임 로직이 컨트롤러를 직접 확인하지 않고 명령에 반응합니다. 이렇게하면 게임 로직에 영향을주는 명령 ( "일시 정지"명령을 기록 할 필요가 없음) 만 기록하면되며이 데이터는 컨트롤러 데이터 기록보다 작을 것입니다. 또한 플레이어가 버튼을 다시 매핑하기로 결정한 경우 제어 체계의 상태를 기록하는 것에 대해 걱정할 필요가 없습니다.

되감기는 결정적인 방법을 사용하고 게임 상태의 스냅 샷을 사용하고보고자하는 시점으로 빨리 감기하는 것 외에는 어려운 문제입니다.

한편, 빨리 감기는 확실히 가능합니다. 게임 로직이 렌더링에 의존하지 않는 한 새로운 게임 프레임을 렌더링하기 전에 원하는만큼 게임 로직을 실행할 수 있습니다. 빨리 감기 속도는 기계에 의해 결정됩니다. 앞으로 크게 건너 뛰려면 되감기에 필요한 것과 동일한 스냅 샷 방법을 사용해야합니다.

결정론에 의존하는 재생 시스템을 작성하는 데있어 가장 중요한 부분은 디버그 데이터 스트림을 기록하는 것입니다. 이 디버그 스트림에는 각 프레임 (RNG 시드, 엔터티 변환, 애니메이션 등)에 가능한 한 많은 정보의 스냅 샷이 포함되어 있으며 재생 중 게임 상태에 대해 기록 된 디버그 스트림을 테스트 할 수 있습니다. 이렇게하면 주어진 프레임의 끝에서 불일치를 신속하게 알 수 있습니다. 이를 통해 알 수없는 비 결정적 버그에서 머리카락을 꺼내려는 수많은 시간을 절약 할 수 있습니다. 초기화되지 않은 변수만큼 간단한 것은 11 시간에 모든 것을 망칠 것입니다.

참고 : 게임에 컨텐츠의 동적 스트리밍이 있거나 여러 스레드 또는 다른 코어에 게임 로직이있는 경우 ... 행운을 빕니다.


0

기록 및 되감기를 모두 사용하려면 모든 이벤트 (사용자 생성, 타이머 생성, 통신 생성 등)를 기록하십시오.

이벤트의 각 이벤트 레코드 시간에 대해 변경된 사항, 이전 값, 새 값.

계산이 임의적이지 않으면 계산 된 값을 기록 할 필요가 없습니다
(이 경우 계산 된 값도 기록하거나 각 임의 계산 후 시드 변경 사항을 기록 할 수 있습니다).

저장된 데이터는 변경 사항 목록입니다.
변경 사항은 다양한 형식 (이진, xml 등)으로 저장할 수 있습니다.
엔터티 ID, 속성 이름, 이전 값, 새 값으로 변경됩니다.

시스템이 이러한 변경 사항을 재생할 수 있는지 확인하십시오 (원하는 엔티티에 액세스, 원하는 특성을 새 상태로 또는 이전 상태로 변경).

예:

  • 시작 시간 = t1, 엔티티 = 플레이어 1, 속성 = 위치, a에서 b로 변경
  • 시작 시간 = t1, 엔티티 = 시스템, 속성 = 게임 모드, c에서 d로 변경
  • 시작 시간 = t2, 엔티티 = 플레이어 2, 속성 = 상태, e에서 f로 변경
  • 더 빨리 되감기 / 빨리 감기 또는 특정 시간 범위 만 기록하려면
    키 프레임이 필요합니다. 항상 기록하는 경우 항상 모든 게임 상태를 저장하십시오.
    특정 시간 범위 만 녹화하는 경우 처음에 초기 상태를 저장하십시오.


    -1

    재생 시스템을 구현하는 방법에 대한 아이디어가 필요 하면 애플리케이션에서 실행 취소 / 다시 실행 을 구현하는 방법을 Google에서 검색하십시오 . 실행 취소 / 다시 실행은 개념적으로 게임 재생과 동일하다는 것은 일부 사람들에게는 명백 할 수도 있지만, 모든 사람에게는 그렇지 않을 수도 있습니다. 되감기 할 수있는 특별한 경우 일뿐이며 응용 프로그램에 따라 특정 시점을 찾으십시오.

    실행 취소 / 다시 실행을 구현하는 사람은 결정적 / 비 결정적, 부동 변수 또는 특정 CPU에 대해 불평하지 않습니다.


    실행 취소 / 다시 실행은 기본적으로 결정적이고 이벤트 중심이며 상태가 가벼운 응용 프로그램에서 발생합니다 (예 : 워드 프로세서 문서의 상태는 전체 레이아웃이 아니라 텍스트 및 선택 사항이며 재 계산할 수 있음).

    그렇다면 CAD / CAM 애플리케이션, 회로 설계 소프트웨어, 모션 트래킹 소프트웨어 또는 워드 프로세서보다 실행 취소 / 다시 실행하는 애플리케이션을 사용한 적이 없습니다. 게임에서 재생하기 위해 실행 취소 / 다시 실행을위한 코드를 복사 할 수 있다고 말하는 것은 아닙니다. 그러나 기본 데이터 구조는 대기열이 아니라 스택입니다.
    당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
    Licensed under cc by-sa 3.0 with attribution required.