Unity3D实现3D迷宫小游戏的示例代码

 

一、前言

闲来无事,从零开始整个《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<Rigidbody>();
          m_Capsule = GetComponent<CapsuleCollider>();
          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迷宫游戏的资料请关注编程教程其它相关文章!

 一、使用SqlParameter的方式代码:using System;using System.Collections.Generic;using System.Component ...