วิธีหลีกเลี่ยงการเขียนโค้ดอย่างหนักในเอ็นจิ้นเกม


22

คำถามของฉันไม่ใช่คำถามที่เข้ารหัส มันใช้กับการออกแบบเอ็นจิ้นเกมโดยทั่วไป

คุณจะหลีกเลี่ยงการเข้ารหัสยากอย่างไร

คำถามนี้ลึกกว่าที่คิดมาก สมมติว่าถ้าคุณต้องการรันเกมที่โหลดไฟล์ที่จำเป็นสำหรับการทำงานคุณจะหลีกเลี่ยงการพูดอะไรบางอย่างload specificfile.wadในรหัสเครื่องยนต์หรือไม่ นอกจากนี้เมื่อโหลดไฟล์คุณจะหลีกเลี่ยงการพูดได้load aspecificmap in specificfile.wadอย่างไร

คำถามนี้ใช้กับการออกแบบเครื่องยนต์ทั้งหมดและเครื่องยนต์ควรมีการเข้ารหัสน้อยที่สุด อะไรคือวิธีที่ดีที่สุดในการบรรลุเป้าหมายนั้น?

คำตอบ:


42

การเข้ารหัสที่ขับเคลื่อนด้วยข้อมูล

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

คุณหาได้aspecificmapอย่างไร เพราะมันอยู่ในไฟล์ข้อมูลที่แสดงรหัสแผนที่และทรัพยากรบนดิสก์

จำเป็นต้องมีทรัพยากร "แกนหลัก" จำนวนน้อยซึ่งเป็นเรื่องยากหรือเป็นไปไม่ได้ที่จะหลีกเลี่ยงการเข้ารหัส ด้วยการทำงานเล็กน้อยสิ่งนี้สามารถ จำกัด ได้เพียงชื่อสินทรัพย์เริ่มต้นที่กำหนดค่าตายตัวเดียวmain.wadหรือคล้ายกัน ไฟล์นี้สามารถที่อาจจะมีการเปลี่ยนแปลงที่ runtime game.exe -wad mymain.wadโดยผ่านอาร์กิวเมนต์บรรทัดคำสั่งที่จะเล่นเกมอาคา

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

เป็นตัวอย่างที่เป็นรูปธรรมจาก codebase ของเราเรามีวัตถุ "ข้อมูลทั่วโลก" ที่โหลดจากฐานข้อมูลทรัพยากร (ซึ่งตัวมันเองเป็นค่าเริ่มต้น./resourcesโฟลเดอร์ แต่สามารถโหลดมากเกินไปด้วยอาร์กิวเมนต์บรรทัดคำสั่ง) ID ข้อมูลฐานข้อมูลของข้อมูลส่วนกลางนี้เป็นชื่อทรัพยากรที่เข้ารหัสยากใน codebase เท่านั้น (เรามีชื่ออื่นเพราะบางครั้งโปรแกรมเมอร์ก็ขี้เกียจ แต่โดยทั่วไปแล้วเราจะแก้ไข / ลบข้อมูลเหล่านั้นในที่สุด) ออบเจ็กต์ข้อมูลส่วนกลางนี้เต็มไปด้วยส่วนประกอบที่มีจุดประสงค์เพื่อจัดเตรียมข้อมูลการกำหนดค่า หนึ่งในองค์ประกอบคือองค์ประกอบ UI Global Data ซึ่งมีการจัดการทรัพยากรไปยังทรัพยากร UI หลักทั้งหมด (แบบอักษร, ไฟล์ Flash, ไอคอน, ข้อมูลการแปล ฯลฯ ) ท่ามกลางจำนวนรายการการกำหนดค่าอื่น ๆ เมื่อนักพัฒนา UI ตัดสินใจเปลี่ยนชื่อเนื้อหา UI หลักจาก/ui/mainmenu.swfเป็น/ui/lobby.swfพวกเขาเพียงแค่อัปเดตการอ้างอิงข้อมูลทั่วโลก ไม่ต้องเปลี่ยนรหัสเครื่องยนต์เลย

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

