Cách nào để thực hiện một hệ thống buff / debuff linh hoạt?


66

Tổng quan:

Rất nhiều game có số liệu thống kê giống RPG cho phép nhân vật "buff", từ đơn giản là "Gây thêm 25% sát thương" cho đến những thứ phức tạp hơn như "Gây 15 sát thương cho kẻ tấn công khi bị tấn công".

Các chi tiết cụ thể của từng loại buff không thực sự phù hợp. Tôi đang tìm kiếm một cách (có lẽ là hướng đối tượng) để xử lý các buff tùy ý.

Chi tiết:

Trong trường hợp cụ thể của tôi, tôi có nhiều nhân vật trong môi trường chiến đấu theo lượt, vì vậy tôi đã hình dung ra các buff được gắn với các sự kiện như "OnTurnStart", "OnReceiveDamage", v.v. Có lẽ mỗi buff là một lớp con của lớp trừu tượng Buff chính, trong đó chỉ các sự kiện có liên quan là quá tải. Sau đó, mỗi nhân vật có thể có một vectơ buff hiện đang được áp dụng.

Liệu giải pháp này có ý nghĩa? Tôi chắc chắn có thể thấy hàng tá loại sự kiện là cần thiết, cảm giác như tạo một lớp con mới cho mỗi buff là quá mức cần thiết và dường như nó không cho phép bất kỳ "tương tác" nào của buff. Đó là, nếu tôi muốn thực hiện giới hạn tăng sát thương để ngay cả khi bạn có 10 buff khác nhau, tất cả sẽ tăng thêm 25% sát thương, bạn chỉ làm thêm 100% thay vì thêm 250%.

Và có nhiều tình huống phức tạp hơn lý tưởng mà tôi có thể kiểm soát. Tôi chắc rằng mọi người đều có thể đưa ra các ví dụ về cách các buff tinh vi hơn có khả năng tương tác với nhau theo cách mà một nhà phát triển trò chơi tôi có thể không muốn.

Là một lập trình viên C ++ tương đối thiếu kinh nghiệm (tôi thường sử dụng C trong các hệ thống nhúng), tôi cảm thấy giải pháp của mình rất đơn giản và có lẽ không tận dụng hết ngôn ngữ hướng đối tượng.

Suy nghĩ? Có ai ở đây đã thiết kế một hệ thống buff khá mạnh mẽ trước đây?

Chỉnh sửa: Về câu trả lời:

Tôi đã chọn một câu trả lời chủ yếu dựa trên chi tiết tốt và một câu trả lời chắc chắn cho câu hỏi tôi đã hỏi, nhưng đọc các câu trả lời cho tôi cái nhìn sâu sắc hơn.

Có lẽ không có gì đáng ngạc nhiên, các hệ thống khác nhau hoặc các hệ thống được điều chỉnh dường như áp dụng tốt hơn cho các tình huống nhất định. Hệ thống nào hoạt động tốt nhất cho trò chơi của tôi sẽ phụ thuộc vào loại, phương sai và số lượng buff tôi dự định có thể áp dụng.

Đối với một trò chơi như Diablo 3 (được đề cập dưới đây), trong đó gần như bất kỳ bit thiết bị nào có thể thay đổi sức mạnh của buff, các buff chỉ là hệ thống thống kê nhân vật có vẻ như là một ý tưởng tốt bất cứ khi nào có thể.

Đối với tình huống theo lượt tôi tham gia, cách tiếp cận dựa trên sự kiện có thể phù hợp hơn.

Trong mọi trường hợp, tôi vẫn hy vọng ai đó đi kèm với một viên đạn ma thuật "OO" lạ mắt sẽ cho phép tôi áp dụng khoảng cách di chuyển +2 cho mỗi lượt buff, giảm 50% sát thương cho buff kẻ tấn công và một dịch chuyển tự động đến một ô gần đó khi bị tấn công từ 3 ô trở lên trong một hệ thống mà không biến buff +5 sức mạnh thành lớp con của chính nó.

Tôi nghĩ rằng điều gần nhất là câu trả lời tôi đã đánh dấu, nhưng sàn vẫn mở. Cám ơn mọi người đã đóng góp ý kiến.


Tôi không đăng bài này như một câu trả lời, vì tôi chỉ đang động não, nhưng còn một danh sách các buff thì sao? Mỗi buff có một hằng số và một yếu tố sửa đổi. Hằng số sẽ là +10 sát thương, hệ số sẽ là 1,10 cho mức tăng sát thương + 10%. Trong tính toán thiệt hại của bạn, bạn lặp lại tất cả các buff, để có được một bộ sửa đổi tổng, và sau đó bạn áp đặt bất kỳ giới hạn nào bạn muốn. Bạn sẽ làm điều này cho bất kỳ loại thuộc tính có thể sửa đổi. Bạn sẽ cần một phương pháp trường hợp đặc biệt cho những điều phức tạp.
William Mariager

Tình cờ tôi đã thực hiện một cái gì đó tương tự cho đối tượng Thống kê của mình khi tôi chế tạo một hệ thống cho vũ khí và phụ kiện có thể trang bị. Như bạn đã nói, đó là một giải pháp đủ tốt cho các buff chỉ sửa đổi các thuộc tính hiện có, nhưng tất nhiên sau đó tôi sẽ muốn một số buff nhất định hết hạn sau khi X quay, các cách khác hết hạn khi hiệu ứng xảy ra Y lần, v.v. đề cập đến điều này trong câu hỏi chính vì nó đã trở nên thực sự lâu rồi.
gkimsey

1
nếu bạn có một phương thức "onReceiveDamage" được gọi bởi một hệ thống nhắn tin, hoặc bằng tay, hoặc một cách khác, thì sẽ đủ dễ dàng để bao gồm một tham chiếu đến ai / những gì bạn đang bị thiệt hại. Vì vậy, sau đó bạn có thể cung cấp thông tin này cho người dùng của mình

Đúng vậy, tôi đã dự kiến ​​mỗi mẫu sự kiện cho lớp Buff trừu tượng sẽ bao gồm các tham số có liên quan như thế. Nó chắc chắn sẽ hoạt động, nhưng tôi do dự vì cảm thấy như nó sẽ không mở rộng tốt. Tôi có một thời gian khó tưởng tượng một MMORPG với hàng trăm buff khác nhau có một lớp riêng được xác định cho mỗi buff, chọn từ một trăm sự kiện khác nhau. Không phải là tôi đang tạo ra nhiều buff (có thể gần 30), nhưng nếu có một hệ thống đơn giản hơn, thanh lịch hơn hoặc linh hoạt hơn, tôi muốn sử dụng nó. Hệ thống linh hoạt hơn = buff / khả năng thú vị hơn.
gkimsey

