Một số cách để tách logic trò chơi khỏi hình ảnh động và vòng lặp vẽ là gì?


9

Trước đây tôi chỉ tạo các trò chơi flash, sử dụng MovieClips và như vậy để tách hoạt hình của tôi khỏi logic trò chơi của tôi. Bây giờ tôi đang cố gắng tạo ra một trò chơi cho Android, nhưng lý thuyết lập trình trò chơi xung quanh việc tách những điều này vẫn làm tôi bối rối. Tôi đến từ một nền tảng phát triển các ứng dụng web không phải trò chơi vì vậy tôi thành thạo các mô hình giống MVC hơn và bị mắc kẹt trong suy nghĩ đó khi tôi tiếp cận lập trình trò chơi.

Tôi muốn làm những thứ như trừu tượng trò chơi của mình bằng cách, ví dụ, có một lớp bảng trò chơi chứa dữ liệu cho một lưới các ô với các thể hiện của một lớp gạch mà mỗi lớp chứa các thuộc tính. Tôi có thể cấp quyền truy cập vòng rút thăm của mình cho điều này và để nó vẽ bảng trò chơi dựa trên các thuộc tính của từng ô trên bảng trò chơi, nhưng tôi không hiểu chính xác hoạt hình nên đi đâu. Theo như tôi có thể nói, loại hoạt hình nằm giữa logic trò chơi trừu tượng (mô hình) và vòng lặp vẽ (khung nhìn). Với tư duy MVC của tôi, thật bực bội khi cố gắng quyết định xem hoạt hình thực sự sẽ đi đâu. Nó sẽ có khá nhiều dữ liệu liên quan đến nó giống như một mô hình, nhưng dường như cần phải được kết hợp rất chặt chẽ với vòng lặp vẽ để có những thứ như hoạt hình độc lập khung.

Làm thế nào tôi có thể thoát ra khỏi suy nghĩ này và bắt đầu suy nghĩ về các mẫu có ý nghĩa hơn cho các trò chơi?

Câu trả lời:


6

Hoạt hình vẫn có thể được phân chia hoàn hảo giữa logic và kết xuất. Trạng thái dữ liệu trừu tượng của hình ảnh động sẽ là thông tin cần thiết cho API đồ họa của bạn để hiển thị hình ảnh động.

Ví dụ, trong các trò chơi 2D, đó có thể là một khu vực hình chữ nhật đánh dấu khu vực hiển thị phần hiện tại của trang sprite của bạn cần được vẽ (khi bạn có một trang bao gồm 30 hình vẽ 80x80 chứa các bước khác nhau của nhân vật nhảy, ngồi xuống, di chuyển, vv). Nó cũng có thể là bất kỳ loại dữ liệu nào bạn không cần để kết xuất, nhưng có thể để tự quản lý trạng thái hoạt hình, như thời gian còn lại cho đến khi bước hoạt hình hiện tại hết hạn hoặc tên của hoạt hình ("đi bộ", "đứng" vv) Tất cả điều đó có thể được đại diện theo bất kỳ cách nào bạn muốn. Đó là phần logic.

Trong phần kết xuất, bạn chỉ cần làm như bình thường, lấy hình chữ nhật đó từ mô hình của bạn và sử dụng trình kết xuất của bạn để thực hiện các cuộc gọi đến API đồ họa.

Trong mã (sử dụng cú pháp C ++ tại đây):

class Sprite //Model
{
    private:
       Rectangle subrect;
       Vector2f position;
       //etc.

    public:
       Rectangle GetSubrect() 
       {
           return subrect;
       }
       //etc.
};

class AnimatedSprite : public Sprite, public Updatable //arbitrary interface for classes that need to change their state on a regular basis
{
    AnimationController animation_controller;
    //etc.
    public:
        void Update()
        {
            animation_controller.Update(); //Good OOP design ;) It will take control of changing animations in time etc. for you
            this.SetSubrect(animation_controller.GetCurrentAnimation().GetRect());
        }
        //etc.
};

Đó là dữ liệu. Trình kết xuất của bạn sẽ lấy dữ liệu đó và vẽ nó. Vì cả Sprites bình thường và hoạt hình đều được vẽ theo cùng một cách, bạn có thể sử dụng đa hình ở đây!

class Renderer
{
    //etc.
    public:
       void Draw(const Sprite &spr)
       {
           graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
       }
};

TMV:

Tôi đã đưa ra một ví dụ khác. Giả sử bạn có một game nhập vai. Ví dụ, mô hình của bạn đại diện cho bản đồ thế giới có thể sẽ cần lưu trữ vị trí của nhân vật trên thế giới dưới dạng tọa độ ô trên bản đồ. Tuy nhiên, khi bạn di chuyển nhân vật, họ đi bộ một vài pixel tại một hình vuông tiếp theo. Bạn có lưu trữ vị trí "giữa các ô" này trong một đối tượng hoạt hình không? Làm thế nào để bạn cập nhật mô hình khi nhân vật cuối cùng đã "đến" tại tọa độ ô tiếp theo trên bản đồ?

