Điều gì sẽ là cách tốt nhất để lưu trữ các chuyển động trên một trò chơi để cho phép quay trở lại?


8

Tôi đang phát triển một trò chơi bảng có lớp trò chơi kiểm soát dòng trò chơi và người chơi gắn liền với lớp trò chơi. Bảng chỉ là một lớp trực quan, nhưng kiểm soát các chuyển động là tất cả của trò chơi.
Người chơi có thể cố gắng thực hiện các chuyển động sai dẫn đến trò chơi nói với người chơi rằng loại chuyển động này không được phép (và cho biết lý do đằng sau nó). Nhưng trong một số trường hợp, người chơi chỉ có thể thực hiện một chuyển động và nhận ra đó không phải là động tác tốt nhất, hoặc chỉ cần bỏ lỡ nhấp vào bảng hoặc chỉ muốn thử một cách tiếp cận khác.
Trò chơi có thể được lưu và tải, do đó, một cách tiếp cận có thể lưu trữ tất cả dữ liệu trước khi di chuyển và không cho phép quay ngược lại, nhưng cho phép người dùng tải lần lượt tự động lưu cuối cùng. Đây có vẻ là một cách tiếp cận tốt, nhưng liên quan đến tương tác của người dùng và việc vẽ lại bảng có thể gây nhàm chán cho người dùng. Có cách nào tốt hơn để làm những thứ này hay kiến ​​trúc thực sự quan trọng với thứ này không?

Lớp trò chơi và lớp người chơi không phức tạp, vì vậy, việc sao chép các lớp là một ý tưởng tốt hoặc tách dữ liệu trò chơi khỏi quy tắc trò chơi một cách tiếp cận tốt hơn hoặc lưu / tải (thậm chí tự động trên một rollback được hỏi) có ổn không?

CẬP NHẬT: cách trò chơi này hoạt động: Nó có một bảng chính nơi bạn thực hiện các động tác (di chuyển đơn giản) và một bảng người chơi phản ứng với các bước di chuyển trên bảng chính. Nó cũng có các động tác phản ứng theo các động tác của người chơi khác và trên các bước di chuyển của chính bạn. Bạn cũng có thể phản ứng khi không đến lượt mình, làm mọi việc trên bảng của bạn. Có thể tôi không thể hoàn tác mọi di chuyển, nhưng tôi thích ý tưởng hoàn tác / làm lại nổi trong một trong những câu trả lời hiện tại.


4
Nhìn vào mẫu memento

1
Chỉ cần lưu trữ lịch sử của tất cả các đối tượng trên bảng mỗi lượt.
Ramhound

2
@Ramhound: Nếu bạn làm theo cách đó, chương trình của bạn có thể dễ dàng biến thành ... chó săn RAM. ;)
Mason Wheeler

1
@MasonWheeler - Bạn không cần phải theo dõi lịch sử vượt quá một điểm nhất định. Rất nhiều chương trình lưu trữ hàng tấn dữ liệu trong một tính năng tương tự và không gặp vấn đề với bộ nhớ.
Ramhound

Bạn có thể lưu trữ tọa độ di chuyển trên một ngăn xếp? Điều này được thực hiện trong các trò chơi cờ vua. YMMV tùy thuộc vào việc đó là trò chơi cờ theo lượt hay chuyển động lỏng như pong.
mike30

Câu trả lời:


16

Tại sao không lưu trữ lịch sử của tất cả các di chuyển được thực hiện (cũng như bất kỳ sự kiện không xác định nào khác)? Bằng cách đó bạn luôn có thể xây dựng lại bất kỳ trạng thái trò chơi nào.

Điều này sẽ tốn ít không gian lưu trữ hơn so với việc lưu trữ tất cả các trạng thái của trò chơi và việc thực hiện sẽ khá đơn giản.


2
Là một phần thưởng bổ sung, tại một số điểm, bạn có thể thêm tính năng phát lại tương tự như StarCraft.
Jonathan Rich

2
+1 Điều này tương tự như một kỹ thuật gọi là Tìm nguồn sự kiện được sử dụng trong các bối cảnh khác.
MattDavey