4
Đây không phải là một câu trả lời tốt cho vấn đề tương tác, nhưng dường như với tôi rằng mô hình trang trí áp dụng tốt ở đây; chỉ cần áp dụng nhiều buff (trang trí) lên nhau. Có thể với một hệ thống để xử lý sự tương tác bằng cách "hợp nhất" các buff với nhau (ví dụ: 10,5% hợp nhất thành một buff 100%).
tro999

Câu trả lời:


32

Đây là một vấn đề phức tạp, bởi vì bạn đang nói về một vài điều khác nhau mà (những ngày này) được gộp lại thành 'buff':

  • bổ nghĩa cho thuộc tính của người chơi
  • hiệu ứng đặc biệt xảy ra trong một số sự kiện
  • kết hợp của trên.

Tôi luôn thực hiện đầu tiên với một danh sách các hiệu ứng hoạt động cho một nhân vật nhất định. Việc xóa khỏi danh sách, cho dù dựa trên thời lượng hay rõ ràng là khá tầm thường nên tôi sẽ không đề cập ở đây. Mỗi Hiệu ứng chứa một danh sách các công cụ sửa đổi thuộc tính và có thể áp dụng nó cho giá trị cơ bản thông qua phép nhân đơn giản.

Sau đó, tôi bọc nó với các chức năng để truy cập các thuộc tính sửa đổi. ví dụ.:

def get_current_attribute_value(attribute_id, criteria):
    val = character.raw_attribute_value[attribute_id]
    # Accumulate the modifiers
    for effect in character.all_effects:
        val = effect.apply_attribute_modifier(attribute_id, val, criteria)
    # Make sure it doesn't exceed game design boundaries
    val = apply_capping_to_final_value(val)
    return val

class Effect():
    def apply_attribute_modifier(attribute_id, val, criteria):
        if attribute_id in self.modifier_list:
            modifier = self.modifier_list[attribute_id]
            # Does the modifier apply at this time?
            if modifier.criteria == criteria:
                # Apply multiplicative modifier
                return val * modifier.amount
        else:
            return val

class Modifier():
    amount = 1.0 # default that has no effect
    criteria = None # applies all of the time

Điều đó cho phép bạn áp dụng hiệu ứng nhân dễ dàng đủ. Nếu bạn cũng cần các hiệu ứng phụ gia, hãy quyết định thứ tự bạn sẽ áp dụng chúng theo thứ tự (có thể là phụ gia cuối cùng) và chạy qua danh sách hai lần. (Tôi có thể có các danh sách sửa đổi riêng biệt trong Hiệu ứng, một cho phép nhân, một cho phụ gia).

Giá trị tiêu chí là cho phép bạn thực hiện "+ 20% so với Undead" - đặt giá trị UNDEAD trên Hiệu ứng và chỉ chuyển giá trị UNDEAD cho get_current_attribute_value()khi bạn tính toán sát thương đối với kẻ thù bất tử.

Ngẫu nhiên, tôi sẽ không muốn thử và viết một hệ thống áp dụng và không áp dụng các giá trị trực tiếp vào giá trị thuộc tính cơ bản - kết quả cuối cùng là các thuộc tính của bạn rất có thể bị trôi đi khỏi giá trị dự định do lỗi. (ví dụ: nếu bạn nhân một cái gì đó với 2, nhưng sau đó giới hạn nó, khi bạn chia nó lại cho 2 lần nữa, nó sẽ thấp hơn so với khi bắt đầu.)

Đối với các hiệu ứng dựa trên sự kiện, chẳng hạn như "Gây 15 sát thương cho kẻ tấn công khi bị tấn công", bạn có thể thêm các phương thức vào lớp Hiệu ứng cho điều đó. Nhưng nếu bạn muốn hành vi khác biệt và tùy tiện (ví dụ: một số hiệu ứng cho sự kiện trên có thể phản ánh thiệt hại trở lại, một số có thể chữa lành cho bạn, nó có thể dịch chuyển bạn một cách ngẫu nhiên, bất cứ điều gì) bạn sẽ cần các chức năng hoặc lớp tùy chỉnh để xử lý nó. Bạn có thể gán các hàm cho trình xử lý sự kiện về hiệu ứng, sau đó bạn chỉ cần gọi trình xử lý sự kiện trên bất kỳ hiệu ứng hoạt động nào.

# This is a method on a Character, called during combat
def on_receive_damage(damage_info):
    for effect in character.all_effects:
        effect.on_receive_damage(character, damage_info)

class Effect():
    self.on_receive_damage_handler = DoNothing # a default function that does nothing
    def on_receive_damage(character, damage_info):
        self.on_receive_damage_handler(character, damage_info)

def reflect_damage(character, damage_info):
    damage_info.attacker.receive_damage(15)

reflect_damage_effect = new Effect()
reflect_damage_effect.on_receive_damage_handler = reflect_damage
my_character.all_effects.add(reflect_damage_effect)

Rõ ràng lớp Hiệu ứng của bạn sẽ có một trình xử lý sự kiện cho mọi loại sự kiện và bạn có thể gán các hàm xử lý cho bao nhiêu tùy ý trong mỗi trường hợp. Bạn không cần phải phân lớp Hiệu ứng, vì mỗi cái được xác định bởi thành phần của bộ sửa đổi thuộc tính và bộ xử lý sự kiện mà nó chứa. (Nó có thể cũng sẽ chứa tên, thời lượng, v.v.)


2
+1 cho chi tiết xuất sắc. Đây là câu trả lời gần nhất để trả lời chính thức câu hỏi của tôi như tôi đã thấy. Thiết lập cơ bản ở đây dường như cho phép rất linh hoạt và một sự trừu tượng nhỏ về những gì có thể là logic trò chơi lộn xộn. Như bạn đã nói, các hiệu ứng thú vị hơn sẽ vẫn cần các lớp riêng, nhưng điều này xử lý phần lớn nhu cầu của hệ thống "buff" thông thường, tôi nghĩ vậy.
gkimsey

+1 để chỉ ra sự khác biệt về khái niệm ẩn ở đây. Không phải tất cả chúng sẽ hoạt động với cùng logic cập nhật dựa trên sự kiện. Xem câu trả lời của @ Ross cho một ứng dụng hoàn toàn khác. Cả hai sẽ phải tồn tại bên cạnh nhau.
ctietze

22

Trong một trò chơi mà tôi đã làm việc với một người bạn cho một lớp học, chúng tôi đã tạo ra một hệ thống buff / debuff khi người dùng bị mắc kẹt trong cỏ cao và gạch tăng tốc và những thứ không, và một số thứ nhỏ như chảy máu và thuốc độc.

Ý tưởng rất đơn giản và trong khi chúng tôi áp dụng nó trong Python, nó khá hiệu quả.

