การแยก Game Engine ออกจากรหัสเกมในเกมที่คล้ายกันโดยมีการกำหนดเวอร์ชัน


15

ฉันมีเกมเสร็จแล้วที่ฉันต้องการปฏิเสธในเวอร์ชั่นอื่น สิ่งเหล่านี้จะเป็นเกมที่คล้ายกันโดยมีการออกแบบประเภทเดียวกันมากกว่าหรือน้อยกว่า แต่ไม่เสมอไปโดยทั่วไปสิ่งต่าง ๆ อาจมีการเปลี่ยนแปลงบางครั้งเล็กน้อยบางครั้งก็ใหญ่

ฉันต้องการให้คอร์โค้ดแยกเป็นเวอร์ชั่นแยกต่างหากจากเกมดังนั้นถ้าฉันแก้ไขข้อบกพร่องที่พบในเกม A การแก้ไขจะปรากฏในเกม B

ฉันพยายามหาวิธีที่ดีที่สุดในการจัดการสิ่งนั้น แนวคิดเริ่มต้นของฉันคือ:

  • สร้างengineโมดูล / โฟลเดอร์ / อะไรก็ตามที่มีทุกสิ่งที่สามารถวางนัยและเป็นอิสระ 100% จากส่วนที่เหลือของเกม ซึ่งจะรวมถึงรหัสบางส่วน แต่ยังเป็นสินทรัพย์ทั่วไปที่ใช้ร่วมกันระหว่างเกม
  • วางเอ็นจินนี้ลงในที่gitเก็บของมันเองซึ่งจะรวมอยู่ในเกมด้วยgit submodule

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

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

ใครมีความคิดประสบการณ์ในการแบ่งปันหรืออะไรก็ตาม


เครื่องยนต์ของคุณใช้ภาษาอะไร บางภาษามีผู้จัดการแพคเกจเฉพาะที่อาจสมเหตุสมผลมากกว่าการใช้ submodules git ตัวอย่างเช่น NodeJS มี npm (ซึ่งสามารถกำหนดเป้าหมาย repos Git เป็นแหล่งที่มา)
ด่าน Pantry

คำถามของคุณเกี่ยวกับวิธีการกำหนดค่าที่ดีที่สุดในการจัดการรหัสทั่วไปหรือวิธีการกำหนดค่าจัดการรหัส "แบบกึ่งทั่วไป" หรือวิธีการออกแบบรหัสวิธีการออกแบบรหัสหรืออะไร
Dunk

สิ่งนี้อาจแตกต่างกันไปในแต่ละภาษาการเขียนโปรแกรมสภาพแวดล้อม แต่คุณอาจพิจารณาไม่เพียง แต่ซอฟต์แวร์เวอร์ชั่นควบคุมเท่านั้น แต่ควรเริ่มด้วยวิธีการแยกเอ็นจิ้นเกมออกจากรหัสเกม (เช่นแพ็คเกจโฟลเดอร์และ API) และใหม่กว่า ใช้เวอร์ชันควบคุม
umlcat

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

คำตอบ:


13

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

ใส่เอ็นจิ้นนี้ไว้ในที่เก็บของคอมไพล์ของตัวเองซึ่งจะรวมอยู่ในเกมในรูปแบบ submodule

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

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

หมายเหตุเกี่ยวกับการควบคุมแหล่งที่มาและไบนารี

เพียงจำไว้ว่าหากคุณคาดหวังว่าสินทรัพย์ไบนารีของคุณจะเปลี่ยนแปลงบ่อยครั้งคุณอาจไม่ต้องการที่จะนำสิ่งเหล่านี้ไปไว้ในการควบคุมแหล่งที่มาที่เป็นข้อความเช่น git แค่พูดว่า ... มีวิธีแก้ปัญหาที่ดีกว่าสำหรับไบนารี สิ่งที่ง่ายที่สุดที่คุณสามารถทำได้ในตอนนี้เพื่อช่วยให้ที่เก็บ "แหล่งเครื่องยนต์" ของคุณสะอาดและมีประสิทธิภาพคือการมีพื้นที่เก็บข้อมูล "engine-binaries" แยกต่างหากที่เก็บเฉพาะไบนารีซึ่งคุณรวมไว้เป็นซับโฟลในโครงการของคุณ วิธีนี้จะช่วยลดความเสียหายด้านประสิทธิภาพที่เกิดขึ้นกับที่เก็บข้อมูล "engine-source" ของคุณซึ่งมีการเปลี่ยนแปลงอยู่ตลอดเวลาและคุณต้องมีการวนซ้ำอย่างรวดเร็วเช่น commit, push, pull และอื่น ๆ ระบบจัดการการควบคุมแหล่งเช่น git และทันทีที่คุณแนะนำไบนารีคุณจะแนะนำเดลต้าขนาดใหญ่จากมุมมองข้อความซึ่งท้ายที่สุดคุณต้องเสียเวลากับการพัฒนาGitLab ภาคผนวก Google เป็นเพื่อนของคุณ


