Quản lý nhiều tài liệu tham khảo của cùng một thực thể trò chơi ở những nơi khác nhau bằng ID


8

Tôi đã thấy những câu hỏi hay về các chủ đề tương tự, nhưng không có câu hỏi nào đề cập đến phương pháp đặc biệt này:

Cho rằng tôi có nhiều bộ sưu tập các thực thể trò chơi trong trò chơi [XNA Game Studio] của mình, với nhiều thực thể thuộc nhiều danh sách, tôi đang xem xét các cách tôi có thể theo dõi bất cứ khi nào một thực thể bị phá hủy và xóa nó khỏi danh sách mà nó thuộc về .

Rất nhiều phương thức tiềm năng có vẻ cẩu thả / hỗn độn, nhưng tôi nhắc nhở về một cách mà tôi đã thấy trước đây, thay vì có nhiều bộ sưu tập thực thể trò chơi, thay vào đó bạn có bộ sưu tập ID thực thể trò chơi . Các ID này ánh xạ tới các thực thể trò chơi thông qua một "cơ sở dữ liệu" trung tâm (có lẽ chỉ là một bảng băm).

Vì vậy, bất cứ khi nào bất kỳ bit mã nào muốn truy cập vào các thành viên của thực thể trò chơi, trước tiên, nó sẽ kiểm tra xem liệu nó có còn trong cơ sở dữ liệu hay không. Nếu không, nó có thể phản ứng tương ứng.

Đây có phải là một cách tiếp cận âm thanh? Dường như nó sẽ loại bỏ nhiều rủi ro / rắc rối khi lưu trữ nhiều danh sách, với sự đánh đổi là chi phí tra cứu mỗi khi bạn muốn truy cập một đối tượng.

Câu trả lời:


3

Câu trả lời ngắn gọn: có.

Câu trả lời dài: Trên thực tế, tất cả các trò chơi tôi đã thấy hoạt động theo cách này. Tra cứu bảng băm là đủ nhanh; và nếu bạn không quan tâm đến các giá trị ID thực tế (nghĩa là chúng không được sử dụng ở bất kỳ nơi nào khác), bạn có thể sử dụng một vectơ thay vì bảng băm. Mà thậm chí còn nhanh hơn.

Là một phần thưởng bổ sung, thiết lập này cho phép sử dụng lại các đối tượng trò chơi của bạn, giảm tỷ lệ phân bổ bộ nhớ của bạn. Khi một đối tượng trò chơi bị phá hủy, thay vì "giải phóng" nó, bạn chỉ cần dọn sạch các trường của nó và đặt vào một số "nhóm đối tượng". Sau đó, khi bạn cần một đối tượng mới, chỉ cần lấy một đối tượng hiện có từ nhóm. Nếu các đối tượng mới sinh sản liên tục, điều này đáng chú ý có thể cải thiện hiệu suất.


Nói chung, điều đó đủ chính xác, nhưng chúng ta cần tính đến những thứ khác trước. Nếu có rất nhiều đối tượng được tạo ra và phá hủy nhanh chóng và hàm băm của chúng ta tốt, hàm băm có thể phù hợp hơn so với một vectơ. Tuy nhiên, nếu bạn nghi ngờ, hãy đi với vectơ.
hokiecsgrad

