วิธีการใช้ระบบ buff / debuff ที่ยืดหยุ่นคืออะไร


66

ข้อมูลทั่วไป:

เกมที่มีสถิติคล้ายกับเกม RPG อนุญาตให้ตัวละคร "ชื่นชอบ" ตั้งแต่ "จัดการความเสียหายพิเศษ 25%" ไปจนถึงสิ่งที่ซับซ้อนกว่าเช่น "จัดการ 15 ความเสียหายกลับไปยังผู้โจมตีเมื่อถูกโจมตี"

ข้อมูลเฉพาะของหนังแต่ละประเภทไม่เกี่ยวข้องกันจริงๆ ฉันกำลังมองหาวิธี (น่าจะเป็นวัตถุเชิง) ในการจัดการ buffs โดยพลการ

รายละเอียด:

ในกรณีเฉพาะของฉันฉันมีตัวละครหลายตัวในสภาพแวดล้อมการต่อสู้แบบเทิร์นเบสดังนั้นฉันจึงคาดว่าบัฟจะถูกผูกติดอยู่กับเหตุการณ์เช่น "OnTurnStart", "OnReceiveDamage" เป็นต้นบางทีแต่ละบัฟนั้นเป็นซับคลาสของคลาสนามธรรมหลักบัฟ เฉพาะเหตุการณ์ที่เกี่ยวข้องเท่านั้นที่มีมากเกินไป จากนั้นตัวละครแต่ละตัวอาจมีเวกเตอร์ของบัฟที่ใช้อยู่ในปัจจุบัน

วิธีนี้เหมาะสมหรือไม่ แน่นอนว่าฉันสามารถเห็นประเภทเหตุการณ์ได้หลายสิบประเภทที่จำเป็นมันทำให้รู้สึกว่าการสร้างคลาสย่อยใหม่สำหรับแต่ละ buff นั้นมากเกินไปและดูเหมือนจะไม่อนุญาตให้มีการโต้ตอบ "buff" ใด ๆ นั่นคือถ้าฉันต้องการที่จะติดตั้งฝาครอบเพื่อเพิ่มความเสียหายดังนั้นแม้ว่าคุณจะมี 10 บัฟที่แตกต่างกันซึ่งทั้งหมดให้ความเสียหายเพิ่ม 25% คุณจะเพิ่ม 100% แทนการเพิ่ม 250%

และมีสถานการณ์ที่ซับซ้อนกว่าที่ฉันสามารถควบคุมได้ ฉันแน่ใจว่าทุกคนจะได้เห็นตัวอย่างว่าคนที่มีความซับซ้อนมากขึ้นสามารถโต้ตอบซึ่งกันและกันได้อย่างไรในฐานะนักพัฒนาเกมที่ฉันไม่ต้องการ

ในฐานะโปรแกรมเมอร์ C ++ ที่ไม่มีประสบการณ์ (โดยทั่วไปฉันใช้ C ในระบบฝังตัว) ฉันรู้สึกว่าโซลูชันของฉันนั้นง่ายและอาจไม่ได้ใช้ประโยชน์จากภาษาเชิงวัตถุอย่างเต็มที่

คิด? มีใครที่นี่ออกแบบระบบหนังที่ค่อนข้างแข็งแกร่งมาก่อนหรือไม่

แก้ไข: เกี่ยวกับคำตอบ:

ฉันเลือกคำตอบเป็นหลักจากรายละเอียดที่ดีและคำตอบที่ชัดเจนสำหรับคำถามที่ฉันถาม แต่การอ่านคำตอบนั้นทำให้ฉันมีความเข้าใจลึกซึ้งมากขึ้น

อาจแปลกใจที่ระบบที่แตกต่างหรือระบบที่ปรับแต่งดูเหมือนว่าจะนำไปใช้ที่ดีขึ้นกับสถานการณ์บางอย่าง ระบบใดที่ทำงานได้ดีที่สุดสำหรับเกมของฉันจะขึ้นอยู่กับประเภทความแปรปรวนและจำนวนบัฟที่ฉันตั้งใจจะสมัคร

สำหรับเกมอย่าง Diablo 3 (ที่กล่าวถึงด้านล่าง) ซึ่งอุปกรณ์เกือบทุกชิ้นสามารถเปลี่ยนความแข็งแกร่งของบัฟได้บัฟเป็นเพียงระบบสถิติตัวละครดูเหมือนว่าเป็นความคิดที่ดีทุกครั้งที่ทำได้

สำหรับสถานการณ์ที่มีการเลี้ยวฉันอยู่ในแนวทางเหตุการณ์ที่อาจจะเหมาะสมกว่า

ไม่ว่าในกรณีใดฉันยังคงหวังว่าจะมีใครบางคนมาพร้อมกับสัญลักษณ์เวทย์มนตร์ "OO" ซึ่งจะทำให้ฉันสามารถใช้ระยะเคลื่อนที่ +2 ต่อการเทิร์นบัฟสร้างความเสียหาย 50% ของความเสียหายที่นำกลับไปที่บัฟของผู้โจมตีและteleport โดยอัตโนมัติเพื่อให้กระเบื้องอยู่บริเวณใกล้เคียงเมื่อถูกโจมตีจาก 3 หรือมากกว่ากระเบื้องห่างหนังในระบบเดียวโดยไม่ต้องเปลี่ยน5 ความแรงของหนังออกเป็นคลาสย่อยของตัวเอง

ฉันคิดว่าสิ่งที่ใกล้เคียงที่สุดคือคำตอบที่ฉันทำเครื่องหมายไว้ แต่พื้นยังเปิดอยู่ ขอบคุณทุกคนสำหรับอินพุต


ฉันไม่ได้โพสต์สิ่งนี้เป็นคำตอบเพราะฉันเป็นเพียงการระดมสมอง แต่มีรายการบัฟอยู่หรือเปล่า? แต่ละบัฟมีค่าคงที่และตัวดัดแปลงตัวคูณ ค่าคงที่คือ +10 ดาเมจ, ปัจจัยจะอยู่ที่ 1.10 สำหรับการเพิ่มดาเมจ + 10% ในการคำนวณความเสียหายของคุณคุณจะทำซ้ำบัฟทั้งหมดเพื่อรับตัวดัดแปลงทั้งหมดจากนั้นคุณจะมีข้อ จำกัด ที่คุณต้องการ คุณทำสิ่งนี้เพื่อแอตทริบิวต์ที่ปรับเปลี่ยนได้ทุกชนิด คุณจะต้องมีวิธีการกรณีพิเศษสำหรับสิ่งที่ซับซ้อนว่า
William Mariager

อนึ่งฉันได้ดำเนินการบางอย่างเช่นนั้นสำหรับวัตถุสถิติของฉันเมื่อฉันสร้างระบบสำหรับอุปกรณ์และอุปกรณ์ที่สวมใส่ได้ อย่างที่คุณพูดมันเป็นคำตอบที่ดีพอสำหรับ buffs ที่ปรับเปลี่ยนคุณลักษณะที่มีอยู่เท่านั้น แต่แน่นอนแล้วฉันจะต้องการให้ buffs บางตัวหมดอายุหลังจาก X turn ผู้อื่นจะหมดอายุเมื่อผลกระทบเกิดขึ้นเช่น Y เป็นต้นเป็นต้น พูดถึงสิ่งนี้ในคำถามหลักเพราะมันใช้เวลานานมากแล้ว
gkimsey

