unity实现第一人称漫游(保姆级教程)

Posted 温柔哥`

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity实现第一人称漫游(保姆级教程)相关的知识,希望对你有一定的参考价值。

前言

        这篇文章是讲解的是如何使用已经写好的代码通过unity实现第一人称漫游的功能,就是说你可以直接把下面的代码拿去用就好,如果你想深入学习,你可以参考代码中的比较详细的注释和查阅相关的文档。

可以先看完成效果

漫游

步骤

1.创建 CameraController 和 PlayerController 两个C# Script

        * CameraController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour

    //我们通过控制Player的旋转方法,来控制相机视角的左右移动,所以我们需要一个Player的Tranform
    public Transform player;


    //定义两个float变量,来获取鼠标移动的值
    private float mouseX, mouseY;
    //我们可以给鼠标增加一个灵敏度
    public float mouseSensitivity;

    //mouseY中的GetAxis方法会返回-1到1之间的浮点数,在鼠标移动的时候,数值会随着方向的变化而变化,在鼠标不动时,数值会回弹到0,所以我们就会遇到鼠标上下移动时回弹的问题
    public float xRotation;

    private void Update()
    
        //在Update方法中,我们使用输入系统中的GetAxis方法来获取鼠标移动的值,乘以鼠标灵敏度再乘以Time.deltatime,鼠标移动的值就这样得到了
        //Input.GetAxis:它会在鼠标移动相应对应轴的过程中返回 -1 到 1 的值
        mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
        mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;

        xRotation -= mouseY;

        //使用数学函数Clamp限制
        xRotation = Mathf.Clamp(xRotation,-70f,70f);

        //这里使用Transform的Rotate()方法来旋转player
        //Vector3.up是向上的一个三维变量,和一个0,1,0的三维变量是一样的
        //我们需要控制player的y轴旋转才能让它左右旋转
        player.Rotate(Vector3.up * mouseX);
        //接下来我们要选转相机了,我们使用tranform.localRotation方法,让相机上下旋转,使用localRotation就可以不被父对象旋转影响,造成一些奇怪的问题
        //因为localRotation是属性,我们还要给他赋值
        transform.localRotation = Quaternion.Euler(xRotation, 0, 0);
    

        *PlayerController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour

    //获得player的CharacterController组件
    private CharacterController cc;

    public float moveSpeed;//移动速度

    public float jumpSpeed;//跳跃速度

    //定义获得按键值的两个变量
    private float horizontalMove, verticalMove;

    //定义三维变量dir控制方向
    private Vector3 dir;

    //重力
    public float gravity;

    private Vector3 velocity;//用来控制Y轴速度

    //我们只需要检测player是否在地上就可以了,这里我们可以使用Physics中的CheckSphere方法,如果定义的球体和物体发生碰撞,返回真
    //为了使用这个方法,我们需要定义几个变量
    public Transform groundCheck;//检测点的中心位置
    public float checkRedius;//检测点的半径
    public LayerMask groundLayer;//需要检测的图层
    //布尔值来存储CheckSphere的返回值
    public bool isGround;


    private void Start()
    
        //获取player的CharacterController组件
        cc = GetComponent<CharacterController>();

    
    private void Update()
    
        isGround = Physics.CheckSphere(groundCheck.position,checkRedius,groundLayer);
        if(isGround && velocity.y < 0)
        
            velocity.y = -2f;
        


        horizontalMove = Input.GetAxis("Horizontal") * moveSpeed;
        verticalMove = Input.GetAxis("Vertical") * moveSpeed;

        dir = transform.forward * verticalMove + transform.right * horizontalMove;
        cc.Move(dir * Time.deltaTime);

        //我们需要获取到跳跃按键的事件,使用Input中的GetButtonDown()方法,他会返回一个布尔值,当按下时才会返回真
        //Jump可以在InputManager中查看
        //在一瞬间有一个向上的速度,在过程中也会随着重力慢慢下降,如果想要让它只跳跃一次的话,加上isGround就行了
        if(Input.GetButtonDown("Jump") && isGround)
        
            velocity.y = jumpSpeed;
        


        velocity.y -= gravity * Time.deltaTime;//这样每秒它就会减去重力的值不断下降
        //再用CharacterController的Move方法来移动y轴
        cc.Move(velocity * Time.deltaTime);
    

2.创建一个Environment空物体,将与环境相关的资源到Environment下,点击Environment,将Layer改为我们自己创建的Ground层

        因为我已经提前创建好了Ground层,你们需要点击Add Layer,在最下面创建Ground层就可以了

 3.新建一个Capsule,命名为Player

 4.将主相机移动到刚刚创建的Player下,将主相机的位置移动到Player的上半部分

 5.给主相机挂上CameraController脚本,设置参数:

        *Player:就是刚刚创建的Player

        *Mouse Sensitivity:200

        *xRotation:0

 6.创建空物体 GroundCheck,将其放到 Player下,将位置移动到Player的底部

 7.将Player的 Capsule Colider 移除掉,添加 Character Controller 组件

 8.将 PlayerController 脚本挂到 Player 上,设置参数:

        *Move Speed:4

        *Jump Speed:5

        *Gravity:9.81

        *Ground Check:就是我们刚创建的Ground Check

        *Check Redius:0.4

        *Ground Layer:我们提前创建好的 Ground 层

 结语

        希望这篇文章可以帮助到你,我这边的素材是从 unity 中的 Asset Store 上下载的,如果你需要这个素材的话可以去搜索 Low-Poly Simply Nature Pack,有什么问题可以在评论区留言,感谢大家的支持!

游戏开发实战Unity手游第一人称视角,双摇杆控制,FPS射击游戏Demo(教程 | 含Demo工程源码)

一、前言

嗨,大家好,我是新发。
有同学私信我,问我能不能写一篇Unity手游第一人称视角控制的教程,

那么,今天就来做个Demo吧~

注:Demo工程源码见文章末尾

最终效果如下:


二、实现方案

1、无主之地,第一人称视角

第一人称视角的游戏大家应该不陌生,比如《无主之地》,

不过它是PC平台的,使用WASD控制移动,使用鼠标来控制镜头角度,单击鼠标左键开枪。注:你也可以接手柄来操作~

那么,如果我们想做移动端(手机端)的第一人称视角,如何做角色控制呢?

2、我之前做的摇杆控制

手机端比较常见的就是摇杆控制了,我之前在几篇博客中都有做过摇杆控制,
《【游戏开发创新】用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)》

《【游戏开发创新】上班通勤时间太长,做一个任意门,告别地铁与塞车(Unity | 建模 | ShaderGraph | 摇杆 | 角色控制)》

《【游戏开发实战】新发教你做游戏(六):教你2个步骤实现摇杆功能》

3、第一人称视角 + 摇杆控制

我上面做的都是第三人称视角的摇杆控制,我们改成第一人称视角即可,也就是第一人称视角+摇杆控制,像这样子,(图片说明:下图是我在《无主之地》游戏截图中P了摇杆的UI

三、开始实战

1、资源获取:Unity AssetStore

我没有《无主之地》的资源,没关系,我们去UnityAssetStore上找一下FPS射击游戏的资源,

注:Unity AssetStore地址:https://assetstore.unity.com/
关于资源的搜索,我之前写过一篇文章:《Unity游戏开发——新发教你做游戏(二):60个Unity免费资源获取网站》

搜索关键字FPS Pack,马上就搜到了一个免费的资源Low Poly FPS Pack


我们点击添加至我的资源(注意需要先登录你的Unity账号),

然后回到Unity编辑器中,点击菜单Windows / Package Manager,打开PackageManager窗口,就可以看到我们刚刚在AssetStore中添加的资源啦,我们把资源包下载并导入我们的工程即可。

注:你得先创建一个空工程,然后再导入资源包。我之前写过《学Unity的猫》系列教程,其中第三章有讲创建工程的步骤,
《【学Unity的猫】——第三章:第一个Unity工程,你好喵星人》

2、Low Poly FPS Pack资源运行效果

Low Poly FPS Pack资源包中已经帮我们做好了一个简单的第一人称FPS游戏Demo,我们打开Assault_Rifle_01_Demo场景,如下

运行,测试效果如下

如你所见,经典的PC平台FPS射击游戏玩法,使用WASD控制移动,使用鼠标来控制镜头角度,单击鼠标左键开枪。
接下来,我们要给它做下手术,改成 摇杆按钮 控制。

3、制作UI界面

3.1、UI素材获取

摇杆图片简单处理,用一个圆就可以了,然后我们还需要一些按钮图标,比如开枪、丢手雷、跳跃、装子弹等,这里推荐我平时经常用的一个查找图标资源的网站,阿里图标库:https://www.iconfont.cn/
比如我搜关键字:枪,就可以看到枪的图标啦~

可以直接免费下载,而且还可以事先修改图片颜色,建议改成白色,这样方便在Unity中设置其他颜色,

根据你自身的需要下载一些图标资源,我下载的图标如下,

注意,因为我们要在UGUI中显示这些图标,需要将它们的Texture Type设置为Sprite (2D and UI),如下

3.2、创建UI摄像机:UICamera

建议UI的显示使用一个单独的摄像机来渲染,我们在场景中创建一个Camera,重命名为UICamera

注:创建摄像机的操作步骤:在Hierarchy视图中鼠标右键,然后点击菜单Camera即可。


设置摄像机的Clear FlagsDepth only,设置Culling Mask只渲染UI层,设置ProjectionOrthographic(正交模式),设置Depth1(确保UI摄像机的比3D摄像机后渲染),

3.3、创建UI画布:Canvas

接下来,我们在Hierarchy视图中鼠标右键,点击菜单UI / Canvas,创建一个Canvas
设置一下参数,如下,目的是让UICamera来渲染Canvas的内容,并设置分辨率适配规则,

3.4、创建Panel:GamePanel

我们在Canvas子节点下创建一个Panel,重命名为GamePanel,并把Image组件禁用,

下面我们再在GamePanel下去创建UI对象。

3.5、制作摇杆

我们先做移动控制的摇杆,在GamePanel子节点下创建一个Image,重命名为moveJointedArm

设置左下角对齐,并调整坐标和尺寸,

像这样,它就是我们摇杆的检测区域,

我们把它的Image组件的颜色的Alpha通道设置为0,这样我们就看不见它了,

我们在moveJointedArm子节点下再创建两个Image,分别命名为bgcenter

分别设置一下尺寸,图片,颜色,如下

效果

同理,做一下右摇杆,

效果

3.6、制作操作按钮

除了移动和旋转,我们还有开枪、丢手雷、跳跃、装子弹的操作,配套需要制作对应的按钮。安排上,

效果

到这里,我们的UI界面就基本做好啦,下面就是写代码的环节了~

4、摇杆控制脚本:JointedArm.cs

摇杆的逻辑实现,我之前写过一篇文章讲过原理:《Unity使用ScrollRect制作摇杆(UGUI)》,这里我就不过对赘述,直接说下操作流程。

4.1、JointedArm.cs脚本代码

创建一个C#脚本,重命名为JointedArm.cs,代码如下:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System;

// 摇杆逻辑
public class JointedArm : ScrollRect, IPointerDownHandler

    public Action<Vector2> onDragCb;
    public Action onStopCb;

    protected float mRadius = 0f;
    
    private Transform trans;
    private RectTransform bgTrans;
    private Camera uiCam;
    private Vector3 originalPos;

    protected override void Awake()
    
        base.Awake();
        trans = transform;
        bgTrans = trans.Find("bg") as RectTransform;
        uiCam = GameObject.Find("UICamera").GetComponent<Camera>();
        originalPos = trans.localPosition;
    

    void Update()
    
        if (Input.GetMouseButtonUp(0))
        
            //松手时,摇杆复位
            trans.localPosition = originalPos;
            this.content.localPosition = Vector3.zero;
        
    

    protected override void Start()
    
        base.Start();
        //计算摇杆块的半径
        mRadius = bgTrans.sizeDelta.x * 0.5f;
    

    public override void OnDrag(PointerEventData eventData)
    
        base.OnDrag(eventData);
        var contentPostion = this.content.anchoredPosition;
        if (contentPostion.magnitude > mRadius)
        
            contentPostion = contentPostion.normalized * mRadius;
            SetContentAnchoredPosition(contentPostion);
        
        //Debug.Log("摇杆滑动,方向:" + contentPostion);

        if(null != onDragCb)
            onDragCb(contentPostion);
    

    public override void OnEndDrag(PointerEventData eventData)
    
        base.OnEndDrag(eventData);
        //Debug.Log("摇杆拖动结束");
        if (null != onStopCb)
            onStopCb();
    

    public void OnPointerDown(PointerEventData eventData)
    
        //点击到摇杆的区域,摇杆移动到点击的位置
        trans.position = uiCam.ScreenToWorldPoint(eventData.position);
        trans.localPosition = new Vector3(trans.localPosition.x, trans.localPosition.y, 0);
    

4.2、挂摇杆脚本,设置成员对象

moveJointedArm节点挂JointedArm脚本,并设置Contentcenter节点,如下,

同理设置右摇杆rotateJointedArm。到此,我们的摇杆就有交互效果了,如下

5、关联UI交互事件

5.1、定义UI成员:GamePanel.cs

我们创建一个GamePanel.cs脚本,声明UI对象,如下

using UnityEngine;

public class GamePanel : MonoBehaviour

 	/// <summary>
    /// 移动摇杆
    /// </summary>
    public JointedArm moveJointedArm;
    /// <summary>
    /// 旋转摇杆
    /// </summary>
    public JointedArm rotateJointedArm;
    /// <summary>
    /// 开枪按钮
    /// </summary>
    public GameObject fireBtn;
    /// <summary>
    /// 丢手雷按钮
    /// </summary>
    public GameObject bombBtn;
    /// <summary>
    /// 跳跃按钮
    /// </summary>
    public GameObject jumpBtn;
    /// <summary>
    /// 装子弹按钮
    /// </summary>
    public GameObject bulletBtn;
	
	void Start()
	
		// TODO 关联UI交互事件
	

5.2、设置UI对象

把它挂到GamePanel节点上,并设置变量对象,如下

5.3、设置摇杆委托

Start函数中添加摇杆的委托,如下

// GamePanel.cs

void Start()

   // 移动控制摇杆
    moveJointedArm.onDragCb = (direction) =>
    
        // TODO 抛出事件
    ;
    moveJointedArm.onStopCb = () =>
    
        // TODO 抛出事件
    ;
    
    // 旋转控制摇杆
    rotateJointedArm.onDragCb = (direction) =>
    
        // TODO 抛出事件
    ;
    rotateJointedArm.onStopCb = () =>
    
        // TODO 抛出事件
    ;

	// ...

我们要抛出一些事件,这里要封装一个事件管理器。

6、事件管理:订阅、注销、抛出

6.1、封装事件管理器:EventDispatcher.cs

我在之前的多篇文章中都有到和用到事件管理器,欢迎阅读我之前写的这些文章,里面都有用到事件管理器,
《【游戏开发框架】自制Unity通用游戏框架UnityXFramework,详细教程(Unity3D技能树 | tolua | 框架 | 热更新)》
《【游戏开发创新】用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)》
《【游戏开发实战】使用Unity 2019制作仿微信小游戏飞机大战(二):搭建基础游戏框架》
《【游戏开发实战】使用Unity制作水果消消乐游戏教程(三):水果拖动与交换逻辑》
《【游戏开发实战】使用Unity制作像天天酷跑一样的跑酷游戏——第七篇:游戏界面的基础UI》
《【学Unity的猫】第十二章:使用Unity制作背包,皮皮的梦想背包》
EventDispatcher脚本代码:

using UnityEngine;
using System.Collections.Generic;

public delegate void MyEventHandler(params object[] objs);

/// <summary>
/// 游戏事件管理器
/// </summary>
public class EventDispatcher

    /// <summary>
    /// 注册事件
    /// </summary>
    /// <param name="evt">事件名</param>
    /// <param name="handler">响应函数</param>
    public void Regist(string evt, MyEventHandler handler)
    
        if (handler == null)
            return;

        if (listeners.ContainsKey(evt))
        
            //这里涉及到Dispath过程中反注册问题,必须使用listeners[type]+=..
            listeners[evt] += handler;
        
        else
        
            listeners.Add(evt, handler);
        
    

    /// <summary>
    /// 注销事件
    /// </summary>
    /// <param name="evt">事件名</param>
    /// <param name="handler">响应函数</param>
    public void UnRegist(string evt, MyEventHandler handler)
    
        if (handler == null)
            return;

        if (listeners.ContainsKey(evt))
        
            //这里涉及到Dispath过程中反注册问题,必须使用listeners[type]-=..
            listeners[evt] -= handler;
            if (listeners[evt] == null)
            
                //已经没有监听者了,移除.
                listeners.Remove(evt);
            
        
    

    /// <summary>
    /// 抛出事件
    /// </summary>
    /// <param name="evt">事件名</param>
    /// <param name="objs">参数</param>
    public void DispatchEvent(string evt, params object[] objs)
    
        try
        
            if (listeners.ContainsKey(evt))
            
                MyEventHandler handler = listeners[evt];
                if (handler != null)
                    handler(objs);
            
        
        catch (System.Exception ex)
        
            Debug.LogErrorFormat(szErrorMessage, evt, ex.Message, ex.StackTrace);
        
    


    public void ClearEvents(string key)
    
        if (listeners.ContainsKey(key))
        
            listeners.Remove(key);
        
    

    private Dictionary<string, MyEventHandler> listeners = new Dictionary<string, MyEventHandler>();
    private readonly string szErrorMessage = "DispatchEvent Error, Event:0, Error:1, 2";

    private static EventDispatcher s_instance;
    public static EventDispatcher instance
    
        get
        
            if (null == s_instance)
                s_instance = new EventDispatcher();
            return s_instance;
        
    

6.2、定义事件名:EventNameDef.cs

我们再创建一个EventNameDef.cs脚本,用于定义事件名,如下

/// <summary>
/// 事件名定义
/// </summary>
public class EventNameDef 

    /// <summary>
    /// 移动
    /// </summary>
    public const string MOVE = "MOVE";
    /// <summary>
    /// 旋转
    /// </summary>
    public const string ROTATE = "ROTATE";
    /// <summary>
    /// 开枪
    /// </summary>
    public const string FIRE = "FIRE";
    /// <summary>
    /// 丢手榴弹
    /// </summary>
    public const string BOMB = "BOMB";
    /// <summary>
    /// 跳跃
    /// </summary>
    public const string JUMP = "JUMP";
    /// <summary>
    /// 装子弹
    /// </summary>
    public const string BULLET = "BULLET";

6.3、抛出事件

我们回到GamePanel.cs脚本,在摇杆的委托中抛出事件,

// GamePanel.cs

void Start()

     // 移动控制摇杆
     moveJointedArm.onDragCb = (direction) =>
     
         EventDispatcher.instance.DispatchEvent(EventNameDef.MOVE, new Vector3(direction.x, 0, direction.y).normalized, true);
     ;
     moveJointedArm.onStopCb = () =>
     
         EventDispatcher.instance.DispatchEvent(EventNameDef.MOVE, Vector3.zero, false);
     ;
     // 旋转控制摇杆
     rotateJointedArm.onDragCb = (direction) =>
     
         EventDispatcher.instance.DispatchEvent(EventNameDef.ROTATE, new Vector3(direction.x, 0, direction.y).normalized);
     ;
     rotateJointedArm.onStopCb = () =>
     
         EventDispatcher.instance.DispatchEvent(EventNameDef.ROTATE, Vector3.zero);
     ;
     
     // ...

6.4、订阅事件和注销事件

摇杆抛出的事件,最终的响应逻辑就是角色移动和旋转,那么我们就要在原来控制角色移动和旋转的脚本中添加事件订阅。
逻辑在哪里呢?逻辑在FPSControllerLPFP.cs脚本和AutomaticGunScriptLPFP.cs脚本中。
画个图,方便大家理解,

我们分别在FPSControllerLPFP.cs脚本和AutomaticGunScriptLPFP.cs脚本中添加事件订阅和注销,如下

// FPSControllerLPFP.cs

private void Start()

	// ...
	// 订阅事件
	EventDispatcher.instance.Regist(EventNameDef.MOVE, OnEventMove);
    EventDispatcher.instance.Regist(EventNameDef.ROTATE, OnEventRotate);
    // ...


private void OnDestroy()

	// 注销事件
    EventDispatcher.instance.UnRegist(EventNameDef.MOVE, OnEventMove);
    EventDispatcher.instance.UnRegist(EventNameDef.ROTATE, OnEventRotate);
    // ...

// AutomaticGunScriptLPFP.cs

private void Start()

	// ...
	// 订阅事件
    EventDispatcher.instance.Regist(EventNameDef.MOVE, OnEventMove)游戏开发实战Unity手游第一人称视角,双摇杆控制,FPS射击游戏Demo(教程 | 含Demo工程源码)

Unity第一人称视角开发

Unity第一人称视角开发

unity第一人称控制器脚步声在哪

LeapMotion在unity中保姆级使用教程

unity第一人称控制器怎么使用鼠标点击