การเรียนรู้กล้อง 2D ใน Unity: บทช่วยสอนสำหรับนักพัฒนาเกม

เผยแพร่แล้ว: 2022-03-11

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

ในบทความนี้ ผมจะอธิบายวิธีออกแบบระบบกล้องสำหรับเกม 2D และผมจะอธิบายบางประเด็นเกี่ยวกับวิธีการนำไปใช้ในเครื่องมือเกมยอดนิยมอย่าง Unity

จาก 2D เป็น 2.5D: ระบบกล้องขยายได้

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

ระบบกล้องที่เรากำลังสร้างที่นี่มีเป้าหมายที่เกมแพลตฟอร์ม 2 มิติ แต่สามารถขยายไปยังเกม 2 มิติประเภทอื่น เกม 2.5 มิติ หรือแม้แต่เกม 3 มิติได้อย่างง่ายดาย

การเรียนรู้กล้อง 2D ใน Unity: บทช่วยสอนสำหรับนักพัฒนาเกม

การเรียนรู้กล้อง 2D ใน Unity: บทช่วยสอนสำหรับนักพัฒนาเกม
ทวีต

ฉันจะแบ่งการทำงานของกล้องออกเป็นสองกลุ่มหลัก: การติดตามกล้องและเอฟเฟกต์กล้อง

การติดตาม

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

เอฟเฟกต์

เราจะนำเอฟเฟกต์เจ๋งๆ มาใช้ เช่น การสั่นของกล้อง การซูมของกล้อง การเฟดของกล้อง และการซ้อนทับสี

เริ่มต้น

สร้างโปรเจ็กต์ 2 มิติใหม่ใน Unity และนำเข้าเนื้อหามาตรฐาน โดยเฉพาะตัวละคร RobotBoy ถัดไป สร้างกล่องกราวด์และเพิ่มตัวอย่างอักขระ คุณควรจะสามารถเดินและกระโดดไปพร้อมกับตัวละครของคุณในฉากปัจจุบันของคุณได้ ตรวจสอบให้แน่ใจว่าได้ตั้งค่ากล้องเป็นโหมด Orthographic (ตั้งค่าเป็น Perspective เป็นค่าเริ่มต้น)

ติดตามเป้าหมาย

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

 [SerializeField] protected Transform trackingTarget; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x, trackingTarget.position.y, transform.position.z); }

ลากตัวละคร RobotBoy จากลำดับชั้นฉากของคุณเหนือฟิลด์ "เป้าหมายการติดตาม" ที่เปิดเผยโดยพฤติกรรมต่อไปนี้ของเรา เพื่อให้สามารถติดตามตัวละครหลักได้

การเพิ่มออฟเซ็ต

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

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

 [SerializeField] float xOffset; [SerializeField] float yOffset; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x + xOffset, trackingTarget.position.y + yOffset, transform.position.z); }

ด้านล่างนี้ คุณสามารถดูการกำหนดค่าที่เป็นไปได้สำหรับสองฟิลด์ใหม่:

ทำให้ทุกอย่างราบรื่น

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

 [SerializeField] protected float followSpeed; // ... protected override void Update() { float xTarget = trackingTarget.position.x + xOffset; float yTarget = trackingTarget.position.y + yOffset; float xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); float yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); transform.position = new Vector3(xNew, yNew, transform.position.z); } 

หยุดอาการวิงเวียนศีรษะ: Axis Locking

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

 [SerializeField] protected bool isXLocked = false; [SerializeField] protected bool isYLocked = false; // ... float xNew = transform.position.x; if (!isXLocked) { xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); } float yNew = transform.position.y; if (!isYLocked) { yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); } 

ระบบเลน

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

ลองนึกภาพสถานการณ์ต่อไปนี้:

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

ทันทีที่ตัวละครเข้าสู่เลนอื่น กล้องจะเปลี่ยนไปใช้เลนนั้นและเคลื่อนที่ต่อไปในแนวนอนจากตรงนั้นไปจนกว่าจะมีการเปลี่ยนเลนถัดไป

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

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

การดำเนินการ