Bản đồ thế giới không biết trực tiếp về vị trí người chơi (nó không có Vector2f hoặc thứ gì đó tương tự lưu trữ vị trí người chơi =, thay vào đó, nó có tham chiếu trực tiếp đến chính đối tượng người chơi, từ đó xuất phát từ AnimatedSprite để bạn có thể chuyển nó đến trình kết xuất dễ dàng và lấy tất cả dữ liệu cần thiết từ nó.

Tuy nhiên, nói chung, tilemap của bạn không thể thực hiện mọi thứ - Tôi có một lớp "Bản đồ", đảm nhiệm việc quản lý tất cả các ô và có thể nó cũng phát hiện va chạm giữa các đối tượng mà tôi giao cho nó và các viên gạch trên bản đồ. Sau đó, tôi có một lớp "RPGMap" khác, hoặc tuy nhiên, bạn muốn gọi nó, nó có cả tham chiếu đến tilemap của bạn và tham chiếu đến người chơi và thực hiện các cuộc gọi Update () thực tế cho người chơi của bạn và tới sơ đồ.

Cách bạn muốn cập nhật mô hình khi người chơi di chuyển phụ thuộc vào những gì bạn muốn làm.

Người chơi của bạn có được phép di chuyển giữa các ô độc lập (kiểu Zelda) không? Đơn giản chỉ cần xử lý đầu vào và di chuyển người chơi phù hợp với mọi khung hình. Hay bạn muốn người chơi nhấn "phải" và nhân vật của bạn tự động di chuyển một ô sang bên phải? Hãy để lớp RPGMap của bạn nội suy vị trí người chơi cho đến khi anh ta đến đích và khóa tất cả các thao tác nhập liệu phím di chuyển.

Dù bằng cách nào, nếu bạn muốn làm cho bản thân dễ dàng hơn, tất cả các mô hình của bạn sẽ có các phương thức Update () nếu chúng thực sự cần một số logic để tự cập nhật (thay vì chỉ thay đổi giá trị của các biến) - Bạn không từ bỏ bộ điều khiển trong mô hình MVC theo cách đó, bạn chỉ cần di chuyển mã từ "một bước ở trên" (bộ điều khiển) xuống mô hình và tất cả các bộ điều khiển thực hiện đều gọi phương thức Update () này của mô hình (Bộ điều khiển trong trường hợp của chúng tôi sẽ là Bản đồ RPG). Bạn vẫn có thể dễ dàng trao đổi mã logic - bạn có thể thay đổi trực tiếp mã của lớp hoặc nếu bạn cần hành vi hoàn toàn khác, bạn chỉ có thể xuất phát từ lớp mô hình của mình và chỉ ghi đè phương thức Update ().

Cách tiếp cận đó làm giảm các cuộc gọi phương thức và những thứ tương tự như vậy - vốn là một trong những nhược điểm chính của mẫu MVC thuần túy (cuối cùng bạn gọi Get This () GetThat () rất thường xuyên) - nó làm cho mã dài hơn và dài hơn nhỏ hơn một chút để đọc và cũng chậm hơn - mặc dù điều đó có thể được chăm sóc bởi trình biên dịch của bạn, người tối ưu hóa rất nhiều thứ như thế.


Bạn có giữ dữ liệu hoạt hình trong lớp chứa logic trò chơi, lớp chứa vòng lặp trò chơi hoặc tách biệt với cả hai không? Ngoài ra, nó hoàn toàn phụ thuộc vào vòng lặp hoặc lớp chứa vòng lặp để hiểu cách dịch dữ liệu hoạt hình thành thực sự vẽ màn hình, phải không? Nó thường sẽ không đơn giản như chỉ cần lấy một chỉnh lưu đại diện cho một phần của bảng sprite và sử dụng nó để cắt một bitmap rút ra từ bảng sprite.
TMV

Tôi đã đưa ra một ví dụ khác. Giả sử bạn có một game nhập vai. Ví dụ, mô hình của bạn đại diện cho bản đồ thế giới có thể sẽ cần lưu trữ vị trí của nhân vật trên thế giới dưới dạng tọa độ ô trên bản đồ. Tuy nhiên, khi bạn di chuyển nhân vật, họ đi bộ một vài pixel tại một hình vuông tiếp theo. Bạn có lưu trữ vị trí "giữa các ô" này trong một đối tượng hoạt hình không? Làm thế nào để bạn cập nhật mô hình khi nhân vật cuối cùng đã "đến" tại tọa độ ô tiếp theo trên bản đồ?
TMV

Tôi đã chỉnh sửa trong câu trả lời cho câu hỏi của bạn, vì các bình luận không cho phép đủ các ký tự cho điều đó.
TravisG

Nếu tôi hiểu mọi thứ chính xác:
TMV

Bạn có thể có một phiên bản của lớp "Animator" bên trong Chế độ xem của mình và nó sẽ có một phương thức "cập nhật" công khai được gọi là mọi khung hình theo chế độ xem. Phương thức cập nhật gọi các phương thức "cập nhật" của các thể hiện của nhiều loại đối tượng hoạt hình riêng lẻ bên trong nó. Trình hoạt hình và hoạt ảnh bên trong nó có tham chiếu đến Mô hình (được truyền qua các hàm tạo của chúng) để chúng có thể cập nhật dữ liệu mô hình nếu một hình động sẽ thay đổi nó. Sau đó, trong vòng vẽ, bạn lấy dữ liệu từ các hình động bên trong hoạt hình theo cách có thể hiểu được bằng Chế độ xem và vẽ.
TMV

2

Tôi có thể phát triển dựa trên điều này nếu bạn muốn, nhưng tôi có một trình kết xuất trung tâm được yêu cầu vẽ trong vòng lặp. Thay vì

handle input

for every entity:
    update entity

for every entity:
    draw entity

Tôi có một hệ thống giống như

handle input (well, update the state. Mine is event driven so this is null)

for every entity:
    update entity //still got game logic here

renderer.draw();

Lớp renderer chỉ đơn giản chứa một danh sách các tham chiếu đến các thành phần có thể vẽ của các đối tượng. Chúng được gán trong các constructor cho đơn giản.

Ví dụ của bạn, tôi sẽ có một lớp GameBoard với một số Gạch. Mỗi viên gạch rõ ràng biết vị trí của nó, và tôi giả sử một loại hoạt hình. Yếu tố được đưa vào một số loại Hoạt hình mà ô sở hữu và có nó chuyển một tham chiếu của chính nó đến một lớp Trình kết xuất. Ở đó, tất cả tách ra. Khi bạn cập nhật Ngói, nó gọi Cập nhật trên hình động..hoặc cập nhật chính nó. Khi Renderer.Draw()được gọi, nó vẽ hình ảnh động.

Hoạt hình độc lập khung không cần quá nhiều để làm với vòng lặp vẽ.


0

Gần đây tôi đã học được các mô hình, vì vậy nếu câu trả lời này không đầy đủ, tôi chắc chắn sẽ có người thêm vào nó.

Phương pháp dường như có ý nghĩa nhất đối với thiết kế trò chơi là tách logic khỏi đầu ra màn hình.

Trong hầu hết các trường hợp, bạn sẽ muốn sử dụng một cách tiếp cận đa luồng, nếu bạn không quen thuộc với chủ đề đó, thì đó là một câu hỏi của riêng nó, đây là đoạn trích của wiki . Về cơ bản, bạn muốn logic trò chơi của mình thực thi trong một luồng, khóa các biến mà nó cần truy cập để đảm bảo tính toàn vẹn dữ liệu. Nếu vòng lặp logic của bạn cực kỳ nhanh (pong 3d hoạt hình siêu lớn?), Bạn có thể cố gắng khắc phục tần số mà vòng lặp thực thi bằng cách ngủ chuỗi cho thời lượng nhỏ (120 hz đã được đề xuất trong diễn đàn này cho các vòng lặp vật lý trò chơi). Đồng thời, luồng khác đang vẽ lại màn hình (60 hz đã được đề xuất trong các chủ đề khác) với các biến được cập nhật, một lần nữa yêu cầu khóa các biến trước khi truy cập chúng.

Trong trường hợp đó, hình động hoặc chuyển tiếp, v.v., đi vào luồng vẽ nhưng bạn phải phát tín hiệu qua một loại cờ nào đó (có thể là biến trạng thái toàn cầu) rằng luồng logic trò chơi không phải làm gì (hoặc làm gì đó khác ... thiết lập thông số bản đồ mới có lẽ).

Một khi bạn nhận được đầu của bạn xung quanh đồng thời, phần còn lại là khá dễ hiểu. Nếu bạn không có kinh nghiệm về đồng thời, tôi thực sự khuyên bạn nên viết một vài chương trình thử nghiệm đơn giản để bạn có thể hiểu được dòng chảy xảy ra như thế nào.

Hi vọng điêu nay co ich :)

[sửa] Trên các hệ thống không hỗ trợ đa luồng, hoạt ảnh vẫn có thể đi vào vòng vẽ, nhưng bạn muốn đặt trạng thái theo cách để báo hiệu cho logic rằng có điều gì đó khác đang xảy ra và không tiếp tục xử lý cấp độ hiện tại / bản đồ / vv ...


1
Tôi không đồng ý ở đây. Trong hầu hết các trường hợp, bạn không muốn đa luồng, đặc biệt nếu đó là một trò chơi nhỏ.
Vịt Cộng sản

@TheCransistDuck Đủ công bằng, chi phí chung và phức tạp cho đa luồng có thể chắc chắn làm cho nó trở nên quá mức cần thiết, cộng với nếu trò chơi nhỏ, nó có thể nhanh chóng cập nhật.
Stephen
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.