Cách thiết kế hệ thống phát lại


75

Vậy làm thế nào tôi có thể thiết kế một hệ thống phát lại?

Bạn có thể biết nó từ một số game nhất định như Warcraft 3 hoặc Starcraft nơi bạn có thể xem lại trò chơi sau khi nó đã được chơi.

Bạn kết thúc với một tập tin phát lại tương đối nhỏ. Vì vậy, câu hỏi của tôi là:

  • Làm thế nào để lưu dữ liệu? (định dạng tùy chỉnh?) (kích thước tệp nhỏ)
  • Điều gì sẽ được cứu?
  • Làm thế nào để làm cho nó chung chung để nó có thể được sử dụng trong các trò chơi khác để ghi lại một khoảng thời gian (và không phải là một trận đấu hoàn chỉnh chẳng hạn)?
  • Làm cho nó có thể chuyển tiếp và tua lại (WC3 không thể tua lại theo như tôi nhớ)

3
Mặc dù các câu trả lời dưới đây cung cấp nhiều thông tin chi tiết có giá trị, tôi chỉ muốn nhấn mạnh tầm quan trọng của việc phát triển trò chơi / công cụ của bạn có tính quyết định cao ( en.wikipedia.org/wiki/Deterministic_alacticm ), vì đó là điều cần thiết để đạt được mục tiêu của bạn.
Ari Patrick

2
Cũng lưu ý rằng các công cụ vật lý không mang tính quyết định (Havok tuyên bố đó là ...) vì vậy giải pháp chỉ lưu trữ đầu vào và dấu thời gian sẽ tạo ra kết quả khác nhau mỗi lần nếu trò chơi của bạn sử dụng vật lý.
Samaursa

5
Hầu hết các động cơ vật lý đều mang tính quyết định miễn là bạn sử dụng dấu thời gian cố định, dù sao bạn cũng nên làm. Tôi sẽ rất ngạc nhiên nếu Havok không. Không xác định là khá khó khăn trên máy tính ...

4
Xác định có nghĩa là cùng đầu vào = cùng đầu ra. Nếu bạn đã nổi trên một nền tảng và nhân đôi trên một nền tảng khác (ví dụ) hoặc vô tình vô hiệu hóa triển khai tiêu chuẩn điểm nổi IEEE của bạn, điều đó có nghĩa là bạn không chạy với cùng một đầu vào, không phải là không xác định.

3
Có phải tôi, hoặc câu hỏi này nhận được tiền thưởng mỗi tuần?
Vịt Cộng sản

Câu trả lời:


39

Bài viết xuất sắc này đề cập đến rất nhiều vấn đề: http://www.gamasutra.com/view/feature/2029/developing_your_own_Vplay_system.php

Một vài điều mà bài viết đề cập và làm tốt:

  • trò chơi của bạn phải có tính quyết định.
  • nó ghi lại trạng thái ban đầu của các hệ thống trò chơi trên khung đầu tiên và chỉ đầu vào của người chơi trong khi chơi trò chơi.
  • định lượng đầu vào để # bit thấp hơn. I E. đại diện cho các số float trong các phạm vi khác nhau (ví dụ: [0, 1] hoặc [-1, 1] trong phạm vi ít bit hơn. Đầu vào lượng tử phải được lấy trong khi chơi trò chơi thực tế.
  • sử dụng một bit đơn để xác định xem luồng đầu vào có dữ liệu mới hay không. Vì một số luồng sẽ không thay đổi thường xuyên, điều này khai thác sự gắn kết tạm thời trong các đầu vào.

Một cách để cải thiện hơn nữa tỷ lệ nén cho phần lớn các trường hợp là tách rời tất cả các luồng đầu vào của bạn và mã hóa hoàn toàn thời lượng chạy chúng một cách độc lập. Đây sẽ là một chiến thắng trong kỹ thuật mã hóa delta nếu bạn mã hóa lần chạy của mình trong 8 bit và bản thân nó chạy vượt quá 8 khung hình (rất có thể trừ khi trò chơi của bạn là một trình tạo nút thực sự). Tôi đã sử dụng kỹ thuật này trong một trò chơi đua xe để nén 8 phút đầu vào từ 2 người chơi trong khi đua quanh một đường đua xuống chỉ còn vài trăm byte.

Về mặt làm cho một hệ thống như vậy có thể sử dụng lại, tôi đã thực hiện hệ thống phát lại xử lý các luồng đầu vào chung, nhưng cũng cung cấp các móc nối để cho phép logic cụ thể của trò chơi đối với đầu vào bàn phím / gamepad / chuột so với các luồng này.