การใช้งานที่เป็นไปได้คือการเพิ่มเลนเป็นวัตถุธรรมดาในฉาก เราจะใช้พิกัดตำแหน่ง Y ที่จับคู่กับออฟเซ็ต Y ในสคริปต์ติดตามด้านบนเพื่อใช้งานระบบ ดังนั้นการวางตำแหน่งบนพิกัด X และ Z จึงไม่สำคัญ

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

และกลุ่ม LaneSystem จะดูแลการเคลื่อนย้ายกล้องระหว่างเลนตามตำแหน่งอ้างอิง followSpeed ​​ถูกใช้ที่นี่อีกครั้งสำหรับการแก้ไขตำแหน่ง เพื่อป้องกันไม่ให้การเปลี่ยนเลนกระทันหันเกินไป:

 [SerializeField] Transform reference; [SerializeField] List<Transform> lanes; [SerializeField] float followSpeed = 5f; // ... void Update() { float targetYCoord = transform.position.y; if (lanes.Count > 1) { int i = 0; for (i = 0; i < lanes.Count - 1; ++i) { if ((reference.position.y > lanes[i].position.y) && (reference.position.y <= lanes[i + 1].position.y)) { targetYCoord = lanes[i].position.y; break; } } if (i == lanes.Count - 1) targetYCoord = lanes[lanes.Count - 1].position.y; } else { targetYCoord = lanes[0].position.y; } float yCoord = Mathf.Lerp(transform.position.y, targetYCoord, Time.deltaTime * followSpeed); transform.position = new Vector3(transform.position.x, yCoord, transform.position.z); }

การใช้งานนี้ไม่ใช่แบบ WYSIWYG และเหลือไว้เป็นแบบฝึกหัดสำหรับผู้อ่าน

ระบบล็อคโหนด

การมีกล้องเคลื่อนที่ไปตามเลนเป็นเรื่องที่ดี แต่บางครั้ง เราจำเป็นต้องล็อคกล้องไว้กับบางสิ่ง ซึ่งเป็นจุดสนใจ (POI) ในฉากเกม

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

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

การดำเนินการ

ในการกำหนดค่าล็อคโหนด เพียงแค่สร้างวัตถุ (สามารถว่างเปล่าหรือเหมือนในภาพหน้าจอด้านล่าง สไปรท์) และแนบองค์ประกอบ Circle Collider 2D ขนาดใหญ่เข้ากับวัตถุนั้นเพื่อทำเครื่องหมายพื้นที่ที่ผู้เล่นจะอยู่เมื่อกล้องจะ เน้นโหนด คุณสามารถเลือกคอลไลเดอร์ประเภทใดก็ได้ ฉันเลือก Circle เป็นตัวอย่างที่นี่ สร้างแท็กที่คุณสามารถตรวจสอบได้ง่ายๆ เช่น "CameraNode" และกำหนดให้กับวัตถุนี้

