Làm thế nào để truyền thông thực thể làm việc?


115

Tôi có hai trường hợp người dùng:

  1. Làm thế nào để entity_Agửi take-damagetin nhắn đến entity_B?
  2. Làm thế nào để entity_Atruy vấn entity_BHP của?

Đây là những gì tôi đã gặp cho đến nay:

  • Hàng đợi tin nhắn
    1. entity_Atạo một take-damagetin nhắn và gửi nó đến entity_Bhàng đợi tin nhắn.
    2. entity_Atạo một query-hptin nhắn và gửi nó đến entity_B. entity_Bđổi lại tạo ra một response-hpthông điệp và gửi nó đến entity_A.
  • Theo dõi công khai
    1. entity_Bđăng ký take-damagetin nhắn (có thể với một số bộ lọc ưu tiên để chỉ gửi tin nhắn có liên quan). entity_Atạo ra take-damagethông điệp mà tài liệu tham khảo entity_B.
    2. entity_Ađăng ký update-hptin nhắn (có thể được lọc). Mỗi khung entity_Bphát update-hptin nhắn.
  • Tín hiệu / Slots
    1. ???
    2. entity_Akết nối một update-hpkhe cắm để entity_B's update-hptín hiệu.

Có điều gì tốt hơn? Tôi có hiểu đúng về cách các chương trình giao tiếp này kết hợp với hệ thống thực thể của công cụ trò chơi không?

Câu trả lời:


67

Câu hỏi hay! Trước khi tôi nhận được các câu hỏi cụ thể mà bạn đã hỏi, tôi sẽ nói: đừng đánh giá thấp sức mạnh của sự đơn giản. Tenpn là đúng. Hãy nhớ rằng tất cả những gì bạn đang cố gắng thực hiện với các phương pháp này là tìm một cách thanh lịch để trì hoãn một cuộc gọi chức năng hoặc tách rời người gọi khỏi callee. Tôi có thể đề xuất coroutines như một cách trực quan đáng ngạc nhiên để giảm bớt một số vấn đề đó, nhưng đó là một chủ đề nhỏ. Đôi khi, tốt hơn hết là bạn chỉ cần gọi hàm và sống với thực tế là thực thể A được ghép trực tiếp với thực thể B. Xem YAGNI.

Điều đó nói rằng, tôi đã sử dụng và hài lòng với mô hình tín hiệu / khe cắm kết hợp với việc truyền tin nhắn đơn giản. Tôi đã sử dụng nó trong C ++ và Lua cho một tiêu đề iPhone khá thành công có lịch trình rất chặt chẽ.

Đối với trường hợp tín hiệu / khe cắm, nếu tôi muốn thực thể A làm điều gì đó để đáp lại thực thể B đã làm (ví dụ: mở khóa cửa khi có thứ gì đó chết) tôi có thể có thực thể A đăng ký trực tiếp vào sự kiện tử vong của thực thể B. Hoặc có thể thực thể A sẽ đăng ký vào từng nhóm thực thể, tăng số lượt truy cập cho mỗi sự kiện được bắn và mở khóa cửa sau khi N trong số họ đã chết. Ngoài ra, "nhóm thực thể" và "N của chúng" thường sẽ được thiết kế xác định trong dữ liệu cấp. (Bên cạnh đó, đây là một lĩnh vực mà các coroutines thực sự có thể tỏa sáng, ví dụ WaitForMultipl ("Dying", entA, entB, entC); door.Unlock ();)

Nhưng điều đó có thể trở nên cồng kềnh khi có các phản ứng được kết hợp chặt chẽ với mã C ++ hoặc các sự kiện trò chơi phù du vốn có: gây sát thương, nạp lại vũ khí, gỡ lỗi, phản hồi AI dựa trên vị trí do người chơi điều khiển. Đây là nơi thông điệp đi qua có thể điền vào các khoảng trống. Về cơ bản, nó tập trung vào một cái gì đó như, "nói cho tất cả các thực thể trong khu vực này nhận sát thương trong 3 giây" hoặc "bất cứ khi nào bạn hoàn thành vật lý để tìm ra người tôi đã bắn, hãy bảo họ chạy chức năng kịch bản này." Thật khó để tìm ra cách thực hiện điều đó một cách độc đáo bằng cách xuất bản / đăng ký hoặc tín hiệu / vị trí.