พวกเขาไม่ได้เปลี่ยนบ่อยนัก แต่ฉันก็สนใจในสิ่งนั้น ฉันไม่รู้อะไรเลยเกี่ยวกับการกำหนดเวอร์ชันไบนารี มีวิธีแก้ปัญหาอะไรบ้าง?
Malharhak

@ Malharhak แก้ไขเพื่อตอบความคิดเห็นของคุณ
วิศวกร

@ Malharak ต่อไปนี้เป็นข้อมูลที่ดีในหัวข้อนี้
วิศวกร

1
+1 สำหรับเก็บสิ่งต่าง ๆในโครงการให้นานที่สุด รหัสทั่วไปให้ความซับซ้อนที่สูงขึ้น ควรหลีกเลี่ยงจนกว่าจะจำเป็นจริงๆ
Gusdor

1
@ Malharhak ไม่โดยเฉพาะอย่างยิ่งเป้าหมายของคุณคือเพียง "เก็บ" ไว้จนกว่าจะถึงเวลาดังที่คุณทราบว่ารหัสดังกล่าวนั้นไม่เปลี่ยนรูป Gusdor กล่าวย้ำว่าสิ่งนี้ - ถูกเตือน - ใคร ๆ ก็สามารถเสียเวลาไปได้อย่างง่ายดายโดยการแยกแยะสิ่งต่าง ๆ ออกมาเร็วเกินไปจากนั้นพยายามหาวิธีที่จะทำให้รหัสนั้นเป็นแบบทั่วไปมากพอที่จะอยู่ร่วมกันได้ มากทั้งพารามิเตอร์และสวิทช์และมันจะกลายเป็นระเบียบน่าเกลียดที่ยังไม่ได้สิ่งที่คุณต้องการเพราะคุณจะจบลงด้วยการเปลี่ยนแปลงต่อโครงการใหม่อยู่แล้ว อย่าคำนึงถึงปัจจัยที่ออกมาเร็วเกินไป มีความอดทน.
วิศวกร

6

ในบางจุดเครื่องยนต์ต้องมีความเชี่ยวชาญและมีความรู้เกี่ยวกับเกม ฉันจะไปแทนเจนต์ที่นี่

ใช้ทรัพยากรใน RTS เกมหนึ่งอาจมีCreditsและเกมCrystalอื่นMetalและPotatoes

คุณควรใช้แนวคิด OO อย่างถูกต้องและดำเนินการให้ได้สูงสุด รหัสนำมาใช้ใหม่ เป็นที่ชัดเจนว่าแนวคิดของการResourceมีอยู่ที่นี่

ดังนั้นเราจึงตัดสินใจว่าทรัพยากรมีดังต่อไปนี้:

  1. เบ็ดในห่วงหลักเพื่อเพิ่ม / ลดลงด้วยตนเอง
  2. วิธีรับจำนวนเงินปัจจุบัน (ส่งคืนint)
  3. วิธีการลบ / เพิ่มตามอำเภอใจ (ผู้เล่นโอนทรัพยากรซื้อ .... )

ขอให้สังเกตว่าความคิดในเรื่องนี้Resourceอาจหมายถึงการฆ่าหรือแต้มในเกม! มันไม่ได้ทรงพลังมาก

ตอนนี้ลองคิดเกี่ยวกับเกม เราสามารถเรียงลำดับของสกุลเงินได้โดยการซื้อขายเป็นสกุลเงินและเพิ่มจุดทศนิยมไปยังผลลัพธ์ สิ่งที่เราไม่สามารถทำได้คือทรัพยากร "ทันที" ชอบพูดว่า "การสร้างกริดพลังงาน"

ให้บอกว่าคุณเพิ่ม InstantResourceคลาสด้วยวิธีการที่คล้ายกัน ตอนนี้คุณ (เริ่มต้น) สร้างมลพิษให้เครื่องยนต์ด้วยทรัพยากร


ปัญหา

ให้นำตัวอย่าง RTS อีกครั้ง สมมติว่าผู้เล่นคนใดบริจาคCrystalให้ผู้เล่นคนอื่น คุณต้องการทำสิ่งที่ชอบ:

if(transfer.target == engine.getPlayerId()) {
    engine.hud.addIncoming("You got "+transfer.quantity+" of "+
        engine.resourceDictionary.getNameOf(transfer.resourceId)+
        " from "+engine.getPlayer(transfer.source).name);
}
engine.getPlayer(transfer.target).getResourceById(transfer.resourceId).add(transfer.quantity)
engine.getPlayer(transfer.source).getResourceById(transfer.resourceId).add(-transfer.quantity)