Về cơ bản, đây là cách nó đã đi:

  • Người dùng đã có một danh sách các buff và debuff hiện đang được áp dụng (lưu ý rằng một buff và debuff tương đối giống nhau, đó chỉ là hiệu ứng có kết quả khác nhau)
  • Buff có nhiều thuộc tính như thời lượng, tên và văn bản để hiển thị thông tin và thời gian tồn tại. Những cái quan trọng là thời gian tồn tại, thời lượng và một tài liệu tham khảo cho diễn viên mà buff này được áp dụng.
  • Đối với Buff, khi nó được gắn vào trình phát qua player.apply (buff / debuff), nó sẽ gọi một phương thức start (), điều này sẽ áp dụng các thay đổi quan trọng cho người chơi như tăng tốc độ hoặc làm chậm.
  • Sau đó chúng tôi sẽ lặp lại qua từng buff trong một vòng cập nhật và các buff sẽ cập nhật, điều này sẽ tăng thời gian sống của chúng. Các lớp con sẽ thực hiện những việc như đầu độc người chơi, cho HP của người chơi theo thời gian, v.v.
  • Khi buff được thực hiện, có nghĩa là timeAlive> = thời gian, logic cập nhật sẽ loại bỏ buff và gọi phương thức finish (), điều này sẽ thay đổi từ việc loại bỏ các giới hạn tốc độ trên người chơi để gây ra bán kính nhỏ (nghĩ về hiệu ứng bom sau một DoT)

Bây giờ làm thế nào để thực sự áp dụng buff từ thế giới là một câu chuyện khác. Đây là thức ăn của tôi cho suy nghĩ mặc dù.


1
Điều này nghe có vẻ như một lời giải thích tốt hơn về những gì tôi đã cố gắng để mô tả ở trên. Nó tương đối đơn giản, chắc chắn dễ hiểu. Về cơ bản, bạn đã đề cập đến ba "sự kiện" ở đó (OnApply, OnTimeTick, OnExpired) để liên kết nó với suy nghĩ của tôi. Như vậy, nó sẽ không hỗ trợ những thứ như trả lại sát thương khi bị đánh và cứ thế, nhưng nó sẽ mở rộng tốt hơn cho rất nhiều buff. Tôi không muốn giới hạn những gì các buff của tôi có thể làm (mà = giới hạn số lượng sự kiện tôi đưa ra phải được gọi bằng logic trò chơi chính), nhưng khả năng mở rộng buff có thể quan trọng hơn. Cảm ơn vì đầu vào của bạn!
gkimsey

Vâng, chúng tôi đã không thực hiện bất cứ điều gì như thế. Nghe có vẻ rất gọn gàng và là một khái niệm tuyệt vời (giống như một người thích Thorns).
Ross

@gkimsey Đối với những thứ như Thorns và các buff thụ động khác, tôi sẽ triển khai logic trong lớp Mob của bạn dưới dạng chỉ số thụ động tương tự như thiệt hại hoặc sức khỏe và tăng chỉ số này khi áp dụng buff. Điều này giúp đơn giản hóa rất nhiều trường hợp khi bạn có nhiều buff cũng như giữ cho giao diện sạch sẽ (10 buff sẽ hiển thị 1 sát thương trả lại thay vì 10) và cho phép hệ thống buff vẫn đơn giản.
3Doubloons

Đây là một cách tiếp cận gần như đơn giản, nhưng tôi bắt đầu nghĩ về bản thân mình khi chơi Diablo 3. Tôi nhận thấy sự đánh cắp sinh mạng, cuộc sống bị tấn công, thiệt hại cho những kẻ tấn công cận chiến, v.v ... đều là những chỉ số của riêng họ trong cửa sổ nhân vật. Cấp, D3 không có hệ thống đệm hoặc tương tác phức tạp nhất trên thế giới, nhưng hầu như không tầm thường. Điều này làm cho rất nhiều ý nghĩa. Tuy nhiên, có khả năng 15 buff khác nhau với 12 hiệu ứng khác nhau sẽ rơi vào đây. Có vẻ kỳ lạ đệm ra bảng thống kê nhân vật ....
gkimsey

11

Tôi không chắc liệu bạn có đang đọc cái này không nhưng tôi đã vật lộn với loại vấn đề này trong một thời gian dài.

Tôi đã thiết kế nhiều loại hệ thống ảnh hưởng khác nhau. Tôi sẽ nhanh chóng đi qua chúng bây giờ. Đây là tất cả dựa trên kinh nghiệm của tôi. Tôi không yêu cầu phải biết tất cả các câu trả lời.


Bộ điều chỉnh tĩnh

Loại hệ thống này chủ yếu dựa vào các số nguyên đơn giản để xác định bất kỳ sửa đổi nào. Ví dụ: +100 đến Max HP, +10 để tấn công, v.v. Hệ thống này cũng có thể xử lý phần trăm là tốt. Bạn chỉ cần đảm bảo rằng việc xếp chồng không vượt khỏi tầm kiểm soát.

Tôi không bao giờ thực sự lưu trữ các giá trị được tạo cho loại hệ thống này. Ví dụ: nếu tôi muốn hiển thị sức khỏe tối đa của một thứ gì đó, tôi sẽ tạo ra giá trị tại chỗ. Điều này ngăn chặn mọi thứ dễ bị lỗi và dễ hiểu hơn cho mọi người liên quan.

(Tôi làm việc trong Java vì vậy những gì tiếp theo là dựa trên Java nhưng nó sẽ hoạt động với một số sửa đổi cho các ngôn ngữ khác) Hệ thống này có thể dễ dàng được thực hiện bằng cách sử dụng enums cho các loại sửa đổi, sau đó là số nguyên. Kết quả cuối cùng có thể được đặt vào một số loại bộ sưu tập có các cặp khóa, giá trị theo thứ tự. Đây sẽ là tra cứu và tính toán nhanh, vì vậy hiệu suất rất tốt.

Nhìn chung, nó hoạt động rất tốt với chỉ cần sửa đổi tĩnh. Mặc dù, mã phải tồn tại ở những vị trí thích hợp cho các bộ sửa đổi được sử dụng: getAttack, getMaxHP, getMeleeDamage, v.v.

Trường hợp phương thức này thất bại (đối với tôi) là sự tương tác rất phức tạp giữa các buff. Không có cách nào thực sự dễ dàng để có tương tác ngoại trừ bằng cách tăng cường một chút. Nó có một số khả năng tương tác đơn giản. Để làm điều đó, bạn phải thực hiện một sửa đổi cho cách bạn lưu trữ các bộ sửa đổi tĩnh. Thay vì sử dụng enum làm khóa, bạn sử dụng Chuỗi. Chuỗi này sẽ là tên Enum + biến phụ. 9 trong số 10 lần, biến phụ không được sử dụng, do đó bạn vẫn giữ tên enum làm khóa.

Hãy làm một ví dụ nhanh: Nếu bạn muốn có thể sửa đổi thiệt hại chống lại các sinh vật bất tử, bạn có thể có một cặp theo thứ tự như thế này: (DAMAGE_Undead, 10) DAMAGE là Enum và Undead là biến phụ. Vì vậy, trong quá trình chiến đấu, bạn có thể làm một cái gì đó như:

dam += attacker.getMod(Mod.DAMAGE + npc.getRaceFamily()); //in this case the race family would be undead