Điều này có thể dễ dàng bị quá mức (so với ví dụ của tenpn). Nó cũng có thể là không hiệu quả nếu bạn có nhiều hành động. Nhưng mặc dù nhược điểm của nó, cách tiếp cận "thông điệp và sự kiện" này rất phù hợp với mã trò chơi theo kịch bản (ví dụ như trong Lua). Mã script có thể định nghĩa và phản ứng với các thông điệp và sự kiện của chính nó mà không cần chăm sóc mã C ++. Và mã tập lệnh có thể dễ dàng gửi tin nhắn kích hoạt mã C ++, như thay đổi cấp độ, phát âm thanh hoặc thậm chí chỉ để vũ khí đặt mức độ thiệt hại mà thông điệp TakeDamage mang lại. Nó giúp tôi tiết kiệm rất nhiều thời gian vì tôi không phải liên tục đùa giỡn với luabind. Và nó cho phép tôi giữ tất cả mã nguồn của mình ở một nơi, vì không có nhiều mã. Khi được ghép đúng cách,

Ngoài ra, kinh nghiệm của tôi với trường hợp sử dụng # 2 là bạn nên xử lý nó như một sự kiện theo hướng khác. Thay vì hỏi sức khỏe của thực thể là gì, hãy tổ chức một sự kiện / gửi tin nhắn bất cứ khi nào sức khỏe tạo ra sự thay đổi đáng kể.

Về giao diện, btw, tôi đã kết thúc với ba lớp để thực hiện tất cả điều này: Eventhost, EventClient và MessageClient. Eventhosts tạo các vị trí, EventCl Client đăng ký / kết nối với chúng và MessageCl Client liên kết một đại biểu với một tin nhắn. Lưu ý rằng mục tiêu ủy nhiệm của MessageClient không nhất thiết phải là cùng một đối tượng sở hữu liên kết. Nói cách khác, MessageCl Client có thể tồn tại chỉ để chuyển tiếp tin nhắn đến các đối tượng khác. FWIW, ẩn dụ máy chủ / máy khách là loại không phù hợp. Nguồn / Chìm có thể là khái niệm tốt hơn.

Xin lỗi, tôi hơi huyên thuyên ở đó. Đó là câu trả lời đầu tiên của tôi :) Tôi hy vọng nó có ý nghĩa.


Cảm ơn câu trả lời. Những hiểu biết tuyệt vời. Lý do tôi quá thiết kế tin nhắn đi qua là vì Lua. Tôi muốn có thể tạo vũ khí mới mà không cần mã C ++ mới. Vì vậy, suy nghĩ của bạn đã trả lời một số câu hỏi không được trả lời của tôi.
deft_code

Đối với các coroutines, tôi cũng là một người rất tin vào coroutines, nhưng tôi không bao giờ chơi với họ trong C ++. Tôi đã có một hy vọng mơ hồ về việc sử dụng coroutines trong mã lua để xử lý các cuộc gọi chặn (ví dụ: chờ chết). Có đáng để nỗ lực không? Tôi sợ rằng tôi có thể bị mù quáng bởi ham muốn mãnh liệt của tôi đối với coroutines trong c ++.
deft_code

Cuối cùng, trò chơi iphone là gì? Tôi có thể lấy thêm thông tin về hệ thống thực thể bạn đã sử dụng không?
deft_code

2
Hệ thống thực thể chủ yếu là trong C ++. Vì vậy, ví dụ, có một lớp Imp xử lý hành vi của Imp. Lua có thể thay đổi các thông số của Imp khi sinh sản hoặc qua tin nhắn. Mục tiêu với Lua là phù hợp với một lịch trình chặt chẽ và việc gỡ lỗi mã Lua rất tốn thời gian. Chúng tôi đã sử dụng Lua cho các cấp độ tập lệnh (thực thể đi đến đâu, các sự kiện xảy ra khi bạn nhấn trình kích hoạt). Vì vậy, ở Lua, chúng tôi sẽ nói những điều như, SpawnEnt ("Imp") trong đó Imp là hiệp hội nhà máy được đăng ký thủ công. Nó sẽ luôn sinh ra trong một nhóm các thực thể toàn cầu. Đẹp và đơn giản. Chúng tôi đã sử dụng rất nhiều smart_ptr và yếu_ptr.
CÂU

1
Vì vậy, BananaRaff: Bạn có nói đây là một bản tóm tắt chính xác cho câu trả lời của bạn: "Tất cả 3 giải pháp bạn đăng đều có công dụng của chúng, cũng như các giải pháp khác. Đừng tìm kiếm một giải pháp hoàn hảo, chỉ cần sử dụng những gì bạn cần . "
Ipsquiggle

