เหตุใดฉันจึงควรแยกวัตถุออกจากการแสดงผล


11

Disclamer:ฉันรู้ว่ารูปแบบของเอนทิตีคืออะไรและฉันไม่ได้ใช้

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

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

นี่มันฟังดูไม่ดีสำหรับฉัน ในทางกลับกันมันง่ายมากที่จะจินตนาการว่าวัตถุเป็นตัวแทนของ 3d (หรือ 2d) และยังง่ายต่อการบำรุงรักษา

มีวิธีการรักษาการแสดงกราฟิกและตรรกะเกมควบคู่กัน (หลีกเลี่ยงปัญหาการซิงโครไนส์) แต่เป็นนามธรรมออกไปจากเอ็นจิ้นการเรนเดอร์หรือไม่? หรือมีวิธีแยกตรรกะเกมและการเรนเดอร์ที่ไม่ทำให้เกิดข้อเสียข้างต้นหรือไม่?

(อาจมีตัวอย่างฉันไม่เข้าใจการพูดที่เป็นนามธรรม)


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

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

คำตอบ:


13

สมมติว่าคุณมีฉากประกอบของโลกเป็นผู้เล่นและเจ้านาย โอ้และนี่เป็นเกมบุคคลที่สามดังนั้นคุณจึงมีกล้องถ่ายรูปด้วย

ดังนั้นฉากของคุณจะเป็นดังนี้:

class Scene {
    World* world
    Player* player
    Enemy* boss
    Camera* camera
}

(อย่างน้อยนั่นคือข้อมูลพื้นฐานวิธีที่คุณมีข้อมูลนั้นขึ้นอยู่กับคุณ)

คุณต้องการอัปเดตและแสดงฉากเมื่อคุณเล่นเกมไม่ใช่เมื่อหยุดชั่วคราวหรือในเมนูหลัก ... เพื่อให้คุณแนบกับสถานะเกม!

State* gameState = new State();
gameState->addScene(scene);

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

State::update(double delta) {
    scene->update(delta);
}

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

State::update(double delta) {
    physicsSystem->applyPhysics(scene);
}

อย่างไรก็ตามตอนนี้คุณสามารถปรับปรุงฉากของคุณได้แล้ว ตอนนี้คุณต้องการแสดงมัน! ซึ่งเราทำสิ่งที่คล้ายกับด้านบน:

State::render() {
    renderSystem->render(scene);
}

ไปแล้ว renderSystem อ่านข้อมูลจากฉากและแสดงภาพที่เหมาะสม แบบง่ายวิธีการแสดงฉากอาจมีลักษณะเช่นนี้:

RenderSystem::renderScene(Scene* scene) {
    Camera* camera = scene->camera;
    lookAt(camera); // Set up the appropriate viewing matrices based on 
                    // the camera location and direction

    renderHeightmap(scene->getWorld()->getHeightMap()); // Just as an example, you might
                                                        // use a height map as your world
                                                        // representation.
    renderModel(scene->getPlayer()->getType()); // getType() will return, for example "orc"
                                                // or "human"

    renderModel(scene->getBoss()->getType());
}

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

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

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

สิ่งนี้ควรตอบสนองความต้องการของคุณ การแสดงกราฟิกและตรรกะนั้นเชื่อมโยงกันเพราะทั้งคู่ใช้ข้อมูลเดียวกัน พวกเขาแยกจากกันเพราะไม่ต้องพึ่งพาคนอื่น!

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


7

ชื่อของคุณถามคำถามที่แตกต่างจากเนื้อหาร่างกายของคุณ ในชื่อเรื่องคุณถามว่าทำไมต้องแยกเหตุผลและการแสดงผลออก แต่ในร่างกายคุณขอการใช้งานระบบตรรกะ / กราฟิก / การแสดงผล

คำถามที่สองได้รับการแก้ไขก่อนหน้านี้แล้วดังนั้นฉันจะมุ่งเน้นไปที่คำถามแรก

เหตุผลในการแยกลอจิกและการเรนเดอร์:

  1. ความคิดที่ถือกันอย่างกว้างขวางว่าวัตถุควรทำสิ่งหนึ่ง
  2. ถ้าคุณต้องการเปลี่ยนจาก 2D เป็น 3D ล่ะ? ถ้าคุณตัดสินใจเปลี่ยนจากระบบการแสดงผลหนึ่งระบบไปเป็นอีกระบบหนึ่งในโครงการ คุณไม่ต้องการคลานผ่านโค้ดทั้งหมดของคุณและทำการเปลี่ยนแปลงครั้งใหญ่ในระหว่างตรรกะของเกม
  3. คุณอาจมีเหตุผลในการทำซ้ำส่วนของรหัสซึ่งโดยทั่วไปถือว่าเป็นความคิดที่ไม่ดี
  4. คุณสามารถสร้างระบบเพื่อควบคุมการเรนเดอร์หรือลอจิกขนาดใหญ่ที่อาจเกิดขึ้นได้โดยไม่ต้องสื่อสารกับชิ้นส่วนเล็ก ๆ
  5. จะเป็นอย่างไรถ้าคุณต้องการกำหนดอัญมณีให้กับผู้เล่น แต่ระบบช้าลงโดยมีหลายแง่มุมที่อัญมณีมี? หากคุณทำให้ระบบการเรนเดอร์ของคุณดีพอคุณสามารถอัปเดตในอัตราที่ต่างกันเพื่อพิจารณาการดำเนินการเรนเดอร์ราคาแพง
  6. ช่วยให้คุณคิดเกี่ยวกับสิ่งต่าง ๆ ที่สำคัญกับสิ่งที่คุณกำลังทำอยู่ ทำไมไขสมองของคุณรอบ ๆ การแปลงเมทริกซ์และสไปรต์ออฟเซ็ทและพิกัดหน้าจอเมื่อสิ่งที่คุณต้องการทำคือการใช้กลไกการกระโดดสองครั้งวาดการ์ดหรือติดตั้งดาบ คุณไม่ต้องการเทพดาที่เป็นตัวแทนดาบของคุณในชุดสีชมพูสดใสเพราะคุณต้องการที่จะย้ายจากมือขวาของคุณไปทางซ้าย

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


6

คำตอบนี้เป็นเพียงการสร้างสัญชาตญาณว่าทำไมการแยกการเรนเดอร์และลอจิกจึงมีความสำคัญแทนที่จะแนะนำตัวอย่างการปฏิบัติโดยตรง

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

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

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

ป้อนคำอธิบายรูปภาพที่นี่

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

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

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

ลองมาตัวอย่างกราฟฉากง่าย ๆ เพื่ออธิบายความคิด

โดยทั่วไปแล้วการอัปเดตจะกระทำผ่านลูปเดี่ยวที่เรียกว่าลูปเกม (หรืออาจผ่านลูปเกมหลายวงโดยแต่ละไฟล์จะรันแยกกัน) เมื่อลูปอัปเดตวัตถุเกมแล้ว มันต้องบอกผ่านการส่งข้อความหรืออินเทอร์เฟซที่วัตถุ 1 และ 2 ที่ปรับปรุงและฟีดมันด้วยการแปลงสุดท้าย

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

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