Dù sao, nó hoạt động khá tốt và nhanh chóng. Nhưng nó thất bại ở các tương tác phức tạp và có mã đặc biệt trên mạng ở khắp mọi nơi. Ví dụ, hãy xem xét tình huống của 25% cơ hội để dịch chuyển tức thời trên cái chết. Đây là một khu phức hợp khá tốt. Hệ thống trên có thể xử lý nó, nhưng không dễ dàng, vì bạn cần những điều sau đây:

  1. Xác định xem người chơi có mod này không.
  2. Ở đâu đó, có một số mã để thực hiện dịch chuyển tức thời, nếu thành công. Vị trí của mã này là một cuộc thảo luận trong chính nó!
  3. Lấy dữ liệu đúng từ bản đồ Mod. Giá trị có nghĩa là gì? Có phải đó là căn phòng nơi họ dịch chuyển quá không? Điều gì nếu một người chơi có hai mod dịch chuyển tức thời trên chúng ?? Số tiền sẽ không được cộng lại với nhau chứ ?????? SỰ THẤT BẠI!

Vì vậy, điều này đưa tôi đến người tiếp theo của tôi:


Hệ thống Buff phức tạp cuối cùng

Tôi đã từng cố gắng tự viết một MMORPG 2D. Đây là một sai lầm khủng khiếp nhưng tôi đã học được rất nhiều!

Tôi viết lại hệ thống ảnh hưởng 3 lần. Cái đầu tiên sử dụng một biến thể ít mạnh mẽ hơn ở trên. Điều thứ hai là những gì tôi sẽ nói về.

Hệ thống này có một loạt các lớp cho mỗi sửa đổi, vì vậy những thứ như: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent. Tôi đã có một triệu người trong số họ - thậm chí cả những thứ như TeleportOnDeath.

Các lớp học của tôi có những thứ sẽ làm như sau:

  • áp dụng hiệu quả
  • loại bỏ
  • checkForInteraction <--- quan trọng

Tự áp dụng và xóa giải thích (mặc dù đối với những thứ như phần trăm, ảnh hưởng sẽ theo dõi mức độ tăng của HP bằng cách đảm bảo khi ảnh hưởng giảm đi, nó chỉ xóa số tiền được thêm vào. Đây là lỗi, lol, và tôi đã mất một thời gian dài để chắc chắn rằng nó đúng. Tôi vẫn không có cảm giác tốt về nó.).

Phương thức checkForInteraction là một đoạn mã phức tạp khủng khiếp. Trong mỗi lớp ảnh hưởng (ví dụ: ChangeHP), nó sẽ có mã để xác định xem điều này có nên được sửa đổi bởi ảnh hưởng đầu vào hay không. Vì vậy, ví dụ, nếu bạn có một cái gì đó như ....

  • Buff 1: Gây 10 sát thương lửa khi tấn công
  • Buff 2: Tăng 25% sát thương lửa.
  • Buff 3: Tăng tất cả sát thương lửa lên 15.

Phương thức checkForInteraction sẽ xử lý tất cả những ảnh hưởng này. Để làm điều này, từng ảnh hưởng đến TẤT CẢ những người chơi gần đó phải được kiểm tra !! Điều này là do loại ảnh hưởng mà tôi đã xử lý với nhiều người chơi trên một phạm vi của một khu vực. Điều này có nghĩa là mã KHÔNG BAO GIỜ có bất kỳ tuyên bố đặc biệt nào như trên - nếu chúng ta vừa mới chết, chúng ta nên kiểm tra dịch chuyển tức thời trên death chết. Hệ thống này sẽ tự động xử lý chính xác vào đúng thời điểm.

Cố gắng viết hệ thống này khiến tôi mất 2 tháng và đầu nổ tung nhiều lần. TUY NHIÊN, nó thực sự mạnh mẽ và có thể tạo ra một số thứ điên rồ - đặc biệt là khi bạn tính đến hai sự thật sau đây cho các khả năng trong trò chơi của tôi: 1. Họ có phạm vi mục tiêu (ví dụ: đơn, tự, chỉ nhóm, tự PB , Mục tiêu PB AE, AE được nhắm mục tiêu, v.v.). 2. Khả năng có thể có nhiều hơn 1 ảnh hưởng đến họ.

Như tôi đã đề cập ở trên, đây là hệ thống ảnh hưởng thứ 2 của trò chơi này. Tại sao tôi lại di chuyển khỏi đây?

Hệ thống này có hiệu suất tồi tệ nhất tôi từng thấy! Nó rất chậm vì nó phải kiểm tra rất nhiều cho từng thứ đang diễn ra. Tôi đã cố gắng cải thiện nó, nhưng coi đó là một thất bại.

Vì vậy, chúng tôi đến phiên bản thứ ba của tôi (và một loại hệ thống buff khác):


Lớp ảnh hưởng phức tạp với xử lý

Vì vậy, đây là sự kết hợp của hai yếu tố đầu tiên: Chúng ta có thể có các biến tĩnh trong một lớp Ảnh hưởng có chứa nhiều chức năng và dữ liệu bổ sung. Sau đó, chỉ cần gọi trình xử lý (đối với tôi, khá nhiều phương thức tiện ích tĩnh thay vì các lớp con cho các hành động cụ thể. Nhưng tôi chắc chắn bạn có thể đi với các lớp con cho các hành động nếu bạn cũng muốn) khi chúng tôi muốn làm gì đó.

Lớp Affect sẽ có tất cả những thứ tốt đẹp, như các loại mục tiêu, thời lượng, số lần sử dụng, cơ hội để thực hiện, v.v.

Chúng tôi vẫn sẽ phải thêm các mã đặc biệt để xử lý các tình huống, ví dụ, dịch chuyển tức thời về cái chết. Chúng tôi vẫn sẽ phải kiểm tra điều này trong mã chiến đấu bằng tay, và sau đó nếu nó tồn tại, chúng tôi sẽ nhận được một danh sách các ảnh hưởng. Danh sách ảnh hưởng này chứa tất cả các ảnh hưởng hiện đang được áp dụng đối với người chơi liên quan đến dịch chuyển tức thời khi chết. Sau đó, chúng tôi sẽ chỉ nhìn vào từng người và kiểm tra xem liệu nó đã thực hiện và đã thành công chưa (Chúng tôi sẽ dừng lại ở lần đầu tiên thành công). Nó đã thành công, chúng tôi chỉ cần gọi người xử lý để giải quyết việc này.

Tương tác có thể được thực hiện, nếu bạn muốn quá. Chỉ cần viết mã để tìm kiếm các buff cụ thể trên người chơi / v.v. Bởi vì nó có hiệu suất tốt (xem bên dưới), nên nó khá hiệu quả để làm điều đó. Nó chỉ cần xử lý phức tạp hơn và như vậy.

