ในความคิดเห็นฉันโพสต์คำตอบแบบเก่าของฉันแบบย่อคำตอบ:
การใช้DrawableGameComponent
ล็อคคุณในลายเซ็นวิธีการชุดเดียวและรูปแบบข้อมูลที่เก็บไว้เฉพาะ โดยทั่วไปแล้วสิ่งเหล่านี้จะเป็นตัวเลือกที่ผิดในตอนแรกและการล็อคอินจะขัดขวางวิวัฒนาการของรหัสในอนาคต
ที่นี่ฉันจะพยายามแสดงให้เห็นว่าทำไมเป็นกรณีนี้โดยการโพสต์รหัสบางส่วนจากโครงการปัจจุบันของฉัน
วัตถุรากรักษาสถานะของโลกของเกมมีUpdate
วิธีการดังนี้:
void Update(UpdateContext updateContext, MultiInputState currentInput)
มีจุดเข้าใช้งานจำนวนมากUpdate
ขึ้นอยู่กับว่าเกมนั้นรันบนเครือข่ายหรือไม่ เป็นไปได้เช่นสำหรับสถานะเกมที่จะย้อนกลับไปยังจุดก่อนหน้านี้ในเวลาและจากนั้นจำลองอีกครั้ง
นอกจากนี้ยังมีวิธีการปรับปรุงเพิ่มเติมเช่น:
void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)
ภายในสถานะเกมจะมีรายชื่อของนักแสดงที่จะได้รับการอัพเดต แต่นี่เป็นอะไรที่ง่ายมากกว่าUpdate
โทรมีการผ่านเพิ่มเติมก่อนและหลังเพื่อจัดการกับฟิสิกส์ นอกจากนี้ยังมีสิ่งที่แปลกใหม่สำหรับการวางไข่ / ทำลายนักแสดงเช่นเดียวกับการเปลี่ยนระดับ
นอกเหนือจากเกมแล้วยังมีระบบ UI ที่ต้องจัดการแยกต่างหาก แต่บางหน้าจอใน UI จำเป็นต้องสามารถทำงานในบริบทเครือข่ายได้เช่นกัน
สำหรับการวาดภาพเนื่องจากลักษณะของเกมมีระบบการเรียงและกำหนดเองที่รับอินพุตจากวิธีการเหล่านี้:
virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)
จากนั้นเมื่อมันรู้ว่าจะวาดอะไรโทรออกโดยอัตโนมัติ:
virtual void Draw(DrawContext drawContext, int tag)
tag
พารามิเตอร์เป็นสิ่งจำเป็นเพราะนักแสดงบางคนสามารถถือไปยังนักแสดงอื่น ๆ - และจึงไม่จำเป็นต้องสามารถที่จะวาดทั้งด้านบนและด้านล่างของนักแสดงที่จัดขึ้น ระบบการเรียงลำดับช่วยให้มั่นใจว่าสิ่งนี้เกิดขึ้นในลำดับที่ถูกต้อง
นอกจากนี้ยังมีระบบเมนูในการวาด และระบบลำดับชั้นสำหรับการวาด UI รอบ ๆ HUD รอบ ๆ เกม
ตอนนี้เปรียบเทียบความต้องการเหล่านั้นกับสิ่งที่DrawableGameComponent
ให้:
public class DrawableGameComponent
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
public int UpdateOrder { get; set; }
public int DrawOrder { get; set; }
}
ไม่มีวิธีที่สมเหตุสมผลในการรับจากระบบที่ใช้DrawableGameComponent
ไปยังระบบที่ฉันอธิบายไว้ข้างต้น (และสิ่งเหล่านี้ไม่ใช่ข้อกำหนดที่ผิดปกติสำหรับเกมที่มีความซับซ้อนเพียงเล็กน้อย)
นอกจากนี้ยังเป็นสิ่งสำคัญที่จะต้องทราบว่าข้อกำหนดเหล่านั้นไม่ได้เกิดขึ้นตั้งแต่เริ่มโครงการ พวกเขาพัฒนาในช่วงหลายเดือนของการพัฒนา
สิ่งที่ผู้คนมักทำถ้าเริ่มDrawableGameComponent
เส้นทางคือพวกเขาเริ่มสร้างโดยใช้แฮ็ก:
แทนที่จะเพิ่มวิธีการพวกเขาลงเอยที่น่าเกลียดในประเภทฐานของตัวเอง (ทำไมไม่ใช้เพียงแค่นั้นโดยตรง)
แทนที่จะเปลี่ยนรหัสสำหรับการเรียงลำดับและเรียกใช้Draw
/ Update
พวกเขาทำสิ่งที่ช้ามากในการเปลี่ยนหมายเลขการสั่งซื้อแบบทันที
และที่เลวร้ายที่สุดของทั้งหมด: แทนที่จะเพิ่มพารามิเตอร์ในเมธอดพวกเขาจะเริ่มต้น "ผ่าน" "พารามิเตอร์" ในตำแหน่งหน่วยความจำที่ไม่ใช่สแต็ก (การใช้Services
สิ่งนี้เป็นที่นิยม) สิ่งนี้ซับซ้อนสับสนผิดพลาดช้าเปราะบางไม่ค่อยปลอดภัยต่อเธรดและมีแนวโน้มที่จะเป็นปัญหาหากคุณเพิ่มเครือข่ายสร้างเครื่องมือภายนอกและอื่น ๆ
นอกจากนี้ยังทำให้การใช้รหัสซ้ำยากขึ้นเพราะตอนนี้การพึ่งพาของคุณถูกซ่อนอยู่ภายใน "ส่วนประกอบ" แทนที่จะเผยแพร่ที่ขอบเขต API
หรือพวกเขาเกือบจะเห็นว่ามันบ้าไปหมดแล้วและเริ่มทำ "ผู้จัดการ" DrawableGameComponent
เพื่อแก้ไขปัญหาเหล่านี้ ยกเว้นพวกเขายังมีปัญหาเหล่านี้ที่ขอบเขต "ผู้จัดการ"
"ผู้จัดการ" เหล่านี้สามารถถูกแทนที่อย่างง่ายดายด้วยชุดของการเรียกใช้เมธอดตามลำดับที่ต้องการ สิ่งอื่นใดที่ซับซ้อนเกินไป
แน่นอนว่าเมื่อคุณเริ่มใช้แฮ็กเหล่านั้นมันจะต้องใช้งานจริงเพื่อยกเลิกแฮ็กเหล่านั้นเมื่อคุณรู้ว่าคุณต้องการมากกว่าสิ่งที่DrawableGameComponent
มีให้ คุณกลายเป็น " ล็อคใน " และการล่อลวงมักจะทำต่อเนื่องเพื่อการแฮ็ก - ซึ่งในทางปฏิบัติจะทำให้ยุ่งเหยิงคุณและ จำกัด ประเภทของเกมที่คุณสามารถทำได้สำหรับคนที่ค่อนข้างง่าย
ผู้คนจำนวนมากดูเหมือนจะมาถึงจุดนี้และมีปฏิกิริยาต่ออวัยวะภายใน - อาจเป็นเพราะพวกเขาใช้DrawableGameComponent
และไม่ต้องการที่จะยอมรับว่า "ผิด" ดังนั้นพวกเขาจึงยึดถือความจริงที่ว่าอย่างน้อยก็เป็นไปได้ที่จะทำให้มันเป็น "งาน"
แน่นอนมันสามารถทำให้ "ทำงาน"! เราเป็นโปรแกรมเมอร์ - การทำสิ่งต่าง ๆ ให้ทำงานภายใต้ข้อ จำกัด ที่ผิดปกตินั้นเป็นหน้าที่ของเรา แต่ข้อ จำกัด นี้ไม่จำเป็นมีประโยชน์หรือมีเหตุผล ...
สิ่งที่ทำให้งุนงงเป็นพิเศษคือมันไม่น่าประหลาดใจที่จะก้าวไปข้างหน้าในประเด็นทั้งหมดนี้ ที่นี่ฉันจะให้รหัสแก่คุณด้วย:
public class Actor
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
}
List<Actor> actors = new List<Actor>();
foreach(var actor in actors)
actor.Update(gameTime);
foreach(var actor in actors)
actor.Draw(gameTime);
(มันง่ายที่จะเพิ่มในการจัดเรียงตามDrawOrder
และUpdateOrder
. วิธีหนึ่งที่ง่ายคือการรักษาสองรายการและใช้List<T>.Sort()
. นอกจากนี้ยังเป็นเรื่องเล็กน้อยที่จะจัดการLoad
/ UnloadContent
.)
ไม่สำคัญว่ารหัสนั้นคืออะไร สิ่งที่สำคัญคือฉันสามารถปรับเปลี่ยนได้เล็กน้อย
เมื่อฉันรู้ว่าฉันต้องการระบบจับเวลาที่แตกต่างกันgameTime
หรือฉันต้องการพารามิเตอร์เพิ่มเติม (หรือUpdateContext
วัตถุทั้งหมดที่มีประมาณ 15 สิ่ง) หรือฉันต้องการลำดับการปฏิบัติการที่แตกต่างกันโดยสิ้นเชิงสำหรับการวาดรูปหรือฉันต้องการวิธีพิเศษบางอย่าง สำหรับการอัปเดตต่าง ๆ ฉันสามารถดำเนินการต่อได้
และฉันสามารถทำได้ทันที ฉันไม่จำเป็นต้องกังวลเกี่ยวกับการเลือกDrawableGameComponent
รหัสของฉัน หรือแย่กว่านั้นคือ: แฮ็คมันด้วยวิธีใดวิธีหนึ่งที่จะทำให้มันยากขึ้นในครั้งต่อไปที่ความต้องการดังกล่าวเกิดขึ้น
จริงๆมีเพียงหนึ่งในสถานการณ์ที่มันเป็นทางเลือกที่ดีที่จะใช้DrawableGameComponent
: และนั่นคือถ้าคุณมีอักษรทำและเผยแพร่การลากและวางองค์ประกอบสไตล์ตัวกลางสำหรับ XNA ซึ่งเป็นวัตถุประสงค์ดั้งเดิมของมัน อันไหน - ฉันจะเพิ่ม - ไม่มีใครทำ!
(ในทำนองเดียวกันมันสมเหตุสมผลดีที่จะใช้Services
เมื่อสร้างประเภทเนื้อหาที่กำหนดเองContentManager
เท่านั้น)