3D RPG Course | Core 学习日记四:鼠标控制人物移动

news2024/11/29 12:55:39

前言

前边我们做好了Navgation智能导航地图烘焙,并且设置好了Player的NavMeshAgent,现在我们可以开始实现鼠标控制人物的移动了。除了控制人物移动以外,我们还需要实现鼠标指针的变换。



实现要点

要实现鼠标控制人物移动,点击地图对应的位置,然后玩家移动到那个位置,我们需要先了解两个关键的API,这两个API对于与用户界面和鼠标交互相关的场景十分关键,可以说大部分的3D场景鼠标交互都是用这两个API。


第一个API是Camera.ScreenPointToRay,官方文档描述:
在这里插入图片描述

这个API的作用是返回一条从摄像机射向屏幕指定坐标位置的一条射线。
函数原型:public Ray ScreenPointToRay (Vector3 pos);


第二个API是Physics.Raycast,官方文档描述:
在这里插入图片描述
这个API的作用是,判断一条射线是否有和场景中任何碰撞体发声碰撞。
函数原型:public static bool Raycast(Ray ray, out RaycastHit hitInfo);


MouseManager代码:

using System.ComponentModel;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class MouseManager : MonoBehaviour {

    public static MouseManager Instance;                        // 单例

    public Texture2D point, doorway, attack, target, arrow;     // 鼠标指针
    
    
    /** 事件广播 **/
    public event Action<Vector3> OnMouseClicked;                // 鼠标点击事件
    public event Action<GameObject> OnEnemyClicked;             // 点击敌人事件

    private RaycastHit hitInfo;                                 // 射线信息

    private void Awake() {
        if (Instance != null) {
            Destroy(gameObject);
        }
        Instance = this;
    }

    private void Update() {
        SetCursorTexture();
        MouseControl();
    }

    public void SetCursorTexture() {
        // 从摄像机发射一条射线到目标点,这在进行射线命中检测时非常有用,特别是与用户界面和鼠标交互相关的场景中
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        
        // 判断射线是否与任何碰撞体发生碰撞,并且通过out参数返回碰撞信息,然后我们通过碰撞体的tag来切换对应的鼠标贴图
        if (Physics.Raycast(ray, out hitInfo)) {
            // 切换鼠标贴图
            switch (hitInfo.collider.gameObject.tag) {
                case "Ground":
                    Cursor.SetCursor(target, new Vector2(16, 16), CursorMode.Auto);
                    break;
                case "Enemy":
                    Cursor.SetCursor(attack, new Vector2(16, 16), CursorMode.Auto);
                    break;
            }
        }
    }

    public void MouseControl() {
        // 如果鼠标左键点下,并且射线有和场景种的物体发生碰撞,则根据碰撞的物体执行对应的操作
        if (Input.GetMouseButton(0) && hitInfo.collider != null) {
            // 如果射线碰撞的物体tag是"Ground"则执行OnMouseClicked绑定的事件
            if (hitInfo.collider.gameObject.CompareTag("Ground")) {
                OnMouseClicked?.Invoke(hitInfo.point);
            }
            // 如果射线碰撞的物体tag是"Enemy"则执行OnEnemyClicked绑定的事件
            if (hitInfo.collider.gameObject.CompareTag("Enemy")) {
                OnEnemyClicked?.Invoke(hitInfo.collider.gameObject);
            }
        }
    }

}

总结:实现鼠标与场景交互功能,我们一般需要进行两个步骤,第一步先使用Camera.ScreenPointToRay方法获得从摄像机发射到对应屏幕坐标位置的射线,第二步使用Physics.Raycast判断这个射线是否有和场景中的碰撞体发生碰撞,并且通过out参数返回碰撞信息hitInfo,如果发生了碰撞,我们根据碰撞信息进行对应的操作。


PlayerController代码:

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

public class PlayerController : MonoBehaviour {

    private NavMeshAgent agent;
    private Animator anim;

    private GameObject attackTarget;
    private float attackCD;

    private void Awake() {
        agent = GetComponent<NavMeshAgent>();
        anim = GetComponent<Animator>();
    }

    private void Start() {
        // 绑定event事件
        MouseManager.Instance.OnMouseClicked += EventMoveToTarget;
        MouseManager.Instance.OnEnemyClicked += EventAttack;
    }