76
// in entity_a's code:
entity_b->takeDamage();

Bạn hỏi làm thế nào trò chơi thương mại làm điều đó. ;)


8
Một phiếu bầu xuống? Nghiêm túc mà nói, đó là cách nó thường được thực hiện! Các hệ thống thực thể rất tuyệt nhưng chúng không giúp đạt được các mốc đầu tiên.
tenpn

Tôi làm game Flash một cách chuyên nghiệp và đây là cách tôi làm. Bạn gọi địch.damage (10) và sau đó bạn tìm kiếm bất kỳ thông tin nào bạn cần từ các công cụ thu thập công khai.
Iain

7
Đây là nghiêm túc làm thế nào công cụ trò chơi thương mại làm điều đó. Anh ấy không đùa. Target.NotifyTakeDamage (DamageType, DamageAmount, DamageDealer, v.v.) thường là như thế nào.
Aps Grapsas

3
Các trò chơi thương mại có sai chính tả "thiệt hại" quá không? :-P
Ricket

15
Vâng, họ làm hỏng chính tả, trong số những thứ khác. :)
LearnCocos2D

17

Một câu trả lời nghiêm túc hơn:

Tôi đã thấy bảng đen được sử dụng rất nhiều. Các phiên bản đơn giản không gì khác hơn là các thanh chống được cập nhật với những thứ như HP của một thực thể, mà các thực thể sau đó có thể truy vấn.

Bảng đen của bạn có thể là chế độ xem thế giới của thực thể này (hỏi bảng đen của B là HP của nó) hoặc chế độ xem thế giới của thực thể (A truy vấn bảng đen của nó để xem mục tiêu của HP của A là gì).

Nếu bạn chỉ cập nhật bảng đen tại một điểm đồng bộ hóa trong khung, thì bạn có thể đọc từ chúng ở một điểm sau từ bất kỳ luồng nào, làm cho đa luồng khá đơn giản để thực hiện.

Bảng đen nâng cao hơn có thể giống như hashtables, ánh xạ chuỗi thành giá trị. Điều này có thể duy trì nhiều hơn nhưng rõ ràng có chi phí thời gian.

Theo truyền thống, bảng đen chỉ là giao tiếp một chiều - nó sẽ không xử lý được thiệt hại.


Tôi chưa bao giờ nghe nói về mô hình bảng đen trước đây.
deft_code

Chúng cũng tốt cho việc giảm sự phụ thuộc, giống như cách một hàng đợi sự kiện hoặc mô hình xuất bản / đăng ký thực hiện.
tenpn

2
Đây cũng là định nghĩa về kinh điển và hướng dẫn cách thức hoạt động của một hệ thống E / C / S lý tưởng. Hệ thống là mã hoạt động theo nó. (Tất nhiên, các thực thể chỉ là long long ints hoặc tương tự, trong một hệ thống ECS ​​thuần túy.)
BRPocock

6

Tôi đã nghiên cứu vấn đề này một chút và tôi đã thấy một giải pháp tốt đẹp.

Về cơ bản đó là tất cả về các hệ thống con. Nó tương tự như ý tưởng bảng đen được đề cập bởi tenpn.

Các thực thể được làm bằng các thành phần, nhưng chúng chỉ là túi tài sản. Không có hành vi được thực hiện trong chính các thực thể.

Giả sử, các thực thể có thành phần Sức khỏe và thành phần Thiệt hại.

Sau đó, bạn có một số MessageManager và ba hệ thống con: ActionSystem, DamageSystem, HealthSystem. Tại một thời điểm, ActionSystem thực hiện các tính toán của mình đối với thế giới trò chơi và tạo ra một sự kiện:

HIT, source=entity_A target=entity_B power=5

Sự kiện này được xuất bản lên MessageManager. Bây giờ tại một thời điểm, Trình quản lý tin nhắn đi qua các tin nhắn đang chờ xử lý và thấy rằng DamageSystem đã đăng ký các tin nhắn HIT. Bây giờ MessageManager cung cấp thông báo HIT cho DamageSystem. DamageSystem đi qua danh sách các thực thể có thành phần Thiệt hại, tính toán các điểm sát thương tùy thuộc vào sức mạnh tấn công hoặc một số trạng thái khác của cả hai thực thể, v.v.

DAMAGE, source=entity_A target=entity_B amount=7