1
Nhận xét của @MattDavey Giải thích kỹ thuật này một cách tuyệt vời. Tôi sẽ chấp nhận điều này bởi vì nó có thể khá dễ thực hiện, bất kể trò chơi có phức tạp đến đâu.
gbianchi

Tìm nguồn cung ứng sự kiện là cách tốt nhất - đưa bạn đến bất kỳ điểm nào trong trò chơi
Murph

8

Xây dựng một hệ thống Hoàn tác về mặt khái niệm khá đơn giản. Bạn chỉ cần theo dõi các thay đổi. Bạn sẽ muốn có một ngăn xếp và một loại đối tượng mô tả một phần trạng thái của trò chơi. Ngăn xếp của bạn phải là một chồng các mảng / danh sách các đối tượng trạng thái trò chơi.

Bí quyết là, đừng ghi lại những động thái mà mọi người thực hiện . Tôi thấy điều này trong các câu trả lời khác, và nó có thể được thực hiện, nhưng nó phức tạp hơn nhiều. Thay vào đó, hãy ghi lại bảng trò chơi trông như thế nào trước khi di chuyển được thực hiện. Ví dụ: khi bạn di chuyển một mảnh, bạn đã thực hiện hai thay đổi: Mảnh còn lại một hình vuông và mảnh được đặt trên một hình vuông mới. Nếu bạn tạo một mảng hiển thị hai hình vuông đó trông như thế nào trước khi di chuyển bắt đầu, thì bạn có thể Hoàn tác di chuyển chỉ bằng cách khôi phục hai hình vuông đó theo cách trước khi di chuyển được thực hiện.

Hoặc, nếu trạng thái trò chơi của bạn được giữ trong các mảnh và không phải là hình vuông, thì những gì bạn ghi lại là vị trí của mảnh trước khi nó di chuyển. (Và nếu quân cờ của bạn tương tác với bất kỳ quân cờ nào khác, chẳng hạn như bắt trong cờ vua, hãy ghi lại những quân cờ đó trước khi chúng được thay đổi.)

Khi bất kỳ di chuyển xảy ra:

  • Tạo khung hoàn tác mới (mảng)
  • Mỗi khi có điều gì đó thay đổi, nếu bạn chưa có thay đổi cho đối tượng đó trong khung Hoàn tác hiện tại, hãy thêm trạng thái của nó vào khung Hoàn tác hiện tại trước khi áp dụng thay đổi.
  • Khi di chuyển kết thúc, đẩy khung Hoàn tác lên ngăn xếp.

Khi người dùng nói Hoàn tác:

  • bật đỉnh ngăn xếp và lấy khung Undo
  • Lặp lại qua từng đối tượng trong khung và khôi phục nó
  • (Tùy chọn): Theo dõi các thay đổi được thực hiện ở đây theo cách chính xác giống như bạn đã làm khi thiết lập khung Hoàn tác và đẩy khung lên ngăn xếp. Đây là cách bạn thực hiện Làm lại. (Nếu bạn làm điều này, việc đẩy khung Hoàn tác mới cũng sẽ xóa ngăn xếp Làm lại.)

4
Đây là một ý tưởng tốt, miễn là trạng thái của trò chơi chiếm ít bộ nhớ. Đóng góp nguồn mở thực sự đầu tiên của tôi là thay đổi hệ thống hoàn tác của Cinelerra thành mô hình "di chuyển bản ghi", bởi vì trong các dự án lớn, nó sẽ đẩy megabyte dữ liệu trạng thái lên ngăn xếp hoàn tác mỗi khi tôi nhấn phím. Nó có thể không bao giờ là một vấn đề với trò chơi cờ bàn, nhưng nếu có bất kỳ cơ hội nào về yêu cầu bộ nhớ mở rộng như vậy, tốt hơn hết là cắn viên đạn ngay bây giờ.
Karl Bielefeldt

2
@KarlBielefeldt, tôi thấy câu trả lời này là chủ trương lưu trữ các thay đổi thành trạng thái duy nhất, không phải toàn bộ trạng thái trò chơi tại mỗi điểm. Câu trả lời có rất nhiều phản ánh hữu ích về vấn đề này, nhưng tôi không thích phần mở đầu "không ghi lại các động thái như các câu trả lời khác nói" khi nó thực sự kết thúc việc ủng hộ một cái gì đó rất giống nhau.