Nếu bạn muốn tua lại nhanh hoặc tìm kiếm ngẫu nhiên, bạn có thể lưu một điểm kiểm tra (toàn bộ trò chơi của bạn) mỗi N khung. N nên được chọn để giảm thiểu kích thước tệp phát lại và cũng đảm bảo thời gian mà người chơi phải chờ là hợp lý trong khi trạng thái được phát lại đến điểm đã chọn. Một cách để khắc phục điều này là đảm bảo rằng các tìm kiếm ngẫu nhiên chỉ có thể được thực hiện cho các vị trí điểm kiểm tra chính xác này. Tua lại là vấn đề đặt trạng thái trò chơi thành điểm kiểm tra ngay trước khung hình đang đề cập, sau đó phát lại các đầu vào cho đến khi bạn đến khung hình hiện tại. Tuy nhiên, nếu N quá lớn, bạn có thể bị quá giang mỗi vài khung hình. Một cách để làm mịn các vướng mắc này là lưu trữ trước một cách không đồng bộ các khung giữa 2 điểm kiểm tra trước đó trong khi bạn đang phát lại một khung được lưu trong bộ đệm từ khu vực điểm kiểm tra hiện tại.


nếu có RNG tham gia thì bao gồm các kết quả của RNG đã nói trong các luồng
ratchet freak

1
@ratchet freak: Với việc sử dụng PRNG một cách xác định, bạn có thể nhận được bằng cách chỉ lưu trữ hạt giống của nó trong các điểm kiểm tra.
NonNumeric

22

Bên cạnh giải pháp "đảm bảo các tổ hợp phím có thể chơi lại", điều này có thể gây khó khăn một cách đáng ngạc nhiên, bạn chỉ có thể ghi lại toàn bộ trạng thái trò chơi trên mỗi khung hình. Với một chút nén thông minh, bạn có thể ép nó xuống đáng kể. Đây là cách Braid xử lý mã tua lại thời gian của nó và nó hoạt động khá tốt.

Vì dù sao bạn cũng cần kiểm tra lại để tua lại, nên bạn có thể muốn thử thực hiện theo cách đơn giản trước khi làm phức tạp mọi thứ.


2
+1 Với một số nén thông minh, bạn thực sự có thể giảm lượng dữ liệu bạn cần lưu trữ (ví dụ: không lưu trữ trạng thái nếu nó không thay đổi so với trạng thái cuối cùng bạn lưu trữ cho đối tượng hiện tại) . Tôi đã thử điều này với vật lý và nó hoạt động rất tốt. Nếu bạn không có vật lý và không muốn tua lại trò chơi hoàn chỉnh, tôi sẽ sử dụng giải pháp của Joe đơn giản vì nó sẽ tạo ra các tệp nhỏ nhất có thể trong trường hợp nếu bạn cũng muốn tua lại, bạn có thể lưu trữ những ngiây cuối cùng của tro choi.
Samaursa

@Samaursa - Nếu bạn sử dụng thư viện nén tiêu chuẩn (ví dụ: gzip) thì bạn sẽ có được cách nén tương tự (có thể tốt hơn) mà không cần phải thực hiện thủ công như kiểm tra xem trạng thái đã thay đổi hay chưa.
Justin

2
@Kragen: Không thực sự đúng. Các thư viện nén tiêu chuẩn chắc chắn là tốt nhưng thường sẽ không thể tận dụng kiến ​​thức về miền cụ thể. Nếu bạn có thể giúp đỡ họ một chút, bằng cách đặt dữ liệu tương tự liền kề và loại bỏ những thứ thực sự không thay đổi, bạn có thể phá vỡ mọi thứ một cách đáng kể.
ZorbaTHut

1
@ZorbaTHut Về lý thuyết thì có, nhưng trên thực tế liệu nó có thực sự đáng nỗ lực?
Justin

4
Việc nó có xứng đáng với nỗ lực hay không hoàn toàn phụ thuộc vào số lượng dữ liệu bạn có. Nếu bạn có RTS với hàng trăm hoặc hàng nghìn đơn vị, điều đó có thể quan trọng. Nếu bạn cần lưu trữ các replay trong bộ nhớ như Braid, điều đó có thể quan trọng.

21

Bạn có thể xem hệ thống của mình như thể nó bao gồm một loạt các trạng thái và hàm, trong đó một hàm f[j]có đầu vào x[j]thay đổi trạng thái hệ thống s[j]thành trạng thái s[j+1], như vậy:

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

Một trạng thái là lời giải thích của toàn bộ thế giới của bạn. Vị trí của người chơi, vị trí của kẻ thù, điểm số, số đạn còn lại, v.v ... Tất cả mọi thứ bạn cần để vẽ một khung của trò chơi của bạn.

Một chức năng là bất cứ điều gì có thể ảnh hưởng đến thế giới. Thay đổi khung hình, nhấn phím, gói mạng.

Đầu vào là dữ liệu mà hàm lấy. Thay đổi khung có thể mất thời gian kể từ khi khung cuối cùng được thông qua, nhấn phím có thể bao gồm phím thực tế được nhấn, cũng như liệu phím shift có được nhấn hay không.

Vì lợi ích của lời giải thích này, tôi sẽ đưa ra các giả định sau:

Giả định 1:

Số lượng trạng thái cho một lần chạy nhất định của trò chơi lớn hơn nhiều so với số lượng chức năng. Bạn có thể có hàng trăm ngàn trạng thái, nhưng chỉ có vài chục chức năng (thay đổi khung, nhấn phím, gói mạng, v.v.). Tất nhiên, lượng đầu vào phải bằng lượng trạng thái trừ đi một trạng thái.