Vì vậy, nó có rất nhiều hiệu suất của hệ thống đầu tiên và vẫn còn rất nhiều phức tạp như hệ thống thứ hai (nhưng không nhiều AS). Trong Java ít nhất, bạn có thể thực hiện một số điều khó khăn để có được hiệu năng của gần như lần đầu tiên trong các trường hợp MOST (ví dụ: có bản đồ enum ( http://docs.oracle.com/javase/6/docs/api/java /util/EnumMap.html ) với Enums là các khóa và ArrayList ảnh hưởng như các giá trị. Điều này cho phép bạn xem nếu bạn nhanh chóng có ảnh hưởng [vì danh sách sẽ là 0 hoặc bản đồ sẽ không có enum] và không có để liên tục lặp lại các danh sách ảnh hưởng của người chơi mà không có lý do. Tôi không ngại lặp đi lặp lại ảnh hưởng nếu chúng ta cần chúng vào lúc này. Tôi sẽ tối ưu hóa sau nếu nó trở thành vấn đề).

Tôi hiện đang mở lại (viết lại trò chơi bằng Java thay vì cơ sở mã FastROM ban đầu) MUD của tôi đã kết thúc vào năm 2005 và gần đây tôi đã gặp phải cách tôi muốn triển khai hệ thống buff của mình? Tôi sẽ sử dụng hệ thống này vì nó hoạt động tốt trong trò chơi thất bại trước đây của tôi.

Vâng, hy vọng ai đó, ở đâu đó, sẽ tìm thấy một vài trong số những hiểu biết hữu ích.


6

Một lớp khác (hoặc hàm địa chỉ) cho mỗi buff không quá mức nếu hành vi của các buff đó khác nhau. Một điều sẽ có + 10% hoặc + 20% buff (tất nhiên, điều đó sẽ được thể hiện tốt hơn dưới dạng hai đối tượng của cùng một lớp), khác sẽ thực hiện các hiệu ứng cực kỳ khác nhau đòi hỏi mã tùy chỉnh. Tuy nhiên, tôi tin rằng tốt hơn là nên có các cách tùy chỉnh logic trò chơi tiêu chuẩn thay vì để mỗi người chơi làm bất cứ điều gì mình muốn (và possibliy can thiệp lẫn nhau theo những cách không lường trước được, làm mất cân bằng trò chơi).

Tôi khuyên bạn nên chia mỗi "chu kỳ tấn công" thành các bước, trong đó mỗi bước có một giá trị cơ bản, một danh sách sửa đổi có thể áp dụng cho giá trị đó (có thể được giới hạn) và giới hạn cuối cùng. Mỗi sửa đổi có một chuyển đổi nhận dạng như mặc định và có thể bị ảnh hưởng bởi 0 hoặc nhiều buff / debuff. Các chi tiết cụ thể của từng sửa đổi sẽ phụ thuộc vào bước áp dụng. Làm thế nào chu trình được thực hiện là tùy thuộc vào bạn (bao gồm tùy chọn kiến ​​trúc hướng sự kiện, như bạn đã thảo luận).

Một ví dụ về chu kỳ tấn công có thể là:

  • tính toán tấn công người chơi (cơ sở + mod);
  • tính toán phòng thủ của đối thủ (cơ sở + mod);
  • làm sự khác biệt (và áp dụng mod) và xác định thiệt hại cơ sở;
  • tính toán bất kỳ hiệu ứng parry / Armor (mod trên sát thương cơ bản) và áp dụng sát thương;
  • tính toán bất kỳ hiệu ứng giật lại (mod trên sát thương cơ bản) và áp dụng cho kẻ tấn công.

Điều quan trọng cần lưu ý là càng sớm trong chu kỳ, một buff sẽ được áp dụng càng nhiều trong kết quả . Vì vậy, nếu bạn muốn chiến đấu "chiến thuật" hơn (trong đó kỹ năng của người chơi quan trọng hơn cấp độ nhân vật), hãy tạo nhiều buff / debuff trên các chỉ số cơ bản. Nếu bạn muốn chiến đấu "cân bằng" hơn (trong đó cấp độ quan trọng hơn - quan trọng trong MMOG để hạn chế tốc độ tiến bộ), chỉ sử dụng buff / debuff sau này trong chu kỳ.

Sự khác biệt giữa "Sửa đổi" và "Buff" mà tôi đã đề cập trước đó có một mục đích: các quyết định về quy tắc và cân bằng có thể được thực hiện trước đây, do đó, bất kỳ thay đổi nào đối với những thay đổi không cần phải phản ánh trong các thay đổi đối với mọi loại sau này. OTOH, số lượng và loại buff chỉ bị giới hạn bởi trí tưởng tượng của bạn, vì mỗi người trong số họ có thể thể hiện hành vi mong muốn của mình mà không cần phải tính đến bất kỳ tương tác nào có thể có giữa họ và những người khác (hoặc thậm chí là sự tồn tại của người khác).

Vì vậy, trả lời câu hỏi: không tạo một lớp cho mỗi Buff, nhưng một lớp cho mỗi (loại) Sửa đổi và gắn Sửa đổi với chu kỳ tấn công, không phải cho nhân vật. Các buff có thể chỉ đơn giản là một danh sách các bộ dữ liệu (Sửa đổi, khóa, giá trị) và bạn có thể áp dụng một bộ đệm cho một nhân vật bằng cách thêm / xóa nó vào bộ đệm của nhân vật. Điều này cũng làm giảm lỗi cửa sổ, vì các chỉ số của nhân vật hoàn toàn không cần phải thay đổi khi áp dụng các buff (vì vậy sẽ ít có nguy cơ khôi phục lại chỉ số về giá trị sai sau khi hết hạn buff).


Đây là một cách tiếp cận thú vị bởi vì nó nằm ở đâu đó giữa hai triển khai mà tôi đã xem xét - nghĩa là, chỉ giới hạn các buff đối với các công cụ sửa đổi chỉ số và kết quả thiệt hại khá đơn giản, hoặc tạo ra một hệ thống rất mạnh mẽ nhưng có thể xử lý mọi vấn đề. Đây là một dạng mở rộng trước đây để cho phép "cái gai" trong khi duy trì một giao diện đơn giản. Trong khi tôi không nghĩ rằng đó là viên đạn ma thuật cho những gì tôi cần, nó chắc chắn trông giống như nó làm cho cân nhiều dễ dàng hơn các phương pháp khác, vì vậy nó có thể là cách để đi. Cảm ơn vì đầu vào của bạn!
gkimsey

3

Tôi không biết liệu bạn có còn đọc nó không, nhưng đây là cách tôi đang thực hiện (mã dựa trên UE4 và C ++). Sau khi suy nghĩ về vấn đề này trong hơn hai tuần (!!), cuối cùng tôi đã tìm thấy điều này:

http://gamedevelopment.tutsplus.com/tutorials/USE-the-composite-design-potype-for-an-rpg-attribut-system--gamedev-243

Và tôi nghĩ rằng, tốt, việc gói gọn thuộc tính duy nhất trong lớp / struct không phải là ý tưởng tồi. Mặc dù vậy, hãy nhớ rằng tôi đang tận dụng lợi thế rất lớn của hệ thống phản chiếu mã UE4, vì vậy nếu không có một số thao tác lại, điều này có thể không phù hợp ở mọi nơi.

Dù sao, tôi đã bắt đầu từ việc gói thuộc tính vào cấu trúc đơn:

USTRUCT(BlueprintType)
struct GAMEATTRIBUTES_API FGAAttributeBase
{
    GENERATED_USTRUCT_BODY()
public:
    UPROPERTY()
        FName AttributeName;
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value")
        float BaseValue;
    /*
        This is maxmum value of this attribute.
    */
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value")
        float ClampValue;
protected:
    float BonusValue;
    //float OldCurrentValue;
    float CurrentValue;
    float ChangedValue;

    //map of modifiers.
    //It could be TArray, but map seems easier to use in this case
    //we need to keep track of added/removed effects, and see 
    //if this effect affected this attribute.
    TMap<FGAEffectHandle, FGAModifier> Modifiers;

public:

    inline float GetFinalValue(){ return BaseValue + BonusValue; };
    inline float GetCurrentValue(){ return CurrentValue; };
    void UpdateAttribute();

    void Add(float ValueIn);
    void Subtract(float ValueIn);

    //inline float GetCurrentValue()
    //{
    //  return FMath::Clamp<float>(BaseValue + BonusValue + AccumulatedBonus, 0, GetFinalValue());;
    //}

    void AddBonus(const FGAModifier& ModifiersIn, const FGAEffectHandle& Handle);
    void RemoveBonus(const FGAEffectHandle& Handle);

    void InitializeAttribute();

    void CalculateBonus();

    inline bool operator== (const FGAAttributeBase& OtherAttribute) const
    {
        return (OtherAttribute.AttributeName == AttributeName);
    }

    inline bool operator!= (const FGAAttributeBase& OtherAttribute) const
    {
        return (OtherAttribute.AttributeName != AttributeName);
    }

    inline bool IsValid() const
    {
        return !AttributeName.IsNone();
    }
    friend uint32 GetTypeHash(const FGAAttributeBase& AttributeIn)
    {
        return AttributeIn.AttributeName.GetComparisonIndex();
    }
};

Nó vẫn chưa hoàn thành nhưng ý tưởng cơ bản, là cấu trúc này theo dõi trạng thái bên trong của nó. Các thuộc tính chỉ có thể được sửa đổi bởi Effects. Cố gắng sửa đổi chúng trực tiếp là không an toàn và không tiếp xúc với các nhà thiết kế. Tôi giả định rằng mọi thứ, có thể tương tác với các thuộc tính là Hiệu ứng. Bao gồm tiền thưởng bằng phẳng từ các mặt hàng. Khi vật phẩm mới được trang bị, hiệu ứng mới (cùng với tay cầm) được tạo ra và nó được thêm vào bản đồ chuyên dụng, xử lý phần thưởng thời lượng vô hạn (những thứ phải được người chơi xóa bằng tay). Khi Hiệu ứng mới được áp dụng, Xử lý mới cho nó được tạo ra (xử lý chỉ là int, được bao bọc bởi struct), và sau đó xử lý đó được truyền xung quanh như một phương tiện để tương tác với hiệu ứng này, cũng như theo dõi nếu hiệu ứng là vẫn hoạt động. Khi hiệu ứng bị xóa, xử lý của nó sẽ được phát cho tất cả các đối tượng quan tâm,

Phần quan trọng thực sự của điều này là TMap (TMap là bản đồ băm). FGAModifier có cấu trúc rất đơn giản:

struct FGAModifier
{
    EGAAttributeOp AttributeMod;
    float Value;
};

Nó chứa loại sửa đổi:

UENUM()
enum class EGAAttributeOp : uint8
{
    Add,
    Subtract,
    Multiply,
    Divide,
    Set,
    Precentage,

    Invalid
};

Và Giá trị là giá trị được tính toán cuối cùng mà chúng ta sẽ áp dụng cho thuộc tính.

Chúng tôi thêm hiệu ứng mới bằng cách sử dụng chức năng đơn giản và sau đó gọi:

void FGAAttributeBase::CalculateBonus()
{
    float AdditiveBonus = 0;
    auto ModIt = Modifiers.CreateConstIterator();
    for (ModIt; ModIt; ++ModIt)
    {
        switch (ModIt->Value.AttributeMod)
        {
        case EGAAttributeOp::Add:
            AdditiveBonus += ModIt->Value.Value;
                break;
            default:
                break;
        }
    }
    float OldBonus = BonusValue;
    //calculate final bonus from modifiers values.
    //we don't handle stacking here. It's checked and handled before effect is added.
    BonusValue = AdditiveBonus; 
    //this is absolute maximum (not clamped right now).
    float addValue = BonusValue - OldBonus;
    //reset to max = 200
    CurrentValue = CurrentValue + addValue;
}

Chức năng này được cho là để tính toán lại toàn bộ chồng tiền thưởng, mỗi lần hiệu ứng được thêm hoặc xóa. Chức năng vẫn chưa hoàn thành (như bạn có thể thấy), nhưng bạn có thể có được ý tưởng chung.

Nắm chặt lớn nhất của tôi bây giờ là xử lý thuộc tính Damaging / Healing (không liên quan đến việc tính toán lại toàn bộ stack), tôi nghĩ rằng tôi đã giải quyết được phần nào, nhưng nó vẫn yêu cầu thử nghiệm nhiều hơn để được 100%.

Trong mọi trường hợp, Atttribution được định nghĩa như thế này (+ Macro không thực, được bỏ qua ở đây):

FGAAttributeBase Health;
FGAAttributeBase Energy;

Vân vân.

Ngoài ra, tôi không chắc chắn 100% về việc xử lý thuộc tính CurrentValue, nhưng nó sẽ hoạt động. Họ là như vậy bây giờ.

Trong mọi trường hợp, tôi hy vọng nó sẽ cứu được một số người lưu trữ bộ đệm, không chắc đây là giải pháp tốt nhất hay thậm chí tốt, nhưng tôi thích nó hơn là theo dõi các hiệu ứng độc lập với các thuộc tính. Làm cho mỗi thuộc tính theo dõi trạng thái riêng của nó dễ dàng hơn nhiều trong trường hợp này và sẽ ít bị lỗi hơn. Về cơ bản chỉ có một điểm thất bại, đó là lớp khá ngắn và đơn giản.


Cảm ơn các liên kết và giải thích công việc của bạn! Tôi nghĩ rằng bạn đang tiến tới những gì tôi yêu cầu. Một vài điều cần chú ý là thứ tự các thao tác (ví dụ: 3 hiệu ứng "thêm" và 2 hiệu ứng "nhân lên" trên cùng một thuộc tính, điều này sẽ xảy ra trước?), Và đây hoàn toàn là hỗ trợ thuộc tính. Ngoài ra còn có khái niệm kích hoạt (như loại hiệu ứng "mất 1 AP khi trúng") để giải quyết, nhưng đó có thể là một cuộc điều tra riêng.
gkimsey

Thứ tự hoạt động, trong trường hợp chỉ cần tính toán tiền thưởng của thuộc tính là dễ thực hiện. Bạn có thể thấy ở đây tôi có và chuyển đổi. Để lặp lại trên tất cả các phần thưởng hiện tại (có thể được cộng, trừ, nhân, chia, v.v.), và sau đó chỉ cần tích lũy chúng. Bạn làm một cái gì đó như BonusValue = (BonusValue * MultiplyBonus + AddBonus-SubtractBonus) / DivideBonus, hoặc tuy nhiên bạn muốn xem phương trình này. Do một điểm vào duy nhất, nó dễ dàng thử nghiệm với nó. Về phần kích hoạt, tôi chưa viết về nó, bởi vì đó là vấn đề khác tôi suy ngẫm và tôi đã thử 3-4 (giới hạn)
Łukasz Baran

các giải pháp, không ai trong số họ làm việc theo cách tôi muốn (mục đích chính của tôi, là để chúng trở nên thân thiện với nhà thiết kế). Ý tưởng chung của tôi, là sử dụng Thẻ và kiểm tra, các hiệu ứng đến với các thẻ. Nếu thẻ khớp, hiệu ứng có thể kích hoạt hiệu ứng khác. (thẻ là tên dễ đọc của con người, như Damage.Fire, Attack.Physical, v.v.). Trên cốt lõi là khái niệm rất dễ dàng, vấn đề là tổ chức dữ liệu, để có thể dễ dàng truy cập (nhanh để tìm kiếm) và dễ dàng thêm các hiệu ứng mới. Bạn có thể kiểm tra mã tại đây github.com/iniside/ActionRPGGame (GameAttribut là mô-đun bạn sẽ quan tâm)
Łukasz Baran

2

Tôi đã làm việc trên một MMO nhỏ và tất cả các vật phẩm, sức mạnh, buff, v.v. đều có 'hiệu ứng'. Một hiệu ứng là một lớp có các biến cho 'AddDefense', 'InstantDamage', 'HealHP', v.v. Các quyền hạn, vật phẩm, v.v. sẽ xử lý thời lượng cho hiệu ứng đó.

Khi bạn truyền sức mạnh hoặc đặt lên một vật phẩm, nó sẽ áp dụng hiệu ứng cho nhân vật trong khoảng thời gian được chỉ định. Sau đó, các cuộc tấn công chính, vv sẽ tính đến các hiệu ứng được áp dụng.

Ví dụ, bạn có một buff thêm phòng thủ. Sẽ có tối thiểu một Hiệu ứng và Thời lượng cho buff đó. Khi truyền nó, nó sẽ áp dụng EffectID cho nhân vật trong khoảng thời gian được chỉ định.

Một ví dụ khác cho một mục, sẽ có cùng các trường. Nhưng thời lượng sẽ là vô hạn hoặc cho đến khi loại bỏ hiệu ứng bằng cách lấy vật phẩm ra khỏi nhân vật.

Phương pháp này cho phép bạn lặp lại danh sách các hiệu ứng hiện đang được áp dụng.

Hy vọng tôi giải thích phương pháp này rõ ràng đủ.


Theo tôi hiểu với kinh nghiệm tối thiểu của mình, đây là cách truyền thống để thực hiện các mod stat trong các game RPG. Nó hoạt động tốt và dễ hiểu và thực hiện. Nhược điểm là nó dường như không để lại cho tôi bất kỳ phòng nào để làm những việc như buff "gai", hoặc một cái gì đó cao cấp hơn hoặc tình huống. Về mặt lịch sử, nó cũng là nguyên nhân của một số khai thác trong game nhập vai, mặc dù chúng khá hiếm và vì tôi đang làm một trò chơi một người chơi, nếu ai đó tìm thấy một khai thác tôi thực sự không quan tâm. Cảm ơn các đầu vào.
gkimsey

2
  1. Nếu bạn là một người dùng thống nhất, đây là một cái gì đó để bắt đầu: http://www.stevegargolinski.com/armory-a-free-and-unfinished-stat-inventory-and-buffdebuff-framework-for-unity/

Tôi đang sử dụng ScriptableOjects dưới dạng buff / phép thuật / tài năng

public class Spell : ScriptableObject 
{
    public SpellType SpellType = SpellType.Ability;
    public SpellTargetType SpellTargetType = SpellTargetType.SingleTarget;
    public SpellCategory SpellCategory = SpellCategory.Ability;
    public MagicSchools MagicSchool = MagicSchools.Physical;
    public CharacterClass CharacterClass = CharacterClass.None;
    public string Description = "no description available";
    public SpellDragType DragType = SpellDragType.Active; 
    public bool Active = false;
    public int TargetCount = 1;
    public float CastTime = 0;
    public uint EffectRange = 3;
    public int RequiredLevel = 1;
    public virtual void OnGUI()
    {
    }
}

sử dụng UnityEngine; sử dụng System.Collections.Generic;

công khai enum BuffType {Buff, Debuff} [System.Serializable] lớp công khai BuffStat {công khai Stat Stat = Stat.Sturdy; công khai phao ModValueInPercent = 0,1f; }

public class Buff : Spell
{
    public BuffType BuffType = BuffType.Buff;
    public BuffStat[] ModStats;
    public bool PersistsThroughDeath = false;
    public int AmountPerTick = 3;
    public bool UseTickTimer = false;
    public float TickTime = 1.5f;
    [HideInInspector]
    public float Ticktimer = 0;
    public float Duration = 360; // in seconds
    public float ModifierPerStack = 1.1f;
    [HideInInspector]
    public float Timer = 0;
    public int Stack = 1;
    public int MaxStack = 1;
}

BuffModul:

using System;
using RPGCore;
using UnityEngine;

public class Buff_Modul : MonoBehaviour
{
    private Unit _unit;

    // Use this for initialization
    private void Awake()
    {
        _unit = GetComponent<Unit>();
    }

    #region BUFF MODUL

    public virtual void RUN_BUFF_MODUL()
    {
        try
        {
            foreach (var buff in _unit.Attr.Buffs)
            {
                CeckBuff(buff);
            }
        }
        catch(Exception e) {throw new Exception(e.ToString());}
    }

    #endregion BUFF MODUL

    public void ClearBuffs()
    {
        _unit.Attr.Buffs.Clear();
    }

    public void AddBuff(string buffName)
    {
        var buff = Instantiate(Resources.Load("Scriptable/Buff/" + buffName, typeof(Buff))) as Buff;
        if (buff == null) return;
        buff.name = buffName;
        buff.Timer = buff.Duration;
        _unit.Attr.Buffs.Add(buff);
        foreach (var buffStat in buff.ModStats)
        {
            switch (buff.BuffType)
            {
                case BuffType.Buff:
                    _unit.Attr.AddBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] + _unit.Attr.StatsItem[buffStat.Stat]) * buffStat.ModValueInPercent));
                    break;
                case BuffType.Debuff:
                    _unit.Attr.RemoveBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] /*+ unit.character.StatsItem[_stat.stat]*/) * buffStat.ModValueInPercent));
                    break;
            }
            Core.StatController(_unit.Attr, buffStat.Stat);
        }
    }

    public void RemoveBuff(Buff buff)
    {
        foreach (var buffStat in buff.ModStats)
        {
            switch (buff.BuffType)
            {
                case BuffType.Buff:
                    _unit.Attr.RemoveBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] + _unit.Attr.StatsItem[buffStat.Stat]) * buffStat.ModValueInPercent));
                    break;
                case BuffType.Debuff:
                    _unit.Attr.AddBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat]  /*+ unit.character.StatsItem[_stat.stat]*/) * buffStat.ModValueInPercent));
                    break;
            }
            Core.StatController(_unit.Attr, buffStat.Stat);
        }
        _unit.Attr.Buffs.Remove(buff);
    }

    void CeckBuff(Buff buff)
    {
        buff.Timer -= Time.deltaTime;
        if (!_unit.IsAlive && !buff.PersistsThroughDeath)
        {
            if (buff.ModStats != null)
                foreach (var stat in buff.ModStats)
                {
                    _unit.Attr.StatsBuff[stat.Stat] = 0;
                }

            RemoveBuff(buff);
        }
        if (_unit.IsAlive && buff.Timer <= 0)
        {
            RemoveBuff(buff);
        }
    }
}