    private void Update() {
        SwitchAnimation();
        AttackCDTimeCounter();
    }

    /// <summary>
    ///     根据人物的移动速度切换相应的动画
    /// </summary>
    private void SwitchAnimation() {
        anim.SetFloat("Speed", agent.velocity.sqrMagnitude);
    }

    /// <summary>
    ///     走向目标位置
    /// </summary>
    /// <param name="target">目标位置</param>
    public void EventMoveToTarget(Vector3 target) {
        // 当我们点击了地图开始行走时,我们要中断正在进行的攻击逻辑
        StopAllCoroutines();
        // 因为在走向敌人并且攻击的逻辑完成之后我们会把NavMeshAgent的isStopped设为true,所以我们在这里要先将其重新置为false
        agent.isStopped = false;
        // 将NavMeshAgent的目标设置为target,这样角色就会寻路移动到target位置
        agent.destination = target;
    }

    /// <summary>
    ///     走向攻击目标并开始攻击
    /// </summary>
    /// <param name="target">攻击目标</param>
    private void EventAttack(GameObject target) {
        if (target != null) {
            attackTarget = target;
            StartCoroutine("MoveAndAttackEnemy");
        }
    }

    /// <summary>
    ///     攻击CD计时器
    /// </summary>
    private void AttackCDTimeCounter() {
        if (attackCD > 0f) {
            attackCD -= Time.deltaTime;
        }
    }

    /// <summary>
    ///     通过协程实现移动到敌人面前并采取攻击
    /// </summary>
    public IEnumerator MoveAndAttackEnemy() {
        // 将NavMeshAgent的isStopped置为false,开启移动
        agent.isStopped = false;


        // 如果玩家此时就在怪物旁边(距离小于等于1),那么玩家在攻击前需要先转身;如果不写这一句的话,当玩家离怪物距离小于1的时候,则玩家不会转身,就直接开始攻击
        transform.LookAt(attackTarget.transform);

        // 如果玩家没有走近怪物身边,则将NavMeshAgent的destination设置为怪物的位置
        while (Vector3.Distance(attackTarget.transform.position, transform.position) > 1.0f) {
            agent.destination = attackTarget.transform.position;
            yield return null;  // 暂停协程等待下一帧继续执行
        }
        
        // 将NavMeshAgent的isStopped置为true,停止移动
        agent.isStopped = true;

        // 攻击
        if (attackCD <= 0) {	// 攻击CD
            // 触发攻击动画
            anim.SetTrigger("Attack");
            // 重置攻击冷却
            attackCD = 0.5f;
        }
    }

}

代码要点:
(1)通过MouseManager的单例访问event并给event绑定事件监听;
(2)通过Animator动画控制器的状态值来触发动画改变;
(3)通过改变NavMeshAgent的destination属性值来控制人物的移动;
(4)因为destination属性值控制人物的移动是异步操作,所以我们不能直接在设置destination属性之后启动攻击动画,以下这种方式是错误的:

agent.destination = attackTarget.transform.position;
anim.SetFloat("Speed", agent.velocity.sqrMagnitude);

我们要通过协程来实现移动到怪物身边之后启动攻击动画的逻辑。