Giả định 2:

Chi phí không gian (bộ nhớ, đĩa) của việc lưu trữ một trạng thái duy nhất lớn hơn nhiều so với việc lưu trữ một chức năng và đầu vào của nó.

Giả định 3:

Chi phí tạm thời (thời gian) của việc trình bày một trạng thái là tương tự nhau, hoặc chỉ một hoặc hai bậc độ lớn dài hơn so với việc tính toán một hàm trên một trạng thái.

Tùy thuộc vào yêu cầu của hệ thống phát lại của bạn, có một số cách để thực hiện hệ thống phát lại, vì vậy chúng tôi có thể bắt đầu với cách đơn giản nhất. Tôi cũng sẽ làm một ví dụ nhỏ bằng cách sử dụng trò chơi cờ vua, được ghi trên các mảnh giấy.

Cách 1:

Cửa hàng s[0]...s[n]. Điều này rất đơn giản, rất đơn giản. Do giả định 2, chi phí không gian của việc này khá cao.

Đối với cờ vua, điều này sẽ được thực hiện bằng cách vẽ toàn bộ bảng cho mỗi lần di chuyển.

Cách 2:

Nếu bạn chỉ cần phát lại về phía trước, bạn có thể chỉ cần lưu trữ s[0]và sau đó lưu trữ f[0]...f[n-1](hãy nhớ, đây chỉ là tên của id của hàm) và x[0]...x[n-1](đầu vào của mỗi hàm này là gì). Để phát lại, bạn chỉ cần bắt đầu với s[0]và tính toán

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

và v.v.

Tôi muốn làm một chú thích nhỏ ở đây. Một số ý kiến ​​khác nói rằng trò chơi "phải có tính quyết định". Bất cứ ai nói rằng cần phải học lại Khoa học máy tính 101, bởi vì trừ khi trò chơi của bạn được chạy trên máy tính lượng tử, TẤT CẢ CÁC CHƯƠNG TRÌNH MÁY TÍNH đều bị XÁC ĐỊNH. Đó là những gì làm cho máy tính rất tuyệt vời.

Tuy nhiên, vì chương trình của bạn rất có thể phụ thuộc vào các chương trình bên ngoài, từ thư viện đến việc triển khai thực tế của CPU, đảm bảo rằng các chức năng của bạn hoạt động giống nhau giữa các nền tảng có thể khá khó khăn.

Nếu bạn sử dụng các số giả ngẫu nhiên, bạn có thể lưu trữ các số được tạo như một phần của đầu vào của mình xhoặc lưu trữ trạng thái của hàm prng như một phần của trạng thái của bạn svà việc triển khai nó như là một phần của chức năng f.

Đối với cờ vua, điều này sẽ được thực hiện bằng cách vẽ bảng ban đầu (được biết đến) và sau đó mô tả từng nước cờ cho biết quân cờ đã đi đâu. Đây là cách họ thực sự làm điều đó.

Cách 3:

Bây giờ, rất có thể bạn muốn có thể tìm kiếm vào phát lại của bạn. Đó là, tính toán s[n]cho một tùy ý n. Bằng cách sử dụng phương pháp 2, bạn cần tính toán s[0]...s[n-1]trước khi bạn có thể tính toán s[n], theo giả định 2, có thể khá chậm.

Để thực hiện điều này, phương thức 3 là tổng quát hóa các phương thức 1 và 2: lưu trữ f[0]...f[n-1]x[0]...x[n-1]giống như phương pháp 2, nhưng cũng lưu trữ s[j], cho tất cả j % Q == 0cho một hằng số đã cho Q. Nói một cách dễ hiểu hơn, điều này có nghĩa là bạn lưu trữ một dấu trang ở một trong số các Qtiểu bang. Ví dụ: cho Q == 100, bạn lưu trữs[0], s[100], s[200]...

Để tính toán s[n]một cách tùy ý n, trước tiên bạn tải phần lưu trữ trước đó s[floor(n/Q)], sau đó tính toán tất cả các hàm từ floor(n/Q)đến n. Nhiều nhất, bạn sẽ được tính toán Qchức năng. Các giá trị nhỏ Qhơn nhanh hơn để tính toán nhưng tiêu tốn nhiều không gian hơn, trong khi các giá trị lớn hơn Qtiêu thụ ít không gian hơn, nhưng mất nhiều thời gian hơn để tính toán.

Phương thức 3 với Q==1giống như phương pháp 1, trong khi phương thức 3 với Q==infphương thức 2.

Đối với cờ vua, điều này sẽ được thực hiện bằng cách vẽ mọi di chuyển, cũng như một trong mỗi 10 ván (cho Q==10).

Cách 4:

Nếu bạn muốn thay đổi phát lại, bạn có thể tạo ra một sự thay đổi nhỏ của phương pháp 3. Giả sử Q==100, và bạn muốn tính toán s[150]thông qua s[90]theo hướng ngược lại. Với phương pháp 3 chưa sửa đổi, bạn sẽ cần thực hiện 50 phép tính để có được s[150]và sau đó thêm 49 phép tính nữa s[149], v.v. Nhưng vì bạn đã tính toán s[149]để có được s[150], bạn có thể tạo bộ đệm s[100]...s[150]khi bạn tính toán s[150]lần đầu tiên và sau đó bạn đã có s[149]trong bộ đệm khi bạn cần hiển thị nó.

