1) ผู้เล่น: สถาปัตยกรรมที่ใช้เครื่องรัฐ + ส่วนประกอบ
ส่วนประกอบปกติสำหรับ Player: HealthSystem, MovementSystem, InventorySystem, ActionSystem class HealthSystem
ผู้เรียนทุกคนชอบ
ฉันไม่แนะนำให้ใช้งานที่Update()
นั่น(มันไม่สมเหตุสมผลในกรณีปกติที่จะมีการอัพเดทในระบบสุขภาพเว้นแต่ว่าคุณต้องการมันสำหรับการกระทำบางอย่างในทุก ๆ เฟรมสิ่งเหล่านี้ไม่ค่อยเกิดขึ้นกรณีหนึ่งคุณอาจนึกถึง - ผู้เล่นได้รับพิษและคุณต้องการเขา การสูญเสียสุขภาพเป็นครั้งคราว - ที่นี่ฉันขอแนะนำให้ใช้ coroutines อีกหนึ่งอย่างต่อเนื่องสร้างสุขภาพหรือใช้พลังงานคุณเพียงแค่ใช้สุขภาพหรือพลังงานในปัจจุบันและโทร coroutine เพื่อเติมให้อยู่ในระดับที่เมื่อเวลามาทำลาย coroutine เมื่อสุขภาพเต็มหรือ เขาได้รับความเสียหายหรือเขาเริ่มทำงานอีกครั้งและอื่น ๆ . ตกลงว่าเป็น offtopic นิด ๆ หน่อย ๆ แต่ผมหวังว่ามันเป็นประโยชน์)
รัฐ: LootState, RunState, WalkState, AttackState, IDLEState
interface IState
ทุกสืบทอดจากรัฐ IState
มีในกรณีของเรามี 4 วิธีสำหรับตัวอย่างLoot() Run() Walk() Attack()
นอกจากนี้เรายังมีclass InputController
จุดที่เราตรวจสอบอินพุตของผู้ใช้ทุกคน
ตอนนี้กับตัวอย่างจริง: ในInputController
เราตรวจสอบว่าเครื่องรีดผู้เล่นคนใดของแล้วถ้าเขายังกดWASD or arrows
Shift
ถ้าเขากดเพียงWASD
แล้วที่เราเรียกว่า_currentPlayerState.Walk();
เมื่อ happends นี้และเรามีcurrentPlayerState
ให้เท่ากับWalkState
แล้วWalkState.Walk()
เรามีส่วนประกอบทั้งหมดที่จำเป็นสำหรับรัฐนี้ - ในกรณีนี้MovementSystem
เราจึงให้ย้ายผู้เล่นpublic void Walk() { _playerMovementSystem.Walk(); }
- คุณเห็นสิ่งที่เรามีที่นี่? เรามีพฤติกรรมที่สองชั้นและเป็นสิ่งที่ดีมากสำหรับการบำรุงรักษารหัสและการแก้จุดบกพร่อง
ทีนี้สำหรับกรณีที่สอง: ถ้าเรากดWASD
+ Shift
ล่ะ? WalkState
แต่สถานะก่อนหน้านี้ของเราคือ ในกรณีนี้Run()
จะถูกเรียกInputController
(ไม่รวมกันนี้Run()
ถูกเรียกเพราะเรามีWASD
+ Shift
เช็คอินInputController
ไม่ใช่เพราะWalkState
) เมื่อเราโทร_currentPlayerState.Run();
ในWalkState
- เรารู้ว่าเราต้องเปลี่ยน_currentPlayerState
ไปRunState
และเราจะทำเช่นนั้นในRun()
ของWalkState
และเรียกมันอีกครั้งภายในวิธีนี้ แต่ตอนนี้มีสถานะที่แตกต่างกันเพราะเราไม่ต้องการให้ดำเนินการสูญเสียกรอบนี้ _playerMovementSystem.Run();
และตอนนี้ของหลักสูตรที่เราเรียกว่า
แต่จะเกิดอะไรขึ้นLootState
เมื่อผู้เล่นไม่สามารถเดินหรือวิ่งจนกว่าเขาจะปล่อยปุ่มได้? ในกรณีนี้เมื่อเราเริ่มปล้นสะดมตัวอย่างเช่นเมื่อE
กดปุ่มเราเรียกว่า_currentPlayerState.Loot();
เราสลับไปLootState
และตอนนี้เรียกมันจากที่นั่น ที่นั่นเรายกตัวอย่างเช่นวิธีการเรียก collsion เพื่อให้ได้ถ้ามีบางอย่างที่จะยกเค้าในช่วง และเราเรียก coroutine ที่เรามีภาพเคลื่อนไหวหรือที่เราเริ่มต้นและตรวจสอบว่าผู้เล่นยังคงกดปุ่มถ้าไม่ coroutine แตกถ้าใช่เราให้เขายกเค้าในตอนท้ายของ coroutine แต่ถ้าหากผู้เล่นกดWASD
? - _currentPlayerState.Walk();
ถูกเรียก แต่นี่คือสิ่งที่น่าสนใจเกี่ยวกับเครื่องจักรสถานะLootState.Walk()
เรามีวิธีการที่ว่างเปล่าที่ไม่ทำอะไรเลยหรืออย่างที่ฉันจะทำในฐานะผู้เล่นบอกว่า: "เฮ้ชายฉันยังไม่ขโมยเลยคุณรอได้ไหม IDLEState
เมื่อเขาสิ้นสุดปล้นเราเปลี่ยนไป
นอกจากนี้คุณสามารถทำสคริปต์อื่นที่เรียกclass BaseState : IState
ว่ามีการใช้วิธีการเริ่มต้นเหล่านี้ทั้งหมด แต่มีvirtual
เพื่อให้คุณสามารถoverride
ในclass LootState : BaseState
ประเภทของคลาส
ระบบที่ใช้องค์ประกอบนั้นยอดเยี่ยมสิ่งเดียวที่ทำให้ฉันรำคาญใจคืออินสแตนซ์หลายตัว และใช้หน่วยความจำมากขึ้นและใช้งานได้กับตัวรวบรวมขยะ ตัวอย่างเช่นหากคุณมีศัตรู 1,000 รายการ พวกเขาทั้งหมดมี 4 องค์ประกอบ วัตถุ 4000 ชิ้นแทน 1,000 Mb ไม่ใช่เรื่องใหญ่มาก (ฉันยังไม่ได้ทดสอบประสิทธิภาพ) ถ้าเราพิจารณาส่วนประกอบทั้งหมดที่ gameobject มีอยู่
2) สถาปัตยกรรมที่สืบทอด แม้ว่าคุณจะสังเกตเห็นว่าเราไม่สามารถกำจัดส่วนประกอบได้อย่างสมบูรณ์ - เป็นไปไม่ได้จริง ๆ ถ้าเราต้องการมีรหัสที่สะอาดและใช้งานได้ นอกจากนี้หากเราต้องการใช้รูปแบบการออกแบบที่ได้รับการแนะนำอย่างสูงให้ใช้ในกรณีที่เหมาะสม (อย่าใช้มากเกินไปก็เรียกว่า overengeneering)
ลองนึกภาพเรามีคลาสผู้เล่นที่มีคุณสมบัติทั้งหมดที่จำเป็นต้องมีในเกม มันมีพลังชีวิตมานาหรือพลังงานสามารถย้ายวิ่งและใช้ความสามารถมีสินค้าคงคลังสามารถประดิษฐ์ไอเท็มไอเท็มยกเค้าได้แม้กระทั่งสามารถสร้างสิ่งกีดขวางหรือป้อมปราการ
ก่อนอื่นฉันจะบอกว่าสินค้าคงคลัง, การสร้าง, การเคลื่อนไหว, การสร้างควรเป็นส่วนประกอบเพราะมันไม่ใช่ความรับผิดชอบของผู้เล่นที่จะมีวิธีการเช่นAddItemToInventoryArray()
- แม้ว่าผู้เล่นสามารถมีวิธีการเช่นPutItemToInventory()
ที่จะเรียกวิธีการที่อธิบายไว้ก่อนหน้านี้ เพิ่มเงื่อนไขบางอย่างขึ้นอยู่กับชั้นที่แตกต่างกัน)
อีกตัวอย่างหนึ่งกับอาคาร ผู้เล่นสามารถโทรหาสิ่งที่ต้องการOpenBuildingWindow()
แต่Building
จะดูแลส่วนที่เหลือทั้งหมดและเมื่อผู้ใช้ตัดสินใจที่จะสร้างสิ่งปลูกสร้างที่เฉพาะเจาะจงเขาจะส่งข้อมูลที่จำเป็นทั้งหมดไปยังผู้เล่นBuild(BuildingInfo someBuildingInfo)
และผู้เล่นจะเริ่มสร้างด้วยภาพเคลื่อนไหวที่จำเป็นทั้งหมด
หลักการของ SOLID - OOP S - ความรับผิดชอบเดี่ยว: สิ่งที่เราเห็นในตัวอย่างก่อนหน้านี้ ใช่แล้ว แต่มรดกอยู่ที่ไหน
ที่นี่: ควรมีการจัดการสุขภาพและคุณสมบัติอื่น ๆ ของผู้เล่นโดยเอนทิตี้อื่น? ผมคิดว่าไม่. ไม่มีผู้เล่นที่ไม่มีสุขภาพถ้ามีเราก็ไม่ได้รับมรดก ตัวอย่างเช่นเรามีIDamagable
, LivingEntity
, ,IGameActor
ของหลักสูตรมีGameActor
IDamagable
TakeDamage()
class LivinEntity : IDamagable {
private float _health; // For fields that are the same between Instances I would use Flyweight Pattern.
public void TakeDamage() {
....
}
}
class GameActor : LivingEntity, IGameActor {
// Here goes state machine and other attached components needed.
}
class Player : GameActor {
// Inventory, Building, Crafting.... components.
}
ดังนั้นที่นี่ฉันไม่สามารถแยกส่วนประกอบจากการสืบทอดได้ แต่เราสามารถผสมได้ตามที่เห็น นอกจากนี้เรายังสามารถสร้างคลาสพื้นฐานสำหรับระบบอาคารได้เช่นกันถ้าเรามีประเภทที่แตกต่างกันและเราไม่ต้องการที่จะเขียนโค้ดเพิ่มเติมเกินความจำเป็น แน่นอนว่าเราสามารถมีอาคารประเภทต่าง ๆ ได้และไม่มีวิธีที่ดีที่จะใช้เป็นส่วนประกอบ!
OrganicBuilding : Building
, TechBuilding : Building
. คุณไม่จำเป็นต้องสร้าง 2 องค์ประกอบและเขียนโค้ดที่นั่นสองครั้งสำหรับการทำงานทั่วไปหรือคุณสมบัติของการสร้าง แล้วเพิ่มพวกมันต่างออกไปคุณสามารถใช้พลังแห่งการสืบทอดและต่อมาของความแตกต่างและการรวมเข้าด้วยกัน
ฉันขอแนะนำให้ใช้บางสิ่งบางอย่างในระหว่าง และไม่ใช้องค์ประกอบมากเกินไป
ฉันขอแนะนำให้อ่านหนังสือเล่มนี้เกี่ยวกับรูปแบบการเขียนโปรแกรมเกม - ฟรีบนเว็บ