HealthSystem đã đăng ký các tin nhắn DAMAGE và bây giờ khi MessageManager xuất bản tin nhắn DAMAGE cho HealthSystem, HealthSystem có quyền truy cập vào cả hai thực thể entity_A và entity_B với các thành phần Sức khỏe của họ, do đó, một lần nữa HealthSystem có thể thực hiện các tính toán tương ứng vào Trình quản lý tin nhắn).

Trong một công cụ trò chơi như vậy, định dạng của tin nhắn là sự kết hợp duy nhất giữa tất cả các thành phần và hệ thống con. Các hệ thống con và thực thể hoàn toàn độc lập và không biết về nhau.

Tôi không biết liệu một số công cụ trò chơi thực sự có thực hiện ý tưởng này hay không, nhưng nó có vẻ khá vững chắc và sạch sẽ và tôi hy vọng một ngày nào đó sẽ tự thực hiện nó cho công cụ trò chơi cấp độ sở thích của mình.


Đây là một câu trả lời tốt hơn nhiều so với câu trả lời được chấp nhận IMO. Tách rời, duy trì và có thể mở rộng (và cũng không phải là một thảm họa ghép nối như câu trả lời đùa của entity_b->takeDamage();)
Danny Yaroslavski

4

Tại sao không có hàng đợi tin nhắn toàn cầu, đại loại như:

messageQueue.push_back(shared_ptr<Event>(new DamageEvent(entityB, 10, entityA)));

Với:

DamageEvent(Entity* toDamage, uint amount, Entity* damageDealer);

Và vào cuối của vòng lặp trò chơi / xử lý sự kiện:

while(!messageQueue.empty())
{
    Event e = messageQueue.front();
    messageQueue.pop_front();
    e.Execute();
}

Tôi nghĩ rằng đây là mẫu lệnh. Và Execute()là một ảo thuần Event, trong đó các dẫn xuất xác định và làm công cụ. Nên ở đây:

DamageEvent::Execute() 
{
    toDamage->takeDamage(amount); // Or of course, you could now have entityA get points, or a recognition of damage, or anything.
}

3

Nếu trò chơi của bạn là một người chơi, chỉ cần sử dụng phương thức đối tượng đích (như đề xuất tenpn).

Nếu bạn (hoặc muốn hỗ trợ) nhiều người chơi (chính xác là nhiều người), hãy sử dụng hàng đợi lệnh.

  • Khi A gây sát thương cho B trên máy khách 1, chỉ cần xếp hàng sự kiện thiệt hại.
  • Đồng bộ hóa các hàng đợi lệnh qua mạng
  • Xử lý các lệnh xếp hàng ở cả hai bên.

2
Nếu bạn nghiêm túc về việc tránh gian lận, A hoàn toàn không sử dụng B trên máy khách. Máy khách sở hữu A gửi lệnh "tấn công B" đến máy chủ, thực hiện chính xác những gì tenpn đã nói; Sau đó, máy chủ sẽ đồng bộ trạng thái đó với tất cả các máy khách có liên quan.

@Joe: Có, nếu có một máy chủ là một điểm hợp lệ để xem xét, nhưng đôi khi bạn có thể tin tưởng vào máy khách (ví dụ: trên bảng điều khiển) để tránh tải máy chủ nặng.
Andreas

2

Tôi sẽ nói: Không sử dụng, miễn là bạn không cần phản hồi rõ ràng ngay lập tức từ thiệt hại.

Thực thể / thành phần gây sát thương / bất cứ điều gì sẽ đẩy các sự kiện đến hàng đợi sự kiện cục bộ hoặc hệ thống ở mức bằng nhau có chứa các sự kiện thiệt hại.

Sau đó nên có một hệ thống lớp phủ với quyền truy cập vào cả hai thực thể yêu cầu các sự kiện từ thực thể a và chuyển nó đến thực thể b. Bằng cách không tạo ra một hệ thống sự kiện chung mà mọi thứ có thể sử dụng từ mọi nơi để truyền sự kiện đến bất cứ lúc nào, bạn tạo ra luồng dữ liệu rõ ràng, giúp mã dễ gỡ lỗi hơn, dễ đo hiệu năng hơn, dễ hiểu và dễ đọc hơn và thường xuyên hơn dẫn đến một hệ thống được thiết kế tốt hơn nói chung.


1

Chỉ cần thực hiện cuộc gọi. Đừng làm theo yêu cầu-hp bị truy vấn bởi hp-truy vấn - nếu bạn theo mô hình đó, bạn sẽ ở trong một thế giới bị tổn thương.