Bạn chỉ cần tạo lại cache mỗi khi bạn cần phải tính toán s[j], đối với j==(k*Q)-1đối với bất kỳ trao k. Lần này, tăng Qsẽ dẫn đến kích thước nhỏ hơn (chỉ cho bộ đệm), nhưng thời gian dài hơn (chỉ để tạo lại bộ đệm). Một giá trị tối ưu Qcó thể được tính nếu bạn biết kích thước và thời gian cần thiết để tính toán trạng thái và hàm.

Đối với cờ vua, điều này sẽ được thực hiện bằng cách vẽ mỗi lần di chuyển, cũng như cứ 10 ván (thì Q==10) sẽ yêu cầu vẽ một tờ giấy riêng, 10 ván cuối cùng bạn đã tính.

Phương pháp 5:

Nếu các trạng thái chỉ đơn giản tiêu tốn quá nhiều không gian hoặc các hàm tiêu tốn quá nhiều thời gian, bạn có thể tạo ra một giải pháp thực sự thực hiện (không phải giả) phát lại ngược lại. Để làm điều này, bạn phải tạo các hàm đảo ngược cho từng chức năng bạn có. Tuy nhiên, điều này đòi hỏi rằng mỗi chức năng của bạn là một mũi tiêm. Nếu điều này là có thể thực hiện được, thì để f'biểu thị nghịch đảo của hàm f, việc tính toán s[j-1]đơn giản như

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

Lưu ý rằng ở đây, chức năng và đầu vào là cả hai j-1, không phải j. Hàm và đầu vào tương tự này sẽ là những hàm bạn sẽ sử dụng nếu bạn đang tính toán

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

Tạo nghịch đảo của các chức năng này là phần khó khăn. Tuy nhiên, bạn thường không thể, vì một số dữ liệu trạng thái thường bị mất sau mỗi chức năng trong trò chơi.

Phương pháp này, như là, có thể đảo ngược tính toán s[j-1], nhưng chỉ khi bạn có s[j]. Điều này có nghĩa là bạn chỉ có thể xem phát lại ngược, bắt đầu từ thời điểm mà bạn quyết định phát lại ngược. Nếu bạn muốn phát lại ngược từ một điểm tùy ý, bạn phải kết hợp điều này với phương pháp 4.

Đối với cờ vua, điều này không thể được thực hiện, vì với một ván nhất định và di chuyển trước đó, bạn có thể biết quân cờ nào đã được di chuyển, nhưng không phải nó di chuyển từ đâu.

Phương pháp 6:

Cuối cùng, nếu bạn không thể đảm bảo tất cả các chức năng của mình là tiêm, bạn có thể thực hiện một mẹo nhỏ để làm như vậy. Thay vì chỉ có mỗi hàm trả về một trạng thái mới, bạn cũng có thể yêu cầu nó trả về dữ liệu mà nó đã loại bỏ, như vậy:

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

Đâu r[j]là dữ liệu bị loại bỏ. Và sau đó tạo các hàm nghịch đảo của bạn để chúng lấy dữ liệu bị loại bỏ, như vậy:

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

Bên cạnh đó của f[j]x[j], bạn cũng phải lưu trữ r[j]cho từng chức năng. Một lần nữa, nếu bạn muốn có thể tìm kiếm, bạn phải lưu trữ dấu trang, chẳng hạn như với phương pháp 4.

Đối với cờ vua, điều này sẽ giống như phương pháp 2, nhưng không giống như phương pháp 2, chỉ nói rằng quân cờ sẽ đi đâu, bạn cũng cần lưu trữ từng quân cờ xuất phát từ đâu.

Thực hiện:

Vì điều này hoạt động cho tất cả các loại trạng thái, với tất cả các loại chức năng, cho một trò chơi cụ thể, bạn có thể đưa ra một số giả định, điều đó sẽ giúp thực hiện dễ dàng hơn. Trên thực tế, nếu bạn thực hiện phương pháp 6 với toàn bộ trạng thái trò chơi, không chỉ bạn sẽ có thể phát lại dữ liệu mà còn quay ngược thời gian và tiếp tục chơi từ bất kỳ thời điểm nào. Điều đó sẽ khá tuyệt vời.

Thay vì lưu trữ tất cả trạng thái trò chơi, bạn chỉ cần lưu trữ mức tối thiểu mà bạn yêu cầu để vẽ một trạng thái nhất định và tuần tự hóa dữ liệu này sau mỗi khoảng thời gian cố định. Các trạng thái của bạn sẽ là các tuần tự hóa này và đầu vào của bạn bây giờ sẽ là sự khác biệt giữa hai tuần tự hóa. Chìa khóa để làm việc này là việc tuần tự hóa sẽ thay đổi rất ít nếu nhà nước thế giới cũng thay đổi rất ít. Sự khác biệt này là hoàn toàn không thể đảo ngược, vì vậy việc thực hiện phương pháp 5 bằng dấu trang là rất có thể.