0

Đây là một câu hỏi thực sự cho tôi. Tôi có một ý tưởng về nó.

  1. Như đã nói trước đây, chúng ta cần thực hiện một Buffdanh sách và trình cập nhật logic cho các buff.
  2. Sau đó, chúng ta cần thay đổi tất cả các cài đặt trình phát cụ thể mỗi khung trong các lớp con của Bufflớp.
  3. Sau đó chúng tôi sẽ nhận được các cài đặt trình phát hiện tại từ trường cài đặt có thể thay đổi.

class Player {
  settings: AllPlayerStats;

  private buffs: Array<Buff> = [];
  private baseSettings: AllPlayerStats;

  constructor(settings: AllPlayerStats) {
    this.baseSettings = settings;
    this.resetSettings();
  }

  addBuff(buff: Buff): void {
    this.buffs.push(buff);
    buff.start(this);
  }

  findBuff(predcate(buff: Buff) => boolean): Buff {...}

  removeBuff(buff: Buff): void {...}

  update(dt: number): void {
    this.resetSettings();
    this.buffs.forEach((item) => item.update(dt));
  }

  private resetSettings(): void {
    //some way to copy base to settings
    this.settings = this.baseSettings.copy();
  }
}

class Buff {
    private owner: Player;        

    start(owner: Player) { this.owner = owner; }