@ dan1111: Tôi ủng hộ một cái gì đó tương tự , nhưng khác biệt tinh tế. Khi bạn nói "bản ghi di chuyển", âm thanh đó giống như "ghi lại các thay đổi." Những gì tôi mô tả là "ghi lại những thứ trông như thế nào trước khi chúng được thay đổi", điều này làm cho hệ thống Undo sạch hơn rất nhiều.
Mason Wheeler

1
@MasonWheeler, tôi đồng ý rằng cách bạn đề xuất thực hiện nó là thanh lịch. Tuy nhiên, với tôi, việc ghi lại trạng thái của những gì chỉ thay đổi chỉ đơn giản là một cách hay để ghi lại các bước di chuyển. Tôi cho rằng nó thực sự chỉ là một cuộc tranh luận về ngữ nghĩa ...

1
@kuhaku số Recording di chuyển được thực hiện được lập biên bản về những gì bạn đang thay đổi để . Những gì tôi đang giải thích ở đây là bạn cần phải thực hiện một kỷ lục về những gì bạn đang thay đổi từ , do đó bạn có thể lấy lại nó.
Mason Wheeler

2

Một cách hay để thực hiện điều này là gói gọn các bước di chuyển của bạn dưới dạng các đối tượng Command. Hãy nghĩ về một giao diện Command có các phương thức move(Position newPosition)undo. Một lớp cụ thể có thể thực hiện giao diện này sao cho movephương thức, nó có thể lưu trữ vị trí hiện tại trên bảng (Vị trí là một lớp có thể giữ các giá trị hàng và cột), sau đó thực hiện chuyển động đến hàng và cột được xác định bởi newPosition. Sau này, phương thức sẽ thêm lệnh ( this) vào một chồng các đối tượng Command toàn cục.

Bây giờ, khi người dùng cần quay lại bước trước đó, chỉ cần bật đối tượng Lệnh cuối cùng từ ngăn xếp và gọi undophương thức của nó . Điều này cũng cung cấp cho bạn khả năng quay ngược lại bao nhiêu bước bạn cần.

Mong rằng sẽ giúp.


2
Command là một mẫu thiết kế cổ điển và chính xác những gì tôi sẽ đề xuất. Hơn nữa, tôi nghĩ rằng mỗi lệnh nên có một con trỏ tới người chơi đã di chuyển để bạn có thể nhận được bản sao của toàn bộ trò chơi ở cuối chỉ bằng cách duyệt qua danh sách và gọi hàm loại "toString" cho mỗi lệnh. Ví dụ: "Annie đã thêm một khách hàng bình thường (lúa mì, bí ngô, củ cải) và một người trợ giúp (Người mua hàng). Bill giao cho khách hàng thường xuyên (lúa mì, bí ngô) với giá +8 tiền mặt", sẽ là mô tả cho một vài người chơi lần lượt tham gia "Tại Cổng Loyang".
John Munsch

Đề nghị tốt đẹp John.
Samir Hasan

1

Va chạm luồng cổ nhưng cách dễ nhất tôi đã tìm thấy để giải quyết vấn đề này, ít nhất là trong các tình huống phức tạp, là chỉ sao chép mọi thứ chết tiệt trước khi vận hành người dùng ...

... Nghe có vẻ lãng phí phải không? Ngoại trừ nếu nó thực sự lãng phí, thì bước tiếp theo của bạn là làm cho việc sao chép rẻ hơn để các phần không bị thay đổi trong bước tiếp theo sẽ không được sao chép sâu.