/// <summary>
///     通过协程实现移动到敌人面前并采取攻击
/// </summary>
public IEnumerator MoveAndAttackEnemy() {
    // 将NavMeshAgent的isStopped置为false,开启移动
    agent.isStopped = false;


    // 如果玩家此时就在怪物旁边(距离小于等于1),那么玩家在攻击前需要先转身;如果不写这一句的话,当玩家离怪物距离小于1的时候,则玩家不会转身,就直接开始攻击
    transform.LookAt(attackTarget.transform);

    // 如果玩家没有走近怪物身边,则将NavMeshAgent的destination设置为怪物的位置
    while (Vector3.Distance(attackTarget.transform.position, transform.position) > 1.0f) {
        agent.destination = attackTarget.transform.position;
        yield return null;  // 暂停协程等待下一帧继续执行
    }
    
    // 将NavMeshAgent的isStopped置为true,停止移动
    agent.isStopped = true;

    // 攻击
    if (attackCD <= 0) {	// 攻击CD
        // 触发攻击动画
        anim.SetTrigger("Attack");
        // 重置攻击冷却
        attackCD = 0.5f;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1176670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VHDL基础知识笔记(1)

1.实体&#xff1a;其电路意义相当于器件&#xff0c;它相当于电路原理图上的元器件符号。它给出了器件的输入输出引脚。实体又被称为模块。 2.结构体&#xff1a;这个部分会给出实体&#xff08;或者说模块&#xff09;的具体实现&#xff0c;指定输入和输出的行为。结构体的…

十分钟搭建自己的在线书库随时随地看小说,Kindle不再盖泡面!

Kindle中国电子书店停运不要慌&#xff0c;十分钟搭建自己的在线书库随时随地看小说&#xff01; 文章目录 Kindle中国电子书店停运不要慌&#xff0c;十分钟搭建自己的在线书库随时随地看小说&#xff01;1.网络书库软件下载安装2.网络书库服务器设置3.内网穿透工具设置4.公网…

5K买的300集全套JAVA面试视频、10W+字文档成功上岸一线互联网大厂

前言&#xff1a; 今年这情况&#xff0c;真心建议所有 Java 后端不要随便被“行情差洗脑”&#xff01; 目前职友集上搜到的 java 岗位仍有22万&#xff0c;招聘需求相比其他行业不算少&#xff01; 大家最应该关注的是&#xff1a;自身技术硬度如何&#xff1f; 毕竟不管在…

Redis学习(十)RedisTemplate 对各种数据类型的支持

目录 一、SpringDataRedis 简介1.1 什么是 Redis&#xff1f;1.2 什么是 Jedis&#xff1f;1.3 什么是 Spring Data Redis&#xff1f; 二、RedisTemplate 中 API 使用2.1 pom.xml 依赖2.2 配置文件2.3 RedisTemplate 的直接方法2.4 String 类型相关操作2.5 Hash 类型相关操作2…

台灯选用什么类型好?双十一值得入手的护眼台灯推荐

如何给孩子挑选一盏能够护眼的台灯一直是许多家长都为之头痛的一大难题&#xff0c;主要是如今市面上的台灯实在太多了&#xff0c;而且迭代速度非常快&#xff0c;再加上这些产品中还混杂了许多不专业品牌、网红产品和低价劣质产品等等&#xff0c;想要挑选到一款好的台灯确实…

MVC模式和三层架构:

MVC模式&#xff1a; M&#xff1a;Model&#xff0c;业务模型&#xff0c;处理业务 V&#xff1a;View&#xff0c;视图&#xff0c;界面展示 C&#xff1a;Controller&#xff0c;控制器&#xff0c;处理请求&#xff0c;调用模型和视图 MVC优点&#xff1a; 职责单一&am…

C++ Lambda表达式 在竞赛中提高你的代码连贯性

本文中的lambda表达式使用方式应该在目前的所有比赛中&#xff08;C11及以上&#xff09;都是可以使用的&#xff0c;因为比较落后的蓝桥杯都更新到了C14。 当题目做的越来越多&#xff0c;难度越来越大&#xff0c;相应的代码就会越来越长&#xff0c;通常在100行左右。 在行…

你的GPU能跑Llama 2等大模型吗?用这个开源项目上手测一测

你的 GPU 内存够用吗&#xff1f;这有一个项目&#xff0c;可以提前帮你查看。 在算力为王的时代&#xff0c;你的 GPU 可以顺畅的运行大模型&#xff08;LLM&#xff09;吗&#xff1f; 对于这一问题&#xff0c;很多人都难以给出确切的回答&#xff0c;不知该如何计算 GPU 内…

云栖大会72小时沉浸式精彩回顾

计算&#xff0c;为了无法计算的价值 2023 杭州云栖大会震撼落幕 自2015年&#xff0c;云计算支撑着移动互联网创新 AI时代&#xff0c;继续支撑所有开发者的创新与梦想 当大会主题再次回归 让我们也打开时空隧道 一起回顾72小时云栖之旅 打造一朵AI时代最开放的云 随着…

SpringBoot单元测试报错“Error creating bean with name ‘serverEndpointExporter‘ ”

问题场景 在SpringBoot中使用单元测试时&#xff0c;出现以下报错&#xff0c;意思是创建名为‘serverEndpointExporter’的bean时出错。 org.springframework.beans.factory.BeanCreationException: Error creating bean with name serverEndpointExporter defined in class…

折叠旗舰新战局:华为先行,OPPO接棒

乌云中的曙光&#xff0c;总能带给人希望。 全球智能手机出货量已经连续八个季度下滑&#xff0c;行业里的乌云挥之不散。不过&#xff0c;也能看到高端市场逆势上涨&#xff0c;散发光亮。个中逻辑在于&#xff0c;当前换机周期已经达到了34个月&#xff0c;只有创新产品才能…

使用R语言构建HTTP爬虫:IP管理与策略

目录 摘要 一、HTTP爬虫与IP管理概述 二、使用R语言进行IP管理 三、爬虫的伦理与合规性 四、注意事项 结论 摘要 本文深入探讨了使用R语言构建HTTP爬虫时如何有效管理IP地址。由于网络爬虫高频、大量的请求可能导致IP被封禁&#xff0c;因此合理的IP管理策略显得尤为重要…

简单的nvm语法

文章目录 导文常用的nvm语法 导文 nvm&#xff08;Node Version Manager&#xff09;是一个用于管理Node.js版本的工具&#xff0c;它提供了一些简单的命令来操作Node.js的安装、切换和管理。 常用的nvm语法 这些命令可以在终端中执行&#xff0c;以方便地进行Node.js版本的管…

汇编-DUP操作符

DUP操作符使用整数表达式作为计数器&#xff0c; 为多个数据项分配存储空间。 在为字符串或数组分配存储空间时&#xff0c;这个操作符尤其有用&#xff0c;并且可以使用初始化或非初始化数据&#xff1a; .data BYTE 20 DUP(0) ;20个字节&#xff0c;都等于0 BYTE 20 …

C++入门学习(2)缺省参数

前言 继上回我们学习了命名空间和在C里面的输入输出之后&#xff0c;我们知道了命名空间可以解决C语言命名冲突的问题&#xff0c;也简单掌握了命名空间的使用&#xff0c;我们已经可以写出简单的C代码&#xff0c;所以大家还是要继续练习敲代码&#xff0c;提高语感。 #includ…

再谈Android重要组件——Handler(Native篇)

前言 最近工作比较忙&#xff0c;没怎么记录东西了。Android的Handler重要性不必赘述&#xff0c;之前也写过几篇关于hanlder的文章了&#xff1a; Handler有多深&#xff1f;连环二十七问Android多线程&#xff1a;深入分析 Handler机制源码&#xff08;二&#xff09; And…

构建金融新核心生态!金融级数字底座“源启”与易捷行云可进化数字原生平台完成互认证

近日&#xff0c;金融级数字底座“源启”顺利与易捷行云可进化数字原生云平台V6完成互认证。易捷行云云平台V6可支持金融机构核心应用实现高速响应、秒级扩容&#xff0c;并切实保障银行核心系统安全稳定&#xff0c;符合“源启”金融级数字底座&#xff08;2.0版&#xff09;技…

开源Open JDK哪家强?Oracle JDK替代解决方案盘点

开源Open JDK哪家强&#xff1f;Oracle JDK替代解决方案盘点 1.1 引言1.2 Open JDK 版本分支1.3 Open JDK 选择推荐1.3.1 Liberica JDK 是什么&#xff1f;1.3.2 Oracle JDK、 OpenJDK 和Liberica JDK 之间有什么区别&#xff1f;1.3.2 OpenJDK 和 Liberica JDK 一样吗&#xf…

2034:D 类音频功率放大器的引脚排列

2034芯片是一款无滤波器5.2W单声道纯D类音频放大器。低EMI适合应用于便携式设备中。 2034芯片的引脚排列&#xff1a; 2034芯片管脚描述&#xff1a; 2034芯片具有关断功能&#xff0c;延长系统的待机时间。过热保护功能增强系统。POP声抑Z功能改进了系统的听觉感受&#xff0…

企业通配符SSL证书的特点

企业通配符SSL证书是一种数字证书&#xff0c;其可以用于保护多个企业网站&#xff0c;对网站传输信息进行加密服务。这种证书通常适用于拥有多个子域名或二级域名的企事业单位。今天就随SSL盾小编了解企业通配符SSL证书的相关信息。 1. 保护所有域名和子域名&#xff1a;企业通…