1
หากคุณมีวิธี "onReceiveDamage" ที่ถูกเรียกโดยระบบการส่งข้อความหรือด้วยตนเองหรือด้วยวิธีอื่นมันควรจะง่ายพอที่จะรวมการอ้างอิงถึงบุคคล / สิ่งที่คุณได้รับความเสียหาย ดังนั้นคุณสามารถทำให้ข้อมูลนี้พร้อมใช้งานสำหรับผู้ใช้ของคุณ

ใช่ฉันคาดหวังว่าเทมเพลตเหตุการณ์แต่ละรายการสำหรับคลาส Buff แบบนามธรรมจะมีพารามิเตอร์ที่เกี่ยวข้องเช่นนั้น มันจะใช้งานได้ แต่ฉันลังเลเพราะรู้สึกว่ามันจะไม่ขยาย ฉันมีช่วงเวลาที่ยากลำบากในการจินตนาการ MMORPG ที่มีบัฟที่แตกต่างกันหลายร้อยบัฟมีคลาสแยกต่างหากที่กำหนดไว้สำหรับการบัฟแต่ละครั้ง ไม่ใช่ว่าฉันกำลังทำสิ่งที่ชื่นชอบมากมาย (อาจใกล้ถึง 30) แต่ถ้ามีระบบที่เรียบง่ายสง่างามกว่าหรือยืดหยุ่นกว่าฉันต้องการใช้งาน ระบบมีความยืดหยุ่นมากขึ้น = ผู้ชื่นชอบ / ความสามารถที่น่าสนใจยิ่งขึ้น
gkimsey

4
นี่ไม่ใช่คำตอบที่ดีสำหรับปัญหาการโต้ตอบ แต่สำหรับฉันแล้วดูเหมือนว่ารูปแบบของมัณฑนากรใช้ได้ดีที่นี่ เพียงใช้บัฟ (มัณฑนากร) เพิ่มขึ้นด้านบนของกันและกัน อาจเป็นเพราะระบบจัดการกับการโต้ตอบด้วยการ "รวม" บัฟเข้าด้วยกัน (เช่น 10x 25% รวมกันเป็นหนึ่งบัฟ 100%)
ashes999

คำตอบ:


32

นี่เป็นปัญหาที่ซับซ้อนเพราะคุณกำลังพูดถึงสิ่งต่าง ๆ ที่ (วันนี้) รวมตัวกันเป็น 'ชื่นชอบ':

  • การดัดแปลงเป็นคุณลักษณะของผู้เล่น
  • เทคนิคพิเศษที่เกิดขึ้นในบางเหตุการณ์
  • การรวมกันของข้างต้น

ฉันมักจะใช้งานครั้งแรกกับรายการผลกระทบที่ใช้งานสำหรับตัวละครบางตัว การลบออกจากรายการไม่ว่าจะขึ้นอยู่กับระยะเวลาหรือว่าเป็นเรื่องเล็กน้อยดังนั้นฉันจะไม่พูดถึงเรื่องนี้ แต่ละเอฟเฟกต์มีรายการตัวดัดแปลงแอททริบิวต์และสามารถนำไปใช้กับค่าพื้นฐานผ่านการคูณอย่างง่าย

จากนั้นฉันก็ปิดมันด้วยฟังก์ชั่นเพื่อเข้าถึงคุณสมบัติที่แก้ไข เช่น.:

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

ที่ช่วยให้คุณใช้เอฟเฟ็กต์คูณได้ง่ายพอ หากคุณต้องการเอฟเฟกต์เพิ่มเติมให้ตัดสินใจว่าคุณจะใช้เอฟเฟ็กต์ในใบสั่งใด (อาจเป็นสารเติมแต่งล่าสุด) และเรียกใช้ผ่านรายการสองครั้ง (ฉันอาจมีรายการตัวแก้ไขแยกต่างหากใน Effect หนึ่งรายการสำหรับหลายรายการและอีกรายการหนึ่งสำหรับเพิ่มเติม)

ค่าเกณฑ์คือให้คุณใช้ "+ 20% เทียบกับ Undead" - ตั้งค่า UNDEAD บนเอฟเฟกต์และส่งผ่านค่า UNDEAD เฉพาะget_current_attribute_value()เมื่อคุณคำนวณความเสียหายที่เกิดกับศัตรูที่ไม่ตาย

อนึ่งฉันจะไม่ถูกล่อลวงให้ลองและเขียนระบบที่ใช้และไม่ใช้ค่าโดยตรงกับค่าคุณลักษณะพื้นฐาน - ผลลัพธ์สุดท้ายคือคุณลักษณะของคุณมีแนวโน้มที่จะคลาดเคลื่อนจากค่าที่คาดไว้เนื่องจากข้อผิดพลาด (เช่นหากคุณคูณบางอย่างด้วย 2 แต่แล้วปิดมันเมื่อคุณหารด้วย 2 อีกครั้งมันจะต่ำกว่าที่มันเริ่มต้นด้วย)