อย่างไรก็ตามมันค่อนข้างยุ่งจริงๆ มันมีวัตถุประสงค์ทั่วไป แต่ยุ่ง ถ้าอย่างนั้นก็resourceDictionaryหมายความว่าทรัพยากรของคุณต้องมีชื่อ! และมันก็เป็นต่อผู้เล่นดังนั้นคุณไม่สามารถมีทรัพยากรทีมได้อีกต่อไป

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

engine.getPlayer(transfer.target).crystal().receiveDonation(transfer)
engine.getPlayer(transfer.source).crystal().sendDonation(transfer)

ด้วยคลาสPlayerและคลาสCurrentPlayerที่CurrentPlayer 's crystalวัตถุจะแสดงสิ่งที่อยู่บนฮัดสำหรับการโอน / ส่งของการบริจาค

สิ่งนี้ทำให้เครื่องยนต์เสียมลภาวะด้วยคริสตัลการบริจาคคริสตัลข้อความบน HUD สำหรับผู้เล่นปัจจุบันและทุกสิ่ง มันทั้งเร็วและง่ายต่อการอ่าน / เขียน / บำรุงรักษา (ซึ่งสำคัญกว่าเพราะมันไม่เร็วกว่าอย่างมาก)


หมายเหตุสุดท้าย

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

  1. "Core" - นี่คือคำจำกัดความของตำราเรียนของเอ็นจิ้นมันเป็นกราฟฉากที่มีตะขอเหตุการณ์มันเกี่ยวข้องกับ shaders และแพ็กเก็ตเครือข่ายและแนวคิดที่เป็นนามธรรมของผู้เล่น
  2. "GameCore" - เป็นเกมที่ค่อนข้างธรรมดาสำหรับประเภทของเกม แต่ไม่ใช่สำหรับเกมทั้งหมด - ตัวอย่างเช่นทรัพยากรใน RTS หรือกระสุนใน FPS ตรรกะของเกมเริ่มซึมเข้าไปที่นี่ นี่คือที่ความคิดก่อนหน้าของทรัพยากรของเราจะเป็น เราได้เพิ่มสิ่งเหล่านี้ที่เหมาะสมกับทรัพยากร RTS ส่วนใหญ่
  3. "GameLogic" โดยเฉพาะกับเกมที่เกิดขึ้นจริง คุณจะพบตัวแปรที่มีชื่อเหมือนcreatureหรือหรือship squadใช้มรดกคุณจะได้รับการเรียนที่ครอบคลุมทั้งหมด 3 ชั้น (ตัวอย่างเช่นCrystal เป็น Resourceซึ่งเป็น GameLoopEventListenerพูด)
  4. "สินทรัพย์" สิ่งเหล่านี้ไร้ประโยชน์สำหรับเกมอื่น ๆ ยกตัวอย่างเช่นการรวมสคริปต์ AI ในครึ่งชีวิต 2 พวกเขาจะไม่ถูกใช้ใน RTS ด้วยเอ็นจิ้นเดียวกัน

สร้างเกมใหม่จากเอนจิ้นเก่า

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


มลพิษในชั้นที่ 1

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


การจำแนกเลเยอร์

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

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


1

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

มี 3 สิ่งที่ (เกือบ) ปรากฏอยู่เสมอในฉากเมนู: พื้นหลังโลโก้เกมและเมนูเอง สิ่งเหล่านี้มักจะแตกต่างกันไปตามเกม สิ่งที่คุณสามารถทำได้สำหรับเนื้อหานี้คือสร้าง MenuScreenGenerator ในเอนจินของคุณซึ่งใช้พารามิเตอร์วัตถุ 3 ตัว: BackGround, โลโก้และเมนู โครงสร้างพื้นฐานของ 3 ส่วนนี้ยังเป็นส่วนหนึ่งของเครื่องยนต์ของคุณ แต่เครื่องยนต์ของคุณไม่ได้บอกว่าชิ้นส่วนเหล่านี้ถูกสร้างขึ้นมาอย่างไรเพียงแค่พารามิเตอร์ที่คุณควรให้

จากนั้นในรหัสเกมจริงของคุณคุณสร้างวัตถุสำหรับ BackGround โลโก้และเมนูและคุณส่งผ่านสิ่งนี้ไปยัง MenuScreenGenerator ของคุณ อีกครั้งเกมของคุณไม่จัดการกับวิธีการสร้างเมนูนั่นเป็นของเครื่องยนต์ เกมของคุณต้องการบอกเอนจิ้นว่ามันควรมีหน้าตาอย่างไรและควรเป็นอย่างไร

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

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