Trong các bình luận, tôi đã đăng một phiên bản cô đọng cho câu trả lời cũ của mình:
Sử dụng DrawableGameComponent
khóa bạn vào một tập hợp các chữ ký phương thức và một mô hình dữ liệu được giữ lại cụ thể. Đây thường là sự lựa chọn sai lầm ban đầu và khóa trong gây cản trở đáng kể cho sự phát triển của mã trong tương lai.
Ở đây tôi sẽ cố gắng minh họa tại sao lại như vậy bằng cách đăng một số mã từ dự án hiện tại của tôi.
Đối tượng gốc duy trì trạng thái của thế giới trò chơi có một Update
phương thức giống như sau:
void Update(UpdateContext updateContext, MultiInputState currentInput)
Có một số điểm vào Update
, tùy thuộc vào việc trò chơi có chạy trên mạng hay không. Chẳng hạn, có thể cho trạng thái trò chơi được quay ngược về thời điểm trước đó và sau đó được mô phỏng lại.
Ngoài ra còn có một số phương pháp giống như cập nhật bổ sung, như:
void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)
Trong trạng thái trò chơi, có một danh sách các diễn viên sẽ được cập nhật. Nhưng điều này không chỉ đơn giảnUpdate
cuộc gọi . Có thêm chuyền trước và sau để xử lý vật lý. Ngoài ra còn có một số công cụ ưa thích để trì hoãn sinh sản / tiêu diệt các diễn viên, cũng như chuyển đổi cấp độ.
Bên ngoài trạng thái trò chơi, có một hệ thống UI cần được quản lý độc lập. Nhưng một số màn hình trong UI cũng cần có khả năng chạy trong ngữ cảnh mạng.
Để vẽ, vì bản chất của trò chơi, có một hệ thống sắp xếp và loại bỏ tùy chỉnh lấy đầu vào từ các phương thức sau:
virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)
Và sau đó, khi nó tìm ra những gì cần vẽ, sẽ tự động gọi:
virtual void Draw(DrawContext drawContext, int tag)
Các tag
tham số là cần thiết vì một số diễn viên có thể giữ được các diễn viên khác - và do đó nhu cầu để có thể vẽ cả trên và dưới diễn viên được tổ chức. Hệ thống phân loại đảm bảo rằng điều này xảy ra theo đúng thứ tự.
Ngoài ra còn có hệ thống menu để vẽ. Và một hệ thống phân cấp để vẽ UI, xung quanh HUD, xung quanh trò chơi.
Bây giờ so sánh các yêu cầu đó với những gì DrawableGameComponent
cung cấp:
public class DrawableGameComponent
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
public int UpdateOrder { get; set; }
public int DrawOrder { get; set; }
}
Không có cách hợp lý để có được từ một hệ thống sử dụng DrawableGameComponent
đến hệ thống mà tôi đã mô tả ở trên. (Và đó không phải là những yêu cầu bất thường đối với một trò chơi có độ phức tạp thậm chí khiêm tốn).
Ngoài ra, điều rất quan trọng cần lưu ý là những yêu cầu đó không chỉ đơn giản là khởi động khi bắt đầu dự án. Họ đã phát triển qua nhiều tháng làm việc phát triển.
Những gì mọi người thường làm, nếu họ bắt đầu DrawableGameComponent
tuyến đường, là họ bắt đầu xây dựng trên các bản hack:
Thay vì thêm các phương thức, họ thực hiện một số phương pháp đúc xấu xí cho loại cơ sở của riêng họ (tại sao không sử dụng trực tiếp?).
Thay vì thay thế mã để sắp xếp và gọi Draw
/ Update
, họ thực hiện một số điều rất chậm để thay đổi số thứ tự đang hoạt động.
Và điều tồi tệ nhất: Thay vì thêm các tham số vào các phương thức, chúng bắt đầu "truyền" "tham số" vào các vị trí bộ nhớ không phải là ngăn xếp (việc sử dụng Services
này là phổ biến). Điều này là phức tạp, dễ bị lỗi, chậm, dễ vỡ, hiếm khi an toàn luồng và có khả năng trở thành vấn đề nếu bạn thêm mạng, tạo các công cụ bên ngoài, v.v.
Nó cũng làm cho mã sử dụng lại khó hơn , vì bây giờ các phụ thuộc của bạn được ẩn bên trong "thành phần", thay vì được xuất bản ở ranh giới API.
Hoặc, họ gần như thấy tất cả những điều này điên rồ đến mức nào, và bắt đầu làm "người quản lý" DrawableGameComponent
để giải quyết những vấn đề này. Ngoại trừ họ vẫn có những vấn đề này ở ranh giới "người quản lý".
Luôn luôn có thể dễ dàng thay thế các "người quản lý" này bằng một loạt các lệnh gọi phương thức theo thứ tự mong muốn. Bất cứ điều gì khác là quá phức tạp.
Tất nhiên, một khi bạn bắt đầu sử dụng những bản hack đó, thì sẽ mất công thực sự để hoàn tác những bản hack đó một khi bạn nhận ra mình cần nhiều hơn những gì DrawableGameComponent
cung cấp. Bạn trở nên " bị nhốt ". Và sự cám dỗ thường là tiếp tục chồng chất lên các bản hack - điều này trong thực tế sẽ làm bạn chán nản và hạn chế các loại trò chơi bạn có thể làm cho những trò chơi tương đối đơn giản.
Rất nhiều người dường như đạt đến điểm này và có phản ứng nội tạng - có thể vì họ sử dụng DrawableGameComponent
và không muốn chấp nhận là "sai". Vì vậy, họ chấp nhận thực tế rằng ít nhất có thể làm cho nó "hoạt động".
Tất nhiên nó có thể được thực hiện để "làm việc"! Chúng tôi là lập trình viên - làm cho mọi thứ hoạt động dưới sự hạn chế bất thường thực tế là công việc của chúng tôi. Nhưng sự hạn chế này là không cần thiết, hữu ích hoặc hợp lý ...
Có gì đặc biệt flabbergasting về là nó là đáng kinh ngạc tầm thường sang một bên bước toàn bộ vấn đề này. Ở đây, tôi thậm chí sẽ cung cấp cho bạn mã:
public class Actor
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
}
List<Actor> actors = new List<Actor>();
foreach(var actor in actors)
actor.Update(gameTime);
foreach(var actor in actors)
actor.Draw(gameTime);
(Thật đơn giản để thêm vào sắp xếp theo DrawOrder
và UpdateOrder
. Một cách đơn giản là duy trì hai danh sách và sử dụng List<T>.Sort()
. Nó cũng không quan trọng để xử lý Load
/ UnloadContent
.)
Nó không quan trọng mã đó thực sự là gì . Điều quan trọng là tôi có thể sửa đổi nó một cách tầm thường .
Khi tôi nhận ra rằng tôi cần một hệ thống thời gian khác gameTime
hoặc tôi cần thêm tham số (hoặc toàn bộ UpdateContext
đối tượng có khoảng 15 thứ trong đó) hoặc tôi cần một thứ tự hoạt động hoàn toàn khác để vẽ hoặc tôi cần một số phương thức bổ sung Đối với các bản cập nhật khác nhau, tôi có thể tiếp tục và làm điều đó .
Và tôi có thể làm điều đó ngay lập tức. Tôi không cần phải cố gắng chọn DrawableGameComponent
ra mã của mình. Hoặc tệ hơn: hack nó theo một cách nào đó sẽ khiến nó trở nên khó khăn hơn vào lần tới khi có nhu cầu như vậy xuất hiện.
Thực sự chỉ có một kịch bản mà nó là một lựa chọn tốt để sử dụng DrawableGameComponent
: và đó là nếu bạn thực sự tạo và xuất bản phần mềm trung gian kiểu kéo và thả thành phần cho XNA. Đó là mục đích ban đầu của nó. Mà - tôi sẽ thêm - không ai đang làm!
(Tương tự, nó chỉ thực sự có ý nghĩaServices
khi sử dụng khi tạo các loại nội dung tùy chỉnh cho ContentManager
.)