สำหรับเอฟเฟกต์ตามเหตุการณ์เช่น "จัดการ 15 ดาเมจกลับไปยังผู้โจมตีเมื่อถูกโจมตี" คุณสามารถเพิ่มวิธีการในคลาสเอฟเฟกต์ แต่ถ้าคุณต้องการพฤติกรรมที่แตกต่างและโดยพลการ (เช่นผลกระทบบางอย่างสำหรับเหตุการณ์ข้างต้นอาจสะท้อนถึงความเสียหายกลับมาบางอย่างอาจรักษาคุณได้ คุณสามารถกำหนดฟังก์ชั่นให้กับตัวจัดการเหตุการณ์บนเอฟเฟกต์จากนั้นคุณสามารถเรียกตัวจัดการเหตุการณ์บนเอฟเฟกต์ที่ใช้งานอยู่

# 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)

เห็นได้ชัดว่าคลาสเอฟเฟกต์ของคุณจะมีตัวจัดการเหตุการณ์สำหรับเหตุการณ์ทุกประเภทและคุณสามารถกำหนดฟังก์ชั่นจัดการให้มากที่สุดเท่าที่คุณต้องการในแต่ละกรณี คุณไม่จำเป็นต้องใช้เอฟเฟกต์คลาสย่อยเนื่องจากแต่ละอันจะถูกกำหนดโดยองค์ประกอบของตัวดัดแปลงแอททริบิวต์และตัวจัดการเหตุการณ์ที่มี (มันอาจจะมีชื่อระยะเวลา ฯลฯ )


2
+1 สำหรับรายละเอียดที่ดีเยี่ยม นี่คือคำตอบที่ใกล้เคียงที่สุดกับการตอบคำถามของฉันอย่างเป็นทางการตามที่ฉันเห็น การตั้งค่าพื้นฐานที่นี่ดูเหมือนจะช่วยให้มีความยืดหยุ่นมากและสิ่งที่เป็นนามธรรมของเกมยุ่งอาจเป็นตรรกะ อย่างที่คุณพูดเอฟเฟ็กต์ขี้ขลาดมากขึ้นจะยังคงต้องการคลาสของตัวเอง แต่สิ่งนี้จัดการกับความต้องการของระบบ "buff" แบบทั่วไปจำนวนมากฉันคิดว่า
gkimsey

+1 สำหรับการชี้ให้เห็นถึงความแตกต่างทางแนวคิดที่ซ่อนอยู่ที่นี่ ไม่ใช่ว่าทุกคนจะทำงานร่วมกับลอจิกการอัพเดทตามเหตุการณ์เดียวกัน ดูคำตอบของ @ Ross สำหรับแอปพลิเคชันอื่นโดยสิ้นเชิง ทั้งสองจะต้องมีอยู่ติดกัน
ctietze

22

ในเกมที่ฉันทำงานกับเพื่อนในชั้นเรียนเราได้สร้างระบบบัฟ / ดีบัฟเมื่อผู้ใช้ติดกับหญ้าสูงและกระเบื้องเร่งความเร็วและสิ่งที่ไม่และสิ่งเล็กน้อยเช่นเลือดและสารพิษ

ความคิดนั้นเรียบง่ายและในขณะที่เรานำไปใช้ใน Python มันค่อนข้างมีประสิทธิภาพ

โดยทั่วไปนี่คือวิธีที่มัน:

  • ผู้ใช้มีรายการบัฟและดีบัฟที่ใช้อยู่ในปัจจุบัน (โปรดทราบว่าบัฟและดีบัฟนั้นค่อนข้างเหมือนกันมันเป็นเพียงเอฟเฟกต์ที่มีผลต่างกัน)
  • ผู้คลั่งไคล้คุณลักษณะต่าง ๆ เช่นระยะเวลาชื่อและข้อความสำหรับการแสดงข้อมูลและเวลามีอยู่ สิ่งสำคัญคือเวลาที่มีชีวิตระยะเวลาและการอ้างอิงถึงนักแสดงหนังนี้นำไปใช้กับ
  • สำหรับบัฟเมื่อติดเข้ากับเครื่องเล่นผ่านทาง player.apply (buff / debuff) มันจะเรียกเมธอด start () วิธีนี้จะนำการเปลี่ยนแปลงที่สำคัญไปใช้กับผู้เล่นเช่นการเพิ่มความเร็วหรือชะลอความเร็วลง
  • จากนั้นเราจะวนซ้ำแต่ละบัฟในลูปการอัปเดตและบัฟจะอัปเดตซึ่งจะเพิ่มเวลาให้กับชีวิต คลาสย่อยจะใช้สิ่งต่าง ๆ เช่นวางยาพิษของผู้เล่นให้ผู้เล่น HP เมื่อเวลาผ่านไป ฯลฯ
  • เมื่อบัฟเสร็จสิ้นแล้วหมายถึง timeAlive> = ระยะเวลาตรรกะการอัปเดตจะลบบัฟและเรียกวิธีเสร็จสิ้น () ซึ่งจะแตกต่างจากการลบข้อ จำกัด ความเร็วบนเครื่องเล่นเพื่อทำให้เกิดรัศมีขนาดเล็ก (คิดว่าเป็นลูกระเบิด) หลังจุด)

ตอนนี้วิธีการปรับใช้ buffs จากโลกจริงเป็นเรื่องที่แตกต่าง นี่คืออาหารของฉันสำหรับความคิด


1
ดูเหมือนว่าคำอธิบายที่ดีกว่าของสิ่งที่ฉันพยายามอธิบายข้างต้น มันค่อนข้างง่ายเข้าใจง่ายอย่างแน่นอน คุณกล่าวถึง "เหตุการณ์" สามอย่างที่นั่น (OnApply, OnTimeTick, OnExpired) เพื่อเชื่อมโยงกับความคิดของฉัน ตามที่เป็นอยู่มันจะไม่สนับสนุนสิ่งต่าง ๆ เช่นการคืนค่าความเสียหายเมื่อถูกโจมตีและอื่น ๆ ฉันไม่ต้องการ จำกัด สิ่งที่บัฟของฉันสามารถทำได้ (ซึ่ง = จำกัด จำนวนของเหตุการณ์ที่ฉันเกิดขึ้นซึ่งต้องถูกเรียกโดยตรรกะของเกมหลัก) แต่ความสามารถในการปรับขนาดของบัฟอาจมีความสำคัญมากกว่า ขอบคุณสำหรับข้อมูลของคุณ!
gkimsey

ใช่เราไม่ได้ทำอะไรแบบนั้น มันฟังดูเนี้ยบจริงๆและเป็นคอนเซ็ปต์ที่ยอดเยี่ยม
Ross

@gkimsey สำหรับสิ่งต่าง ๆ เช่น Thorns และ buffs แฝงอื่น ๆ ฉันจะใช้ตรรกะในคลาส Mob ของคุณเป็นสถิติแบบพาสซีฟที่คล้ายกับความเสียหายหรือสุขภาพและเพิ่มสถิตินี้เมื่อใช้บัฟ สิ่งนี้จะช่วยลดความยุ่งยากได้มากเมื่อคุณมีหนามหลายหนามรวมทั้งรักษาอินเทอร์เฟซให้สะอาด (10 บัฟจะแสดง 1 ดาเมจที่เสียหายมากกว่า 10) และช่วยให้ระบบบัฟยังคงเรียบง่าย
3Doubloons

นี่เป็นวิธีการที่เรียบง่ายแทบจะไม่ตอบโต้ แต่ฉันเริ่มคิดถึงตัวเองเมื่อเล่น Diablo 3 ฉันสังเกตเห็นว่าชีวิตขโมยชีวิตที่ถูกโจมตีความเสียหายจากการโจมตีระยะประชิด ฯลฯ ล้วนเป็นสถิติของตัวเองในหน้าต่างตัวละคร จริงอยู่ที่ D3 ไม่มีระบบการขัดหรือการโต้ตอบที่ซับซ้อนที่สุดในโลก แต่มันก็ไม่สำคัญ มันสมเหตุสมผลมาก ถึงกระนั้นก็มี 15 บัฟที่แตกต่างกันซึ่งมีเอฟเฟกต์ 12 อย่างที่จะตกอยู่ในนี้ ดูเหมือนว่า
แพ็ด

11

ฉันไม่แน่ใจว่าคุณกำลังอ่านสิ่งนี้อยู่หรือไม่ แต่ฉันได้ต่อสู้กับปัญหาแบบนี้มาเป็นเวลานาน

ฉันได้ออกแบบระบบที่มีผลกระทบหลายประเภท ฉันจะข้ามพวกเขาไปชั่วครู่ ทั้งหมดนี้ขึ้นอยู่กับประสบการณ์ของฉัน ฉันไม่ได้อ้างว่ารู้คำตอบทั้งหมด


ตัวดัดแปลงแบบคงที่

ระบบประเภทนี้ส่วนใหญ่อาศัยจำนวนเต็มอย่างง่าย ๆ เพื่อตรวจสอบการดัดแปลงใด ๆ ตัวอย่างเช่น +100 ถึง Max HP, +10 เพื่อโจมตีเป็นต้น ระบบนี้ยังสามารถจัดการร้อยละเช่นกัน คุณเพียงแค่ต้องแน่ใจว่าการซ้อนกันไม่ได้อยู่เหนือการควบคุม