Tôi đã thấy điều này được thực hiện trong một số trò chơi lớn, chủ yếu là phát lại dữ liệu gần đây ngay lập tức khi một sự kiện (một mảnh trong khung hình / giây hoặc điểm trong các trò chơi thể thao) xảy ra.

Tôi hy vọng lời giải thích này không quá nhàm chán.

Điều này không có nghĩa là một số chương trình hoạt động như chúng không mang tính quyết định (chẳng hạn như MS Windows ^^). Bây giờ, nghiêm túc, nếu bạn có thể tạo một chương trình không xác định trên máy tính xác định, bạn có thể chắc chắn rằng mình sẽ đồng thời giành được huy chương Trường, giải thưởng Turing và thậm chí có thể là giải Oscar và Grammy cho tất cả những gì đáng giá.


Trên "TẤT CẢ CÁC CHƯƠNG TRÌNH MÁY TÍNH ĐƯỢC XÁC ĐỊNH", bạn đang bỏ qua việc xem xét các chương trình dựa vào luồng. Mặc dù luồng chủ yếu được sử dụng để tải tài nguyên hoặc để tách vòng lặp kết xuất, có những trường hợp ngoại lệ cho điều đó, và tại thời điểm đó, bạn không thể yêu cầu xác định thực sự nữa, trừ khi bạn nghiêm ngặt về việc thực thi quyết định. Cơ chế khóa một mình sẽ không đủ. Bạn sẽ không thể chia sẻ BẤT K data dữ liệu có thể thay đổi nào mà không cần làm thêm. Trong nhiều tình huống, một trò chơi không cần mức độ nghiêm ngặt đó vì mục đích riêng của nó, nhưng có thể cho những thứ như replay.
krdluzni

1
@krdluzni Luồng, song song và số ngẫu nhiên từ các nguồn ngẫu nhiên thực sự không làm cho các chương trình không mang tính quyết định. Thời gian xử lý chủ đề, khóa chết, bộ nhớ chưa được khởi tạo và thậm chí cả điều kiện cuộc đua chỉ là những đầu vào bổ sung mà chương trình của bạn thực hiện. Sự lựa chọn của bạn để loại bỏ các đầu vào này hoặc thậm chí không xem xét chúng (vì bất kỳ lý do gì) sẽ không ảnh hưởng đến thực tế là chương trình của bạn sẽ thực hiện chính xác cùng một đầu vào chính xác. "Không xác định" là một thuật ngữ Khoa học Máy tính rất chính xác, vì vậy vui lòng tránh sử dụng nó nếu bạn không biết ý nghĩa của nó.

@oscar (Có thể hơi ngắn gọn, bận rộn, có thể chỉnh sửa sau): Mặc dù trong một số ý nghĩa lý thuyết nghiêm ngặt, bạn có thể yêu cầu định thời chuỗi, v.v. làm đầu vào, điều này không hữu ích trong bất kỳ ý nghĩa thực tế nào, vì nhìn chung chúng không thể được quan sát bởi chương trình tự hoặc được kiểm soát hoàn toàn bởi nhà phát triển. Hơn nữa, một chương trình không mang tính quyết định sẽ khác biệt đáng kể, nó không mang tính quyết định (theo nghĩa máy trạng thái). Tôi hiểu ý nghĩa của thuật ngữ này. Tôi ước họ đã chọn một cái gì đó khác, thay vì quá tải một thuật ngữ đã tồn tại từ trước.
krdluzni

@krdluzni Quan điểm của tôi trong việc thiết kế các hệ thống phát lại với các yếu tố không thể đoán trước như thời gian xử lý luồng (nếu chúng ảnh hưởng đến khả năng tính toán chính xác của bạn), là đối xử với chúng giống như bất kỳ nguồn đầu vào nào khác, giống như đầu vào của người dùng. Tôi không thấy bất kỳ ai phàn nàn một chương trình là "không xác định" bởi vì nó mất đầu vào của người dùng hoàn toàn không thể đoán trước. Về thuật ngữ, nó không chính xác và khó hiểu. Tôi muốn họ sử dụng một cái gì đó như "thực tế không thể đoán trước" hoặc một cái gì đó tương tự. Và không, không phải là không thể, kiểm tra gỡ lỗi phát lại của VMWare.

9

Một điều mà các câu trả lời khác chưa được đề cập là sự nguy hiểm của phao. Bạn không thể tạo một ứng dụng hoàn toàn xác định bằng cách sử dụng float.

Sử dụng phao, bạn có thể có một hệ thống hoàn toàn xác định, nhưng chỉ khi:

  • Sử dụng chính xác nhị phân
  • Sử dụng cùng một CPU

Điều này là do sự biểu diễn bên trong của các float thay đổi từ CPU này sang CPU khác - đáng kể nhất là giữa CPU AMD và CPU intel. Miễn là các giá trị nằm trong các thanh ghi FPU, chúng chính xác hơn so với bề ngoài của C, vì vậy mọi tính toán trung gian đều được thực hiện với độ chính xác cao hơn.