    update(dt: number): void {
      //here we change anything we want in subclasses like
      this.owner.settings.hp += 15;
      //if we need base value, just make owner.baseSettings public but don't change it! only read

      //also here logic for removal buff by time or something
    }
}

Theo cách này, có thể dễ dàng thêm các số liệu thống kê người chơi mới, không có thay đổi đối với logic của các Bufflớp con.


0

Tôi biết điều này khá cũ nhưng nó đã được liên kết trong một bài đăng mới hơn và tôi có một vài suy nghĩ về nó tôi muốn chia sẻ. Thật không may, hiện tại tôi không có ghi chú của mình, vì vậy tôi sẽ cố gắng đưa ra một cái nhìn tổng quan chung về những gì tôi đang nói và tôi sẽ chỉnh sửa chi tiết và một số mã ví dụ khi tôi có nó ở phía trước tôi.

Đầu tiên, tôi nghĩ rằng từ góc độ thiết kế, hầu hết mọi người đang bị cuốn vào những kiểu buff có thể được tạo ra và cách chúng được áp dụng và quên đi các nguyên tắc cơ bản của lập trình hướng đối tượng.

Ý tôi là sao Nó không thực sự quan trọng cho dù một cái gì đó là một buff hay debuff, cả hai đều là những sửa đổi chỉ ảnh hưởng đến một cái gì đó theo cách tích cực hoặc tiêu cực. Mã không quan tâm đó là cái nào. Đối với vấn đề đó, cuối cùng không có vấn đề gì nếu thêm một số liệu thống kê hoặc nhân chúng, đó chỉ là các toán tử khác nhau và một lần nữa mã không quan tâm đó là cái gì.