วิธีนี้มีข้อดีอื่น ๆ มากมาย สำหรับหนึ่งมันทำให้การบรรจุทรัพยากรและการรวมเป็นหนึ่งในกระบวนการทั้งหมด เส้นทางที่เข้ารหัสยากในเอ็นจิ้นก็มีแนวโน้มที่จะหมายความว่าเส้นทางเดียวกันเหล่านั้นจะต้องถูกกำหนดค่าตายตัวในสิ่งที่สคริปต์หรือเครื่องมือที่ทำขึ้นสินทรัพย์เกมและเส้นทางเหล่านั้นสามารถซิงค์ได้หมด เราสามารถสร้างบันเดิลสินทรัพย์โดยใช้คำสั่งเดียวที่คล้ายกันbundle.exe -root config.data -out main.wadและรู้ว่ามันจะรวมสินทรัพย์ทั้งหมดที่เราต้องการ นอกจากนี้เนื่องจาก Bundler จะติดตามการอ้างอิงทรัพยากรเรารู้ว่าจะรวมเฉพาะสินทรัพย์ที่เราต้องการและข้ามปุยซ้ายที่เหลืออยู่ตลอดชีวิตของโครงการอย่างหลีกเลี่ยงไม่ได้ (รวมทั้งเราสามารถสร้างรายการโดยอัตโนมัติได้ ปุยเพื่อการตัดแต่งกิ่ง)

กรณีมุมที่ยุ่งยากของสิ่งทั้งหมดนี้อยู่ในสคริปต์ การทำให้เครื่องยนต์ขับเคลื่อนข้อมูลนั้นเป็นแนวคิดง่าย ๆ แต่ฉันได้เห็นโครงการมากมาย (งานอดิเรกไปยัง AAA) ซึ่งสคริปต์ถูกพิจารณาว่าเป็นข้อมูลและ "อนุญาต" เพื่อใช้เส้นทางทรัพยากรอย่างไม่เจาะจง อย่าทำอย่างนั้น หากไฟล์ Lua ต้องการทรัพยากรและเพียงเรียกใช้ฟังก์ชันเช่นtextures.lua("/path/to/texture.png")นั้นไปป์ไลน์ของสินทรัพย์จะมีปัญหามากมายที่รู้ว่าสคริปต์ต้องการ/path/to/texture.pngทำงานอย่างถูกต้องและอาจพิจารณาว่าพื้นผิวนั้นไม่ได้ใช้งานและไม่จำเป็น สคริปต์ควรได้รับการปฏิบัติเหมือนรหัสอื่น ๆ : ข้อมูลใด ๆ ที่พวกเขาต้องการรวมถึงทรัพยากรหรือตารางควรระบุไว้ในรายการการกำหนดค่าที่เอ็นจิ้นและไพพ์ไลน์ของทรัพยากรสามารถตรวจสอบการพึ่งพาได้ ข้อมูลที่ระบุว่า "สคริปต์โหลดfoo.lua" ควรพูดว่า "foo.luaและให้พารามิเตอร์เหล่านี้ "ซึ่งพารามิเตอร์รวมถึงทรัพยากรใด ๆ ที่จำเป็นหากสคริปต์สุ่มวางไข่ศัตรูเช่นส่งรายการศัตรูที่เป็นไปได้ลงในสคริปต์จากไฟล์กำหนดค่านั้นจากนั้นเครื่องยนต์สามารถโหลดศัตรูที่มีระดับล่วงหน้าได้ ( เนื่องจากมันรู้รายการที่สมบูรณ์ของการวางไข่ที่เป็นไปได้) และไพพ์ไลน์ของทรัพยากรรู้ที่จะรวมศัตรูทั้งหมดเข้ากับเกม (เนื่องจากพวกมันถูกอ้างอิงอย่างชัดเจนจากข้อมูลการกำหนดค่า) หากสคริปต์สร้างสตริงชื่อพา ธ และเรียกใช้loadฟังก์ชัน เอ็นจิ้นหรือไพพ์ไลน์ของทรัพยากรมีวิธีการที่จะรู้ว่าสินทรัพย์ใดที่สคริปต์อาจพยายามโหลด


คำตอบที่ดีมีประโยชน์มากและยังอธิบายถึงข้อผิดพลาดและข้อผิดพลาดที่ผู้คนทำเมื่อใช้สิ่งนี้! 1
WHN

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

12

เช่นเดียวกับที่คุณหลีกเลี่ยงการเข้ารหัสในฟังก์ชั่นทั่วไป

คุณส่งพารามิเตอร์และเก็บข้อมูลของคุณในไฟล์กำหนดค่า

ในสถานการณ์ดังกล่าวไม่มีความแตกต่างในด้านวิศวกรรมซอฟต์แวร์ระหว่างการเขียนโปรแกรมและการเขียนชั้นเรียน

MgrAssets
public:
  errorCode loadAssetFromDisk( filePath )
  errorCode getMap( mapName, map& )

private:
  maps[name, map]

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

จากนั้นทุกอย่างจะถูกขับเคลื่อนด้วยไฟล์กำหนดค่า "หลัก"