Rõ ràng là điều này sẽ ảnh hưởng đến AMD và intel bit như thế nào - giả sử một người sử dụng số float 80 bit và 64 người khác chẳng hạn - nhưng tại sao lại yêu cầu nhị phân giống nhau?

Như tôi đã nói, độ chính xác cao hơn được sử dụng miễn là các giá trị nằm trong các thanh ghi FPU . Điều này có nghĩa là bất cứ khi nào bạn biên dịch lại, tối ưu hóa trình biên dịch của bạn có thể trao đổi các giá trị trong và ngoài các thanh ghi FPU, dẫn đến kết quả khác nhau một cách tinh tế.

Bạn có thể giúp điều này bằng cách đặt cờ _control87 () / _ controlfp () để sử dụng độ chính xác thấp nhất có thể. Tuy nhiên, một số thư viện cũng có thể chạm vào điều này (ít nhất là một số phiên bản của d3d đã làm).


3
Với GCC, bạn có thể sử dụng -ffloat-store để buộc các giá trị ra khỏi các thanh ghi và cắt ngắn thành 32/64 bit chính xác, mà không cần phải lo lắng về các thư viện khác làm rối các cờ điều khiển của bạn. Rõ ràng, điều này sẽ tác động tiêu cực đến tốc độ của bạn (nhưng cũng sẽ có bất kỳ lượng tử hóa nào khác).

8

Lưu trạng thái ban đầu của trình tạo số ngẫu nhiên của bạn. Sau đó lưu, đánh dấu thời gian, từng đầu vào (chuột, bàn phím, mạng, bất cứ điều gì). Nếu bạn có một trò chơi nối mạng, có lẽ bạn đã có tất cả những thứ này.

Đặt lại RNG và phát lại đầu vào. Đó là nó.

Điều này không giải quyết được quanh co, mà không có giải pháp chung, ngoài việc phát lại từ đầu nhanh nhất có thể. Bạn có thể cải thiện hiệu suất cho việc này bằng cách kiểm tra toàn bộ trạng thái trò chơi sau mỗi X giây, sau đó bạn sẽ chỉ cần phát lại nhiều lần, nhưng toàn bộ trạng thái trò chơi cũng có thể rất tốn kém để lấy.

Các chi tiết của định dạng tệp không quan trọng, nhưng hầu hết các công cụ đều có cách để tuần tự hóa các lệnh và trạng thái - để kết nối mạng, lưu hoặc bất cứ điều gì. Chỉ cần sử dụng nó.


4

Tôi sẽ bỏ phiếu chống lại phát lại xác định. FAR đơn giản hơn và FAR ít bị lỗi hơn để lưu trạng thái của mọi thực thể cứ sau 1 / N giây.

Chỉ lưu những gì bạn muốn hiển thị khi phát lại - nếu đó chỉ là vị trí và tiêu đề, tốt thôi, nếu bạn cũng muốn hiển thị số liệu thống kê, hãy lưu lại, nhưng nói chung hãy lưu càng ít càng tốt.

Tinh chỉnh mã hóa. Sử dụng càng ít bit càng tốt cho mọi thứ. Phát lại không cần phải hoàn hảo miễn là nó trông đủ tốt. Ngay cả khi bạn sử dụng float cho, giả sử, tiêu đề, bạn có thể lưu nó trong một byte và nhận 256 giá trị có thể (độ chính xác 1,4)). Điều đó có thể là đủ hoặc thậm chí quá nhiều cho vấn đề cụ thể của bạn.

Sử dụng mã hóa delta. Trừ khi các thực thể của bạn dịch chuyển tức thời (và nếu có, hãy xử lý trường hợp riêng), mã hóa các vị trí là sự khác biệt giữa vị trí mới và vị trí cũ - đối với các chuyển động ngắn, bạn có thể thoát khỏi các bit ít hơn nhiều so với bạn cần cho các vị trí đầy đủ .

Nếu bạn muốn tua lại dễ dàng, hãy thêm khung hình chính (dữ liệu đầy đủ, không có deltas) mỗi N khung. Bằng cách này, bạn có thể thoát khỏi độ chính xác thấp hơn đối với đồng bằng và các giá trị khác, lỗi làm tròn sẽ không quá khó khăn nếu bạn đặt lại thành giá trị "đúng" theo định kỳ.

Cuối cùng, gzip toàn bộ điều :)


1
Điều này phụ thuộc một chút vào loại trò chơi mặc dù.
Jari Komppa

Tôi sẽ rất cẩn thận với tuyên bố này. Đặc biệt đối với các dự án lớn hơn với sự phụ thuộc của bên thứ 3, việc cứu nhà nước có thể là không thể. Trong khi đặt lại và phát lại đầu vào luôn luôn có thể.
TomSmartBishop 17/07/18

2

Nó khó. Đầu tiên và hầu hết tất cả đọc câu trả lời của Jari Komppa.

Một phát lại được thực hiện trên máy tính của tôi có thể không hoạt động trên máy tính của bạn vì kết quả nổi là rất khác nhau. Đây là một vấn đề lớn.