ฉันไม่เคยแคชค่าที่สร้างขึ้นสำหรับระบบประเภทนี้จริงๆ ตัวอย่างเช่นถ้าฉันต้องการแสดงสุขภาพสูงสุดของบางสิ่งบางอย่างฉันจะสร้างมูลค่าทันที สิ่งนี้ป้องกันไม่ให้เกิดข้อผิดพลาดได้ง่ายและเข้าใจง่ายสำหรับทุกคนที่เกี่ยวข้อง

(ฉันทำงานใน Java ดังนั้นสิ่งที่ตามมาคือ Java แต่ควรทำงานกับการแก้ไขบางอย่างสำหรับภาษาอื่น ๆ ) ระบบนี้สามารถทำได้อย่างง่ายดายโดยใช้ enums สำหรับประเภทการดัดแปลงแล้วจำนวนเต็ม ผลลัพธ์สุดท้ายสามารถวางลงในคอลเลกชันบางประเภทที่มีคู่ของคีย์มูลค่าสั่งซื้อ นี่จะเป็นการค้นหาและการคำนวณที่รวดเร็วดังนั้นประสิทธิภาพจึงดีมาก

โดยรวมแล้วมันใช้งานได้ดีมากกับตัวดัดแปลงแบบคงที่ แม้ว่าโค้ดจะต้องมีอยู่ในสถานที่ที่เหมาะสมสำหรับการใช้งานตัวดัดแปลง: getAttack, getMaxHP, getMeleeDamage และอื่น ๆ

ที่วิธีนี้ล้มเหลว (สำหรับฉัน) มีปฏิสัมพันธ์ที่ซับซ้อนมากระหว่างชื่นชอบ ไม่มีวิธีง่าย ๆ ที่แท้จริงในการโต้ตอบยกเว้นการสลัมเล็กน้อย มันมีความเป็นไปได้ในการโต้ตอบง่ายๆ ในการดำเนินการดังกล่าวคุณต้องทำการปรับเปลี่ยนวิธีการจัดเก็บตัวดัดแปลงแบบคงที่ แทนที่จะใช้ enum เป็นคีย์คุณจะใช้ String สตริงนี้จะเป็นชื่อ Enum + ตัวแปรพิเศษ 9 ครั้งจาก 10 ไม่ใช้ตัวแปรพิเศษดังนั้นคุณยังคงชื่อ enum ไว้เป็นกุญแจ

ลองทำตัวอย่างด่วน: ถ้าคุณต้องการที่จะสามารถแก้ไขความเสียหายต่อสิ่งมีชีวิตที่ไม่ตายคุณสามารถมีคู่ที่ได้รับคำสั่งดังนี้: (DAMAGE_Undead, 10) ความเสียหายคือ Enum และ Undead เป็นตัวแปรพิเศษ ดังนั้นระหว่างการต่อสู้คุณสามารถทำสิ่งต่อไปนี้:

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

อย่างไรก็ตามมันใช้งานได้ดีและรวดเร็ว แต่มันล้มเหลวในการโต้ตอบที่ซับซ้อนและมีรหัส "พิเศษ" ทุกที่ ตัวอย่างเช่นพิจารณาสถานการณ์ของ“ โอกาส 25% ที่จะส่งผ่านทางไกลต่อความตาย” นี่คือคอมเพล็กซ์ที่“ ยุติธรรม” ระบบดังกล่าวสามารถจัดการกับมันได้ แต่ไม่ง่ายอย่างที่คุณต้องการ:

  1. ตรวจสอบว่าผู้เล่นมี mod นี้หรือไม่
  2. มีบางรหัสที่จะดำเนินการ teleportation หากประสบความสำเร็จ ตำแหน่งของรหัสนี้เป็นการสนทนาในตัวมันเอง!
  3. รับข้อมูลที่ถูกต้องจากแผนที่ Mod คุณค่าหมายถึงอะไร? เป็นห้องที่พวกเขาส่งผ่านทางไกลด้วยหรือไม่ จะเป็นอย่างไรถ้าผู้เล่นมีสองเทเลพอร์ตพอร์ท? จำนวนเงินจะไม่ถูกรวมเข้าด้วยกัน ?????? ความล้มเหลว!

ดังนั้นสิ่งนี้จะพาฉันไปที่หน้าถัดไปของฉัน:


ระบบ Buff ที่ซับซ้อนที่สุด

ฉันเคยพยายามเขียน 2D MMORPG ด้วยตัวเอง นี่เป็นความผิดพลาดอันยิ่งใหญ่ แต่ฉันได้เรียนรู้มากมาย!

ฉันเขียนผลของระบบอีก 3 ครั้ง คนแรกใช้การเปลี่ยนแปลงที่มีประสิทธิภาพน้อยกว่าของข้างต้น อย่างที่สองคือสิ่งที่ฉันจะพูดคุยเกี่ยวกับ

ระบบนี้มีชุดคลาสสำหรับการแก้ไขแต่ละครั้งดังนั้นสิ่งต่าง ๆ เช่น: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent ฉันมีพวกหนึ่งล้านคน - แม้แต่อย่าง TeleportOnDeath

ชั้นเรียนของฉันมีสิ่งต่าง ๆ ที่จะทำดังต่อไปนี้:

  • applyAffect
  • removeAffect
  • checkForInteraction <--- สำคัญ

นำไปใช้และลบอธิบายตนเอง (แม้ว่าจะเป็นสิ่งต่าง ๆ เช่นเปอร์เซ็นผลกระทบจะติดตามว่า HP เพิ่มขึ้นเท่าใดเพื่อให้แน่ใจว่าเมื่อเอฟเฟกต์หายไปมันจะลบยอดเงินที่เพิ่มเข้าไปเท่านั้นนี่คือ buggy, lol และ ฉันใช้เวลานานมากเพื่อให้แน่ใจว่าถูกต้องฉันยังไม่ได้รับความรู้สึกที่ดีเกี่ยวกับมัน)

วิธีการ checkForInteraction นั้นเป็นโค้ดที่ซับซ้อนอย่างน่ากลัว ในแต่ละคลาสที่ได้รับผลกระทบ (เช่น: ChangeHP) มันจะมีรหัสเพื่อตรวจสอบว่าสิ่งนี้ควรได้รับการแก้ไขโดยอินพุตที่ส่งผลกระทบหรือไม่ ตัวอย่างเช่นถ้าคุณมีบางอย่างเช่น ....

  • บัฟ 1: สร้างความเสียหาย 10 ไฟกับการโจมตี
  • บัฟ 2: เพิ่มความเสียหายทางไฟทั้งหมด 25%
  • บัฟ 3: เพิ่มความเสียหายจากไฟทั้งหมด 15

เมธอด checkForInteraction จะจัดการกับปัญหาเหล่านี้ทั้งหมด เพื่อที่จะทำสิ่งนี้จะมีการตรวจสอบผู้เล่นทุกคนที่อยู่ใกล้ ๆ ด้วย !! นี่เป็นเพราะประเภทของผลกระทบที่ฉันจัดการกับผู้เล่นหลายคนในช่วงระยะเวลาหนึ่งของพื้นที่ นี่หมายความว่ารหัสไม่เคยมีคำแถลงพิเศษเหมือนด้านบน -“ ถ้าเราเพิ่งตายเราควรตรวจสอบ teleport on death” ระบบนี้จะจัดการอย่างถูกต้องโดยอัตโนมัติในเวลาที่เหมาะสม