เพิ่มคุณสมบัติต่อไปนี้ในสคริปต์ติดตามบนกล้องของคุณ:

 public Transform TrackingTarget { get { return trackingTarget; } set { trackingTarget = value; } }

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

 public class LockBehavior : MonoBehaviour { #region Public Fields [SerializeField] Camera camera; [SerializeField] string tag; #endregion #region Private private Transform previousTarget; private TrackingBehavior trackingBehavior; private bool isLocked = false; #endregion // Use this for initialization void Start() { trackingBehavior = camera.GetComponent<TrackingBehavior>(); } void OnTriggerEnter2D(Collider2D other) { if (other.tag == tag && !isLocked) { isLocked = true; PushTarget(other.transform); } } void OnTriggerExit2D(Collider2D other) { if (other.tag == tag && isLocked) { isLocked = false; PopTarget(); } } private void PushTarget(Transform newTarget) { previousTarget = trackingBehavior.TrackingTarget; trackingBehavior.TrackingTarget = newTarget; } private void PopTarget() { trackingBehavior.TrackingTarget = previousTarget; } } 

ซูมกล้อง

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

การซูมกล้อง 2D ใน Unity 3D สามารถทำได้โดยจัดการ orthographicSize ของกล้อง การแนบสคริปต์ถัดไปเป็นส่วนประกอบในกล้องและการใช้วิธี SetZoom เพื่อเปลี่ยนปัจจัยการซูมจะสร้างเอฟเฟกต์ที่ต้องการ 1.0 หมายถึงไม่มีการซูม 0.5 หมายถึงซูมเข้าสองครั้ง 2 หมายถึงซูมออกสองครั้ง เป็นต้น

 [SerializeField] float zoomFactor = 1.0f; [SerializeField] float zoomSpeed = 5.0f; private float originalSize = 0f; private Camera thisCamera; // Use this for initialization void Start() { thisCamera = GetComponent<Camera>(); originalSize = thisCamera.orthographicSize; } // Update is called once per frame void Update() { float targetSize = originalSize * zoomFactor; if (targetSize != thisCamera.orthographicSize) { thisCamera.orthographicSize = Mathf.Lerp(thisCamera.orthographicSize, targetSize, Time.deltaTime * zoomSpeed); } } void SetZoom(float zoomFactor) { this.zoomFactor = zoomFactor; }

หน้าจอสั่น

เมื่อใดก็ตามที่เราต้องการแสดงแผ่นดินไหว การระเบิด หรือเอฟเฟกต์อื่นๆ ในเกมของเรา เอฟเฟกต์การสั่นของกล้องก็มีประโยชน์

ตัวอย่างการใช้งานวิธีการทำที่มีอยู่ใน GitHub: gist.github.com/ftvs/5822103 การใช้งานค่อนข้างตรงไปตรงมา ต่างจากเอฟเฟกต์อื่น ๆ ที่เราได้กล่าวถึงไปแล้ว มันอาศัยการสุ่มเพียงเล็กน้อย

จาง & ซ้อนทับ

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

นี่คือตัวอย่างการกำหนดค่า โปรดทราบว่าวัตถุแผง UI ถูกกำหนดให้กับลูก "การซ้อนทับกล้อง" ของวัตถุกล้องหลัก Camera Overlay เปิดเผยสคริปต์ที่เรียกว่า Overlay ซึ่งมีคุณสมบัติดังต่อไปนี้:

 [SerializeField] Image overlay; // ... public void SetOverlayColor(Color color) { overlay.color = color; } 

เพื่อให้เอฟเฟกต์จางลง ให้เปลี่ยนสคริปต์โอเวอร์เลย์ของคุณโดยเพิ่มการแก้ไขให้กับสีเป้าหมายที่คุณตั้งค่าด้วย SetOverlayColor เช่นเดียวกับในสคริปต์ถัดไป และตั้งค่าสีเริ่มต้นของพาเนลของเราเป็นสีดำ (หรือสีขาว) และสีเป้าหมาย จนถึงสีสุดท้ายของโอเวอร์เลย์ของคุณ คุณสามารถเปลี่ยน fadeSpeed ​​เป็นสิ่งที่เหมาะกับความต้องการของคุณ ฉันคิดว่า 0.8 เหมาะสำหรับผู้เริ่มต้น ค่าของ fadeSpeed ​​ทำงานเป็นตัวแก้ไขเวลา 1.0 หมายความว่าจะเกิดขึ้นในหลายเฟรม แต่ภายในกรอบเวลา 1 วินาที 0.8 หมายความว่าจริง ๆ แล้วจะใช้เวลา 1/0.8 = 1.25 วินาทีจึงจะเสร็จสมบูรณ์

 public class Overlay : MonoBehaviour { #region Fields [SerializeField] Image overlay; [SerializeField] float fadeSpeed = 5f; [SerializeField] Color targetColor; #endregion void Update() { if (overlay.color != targetColor) { overlay.color = Color.Lerp(overlay.color, targetColor, Time.deltaTime * fadeSpeed); } } #region Public public void SetOverlayColor(Color color) { targetColor = color; } #endregion }

สรุป

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

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

ที่เกี่ยวข้อง: ความสามัคคีกับ MVC: วิธีเพิ่มระดับการพัฒนาเกมของคุณ