Vâng, tôi phải đồng ý: vector không hoàn toàn nhanh hơn bảng băm. Nhưng nó nhanh hơn 99% thời gian (-8
Nevermind

Nếu bạn muốn sử dụng lại các vị trí (mà bạn chắc chắn muốn), hơn bạn cần đảm bảo rằng các ID không hợp lệ được nhận ra. Ví dụ. Đối tượng tại ID 7 nằm trong Danh sách A, B và C. Đối tượng bị hủy và trong cùng một vị trí, một đối tượng khác được tạo. Đối tượng này tự đăng ký vào Danh sách A và C. Mục nhập của đối tượng đầu tiên trong Danh sách B bây giờ "trỏ" đến đối tượng mới được tạo, điều này sai trong trường hợp này. Điều này có thể được giải quyết hoàn hảo với Trình quản lý xử lý từ Scott Bilas scottbilas.com/publications/gem-resmgr . Tôi đã sử dụng điều này trong các trò chơi thương mại và nó hoạt động như một bùa mê.
DarthCoder

3

Đây có phải là một cách tiếp cận âm thanh? Dường như nó sẽ loại bỏ nhiều rủi ro / rắc rối khi lưu trữ nhiều danh sách, với sự đánh đổi là chi phí tra cứu mỗi khi bạn muốn truy cập một đối tượng.

Tất cả những gì bạn đã làm là chuyển gánh nặng thực hiện kiểm tra từ lúc phá hủy sang thời điểm sử dụng. Tôi sẽ không tuyên bố rằng tôi chưa bao giờ làm điều này trước đây, nhưng tôi không coi đó là 'âm thanh'.

Tôi đề nghị sử dụng một trong nhiều lựa chọn thay thế mạnh mẽ hơn. Nếu số lượng danh sách được biết đến, bạn có thể thoát khỏi chỉ cần xóa đối tượng khỏi tất cả các danh sách. Điều đó là vô hại nếu đối tượng không nằm trong một danh sách nhất định và bạn được đảm bảo đã xóa nó một cách chính xác.

Nếu số lượng danh sách là không xác định hoặc không thực tế thì lưu trữ các tham chiếu ngược đến chúng trên đối tượng. Việc triển khai điển hình của điều này là mẫu quan sát viên , nhưng bạn có thể có thể đạt được hiệu ứng tương tự với các đại biểu, họ gọi lại để xóa mục khỏi bất kỳ danh sách có chứa nào khi cần thiết.

Hoặc đôi khi bạn không thực sự cần nhiều danh sách. Thường thì bạn có thể giữ một đối tượng chỉ trong một danh sách và sử dụng các truy vấn động để tạo các bộ sưu tập tạm thời khác khi bạn cần chúng. ví dụ. Thay vì có một danh sách riêng cho kho đồ của một nhân vật, bạn chỉ có thể lấy ra tất cả các đối tượng trong đó "mang theo" bằng với ký tự hiện tại. Điều này nghe có vẻ không hiệu quả, nhưng nó đủ tốt cho cơ sở dữ liệu quan hệ và có thể được tối ưu hóa bằng cách lập chỉ mục thông minh.


Vâng, trong triển khai hiện tại của tôi, mọi danh sách hoặc một đối tượng muốn tham chiếu đến một thực thể trò chơi cần phải đăng ký vào sự kiện bị phá hủy của nó và phản ứng tương ứng. Điều này có thể tốt hoặc tốt hơn trong một số cách so với tra cứu ID.
vargonia

1
Trong cuốn sách của tôi, điều này hoàn toàn ngược lại với một giải pháp mạnh mẽ. So với tra cứu theo id, bạn có mã THÊM, với nhiều lỗi hơn, cơ hội THÊM để quên thứ gì đó và khả năng tiêu diệt đối tượng MÀ KHÔNG thông báo cho mọi người khởi động. Với tra cứu theo id, bạn có một ít mã mà hầu như không bao giờ thay đổi, một điểm hủy duy nhất và đảm bảo 100% rằng bạn sẽ không bao giờ truy cập vào một đối tượng bị phá hủy.
Không bao giờ bắt đầu

1
Với tra cứu theo id, bạn có thêm mã ở mọi nơi bạn cần để sử dụng đối tượng. Với cách tiếp cận dựa trên người quan sát, bạn không có thêm mã nào ngoại trừ khi thêm và xóa các mục khỏi danh sách, đây có thể là một số vị trí trong toàn bộ chương trình. Nếu bạn tham chiếu các mục nhiều hơn bạn tạo, hủy hoặc di chuyển chúng giữa các danh sách, thì cách tiếp cận người quan sát sẽ dẫn đến ít mã hơn.
Kylotan

Có lẽ có nhiều nơi tham chiếu các đối tượng hơn là những nơi tạo / hủy chúng. Tuy nhiên, mỗi tham chiếu chỉ yêu cầu một chút mã hoặc không có gì cả nếu việc tra cứu đã được bao bọc bởi thuộc tính (nên hầu hết thời gian). Ngoài ra, bạn CÓ THỂ quên đăng ký để hủy đối tượng, nhưng bạn KHÔNG thể quên tìm kiếm một đối tượng.
Nevermind

1

Đây dường như chỉ là một ví dụ cụ thể hơn về vấn đề "làm thế nào để loại bỏ các đối tượng khỏi danh sách, trong khi lặp qua nó", rất dễ giải quyết (lặp theo thứ tự ngược có lẽ là cách đơn giản nhất cho danh sách kiểu mảng như C # List).

Nếu một thực thể sẽ được tham chiếu từ nhiều nơi, thì dù sao nó cũng phải là một loại tham chiếu . Vì vậy, bạn không phải trải qua một hệ thống ID phức tạp - một lớp trong C # đã cung cấp các hướng dẫn cần thiết.

Và cuối cùng - tại sao phải "kiểm tra cơ sở dữ liệu"? Chỉ cần cung cấp cho mỗi thực thể một IsDeadcờ và kiểm tra xem.


Tôi thực sự không biết C #, nhưng kiến ​​trúc này thường được sử dụng trong C và C ++ trong đó loại tham chiếu thông thường (con trỏ) của ngôn ngữ không an toàn để sử dụng nếu đối tượng bị phá hủy. Có một số cách để đối phó với nó, nhưng tất cả chúng đều liên quan đến một lớp không xác định, và đây là một giải pháp phổ biến. (Một danh sách các backpoint như Kylotan gợi ý là một cái khác - mà bạn chọn tùy thuộc vào việc bạn muốn sử dụng bộ nhớ hay thời gian.)

C # là rác được thu thập, vì vậy không có vấn đề gì nếu bạn từ bỏ các trường hợp. Đối với các tài nguyên không được quản lý (tức là: bởi trình thu gom rác) có IDisposablemẫu, rất giống với việc đánh dấu một đối tượng là IsDead. Rõ ràng nếu bạn cần gộp các thể hiện, thay vì sử dụng chúng, thì cần phải có một chiến lược phức tạp hơn.
Andrew Russell

Cờ IsDead là thứ tôi đã sử dụng thành công trong quá khứ; Tôi chỉ thích ý tưởng về trò chơi bị sập nếu tôi không kiểm tra trạng thái chết thay vì tiếp tục một cách vui vẻ, với kết quả kỳ lạ và khó gỡ lỗi. Sử dụng một tra cứu cơ sở dữ liệu sẽ là trước đây; IsDead cờ sau.
vargonia

3
@vargonia Với mẫu dùng một lần, điều thường xảy ra là bạn kiểm tra IsDisposed ở đầu mỗi chức năng và thuộc tính trong một lớp. Nếu trường hợp được xử lý, bạn ném ObjectDisposedException(thường sẽ "sụp đổ" với một ngoại lệ không được xử lý ). Bằng cách di chuyển kiểm tra đến chính đối tượng (thay vì kiểm tra đối tượng bên ngoài), bạn cho phép đối tượng xác định hành vi "tôi chết" của chính mình và loại bỏ gánh nặng kiểm tra từ người gọi. Đối với mục đích của bạn, bạn có thể thực hiện IDisposablehoặc tạo IsDeadcờ của riêng bạn với chức năng tương tự.
Andrew Russell

1

Tôi thường sử dụng phương pháp này trong trò chơi C # /. NET của riêng tôi. Ngoài các lợi ích khác (và các mối nguy hiểm!) Được mô tả ở đây, nó cũng có thể giúp tránh các vấn đề nối tiếp.

Nếu bạn muốn tận dụng các phương tiện tuần tự hóa nhị phân tích hợp sẵn của .NET Framework, thì việc sử dụng ID thực thể có thể giúp giảm thiểu kích thước của biểu đồ đối tượng được viết ra. Theo mặc định, trình định dạng nhị phân của .NET sẽ tuần tự hóa toàn bộ biểu đồ đối tượng ở cấp trường. Hãy nói rằng tôi muốn tuần tự hóa một Shipví dụ. Nếu Shipcó một _ownertrường tham chiếu đến Playerngười sở hữu nó, thì Playertrường hợp đó cũng sẽ được tuần tự hóa. Nếu Playerchứa một _shipstrường (của, giả sử, ICollection<Ship>), thì tất cả các tàu của người chơi cũng sẽ được viết ra, cùng với bất kỳ đối tượng nào khác được tham chiếu ở cấp trường (theo cách đệ quy). Thật dễ dàng để vô tình tuần tự hóa một biểu đồ đối tượng khổng lồ khi bạn chỉ muốn tuần tự hóa một phần nhỏ của nó.

Nếu, thay vào đó, tôi có một _ownerIdtrường, thì tôi có thể sử dụng giá trị đó để giải quyết Playertham chiếu theo yêu cầu. API công khai của tôi thậm chí có thể không thay đổi, với thuộc Ownertính chỉ cần thực hiện tra cứu.

Mặc dù tra cứu dựa trên hàm băm nói chung là rất nhanh, nhưng chi phí bổ sung có thể trở thành vấn đề đối với các tập thực thể rất lớn với việc tra cứu thường xuyên. Nếu nó trở thành vấn đề với bạn, bạn có thể lưu trữ tham chiếu bằng cách sử dụng trường không được tuần tự hóa. Ví dụ: bạn có thể làm một cái gì đó như thế này:

public class Ship
{
    private int _ownerId;
    [NonSerialized] private Lazy<Player> _owner;

    public Player Owner
    {
        get { return _owner.Value; }
    }

    public Ship(Player owner)
    {
        _ownerId = owner.PlayerID;
        EnsureCache();
    }

    private void EnsureCache()
    {
        if (_owner == null)
            _owner = new Lazy<Player>(() => Game.Current.Players[_ownerId]);
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        EnsureCache();
    }
}

Tất nhiên, cách tiếp cận này cũng có thể làm cho việc tuần tự hóa trở nên khó khăn hơn khi bạn thực sự muốn tuần tự hóa một biểu đồ đối tượng lớn. Trong những trường hợp đó, bạn sẽ cần nghĩ ra một số loại 'container' để đảm bảo rằng tất cả các đối tượng cần thiết đều được bao gồm.


0

Một cách khác để xử lý việc dọn dẹp của bạn là đảo ngược điều khiển và sử dụng một đối tượng Dùng một lần. Bạn có thể có một yếu tố phân bổ các thực thể của bạn, vì vậy hãy vượt qua tất cả các danh sách mà nó cần được thêm vào. Thực thể giữ một tham chiếu đến tất cả các danh sách mà nó là một phần của và thực hiện IDis Dùng. Trong phương thức xử lý, nó loại bỏ chính nó khỏi tất cả các danh sách. Bây giờ bạn chỉ phải quan tâm đến việc quản lý danh sách ở hai nơi: nhà máy (sáng tạo) và xử lý. Xóa trong đối tượng cũng đơn giản như entity.Dispose ().

Vấn đề về tên và tham chiếu trong C # thực sự chỉ quan trọng để quản lý các thành phần hoặc loại giá trị. Nếu bạn có danh sách các thành phần được liên kết với một thực thể, sử dụng trường ID làm khóa băm có thể hữu ích. Nếu bạn chỉ có danh sách các thực thể, chỉ cần sử dụng danh sách có tham chiếu. Tài liệu tham khảo C # là an toàn và đơn giản để làm việc với.

Để xóa hoặc thêm các mục khỏi danh sách, bạn có thể sử dụng Hàng đợi hoặc bất kỳ số giải pháp nào khác để xử lý việc thêm và xóa khỏi danh sách.

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.