Vì vậy, tôi đang đi đâu với điều này? Việc thiết kế một lớp buff / debuff tốt (đọc: đơn giản, thanh lịch) không quá khó, điều khó khăn là thiết kế các hệ thống tính toán và duy trì trạng thái trò chơi.

Nếu tôi đang thiết kế một hệ thống buff / debuff thì đây là một số điều tôi sẽ xem xét:

  • Một lớp buff / debuff để thể hiện hiệu ứng của chính nó.
  • Một lớp kiểu buff / debuff để chứa thông tin về những gì buff ảnh hưởng và làm thế nào.
  • Tất cả các nhân vật, vật phẩm và có thể là Vị trí đều cần phải có một danh sách hoặc thuộc tính bộ sưu tập để chứa các buff và debuff.

Một số chi tiết cụ thể về loại buff / debuff nên chứa:

  • Ai / cái gì có thể được áp dụng cho, IE: người chơi, quái vật, địa điểm, vật phẩm, v.v.
  • Đó là loại hiệu ứng nào (tích cực, tiêu cực), cho dù đó là nhân hay phụ, và loại ảnh hưởng nào, IE: tấn công, phòng thủ, di chuyển, v.v.
  • Khi cần kiểm tra (chiến đấu, thời gian trong ngày, v.v.).
  • Cho dù nó có thể được gỡ bỏ, và nếu vậy làm thế nào nó có thể được gỡ bỏ.

Đó chỉ là một sự khởi đầu, nhưng từ đó bạn chỉ cần xác định những gì bạn muốn và hành động theo nó bằng trạng thái trò chơi bình thường của bạn. Ví dụ: giả sử bạn muốn tạo một mục bị nguyền rủa làm giảm tốc độ di chuyển ...

Miễn là tôi đã đặt các loại thích hợp vào vị trí, thật đơn giản để tạo một bản ghi buff có nội dung:

  • Loại: Lời nguyền
  • ObjectType: Mục
  • StatC Category: Tiện ích
  • StatAffected: MovementSpeed
  • Thời lượng: Vô hạn
  • Kích hoạt: OnEquip

Và cứ thế, và khi tôi tạo một buff, tôi chỉ định nó là BuffType of Curse và mọi thứ khác đều tùy thuộc vào công cụ ...

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.