พยายามที่จะเขียนระบบนี้ฉันใช้เวลา 2 เดือนและทำโดยหัวระเบิดหลายครั้ง อย่างไรก็ตามมันมีพลังจริงๆและสามารถทำสิ่งต่าง ๆ ที่บ้าคลั่งได้โดยเฉพาะอย่างยิ่งเมื่อคุณคำนึงถึงข้อเท็จจริงสองประการต่อไปนี้สำหรับความสามารถในเกมของฉัน: 1. พวกเขามีช่วงเป้าหมาย (เช่น: เดี่ยวตัวเองกลุ่มเดียวเท่านั้น เป้าหมาย PB AE เป้าหมาย AE เป้าหมายและอื่น ๆ ) 2. ความสามารถอาจมีผลกระทบมากกว่า 1 รายการ

ดังที่ฉันได้กล่าวไว้ข้างต้นนี่เป็นระบบลำดับที่ 2 ใน 3 ของเกม ทำไมฉันถึงย้ายออกไปจากนี้

ระบบนี้มีประสิทธิภาพที่แย่ที่สุดที่ฉันเคยเห็น! มันช้ามากเพราะมันต้องทำการตรวจสอบมากสำหรับแต่ละสิ่งที่เกิดขึ้น ฉันพยายามปรับปรุง แต่ก็ถือว่ามันล้มเหลว

ดังนั้นเรามาถึงรุ่นที่สามของฉัน (และระบบบัฟประเภทอื่น):


คลาสที่มีผลกระทบซับซ้อนกับตัวจัดการ

ดังนั้นนี่เป็นการรวมกันของสองอย่างแรก: เราสามารถมีตัวแปรแบบสแตติกในคลาส Affect ที่มีฟังก์ชันการทำงานจำนวนมากและข้อมูลเพิ่มเติม จากนั้นเพียงแค่เรียกตัวจัดการ (สำหรับฉันวิธีการยูทิลิตี้คงที่บางอย่างแทน subclasses สำหรับการกระทำที่เฉพาะเจาะจง แต่ฉันแน่ใจว่าคุณสามารถไปกับ subclasses สำหรับการกระทำหากคุณต้องการด้วย) เมื่อเราต้องการทำอะไร

คลาส Affect จะมีสิ่งของที่ฉ่ำทั้งหมดเช่นชนิดเป้าหมายระยะเวลาจำนวนการใช้โอกาสที่จะดำเนินการและอื่น ๆ

เรายังคงต้องเพิ่มรหัสพิเศษเพื่อจัดการกับสถานการณ์เช่นเทเลพอร์ตบนความตาย เรายังคงต้องตรวจสอบสิ่งนี้ในรหัสการต่อสู้ด้วยตนเองและถ้ามันมีอยู่เราจะได้รับรายการผลกระทบ รายการผลกระทบนี้มีผลกระทบทั้งหมดที่มีผลกับผู้เล่นที่จัดการกับการเคลื่อนย้ายด้วยความตาย จากนั้นเราก็จะดูแต่ละคนและตรวจสอบเพื่อดูว่ามันดำเนินการและประสบความสำเร็จ (เราจะหยุดที่ประสบความสำเร็จครั้งแรก) มันประสบความสำเร็จเราแค่เรียกผู้ดูแลจัดการเรื่องนี้

การโต้ตอบสามารถทำได้ถ้าคุณต้องการเช่นกัน มันแค่ต้องเขียนโค้ดเพื่อค้นหา buffs เฉพาะของผู้เล่น / etc เนื่องจากมีประสิทธิภาพที่ดี (ดูด้านล่าง) จึงควรมีประสิทธิภาพพอสมควร มันแค่ต้องการตัวจัดการที่ซับซ้อนมากขึ้นเรื่อย ๆ