Nhưng sau đó, nếu bạn có số ngẫu nhiên là lưu trữ giá trị hạt giống trong phát lại. Sau đó tải tất cả các trạng thái mặc định và đặt số ngẫu nhiên cho hạt giống đó. Từ đó bạn có thể chỉ cần ghi lại trạng thái phím / chuột hiện tại và khoảng thời gian theo cách đó. Sau đó chạy tất cả các sự kiện bằng cách sử dụng đó làm đầu vào.

Để di chuyển xung quanh các tệp (khó hơn nhiều), bạn sẽ cần phải bỏ BỘ NHỚ. Giống như, nơi mỗi đơn vị, tiền, thời gian trôi qua, tất cả các trạng thái trò chơi. Sau đó chuyển tiếp nhanh nhưng phát lại mọi thứ nhưng bỏ qua kết xuất, âm thanh, vv cho đến khi bạn đến đích thời gian bạn muốn. Điều này có thể xảy ra mỗi phút hoặc 5 phút tùy thuộc vào tốc độ chuyển tiếp của nó.

Các điểm chính là - Xử lý các số ngẫu nhiên - Sao chép đầu vào (người chơi và người chơi từ xa) - Trạng thái đổ rác để nhảy xung quanh các tệp và ... - ĐÃ NÓI KHÔNG NÓI (vâng, tôi phải hét lên)


2

Tôi hơi ngạc nhiên khi không ai đề cập đến tùy chọn này, nhưng nếu trò chơi của bạn có thành phần nhiều người chơi, bạn có thể đã thực hiện rất nhiều công việc khó khăn cần thiết cho tính năng này. Rốt cuộc, có gì nhiều người chơi nhưng cố gắng chơi lại các chuyển động của người khác vào một thời điểm (hơi) khác trên máy tính của bạn?

Điều này cũng mang lại cho bạn những lợi ích của kích thước tệp nhỏ hơn như là một tác dụng phụ, một lần nữa giả sử bạn đã làm việc với mã mạng thân thiện với băng thông.

Theo nhiều cách, nó kết hợp cả hai tùy chọn "cực kỳ xác định" và "ghi lại mọi thứ". Bạn vẫn sẽ cần sự quyết đoán - nếu chơi lại của bạn về cơ bản là các bot chơi lại trò chơi theo đúng cách bạn chơi ban đầu, bất kỳ hành động nào họ thực hiện có thể có kết quả ngẫu nhiên đều cần phải có kết quả tương tự.

Định dạng dữ liệu có thể đơn giản như một đống lưu lượng truy cập mạng, mặc dù tôi tưởng tượng rằng sẽ không hại gì khi dọn dẹp nó một chút (rốt cuộc bạn không phải lo lắng về độ trễ khi phát lại). Bạn chỉ có thể chơi lại một phần của trò chơi bằng cách sử dụng cơ chế điểm kiểm tra mà người khác đã đề cập - thông thường, một trò chơi nhiều người sẽ gửi toàn bộ trạng thái cập nhật trò chơi thường xuyên, vì vậy một lần nữa bạn có thể đã thực hiện công việc này.


0

Để có được tệp phát lại nhỏ nhất có thể, bạn sẽ cần đảm bảo rằng trò chơi của bạn có tính quyết định. Thông thường, điều này liên quan đến việc xem xét trình tạo số ngẫu nhiên của bạn và xem nơi nó được sử dụng trong logic trò chơi.

Rất có thể bạn sẽ cần phải có RNG logic trò chơi và RNG mọi thứ khác cho những thứ như GUI, hiệu ứng hạt, âm thanh. Khi bạn đã hoàn thành việc này, bạn cần ghi lại trạng thái ban đầu của RNG logic trò chơi, sau đó ra lệnh cho tất cả người chơi ở mọi khung hình.

Đối với nhiều trò chơi, có một mức độ trừu tượng giữa đầu vào và logic trò chơi trong đó đầu vào được chuyển thành các lệnh. Ví dụ: nhấn nút A trên bộ điều khiển sẽ dẫn đến lệnh kỹ thuật số "nhảy" được đặt thành đúng và logic trò chơi phản ứng với các lệnh mà không cần kiểm tra trực tiếp bộ điều khiển. Bằng cách này, bạn sẽ chỉ cần ghi lại các lệnh tác động đến logic trò chơi (không cần ghi lại lệnh "Tạm dừng") và rất có thể dữ liệu này sẽ nhỏ hơn ghi dữ liệu của bộ điều khiển. Bạn cũng không phải lo lắng về việc ghi lại trạng thái của sơ đồ điều khiển trong trường hợp người chơi quyết định sắp xếp lại các nút.

Tua lại là một vấn đề khó khăn khi sử dụng phương pháp xác định và ngoài việc sử dụng ảnh chụp nhanh của trạng thái trò chơi và chuyển tiếp nhanh đến thời điểm bạn muốn xem, bạn không thể làm gì khác ngoài việc ghi lại toàn bộ trạng thái trò chơi từng khung hình.