Bạn có thể muốn có một cái nhìn về Mono Continuations là tốt. Tôi nghĩ nó sẽ lý tưởng cho các NPC.


1

Vậy chuyện gì sẽ xảy ra nếu chúng ta có người chơi A và B cố gắng đánh nhau trong cùng một chu kỳ cập nhật ()? Giả sử Cập nhật () cho người chơi A xảy ra trước Cập nhật () cho người chơi B trong Chu kỳ 1 (hoặc đánh dấu hoặc bất cứ điều gì bạn gọi nó). Có hai kịch bản tôi có thể nghĩ ra:

  1. Xử lý ngay lập tức thông qua một tin nhắn:

    • Người chơi A.Update () thấy người chơi muốn đánh B, người chơi B nhận được thông báo về thiệt hại.
    • người chơi B.HandleMessage () cập nhật các điểm nhấn cho người chơi B (anh ta chết)
    • người chơi B.Update () thấy người chơi B đã chết .. anh ta không thể tấn công người chơi A

Điều này là không công bằng, người chơi A và B nên đánh nhau, người chơi B đã chết trước khi đánh A chỉ vì thực thể / trò chơi đó đã cập nhật () sau đó.

  1. Xếp hàng tin nhắn

    • Người chơi A.Update () thấy người chơi muốn đánh B, người chơi B nhận được một thông báo thông báo thiệt hại và lưu nó trong hàng đợi
    • Người chơi A.Update () kiểm tra hàng đợi của nó, nó trống
    • Người chơi B.Update () trước tiên kiểm tra di chuyển để người chơi B gửi tin nhắn cho người chơi A với thiệt hại cũng như
    • Người chơi B.Update () cũng xử lý các tin nhắn trong hàng đợi, xử lý thiệt hại từ người chơi A
    • Chu kỳ mới (2): Người chơi A muốn uống một lọ thuốc sức khỏe để Người chơi A.Update () được gọi và di chuyển được xử lý
    • Người chơi A.Update () kiểm tra hàng đợi tin nhắn và xử lý thiệt hại từ người chơi B

Một lần nữa, điều này là không công bằng .. người chơi A có nhiệm vụ lấy các điểm nhấn trong cùng lượt / chu kỳ / đánh dấu!


4
Bạn không thực sự trả lời câu hỏi nhưng tôi nghĩ câu trả lời của bạn sẽ tạo ra một câu hỏi xuất sắc. Tại sao không đi trước và hỏi làm thế nào để giải quyết ưu tiên "không công bằng" như vậy?
bummzack

Tôi nghi ngờ rằng hầu hết các trò chơi quan tâm đến sự không công bằng này bởi vì chúng cập nhật thường xuyên đến mức hiếm khi nó là một vấn đề. Một cách giải quyết đơn giản là xen kẽ giữa lặp lại tiến và lùi thông qua danh sách thực thể khi cập nhật.
Kylotan

Tôi sử dụng 2 cuộc gọi vì vậy tôi gọi Update () cho tất cả các thực thể, sau đó sau vòng lặp tôi lặp lại và gọi một cái gì đó như thế pEntity->Flush( pMessages );. Khi entity_A tạo ra một sự kiện mới, nó sẽ không được đọc bởi entity_B trong khung đó (nó cũng có cơ hội lấy thuốc), sau đó cả hai đều nhận được thiệt hại và sau đó họ xử lý thông điệp về thuốc chữa bệnh sẽ là lần cuối cùng trong hàng đợi . Người chơi B vẫn chết vì tin nhắn thuốc là cuối cùng trong hàng đợi: P nhưng nó có thể hữu ích cho các loại tin nhắn khác như xóa con trỏ đến các thực thể chết.
Pablo Ariel

Tôi nghĩ ở cấp độ khung hình, hầu hết các triển khai trò chơi chỉ đơn giản là không công bằng. như Kylotan đã nói.
v.oddou

Vấn đề này rất dễ giải quyết. Chỉ cần áp dụng thiệt hại cho nhau trong xử lý tin nhắn hoặc bất cứ điều gì. Bạn chắc chắn không nên gắn cờ người chơi là người chết trong trình xử lý tin nhắn. Trong "Cập nhật ()" bạn chỉ cần thực hiện "if (hp <= 0) die ();" (ở đầu "Cập nhật ()" chẳng hạn). Bằng cách đó cả hai có thể giết nhau cùng một lúc. Ngoài ra: Thường thì bạn không gây sát thương trực tiếp cho người chơi, nhưng thông qua một số đối tượng trung gian như viên đạn.
Tara
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.