1
ใช่ซึ่งรวมถึงกลไกบางอย่างที่จะนำตรรกะที่กำหนดเองมาใช้ อาจเป็นเพราะการฝังภาษาเช่น C #, Python ฯลฯ เพื่อขยายคุณสมบัติหลักของเครื่องยนต์โดยฟังก์ชั่นที่ผู้ใช้กำหนด
qCring

3

ฉันชอบคำตอบอื่น ๆ ดังนั้นฉันจะตรงกันข้าม ;)

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

วิธีการที่ขับเคลื่อนด้วยข้อมูลที่ "บริสุทธิ์" จะช่วยให้คุณเริ่มต้นการปฏิบัติการด้วยพารามิเตอร์บรรทัดคำสั่งที่จำเป็นสำหรับการโหลดการกำหนดค่าเริ่มต้น แต่เครื่องยนต์จะต้องถูกเข้ารหัสเพื่อให้ทราบวิธีการตีความข้อมูลนั้น เช่นถ้าแฟ้มการกำหนดค่าของคุณ JSON คุณจะต้องยากรหัสตัวแปรที่คุณมองหาเช่นเครื่องยนต์จะต้องรู้ที่จะมองหา"intro_movies"และ"level_list"และอื่น ๆ

อย่างไรก็ตามเอ็นจิ้น "ที่ถูกสร้างมาอย่างดี" สามารถทำงานได้กับเกมที่แตกต่างกันมากมายเพียงแค่ทำการสลับข้อมูลการกำหนดค่าและข้อมูลที่อ้างอิง

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

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

struct Weapon
{
    enum IconID icon;
    enum ModelID model;
    int damage;
    int rateOfFire;
    // etc...
};

const struct Weapon g_weapons[] =
{
    { ICON_PISTOL, MODEL_PISTOL, 5, 6 },
    { ICON_RIFLE, MODEL_RIFLE, 10, 20 },
    // etc...
};

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


การแยกวิเคราะห์ json ไม่ใช่เรื่องยากนัก "ต้นทุน" ที่เกี่ยวข้องเท่านั้นคือการเรียนรู้ (โดยเฉพาะการเรียนรู้ที่จะใช้โมดูลหรือไลบรารีที่เหมาะสม Go มีการสนับสนุน json ที่ดี)
Wildcard

มันไม่ยากมาก แต่มันต้องทำมากกว่าการเรียนรู้ เช่นฉันรู้วิธีการแยกวิเคราะห์ JSON ฉันได้เขียนโปรแกรมแยกวิเคราะห์สำหรับรูปแบบไฟล์อื่น ๆ แต่ฉันต้องการค้นหาและติดตั้งโซลูชันบุคคลที่สาม ใช้เวลามากกว่าที่จะไม่ทำ
dash-tom-bang

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

ฉันไม่แน่ใจว่าอาร์กิวเมนต์ของคุณคืออะไร ในคำตอบนี้ฉันสนับสนุน YAGNI; หากคุณไม่จำเป็นต้องใช้ / เสียเวลาในการทำสิ่งที่จะไม่ช่วยให้คุณทำไม่ได้ ถ้าคุณต้องการที่จะใช้เวลาในการที่ดี บางทีคุณอาจต้องใช้เวลาในภายหลังบางทีคุณอาจจะไม่ทำ แต่การทำสิ่งนั้นไว้ล่วงหน้าเป็นเพียงการเบี่ยงเบนความสนใจของคุณจากการทำเกมจริงๆ การพัฒนาเกมเป็นเรื่องเล็กน้อย ทุกงานที่ทำเกมนั้นง่ายมาก เป็นเพียงเกมส่วนใหญ่ที่มีงานง่าย ๆ นับล้านงานและผู้รับผิดชอบที่รับผิดชอบจะเลือกเกมที่จะไปถึงเป้าหมายนั้นให้เร็วที่สุด
dash-tom-bang

2
จริงๆแล้วฉันตอบคำตอบของคุณ; ไม่มีการโต้แย้งที่แท้จริงเช่นนี้ ฉันแค่อยากจะทราบว่า JSON ไม่ใช่เรื่องยากที่จะแยกวิเคราะห์ อ่านอีกครั้งฉันคิดว่าฉันส่วนใหญ่ตอบสนองต่อตัวอย่าง"แต่แล้วคุณต้องทำการแยกวิเคราะห์และแปลและแจ๊สทั้งหมดนั้น" แต่ฉันยอมรับว่าสำหรับเกมของโครงการส่วนบุคคลและเช่น YAGNI :)
Wildcard
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.