Trong trường hợp của tôi, tôi thường làm việc với hàng gigabyte dữ liệu nhưng người dùng thường chỉ chạm vào megabyte cho một thao tác, vì vậy sao chép mọi thứ thường sẽ rất tốn kém và lãng phí một cách lố bịch .... nhưng tôi đã thiết lập các cấu trúc dữ liệu này xoay quanh nông sao chép những gì không thay đổi và tôi thấy đây là giải pháp đơn giản nhất và nó cũng mở ra rất nhiều khả năng mới, như cấu trúc dữ liệu bền bỉ bất biến, đa luồng an toàn hơn, mạng gật đầu nhập một cái gì đó và tạo ra một cái gì đó mới, không phá hủy, chỉnh sửa các đối tượng (có thể sao chép nông, giả sử, dữ liệu lưới đắt tiền của họ nhưng có bản sao có vị trí độc nhất của riêng mình trên thế giới trong khi hầu như không chiếm bộ nhớ), v.v.

store all scene/application state in undo
perform user operation

on undo/redo:
   swap stored state with application state

Mọi dự án đều tuân theo mô hình cơ bản này thay vì phải ghi lại cẩn thận từng thay đổi trạng thái nhỏ và cho hàng tá loại dữ liệu khác nhau không phù hợp với mô hình đồng nhất (vẽ hình ảnh / kết cấu, thay đổi thuộc tính, thay đổi lưới, thay đổi trọng lượng, thay đổi cảnh phân cấp, thay đổi shader không liên quan đến thuộc tính như hoán đổi cái này với cái khác, thay đổi phong bì, v.v.). Thay vào đó, nếu điều đó quá lãng phí về bộ nhớ và thời gian, thì chúng tôi không đưa ra một hệ thống hoàn tác fancier, thay vào đó chúng tôi tìm cách tối ưu hóa việc sao chép để có thể tạo một phần bản sao trong tất cả các cấu trúc dữ liệu đắt nhất. Điều đó để lại như là một chi tiết tối ưu hóa, nơi bạn có thể thực hiện chức năng chính xác hoàn tác ngay lập tức sẽ luôn luôn chính xác và luôn luôn chính xác là tuyệt vời khi, trong quá khứ,

Một cách khác là ghi lại các deltas (thay đổi) riêng lẻ và trước đây tôi đã từng làm điều đó, nhưng đối với phần mềm quy mô lớn rất phức tạp, nó thường rất dễ bị lỗi và tẻ nhạt vì nó rất dễ cho nhà phát triển plugin để quên ghi lại một số thay đổi trong ngăn xếp hoàn tác khi họ đưa các khái niệm hoàn toàn mới vào hệ thống.

Bây giờ, một điều bất kể bạn sao chép toàn bộ trạng thái trò chơi hay ghi lại deltas là tránh các con trỏ (giả sử C hoặc C ++) khi có thể, nếu không nó sẽ làm phức tạp mọi thứ rất nhiều. Nếu bạn sử dụng các chỉ mục, mọi thứ sẽ trở nên đơn giản hơn, vì các chỉ mục không hợp lệ với các bản sao (một trong toàn bộ trạng thái ứng dụng hoặc một phần của nó). Ví dụ với cấu trúc dữ liệu biểu đồ, nếu bạn muốn sao chép toàn bộ hoặc chỉ ghi lại deltas khi người dùng tạo kết nối mới hoặc ngắt kết nối trong biểu đồ, trạng thái sẽ muốn lưu trữ liên kết đến các nút cũ và bạn có thể chạy vào các vấn đề với sự vô hiệu và những thứ đó. Sẽ dễ dàng hơn nhiều nếu các kết nối sử dụng các chỉ mục tương đối vào một mảng, vì việc giữ các chỉ mục đó không bị vô hiệu hóa dễ dàng hơn nhiều so với các con trỏ.


1
Chủ đề cổ xưa .. vấn đề hàng ngày trên một hệ thống hoàn tác;).
gbianchi

0

Tôi sẽ duy trì một danh sách hoàn tác các di chuyển (hợp lệ) mà người chơi đã thực hiện.
Mỗi mục trong danh sách phải chứa đủ thông tin để khôi phục trạng thái trò chơi về trạng thái trước khi di chuyển được thực hiện. Tùy thuộc vào số lượng trạng thái trò chơi có và số bước hoàn tác bạn muốn hỗ trợ, đây có thể là ảnh chụp nhanh trạng thái trò chơi hoặc thông tin cho công cụ trò chơi biết cách đảo ngược di chuyển.

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.