ดังนั้นมันจึงมีประสิทธิภาพของระบบแรกและยังคงมีความซับซ้อนมากมายเช่นระบบที่สอง (แต่ไม่มากเท่า) อย่างน้อยใน Java คุณสามารถทำสิ่งที่ยุ่งยากเพื่อให้ได้ประสิทธิภาพเกือบเป็นกรณีแรกในกรณีส่วนใหญ่ (เช่น: มีแผนที่ enum ( http://docs.oracle.com/javase/6/docs/api/java) /util/EnumMap.html ) ที่มี Enums เป็นคีย์และ ArrayList ที่มีผลกระทบกับค่าซึ่งจะช่วยให้คุณดูว่าคุณมีผลกระทบอย่างรวดเร็วหรือไม่ [เนื่องจากรายการจะเป็น 0 หรือแผนที่จะไม่มี enum] และไม่มี เพื่อย้ำเหนือรายการผลกระทบของผู้เล่นอย่างต่อเนื่องโดยไม่มีเหตุผลฉันไม่คิดว่าจะทำซ้ำถ้าหากเราต้องการพวกเขาในขณะนี้

ขณะนี้ฉันกำลังเปิดใหม่ (เขียนเกมใหม่ใน Java แทนที่จะเป็นรหัสฐาน FastROM เดิม) MUD ของฉันที่สิ้นสุดในปี 2548 และฉันเพิ่งพบว่าฉันต้องการนำระบบบัฟมาใช้อย่างไร ฉันกำลังจะใช้ระบบนี้เพราะมันทำงานได้ดีในเกมที่ล้มเหลวก่อนหน้าของฉัน

หวังว่าบางคนจะพบว่าข้อมูลเชิงลึกเหล่านี้มีประโยชน์บ้าง


6

คลาสที่แตกต่างกัน (หรือฟังก์ชั่นที่สามารถกำหนดแอดเดรสได้) สำหรับแต่ละ buff จะไม่เกินค่าหากพฤติกรรมของ buffs เหล่านั้นแตกต่างจากกัน สิ่งหนึ่งที่จะมี + 10% หรือ + 20% บัฟ (แน่นอนว่าจะแสดงได้ดีกว่าเป็นวัตถุสองรายการในคลาสเดียวกัน) ส่วนอื่น ๆ จะใช้เอฟเฟ็กต์ที่แตกต่างกันอย่างดุเดือดซึ่งต้องใช้รหัสที่กำหนดเองอยู่ดี อย่างไรก็ตามฉันเชื่อว่ามันจะดีกว่าถ้ามีวิธีมาตรฐานในการปรับแต่งตรรกะของเกมแทนที่จะปล่อยให้ buff แต่ละตัวทำสิ่งที่มันพอใจ (และเป็นไปได้ที่จะรบกวนซึ่งกันและกันในรูปแบบที่ไม่คาดฝันรบกวนสมดุลของเกม)

ฉันขอแนะนำให้แบ่งแต่ละ "รอบการโจมตี" ออกเป็นขั้นตอนโดยที่แต่ละขั้นตอนมีค่าฐานรายการสั่งการแก้ไขที่สามารถนำไปใช้กับค่านั้น (อาจต่อยอด) และขีด จำกัด ขั้นสุดท้าย การปรับเปลี่ยนแต่ละครั้งจะมีการแปลงข้อมูลประจำตัวเป็นค่าเริ่มต้นและสามารถได้รับอิทธิพลจากศูนย์หรือมากกว่าบัฟ / debuff ข้อมูลเฉพาะของการดัดแปลงแต่ละอย่างจะขึ้นอยู่กับขั้นตอนที่ใช้ วิธีการใช้งานรอบขึ้นอยู่กับคุณ (รวมถึงตัวเลือกของสถาปัตยกรรมที่ขับเคลื่อนด้วยเหตุการณ์ตามที่คุณพูดคุยกัน)

ตัวอย่างหนึ่งของวงจรการโจมตีคือ:

  • คำนวณการโจมตีผู้เล่น (ฐาน + mods);
  • คำนวณการป้องกันฝ่ายตรงข้าม (ฐาน + mods);
  • ทำความแตกต่าง (และใช้ mods) และตัดสินความเสียหายพื้นฐาน;
  • คำนวณผลกระทบ Parry / ชุดเกราะใด ๆ (แก้ไขความเสียหายฐาน) และใช้ความเสียหาย;
  • คำนวณผลสะท้อนกลับ (mods ต่อความเสียหายพื้นฐาน) และนำไปใช้กับผู้โจมตี

สิ่งสำคัญที่ควรทราบคือการนำบัฟก่อนหน้านี้มาใช้จะมีผลมากขึ้นเท่านั้น ดังนั้นหากคุณต้องการการต่อสู้แบบ "ยุทธวิธี" (ที่ทักษะผู้เล่นมีความสำคัญมากกว่าระดับตัวละคร) จะสร้างบัฟ / ดีบัฟมากมายในสถิติพื้นฐาน หากคุณต้องการการต่อสู้ที่ "สมดุล" มากขึ้น (ซึ่งระดับมีความสำคัญมากกว่า - สำคัญใน MMOGs เพื่อ จำกัด อัตราความคืบหน้า) ใช้บัฟ / ดีบัฟในรอบต่อไปเท่านั้น

ความแตกต่างระหว่าง "การปรับเปลี่ยน" และ "ผู้ชื่นชอบ" ที่ฉันกล่าวถึงก่อนหน้านี้มีวัตถุประสงค์: การตัดสินใจเกี่ยวกับกฎและความสมดุลสามารถนำมาใช้กับอดีตดังนั้นการเปลี่ยนแปลงใด ๆ ที่ไม่จำเป็นต้องสะท้อนให้เห็นถึงการเปลี่ยนแปลงในทุกระดับของหลัง OTOH ตัวเลขและชนิดของบัฟนั้นถูก จำกัด ด้วยจินตนาการของคุณเท่านั้นเพราะแต่ละคนสามารถแสดงพฤติกรรมที่ต้องการได้โดยไม่ต้องคำนึงถึงความเป็นไปได้ที่จะมีปฏิสัมพันธ์ระหว่างพวกเขากับคนอื่น ๆ (หรือแม้แต่การดำรงอยู่ของผู้อื่น)

ดังนั้นการตอบคำถาม: อย่าสร้างคลาสสำหรับ Buff แต่ละอัน แต่อย่างใดอย่างหนึ่งสำหรับแต่ละ Modification (และประเภท) การปรับและผูก Modification เข้ากับวงจรการโจมตีไม่ใช่ตัวละคร buffs สามารถเป็นรายการของ tuples (Modification, key, value) และคุณสามารถนำ buff ไปใช้กับตัวละครได้โดยเพียงแค่เพิ่ม / ลบมันเข้าไปในชุดของ buffs ของตัวละคร สิ่งนี้จะลดหน้าต่างเพื่อหาข้อผิดพลาดเนื่องจากสถิติของตัวละครไม่จำเป็นต้องเปลี่ยนเลยเมื่อมีการใช้บัฟ (ดังนั้นจึงมีความเสี่ยงน้อยกว่าในการกู้คืนสถิติให้เป็นค่าที่ไม่ถูกต้องหลังจากบัฟหมดอายุ)


นี่เป็นวิธีการที่น่าสนใจเพราะอยู่ระหว่างการใช้งานสองอย่างที่ฉันได้พิจารณานั่นคือการ จำกัด บัฟให้กับสถิติที่ค่อนข้างง่ายและการปรับเปลี่ยนความเสียหายที่เกิดขึ้นเป็นผลหรือการสร้างระบบที่แข็งแกร่ง แต่ค่าใช้จ่ายสูง นี่คือการขยายตัวของอดีตเพื่อให้ "หนาม" ในขณะที่ยังคงอินเตอร์เฟซที่เรียบง่าย ขณะที่ฉันไม่คิดว่ามันเป็น bullet มายากลสำหรับสิ่งที่ฉันต้องการก็แน่นอนดูเหมือนว่ามันจะทำให้ความสมดุลมากง่ายกว่าวิธีการอื่น ๆ ดังนั้นมันอาจจะเป็นวิธีที่จะไป ขอบคุณสำหรับข้อมูลของคุณ!
gkimsey

3

ฉันไม่รู้ว่าคุณยังอ่านอยู่หรือไม่ แต่นี่คือวิธีที่ฉันทำตอนนี้ (โค้ดอ้างอิงจาก UE4 และ C ++) หลังจากไตร่ตรองปัญหานี้มานานกว่าสองสัปดาห์ (!!) ในที่สุดฉันก็พบสิ่งนี้:

http://gamedevelopment.tutsplus.com/tutorials/using-the-composite-design-pattern-for-an-rpg-attributes-system--gamedev-243

และฉันคิดว่าการห่อหุ้มคุณสมบัติเดี่ยวภายในคลาส / โครงสร้างนั้นไม่ใช่ความคิดที่เลวร้ายเลย โปรดจำไว้ว่าฉันได้รับประโยชน์อย่างมากจากการสร้าง UE4 ในระบบการสะท้อนรหัสดังนั้นหากไม่มีการทำใหม่บางอย่างอาจไม่เหมาะกับทุกที่

ต่อไปฉันเริ่มต้นจากคุณลักษณะการห่อเป็นโครงสร้างเดียว:

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();
    }
};

มันยังไม่เสร็จ แต่เป็นแนวคิดพื้นฐานนั่นคือโครงสร้างนี้ติดตามสถานะภายใน คุณสมบัติสามารถแก้ไขได้โดยผลกระทบเท่านั้น การพยายามปรับเปลี่ยนโดยตรงไม่ปลอดภัยและไม่เปิดเผยกับนักออกแบบ ฉันสมมติว่าทุกอย่างที่สามารถโต้ตอบกับคุณลักษณะได้คือเอฟเฟกต์ รวมถึงโบนัสแบนจากรายการ เมื่อมีการติดตั้งไอเท็มใหม่เอฟเฟกต์ใหม่ (พร้อมที่จับ) จะถูกสร้างขึ้นและจะถูกเพิ่มลงในแผนที่เฉพาะซึ่งจัดการโบนัสระยะเวลาไม่ จำกัด เมื่อเอฟเฟกต์ใหม่ถูกนำไปใช้จะมีการสร้าง Handle ใหม่ขึ้นมา (handle เป็นเพียง int, ห่อด้วย struct) จากนั้นหมายเลขอ้างอิงนั้นจะถูกส่งไปทั่วเพื่อเป็นวิธีในการโต้ตอบกับเอฟเฟกต์นี้ ยังคงใช้งานอยู่ เมื่อเอฟเฟกต์ถูกลบออกหมายเลขอ้างอิงจะถูกกระจายไปยังวัตถุที่สนใจทั้งหมด