Mặt khác, chuyển tiếp nhanh chắc chắn là có thể làm được. Miễn là logic trò chơi của bạn không phụ thuộc vào kết xuất của bạn, bạn có thể chạy logic trò chơi bao nhiêu lần tùy ý trước khi kết xuất khung hình mới của trò chơi. Tốc độ chuyển tiếp nhanh sẽ bị ràng buộc bởi máy của bạn. Nếu bạn muốn bỏ qua phía trước với gia số lớn, bạn sẽ cần sử dụng cùng một phương pháp chụp nhanh như bạn cần để tua lại.

Có thể phần quan trọng nhất của việc viết một hệ thống phát lại dựa trên tính xác định là ghi lại một luồng dữ liệu gỡ lỗi. Luồng gỡ lỗi này chứa một ảnh chụp nhanh nhất có thể mỗi khung hình (hạt RNG, biến đổi thực thể, hình động, v.v.) và có thể kiểm tra luồng gỡ lỗi được ghi lại theo trạng thái của trò chơi trong các lần phát lại. Điều này sẽ cho phép bạn nhanh chóng cho bạn biết sự không phù hợp ở cuối bất kỳ khung nào. Điều này sẽ giúp tiết kiệm vô số thời gian muốn kéo tóc ra khỏi những lỗi không xác định. Một cái gì đó đơn giản như một biến chưa được khởi tạo sẽ làm rối tung mọi thứ vào giờ thứ 11.

LƯU Ý: Nếu trò chơi của bạn liên quan đến truyền phát nội dung động hoặc bạn có logic trò chơi trên nhiều luồng hoặc trên các lõi khác nhau ... chúc may mắn.


0

Để bật cả ghi và tua lại, ghi lại tất cả các sự kiện (do người dùng tạo, bộ đếm thời gian được tạo, giao tiếp được tạo, ...).

Đối với mỗi thời gian ghi sự kiện của sự kiện, những gì đã được thay đổi, giá trị trước đó, giá trị mới.

Các giá trị được tính toán không cần phải được ghi lại trừ khi tính toán là ngẫu nhiên
(Trong những trường hợp này, bạn cũng có thể ghi lại các giá trị được tính hoặc ghi lại các thay đổi vào hạt giống sau mỗi phép tính ngẫu nhiên).

Dữ liệu được lưu là một danh sách các thay đổi.
Thay đổi có thể được lưu ở các định dạng khác nhau (nhị phân, xml, ...).
Thay đổi bao gồm id thực thể, tên thuộc tính, giá trị cũ, giá trị mới.

Đảm bảo hệ thống của bạn có thể phát lại các thay đổi này (truy cập thực thể mong muốn, thay đổi thuộc tính mong muốn chuyển sang trạng thái mới hoặc lùi về trạng thái cũ).

Thí dụ:

  • thời gian từ start = t1, entity = player 1, property = location, đã thay đổi từ a thành b
  • thời gian từ start = t1, entity = system, property = game mode, đã thay đổi từ c thành d
  • thời gian từ start = t2, entity = player 2, property = state, đã thay đổi từ e thành f
  • Để cho phép tua nhanh / tua nhanh hơn hoặc chỉ ghi các khoảng thời gian nhất định,
    các khung chính là cần thiết - nếu ghi lại mọi lúc, mọi lúc và sau đó lưu toàn bộ trạng thái trò chơi.
    Nếu chỉ ghi một khoảng thời gian nhất định, lúc đầu, hãy lưu trạng thái ban đầu.


    -1

    Nếu bạn cần ý tưởng về cách triển khai hệ thống phát lại của mình, hãy tìm kiếm google để biết cách triển khai hoàn tác / làm lại trong một ứng dụng. Có thể rõ ràng đối với một số người, nhưng có thể không phải tất cả, rằng hoàn tác / làm lại về mặt khái niệm giống như phát lại cho các trò chơi. Nó chỉ là một trường hợp đặc biệt trong đó bạn có thể tua lại, và tùy thuộc vào ứng dụng, tìm kiếm đến một thời điểm cụ thể.

    Bạn sẽ thấy rằng không ai thực hiện hoàn tác / làm lại phàn nàn về các biến xác định / không xác định, biến float hoặc CPU cụ thể.


    Hoàn tác / làm lại xảy ra trong các ứng dụng mà về cơ bản là xác định, hướng sự kiện và ánh sáng trạng thái (ví dụ: trạng thái của tài liệu xử lý văn bản chỉ là văn bản và lựa chọn, không phải là toàn bộ bố cục, có thể được tính toán lại).

    Rõ ràng là bạn chưa bao giờ sử dụng các ứng dụng CAD / CAM, phần mềm thiết kế mạch, phần mềm theo dõi chuyển động hoặc bất kỳ ứng dụng nào có thể hoàn tác / làm lại phức tạp hơn trình xử lý văn bản. Tôi không nói rằng mã cho hoàn tác / làm lại có thể được sao chép để phát lại trên một trò chơi, chỉ là về mặt khái niệm giống nhau (lưu trạng thái và phát lại chúng sau). Tuy nhiên, cấu trúc dữ liệu chính không phải là một hàng đợi mà là một ngăn xếp.
    Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
    Licensed under cc by-sa 3.0 with attribution required.