Tách biệt trạng thái thế giới và hoạt hình trong một trò chơi theo lượt


9

Làm thế nào để bạn xử lý việc tách hoạt hình khỏi trạng thái thế giới trong một trò chơi theo lượt? Hiện tôi đang làm việc trên một trò chơi dựa trên lưới 2D. Các mã dưới đây được đơn giản hóa để giải thích tốt hơn.

Khi một diễn viên di chuyển, tôi muốn tạm dừng dòng chảy trong khi sinh vật hoạt hình và di chuyển đến vị trí mới. Nếu không, màn hình có thể tụt hậu đáng kể so với trạng thái thế giới, điều này sẽ gây ra sự xuất hiện hình ảnh kỳ lạ. Tôi cũng muốn có những hình ảnh động không chặn dòng chảy của trò chơi - một hiệu ứng hạt có thể mở ra qua nhiều lượt mà không ảnh hưởng đến trò chơi.

Những gì tôi đã làm là giới thiệu hai loại hình động, mà tôi gọi là hình động chặn và hình động không chặn. Khi người chơi muốn di chuyển, mã được thực thi là

class Actor {
    void move(PositionInfo oldPosition, PositionInfo newPosition) {
        if(isValidMove(oldPosition, newPosition) {
             getGame().pushBlockingAnimation(new MoveAnimation(this, oldPosition, newPosition, ....));
             player.setPosition(newPosition);
        }
    }
}

Sau đó, vòng lặp cập nhật chính hiện:

class Game {
    void update(float dt) {
        updateNonBlockingAnimations(dt); //Effects like particle explosions, damage numbers, etc. Things that don't block the flow of turns.
        if(!mBlockingAnimations.empty()) {
            mBlockingAnimations.get(0).update(dt);
        } else {
             //.. handle the next turn. This is where new animations will be enqueued..//
        }
        cleanUpAnimations(); // remove finished animations
    }
}

... Nơi hoạt hình cập nhật vị trí màn hình của Diễn viên.

Một ý tưởng khác mà tôi đang thực hiện là có một hoạt ảnh chặn đồng thời, trong đó nhiều hoạt ảnh chặn sẽ được cập nhật đồng thời, nhưng lượt tiếp theo sẽ không xảy ra cho đến khi tất cả chúng được thực hiện.

Điều này có vẻ như một cách lành mạnh để làm việc? Có ai có bất kỳ đề nghị, hoặc thậm chí tham khảo về cách các trò chơi tương tự khác làm điều đó.

Câu trả lời:


2

Nếu hệ thống của bạn làm việc cho bạn, tôi không thấy bất kỳ lý do nào để không làm như vậy.

Chà, một lý do: Nó có thể trở nên lộn xộn khi bạn không dễ dàng đánh giá hậu quả của "player.setPocation (newPocation);" nữa không.

Ví dụ (chỉ làm cho mọi thứ lên): Hãy tưởng tượng bạn di chuyển trình phát với setPocation của bạn lên trên một số bẫy. Bản thân lệnh gọi "player.setPocation" sẽ kích hoạt một số cuộc gọi khác đến .. giả sử một mã trình nghe bẫy, từ đó sẽ tự đẩy một hình ảnh động chặn hiển thị một "giật gân gây tổn thương" lên trên chính nó.

Bạn thấy vấn đề: trò giật gân được hiển thị đồng thời với sự di chuyển của người chơi về phía cái bẫy. (Hãy giả sử ở đây rằng bạn không muốn điều này, nhưng muốn hoạt hình bẫy được phát ngay sau khi di chuyển kết thúc;)).

Vì vậy, miễn là bạn có thể ghi nhớ tất cả các hậu quả của hành động mà bạn thực hiện sau các cuộc gọi "PushBlockingAnimation" đó, mọi thứ đều ổn.

Bạn có thể cần bắt đầu bọc logic trò chơi vào "chặn thứ gì đó" - các lớp để chúng có thể được xếp hàng để thực hiện sau khi hoạt ảnh kết thúc. Hoặc bạn chuyển một cuộc gọi lại "call ThisAfterAnimationFinishes" cho PushBlockingAnimation và di chuyển setPocation vào chức năng gọi lại.

Một cách tiếp cận khác sẽ là ngôn ngữ kịch bản. Ví dụ, Lua có "coroutine.yield" trả về hàm với trạng thái đặc biệt để có thể gọi nó sau để tiếp tục ở câu lệnh tiếp theo. Bằng cách này, bạn có thể dễ dàng chờ kết thúc hoạt hình để thực thi logic trò chơi mà không làm lộn xộn giao diện chương trình "bình thường" của mã của bạn. (có nghĩa là mã của bạn sẽ vẫn trông giống như "playAnimation (); setP vị trí ();" thay vì chuyển các cuộc gọi lại xung quanh và làm lộn xộn logic trò chơi qua nhiều chức năng).

Unity3D (và ít nhất một hệ thống trò chơi khác mà tôi biết) có câu lệnh "lợi nhuận hoàn trả" C # để mô phỏng điều này trực tiếp trong mã C #.

// instead of simple call to move(), you have to "iterate" it in a foreach-like loop
IEnumerable<Updatable> move(...) {
    // playAnimation returns an object that contain an update() and finished() method.
    // the caller will not iterate over move() again until the object is "finished"
    yield return getGame().playAnimation(...)
    // the next statement will be executed *after* the animation finished
    player.setPosition(newPosition);
}

Tất nhiên, trong sơ đồ này, lệnh gọi di chuyển () nhận tất cả các công việc bẩn bây giờ. Bạn có thể sẽ chỉ áp dụng kỹ thuật này cho các chức năng cụ thể nơi bạn biết chính xác ai gọi chúng từ đâu. Vì vậy, hãy coi đây chỉ là một ý tưởng cơ bản về cách các công cụ khác được tổ chức - có lẽ nó quá phức tạp đối với vấn đề cụ thể hiện tại của bạn.

Tôi khuyên bạn nên bám sát ý tưởng PushBlockingAnimation của mình cho đến khi bạn gặp rắc rối trong thế giới thực. Sau đó sửa đổi nó. ;)


Cảm ơn bạn rất nhiều vì đã dành thời gian để viết phản hồi này, tôi thực sự đánh giá cao nó.
mdkess
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.