ส่วนสำคัญที่แท้จริงของสิ่งนี้คือ TMap (TMap คือแผนที่ที่แฮช) FGAModifier เป็นโครงสร้างที่ง่ายมาก:

struct FGAModifier
{
    EGAAttributeOp AttributeMod;
    float Value;
};

มันมีประเภทของการปรับเปลี่ยน:

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

    Invalid
};

และมูลค่าซึ่งเป็นมูลค่าการคำนวณขั้นสุดท้ายเราจะนำไปใช้กับแอตทริบิวต์

เราเพิ่มเอฟเฟกต์ใหม่โดยใช้ฟังก์ชั่นเรียบง่ายแล้วโทร:

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;
}

ฟังก์ชั่นนี้ควรจะคำนวณโบนัสทั้งกองใหม่ทุกครั้งที่มีการเพิ่มหรือลบเอฟเฟกต์ ฟังก์ชั่นยังไม่เสร็จสมบูรณ์ (ตามที่คุณเห็น) แต่คุณสามารถรับแนวคิดทั่วไปได้

จับที่ใหญ่ที่สุดของฉันตอนนี้คือการจัดการแอตทริบิวต์ความเสียหาย / การรักษา (โดยไม่เกี่ยวข้องกับการคำนวณกองทั้งหมด) ฉันคิดว่าฉันได้รับการแก้ไขบ้าง แต่ก็ยังต้องใช้การทดสอบมากขึ้นเป็น 100%

ในแอตทริบิวต์ใด ๆ ของเคสจะถูกกำหนดเช่นนี้ (+ มาโครจริงไม่ต้องใส่ที่นี่):

FGAAttributeBase Health;
FGAAttributeBase Energy;

เป็นต้น

นอกจากนี้ฉันไม่แน่ใจ 100% เกี่ยวกับการจัดการค่าของแอตทริบิวต์ปัจจุบัน แต่ควรใช้งานได้ พวกเขาเป็นอย่างนี้

ไม่ว่าในกรณีใด ๆ ฉันหวังว่ามันจะช่วยประหยัดบางคนที่จะเป็นหัวหน้าแคชไม่แน่ใจว่านี่เป็นทางออกที่ดีที่สุดหรือดีกว่า แต่ฉันชอบมันมากกว่าการติดตามเอฟเฟกต์อิสระจากคุณลักษณะต่างๆ การทำให้แต่ละการติดตามคุณสมบัติเป็นสถานะของตัวเองได้ง่ายขึ้นในกรณีนี้และควรมีข้อผิดพลาดน้อยลง มีเพียงจุดเดียวของความล้มเหลวซึ่งเป็นชั้นค่อนข้างสั้นและเรียบง่าย


ขอบคุณสำหรับลิงค์และคำอธิบายผลงานของคุณ! ฉันคิดว่าคุณกำลังก้าวไปสู่สิ่งที่ฉันต้องการ บางสิ่งที่ควรคำนึงถึงคือลำดับของการปฏิบัติงาน (ตัวอย่างเช่นเอฟเฟกต์ 3 "เพิ่ม" และเอฟเฟกต์ "คูณ" 2 ประการในแอตทริบิวต์เดียวกันซึ่งควรเกิดขึ้นก่อน) และนี่เป็นการสนับสนุนแอตทริบิวต์อย่างแท้จริง นอกจากนี้ยังมีความคิดเกี่ยวกับทริกเกอร์ (เช่นประเภทของเอฟเฟกต์ "เสีย 1 AP เมื่อกดปุ่ม") แต่นั่นน่าจะเป็นการสอบสวนแยกต่างหาก
gkimsey

ลำดับของการดำเนินการในกรณีที่เพียงแค่คำนวณโบนัสของคุณลักษณะนั้นทำได้ง่าย คุณสามารถดูที่นี่ว่าฉันมีและสวิตช์ สำหรับการทำซ้ำโบนัสปัจจุบันทั้งหมด (ซึ่งสามารถเพิ่ม, ลบ, คูณ, หาร ฯลฯ ) แล้วสะสมได้ คุณทำอะไรเช่น BonusValue = (BonusValue * MultiplyBonus + AddBonus-SubtractBonus) / DivideBonus หรือคุณต้องการดูสมการนี้ เนื่องจากมีจุดเข้าเดียวจึงง่ายต่อการทดสอบ สำหรับทริกเกอร์ฉันยังไม่ได้เขียนเกี่ยวกับมันเพราะนั่นเป็นอีกปัญหาที่ฉันไตร่ตรองและฉันลอง 3-4 (จำกัด )
Łukasz Baran

การแก้ปัญหาไม่มีพวกเขาทำงานอย่างที่ฉันต้องการ (เป้าหมายหลักของฉันคือให้พวกเขาเป็นมิตรกับนักออกแบบ) แนวคิดทั่วไปของฉันคือการใช้แท็กและตรวจสอบผลกระทบขาเข้ากับแท็ก หากการจับคู่แท็กเอฟเฟกต์สามารถกระตุ้นเอฟเฟกต์อื่น ๆ ได้ (แท็กเป็นชื่อที่มนุษย์สามารถอ่านได้ง่ายเช่น Damage.Fire, Attack.Hysical เป็นต้น) บนแกนหลักของแนวคิดที่ง่ายมากปัญหาคือการจัดระเบียบข้อมูลเพื่อให้สามารถเข้าถึงได้ง่าย (เร็วสำหรับการค้นหา) และเพิ่มเอฟเฟกต์ใหม่ ๆ ได้อย่างง่ายดาย คุณสามารถตรวจสอบรหัสได้ที่นี่github.com/iniside/ActionRPGGame (GameAttributes เป็นโมดูลที่คุณสนใจ)
Łukasz Baran

2

ฉันทำงานกับ MMO ขนาดเล็กและสิ่งของพลังผู้คลั่งไคล้และอื่น ๆ ล้วนมี 'เอฟเฟกต์' ผลที่ได้คือคลาสที่มีตัวแปรสำหรับ 'AddDefense', 'InstantDamage', 'HealHP' ฯลฯ อำนาจรายการและอื่น ๆ จะจัดการกับระยะเวลาสำหรับผลกระทบนั้น

เมื่อคุณใช้พลังงานหรือใส่ไอเท็มมันจะใช้เอฟเฟกต์กับตัวละครตามระยะเวลาที่กำหนด จากนั้นการโจมตีหลัก ฯลฯ การคำนวณจะคำนึงถึงผลกระทบที่ใช้

ตัวอย่างเช่นคุณมีบัฟที่เพิ่มการป้องกัน จะมี EffectID และ Duration เป็นอย่างต่ำสำหรับ buff นั้น เมื่อร่ายมันจะใช้ EffectID กับตัวละครตามระยะเวลาที่กำหนด

