一、前言
闲来无事,从零开始整个《3D迷宫》小游戏。
本篇文章会详细介绍构思、实现思路,希望可以帮助到有缘人。
二、构思
首先,要实现一个小游戏,心里肯定要有一个大概的想法,然后就是将想法完善起来。
我的想法就是一个用立体的墙搭建的迷宫,然后控制人物在迷宫中移动,最后找到出口,就这么简单。
当然,这是一个雏形,比如可以加点音效、背景、关卡、解密等。
那么整理一下实现思路就是:
- 构建3D迷宫
- 实现人物移动
- 实现出入口逻辑
OK,下面就正式开发。
三、正式开发
3-1、搭建场景
首先,新建个项目,我用了Unity 2019.4.7f1版本,项目名称跟位置按照自己的喜好设置即可:
接下来构建迷宫,先新建一个Plane,让它最够大,扩大10倍:
新建Cube,调整大小缩放,让它看起来像是一堵墙,然后构建迷宫:
3-2、设置出入口
放两个Cube,设置缩放,将出口名字改成Exit,这样就行了,到时候通过碰撞检测检测小球是否到达出口即可。
3-3、添加角色
在Hierarchy视图,右击选择3D Objcet→Capsule,新建一个球体,添加Rigibody组件:
设置Drag抓地力为1。
就这样设置就行了,在实际运行中如果参数不合适还可以再调整。
将小球移动到入口的位置。
3-4、实现角色移动
这里直接使用官方的第一人称移动代码RigidbodyFirstPersonController .cs:
public class RigidbodyFirstPersonController : MonoBehaviour { [Serializable] public class MovementSettings { public float ForwardSpeed = 8.0f; // Speed when walking forward public float BackwardSpeed = 4.0f; // Speed when walking backwards public float StrafeSpeed = 4.0f; // Speed when walking sideways public float RunMultiplier = 2.0f; // Speed when sprinting public KeyCode RunKey = KeyCode.LeftShift; public float JumpForce = 30f; public AnimationCurve SlopeCurveModifier = new AnimationCurve(new Keyframe(-90.0f, 1.0f), new Keyframe(0.0f, 1.0f), new Keyframe(90.0f, 0.0f)); [HideInInspector] public float CurrentTargetSpeed = 8f; #if !MOBILE_INPUT private bool m_Running; #endif public void UpdateDesiredTargetSpeed(Vector2 input) { if (input == Vector2.zero) return; if (input.x > 0 || input.x < 0) { //strafe CurrentTargetSpeed = StrafeSpeed; } if (input.y < 0) { //backwards CurrentTargetSpeed = BackwardSpeed; } if (input.y > 0) { //forwards //handled last as if strafing and moving forward at the same time forwards speed should take precedence CurrentTargetSpeed = ForwardSpeed; } #if !MOBILE_INPUT if (Input.GetKey(RunKey)) { CurrentTargetSpeed *= RunMultiplier; m_Running = true; } else { m_Running = false; } #endif } #if !MOBILE_INPUT public bool Running { get { return m_Running; } } #endif } [Serializable] public class AdvancedSettings { public float groundCheckDistance = 0.01f; // distance for checking if the controller is grounded ( 0.01f seems to work best for this ) public float stickToGroundHelperDistance = 0.5f; // stops the character public float slowDownRate = 20f; // rate at which the controller comes to a stop when there is no input public bool airControl; // can the user control the direction that is being moved in the air [Tooltip("set it to 0.1 or more if you get stuck in wall")] public float shellOffset; //reduce the radius by that ratio to avoid getting stuck in wall (a value of 0.1f is nice) } public Camera cam; public MovementSettings movementSettings = new MovementSettings(); public MouseLook mouseLook = new MouseLook(); public AdvancedSettings advancedSettings = new AdvancedSettings(); private Rigidbody m_RigidBody; private CapsuleCollider m_Capsule; private float m_YRotation; private Vector3 m_GroundContactNormal; private bool m_Jump, m_PreviouslyGrounded, m_Jumping, m_IsGrounded; public Vector3 Velocity { get { return m_RigidBody.velocity; } } public bool Grounded { get { return m_IsGrounded; } } public bool Jumping { get { return m_Jumping; } } public bool Running { get { #if !MOBILE_INPUT return movementSettings.Running; #else return false; #endif } } private void Start() { m_RigidBody = GetComponent(); m_Capsule = GetComponent (); mouseLook.Init (transform, cam.transform); } private void Update() { RotateView(); if (CrossPlatformInputManager.GetButtonDown("Jump") && !m_Jump) { m_Jump = true; } } private void FixedUpdate() { GroundCheck(); Vector2 input = GetInput(); if ((Mathf.Abs(input.x) > float.Epsilon || Mathf.Abs(input.y) > float.Epsilon) && (advancedSettings.airControl || m_IsGrounded)) { // always move along the camera forward as it is the direction that it being aimed at Vector3 desiredMove = cam.transform.forward*input.y + cam.transform.right*input.x; desiredMove = Vector3.ProjectOnPlane(desiredMove, m_GroundContactNormal).normalized; desiredMove.x = desiredMove.x*movementSettings.CurrentTargetSpeed; desiredMove.z = desiredMove.z*movementSettings.CurrentTargetSpeed; desiredMove.y = desiredMove.y*movementSettings.CurrentTargetSpeed; if (m_RigidBody.velocity.sqrMagnitude < (movementSettings.CurrentTargetSpeed*movementSettings.CurrentTargetSpeed)) { m_RigidBody.AddForce(desiredMove*SlopeMultiplier(), ForceMode.Impulse); } } if (m_IsGrounded) { m_RigidBody.drag = 5f; if (m_Jump) { m_RigidBody.drag = 0f; m_RigidBody.velocity = new Vector3(m_RigidBody.velocity.x, 0f, m_RigidBody.velocity.z); m_RigidBody.AddForce(new Vector3(0f, movementSettings.JumpForce, 0f), ForceMode.Impulse); m_Jumping = true; } if (!m_Jumping && Mathf.Abs(input.x) < float.Epsilon && Mathf.Abs(input.y) < float.Epsilon && m_RigidBody.velocity.magnitude < 1f) { m_RigidBody.Sleep(); } } else { m_RigidBody.drag = 0f; if (m_PreviouslyGrounded && !m_Jumping) { StickToGroundHelper(); } } m_Jump = false; } private float SlopeMultiplier() { float angle = Vector3.Angle(m_GroundContactNormal, Vector3.up); return movementSettings.SlopeCurveModifier.Evaluate(angle); } private void StickToGroundHelper() { RaycastHit hitInfo; if (Physics.SphereCast(transform.position, m_Capsule.radius * (1.0f - advancedSettings.shellOffset), Vector3.down, out hitInfo, ((m_Capsule.height/2f) - m_Capsule.radius) + advancedSettings.stickToGroundHelperDistance, Physics.AllLayers, QueryTriggerInteraction.Ignore)) { if (Mathf.Abs(Vector3.Angle(hitInfo.normal, Vector3.up)) < 85f) { m_RigidBody.velocity = Vector3.ProjectOnPlane(m_RigidBody.velocity, hitInfo.normal); } } } private Vector2 GetInput() { Vector2 input = new Vector2 { x = CrossPlatformInputManager.GetAxis("Horizontal"), y = CrossPlatformInputManager.GetAxis("Vertical") }; movementSettings.UpdateDesiredTargetSpeed(input); return input; } private void RotateView() { //avoids the mouse looking if the game is effectively paused if (Mathf.Abs(Time.timeScale) < float.Epsilon) return; // get the rotation before it's changed float oldYRotation = transform.eulerAngles.y; mouseLook.LookRotation (transform, cam.transform); if (m_IsGrounded || advancedSettings.airControl) { // Rotate the rigidbody velocity to match the new direction that the character is looking Quaternion velRotation = Quaternion.AngleAxis(transform.eulerAngles.y - oldYRotation, Vector3.up); m_RigidBody.velocity = velRotation*m_RigidBody.velocity; } } /// sphere cast down just beyond the bottom of the capsule to see if the capsule is colliding round the bottom private void GroundCheck() { m_PreviouslyGrounded = m_IsGrounded; RaycastHit hitInfo; if (Physics.SphereCast(transform.position, m_Capsule.radius * (1.0f - advancedSettings.shellOffset), Vector3.down, out hitInfo, ((m_Capsule.height/2f) - m_Capsule.radius) + advancedSettings.groundCheckDistance, Physics.AllLayers, QueryTriggerInteraction.Ignore)) { m_IsGrounded = true; m_GroundContactNormal = hitInfo.normal; } else { m_IsGrounded = false; m_GroundContactNormal = Vector3.up; } if (!m_PreviouslyGrounded && m_IsGrounded && m_Jumping) { m_Jumping = false; } } }
MouseLook.cs:
public class MouseLook { public float XSensitivity = 2f; public float YSensitivity = 2f; public bool clampVerticalRotation = true; public float MinimumX = -90F; public float MaximumX = 90F; public bool smooth; public float smoothTime = 5f; public bool lockCursor = true; private Quaternion m_CharacterTargetRot; private Quaternion m_CameraTargetRot; private bool m_cursorIsLocked = true; public void Init(Transform character, Transform camera) { m_CharacterTargetRot = character.localRotation; m_CameraTargetRot = camera.localRotation; } public void LookRotation(Transform character, Transform camera) { float yRot = CrossPlatformInputManager.GetAxis("Mouse X") * XSensitivity; float xRot = CrossPlatformInputManager.GetAxis("Mouse Y") * YSensitivity; m_CharacterTargetRot *= Quaternion.Euler (0f, yRot, 0f); m_CameraTargetRot *= Quaternion.Euler (-xRot, 0f, 0f); if(clampVerticalRotation) m_CameraTargetRot = ClampRotationAroundXAxis (m_CameraTargetRot); if(smooth) { character.localRotation = Quaternion.Slerp (character.localRotation, m_CharacterTargetRot, smoothTime * Time.deltaTime); camera.localRotation = Quaternion.Slerp (camera.localRotation, m_CameraTargetRot, smoothTime * Time.deltaTime); } else { character.localRotation = m_CharacterTargetRot; camera.localRotation = m_CameraTargetRot; } UpdateCursorLock(); } public void SetCursorLock(bool value) { lockCursor = value; if(!lockCursor) {//we force unlock the cursor if the user disable the cursor locking helper Cursor.lockState = CursorLockMode.None; Cursor.visible = true; } } public void UpdateCursorLock() { //if the user set "lockCursor" we check & properly lock the cursos if (lockCursor) InternalLockUpdate(); } private void InternalLockUpdate() { if(Input.GetKeyUp(KeyCode.Escape)) { m_cursorIsLocked = false; } else if(Input.GetMouseButtonUp(0)) { m_cursorIsLocked = true; } if (m_cursorIsLocked) { Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } else if (!m_cursorIsLocked) { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; } } Quaternion ClampRotationAroundXAxis(Quaternion q) { q.x /= q.w; q.y /= q.w; q.z /= q.w; q.w = 1.0f; float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan (q.x); angleX = Mathf.Clamp (angleX, MinimumX, MaximumX); q.x = Mathf.Tan (0.5f * Mathf.Deg2Rad * angleX); return q; } }
将所有的墙的父物体设置为地板。
设置摄像机的位置和父物体:
运行程序:
3-5、出入口逻辑
出口用碰撞检测,新建脚本ExitControl.cs,编辑代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class ExitControl : MonoBehaviour { void OnCollisionEnter(Collider col) { if (col.gameObject.name == "Capsule") { SceneManager.LoadScene(SceneManager.GetActiveScene().name); } } }
将代码附给Exit对象。
结束了。
四、总结
本文实现了一个《3D迷宫》小游戏。
首先,搭建场景,然后实现角色移动,出入口逻辑。
整天代码比较简单,官方的移动代码也可以学习一下。
以上就是基于Unity3D实现3D迷宫小游戏的示例代码的详细内容,更多关于Unity3D迷宫游戏的资料请关注脚本之家其它相关文章!