อีกตัวอย่างหนึ่งสำหรับรายการจะมีเขตข้อมูลเดียวกัน แต่ระยะเวลาจะไม่สิ้นสุดหรือจนกว่าเอฟเฟกต์จะถูกลบออกโดยการลบรายการออกจากตัวละคร

วิธีนี้อนุญาตให้คุณวนซ้ำรายการเอฟเฟกต์ที่ใช้อยู่ในปัจจุบัน

หวังว่าฉันจะอธิบายวิธีนี้อย่างชัดเจนเพียงพอ


ตามที่ฉันเข้าใจด้วยประสบการณ์ที่น้อยที่สุดของฉันนี่เป็นวิธีดั้งเดิมในการติดตั้ง stat mods ในเกม RPG มันใช้งานได้ดีและง่ายต่อการเข้าใจและนำไปใช้ ข้อเสียคือมันดูเหมือนจะไม่ทิ้งฉันไว้ที่ห้องใด ๆ เพื่อทำสิ่งต่าง ๆ เช่น "หนาม" หรือสิ่งที่ก้าวหน้ากว่าหรือสถานการณ์ นอกจากนี้ยังเคยเป็นสาเหตุของการหาประโยชน์บางอย่างในเกม RPG แม้ว่ามันจะค่อนข้างหายากและเนื่องจากฉันสร้างเกมผู้เล่นคนเดียว ขอบคุณสำหรับการป้อนข้อมูล
gkimsey

2
  1. หากคุณเป็นผู้ใช้สามัคคีนี่คือสิ่งที่จะเริ่มต้น: http://www.stevegargolinski.com/armory-a-free-and-unfinished-stat-inventory-and-buffdebuff-framework-for-unity/

ฉันใช้ ScriptableOjects เป็น buffs / spells / พรสวรรค์

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()
    {
    }
}

ใช้ UnityEngine; ใช้ System.Collections.Generic;

BuffType enum สาธารณะ {Buff, Debuff} [System.Serializable] คลาสสาธารณะ BuffStat {สถิติ Stat สาธารณะ = สถิติความแข็งแกร่ง; public float 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

นี่เป็นคำถามจริงสำหรับฉัน ฉันมีหนึ่งความคิดเกี่ยวกับมัน

  1. ดังที่ได้กล่าวไว้ก่อนหน้านี้เราจำเป็นต้องใช้Buffรายการและตัวอัปเดตเชิงตรรกะสำหรับผู้ชื่นชอบ
  2. จากนั้นเราต้องเปลี่ยนการตั้งค่าผู้เล่นเฉพาะทุกเฟรมในคลาสย่อยของBuffคลาส
  3. จากนั้นเราจะได้รับการตั้งค่าผู้เล่นปัจจุบันจากการตั้งค่าเปลี่ยนฟิลด์

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
    }
}

ด้วยวิธีนี้คุณสามารถเพิ่มสถิติผู้เล่นใหม่ได้อย่างง่ายดายโดยไม่มีการเปลี่ยนแปลงในตรรกะของBuffคลาสย่อย


0

ฉันรู้ว่ามันค่อนข้างเก่า แต่ก็มีการเชื่อมโยงในโพสต์ใหม่และฉันมีความคิดบางอย่างเกี่ยวกับมันฉันต้องการแบ่งปัน น่าเสียดายที่ฉันไม่มีบันทึกของฉันกับฉันในตอนนี้ดังนั้นฉันจะพยายามให้ภาพรวมทั่วไปของสิ่งที่ฉันกำลังพูดถึงและฉันจะแก้ไขในรายละเอียดและโค้ดตัวอย่างเมื่อฉันมีมันอยู่ข้างหน้า ฉัน.

ก่อนอื่นฉันคิดว่าจากมุมมองของการออกแบบคนส่วนใหญ่เริ่มหลงไหลในประเภทของบัฟที่สามารถสร้างได้และวิธีนำไปใช้และลืมหลักการพื้นฐานของการเขียนโปรแกรมเชิงวัตถุ

ฉันหมายถึงอะไร ไม่สำคัญว่าจะมีอะไรเป็นบัฟหรือดีบัฟพวกเขาเป็นทั้งตัวดัดแปลงที่ส่งผลต่อบางอย่างทั้งในเชิงบวกและเชิงลบ รหัสไม่สนใจสิ่งที่เป็น สำหรับเรื่องนั้นในที่สุดมันก็ไม่สำคัญว่าจะมีบางอย่างเพิ่มสถิติหรือทวีคูณพวกเขาเป็นเพียงโอเปอเรเตอร์ที่แตกต่างกันและอีกครั้งที่โค้ดไม่สนใจสิ่งที่

แล้วฉันจะไปกับอะไร การออกแบบคลาส buff / debuff ที่ดี (อ่าน: เรียบง่าย, สง่างาม) นั้นไม่ใช่เรื่องยากอะไรคือสิ่งที่ยากคือการออกแบบระบบที่คำนวณและรักษาสถานะของเกม

ถ้าฉันออกแบบระบบบัฟ / ดีบัฟนี่คือสิ่งที่ฉันควรพิจารณา:

  • คลาส buff / debuff เพื่อแสดงเอฟเฟกต์เอง
  • คลาสชนิด buff / debuff ที่มีข้อมูลเกี่ยวกับสิ่งที่ buff ส่งผลกระทบและวิธีการ
  • ตัวละครรายการและสถานที่อาจเป็นไปได้ทั้งหมดต้องมีรายการหรือคุณสมบัติการเก็บเพื่อให้มีบัฟและบั๊ก

ข้อมูลจำเพาะบางอย่างสำหรับประเภทของ buff / debuff:

  • ใครสามารถใช้กับใครได้บ้าง: ผู้เล่นมอนสเตอร์ที่อยู่ไอเท็ม ฯลฯ
  • ผลกระทบประเภทใด (บวกลบ) ไม่ว่าจะเป็นแบบทวีคูณหรือแบบเสริมและสถิติประเภทใดที่ส่งผลกระทบ IE: การโจมตีการป้องกันการเคลื่อนไหว ฯลฯ
  • เมื่อใดควรตรวจสอบ (การต่อสู้เวลาของวัน ฯลฯ )
  • สามารถลบได้หรือไม่และถ้าเป็นเช่นนั้นจะสามารถลบออกได้อย่างไร

นั่นเป็นเพียงการเริ่มต้น แต่จากตรงนั้นคุณแค่กำหนดสิ่งที่คุณต้องการและทำมันโดยใช้สถานะเกมปกติของคุณ ตัวอย่างเช่นสมมติว่าคุณต้องการสร้างรายการที่ถูกสาปซึ่งช่วยลดความเร็วในการเคลื่อนที่ ...

ตราบใดที่ฉันได้ใส่ประเภทที่เหมาะสมมันง่ายที่จะสร้างบันทึกบัฟที่กล่าวว่า

  • ประเภท: คำสาป
  • ObjectType: รายการ
  • StatCategory: ยูทิลิตี้
  • StatAffected: MovementSpeed
  • ระยะเวลา: ไม่มีที่สิ้นสุด
  • ทริกเกอร์: OnEquip

และอื่น ๆ และเมื่อฉันสร้าง buff ฉันเพียงกำหนด BuffType of Curse และทุกอย่างอื่นขึ้นอยู่กับเครื่องยนต์ ...

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.