Unity组件开发--相机跟随角色和旋转

1.相机跟随组件,节点:

2.相机跟随组件脚本:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Burst.Intrinsics;
using UnityEngine;
using UnityEngine.UI;

public class CameraFollow : Singleton<CameraFollow> {
    public Transform firstAngleTarget; //第一人称跟随的目标
    public Transform threeAngleTarget; //第三人称跟随的目标
    public float radius;
    public float polarDeg;
    public float elevationDeg;
    private Transform target;
    public bool isLookAt;

    public float lerpSpeed = 0;

    //是否第一人称视角
    private bool isFirstAngle = false;

    LayerMask mask;
    /// <summary>
    /// 极坐标转换成笛卡尔坐标
    /// </summary>
    /// <param name="radius"></param>
    /// <param name="angle"></param>
    /// <returns></returns>
    /// 

    private bool isDriven;

    private void Awake() {
        EventManager.Instance.AddListener(EventName.PlayerDriving, (s, e) => {  //开车事件触发
            var arg = e as PlayerDrivingEventArgs;
            if (arg.driveObj && arg.driveObj.GetComponent<VehicleBase>() && arg.driveObj.GetComponent<VehicleBase>().driveCam) {
                gameObject.SetActive(false);
            }
            else {
                isDriven = true;
            }
        });

        EventManager.Instance.AddListener(EventName.PlayerDown, (s, e) => {  //开车事件触发
            var arg = e as PlayerDrivingEventArgs;                                              //isDriven = false;
            if (arg.driveObj && arg.driveObj.GetComponent<VehicleBase>() && arg.driveObj.GetComponent<VehicleBase>().driveCam) {
                gameObject.SetActive(true);
            }
            else {
                isDriven = false;
            }
        });
    }

    private void Start()
    {

        mask.value = (1 << LayerMask.NameToLayer("Ground")) | (1 << LayerMask.NameToLayer("Wall"));
        this.target = this.threeAngleTarget;
        EventManager.Instance.AddListener(EventName.ChangeAngle, changeAngle);

        var offset = SphericalToCartesian(radius, DegreeToRadian(polarDeg), DegreeToRadian(elevationDeg));
        //transform.position = Vector3.Lerp(transform.position, target.position + offset, Time.deltaTime * 10);
        transform.position = target.position + offset;
    }

    private void changeAngle(object sender, EventArgs e) {
        var data = e as AngleChangeEventArgs;
        if (data != null) {
            if (data.angleIndex == 1) {
                this.target = this.firstAngleTarget;
                this.radius = 0;
                isFirstAngle = true;
                transform.position = target.position;
                transform.forward = target.forward;
            } else if (data.angleIndex == 3) {
                this.target = this.threeAngleTarget;
                this.radius = 6;
                isFirstAngle = false;
            }
        }
        Debug.Log("摄像机视角改变" + e);
    }
    public Vector2 PolarToCartesian(float radius, float angle) {
        float x = radius * Mathf.Cos(angle);
        float y = radius * Mathf.Sin(angle);
        return new Vector2(x, y);
    }

    public static float DegreeToRadian(float degree) {
        return degree * Mathf.Deg2Rad;
    }

    public static Vector3 SphericalToCartesian(float radius, float polar, float elevation) {
        float a = radius * Mathf.Cos(elevation);
        float x = a * Mathf.Cos(polar);
        float y = radius * Mathf.Sin(elevation);
        float z = a * Mathf.Sin(polar);
        return new Vector3(x, y, z);
    }

    public static void CartesianToSpherical(Vector3 cartesian, out float radius, out float polar, out float elevation) {
        radius = Mathf.Sqrt(Mathf.Pow(cartesian.x, 2) + Mathf.Pow(cartesian.y, 2) + Mathf.Pow(cartesian.z, 2));
        polar = Mathf.Atan2(cartesian.z, cartesian.x);
        elevation = Mathf.Asin(cartesian.y / radius);
    }



    void LateUpdate() {

        if (isDriven) {
            var offset = SphericalToCartesian(6f, DegreeToRadian(270), DegreeToRadian(-15));
            transform.position = Vector3.Lerp(transform.position, target.TransformPoint(target.localPosition + offset), Time.deltaTime * 3);
            //transform.position = target.TransformPoint(target.localPosition + offset);
            transform.LookAt(target);
            return;
        }

        if (isFirstAngle) {
            //var offset = SphericalToCartesian(radius, DegreeToRadian(polarDeg), DegreeToRadian(elevationDeg));
            target.eulerAngles = new Vector3(elevationDeg, -polarDeg, 0);
            transform.forward = target.forward;
            transform.position = target.position;
            if (PlayerController.Instance.animator) {
                var euler = PlayerController.Instance.animator.transform.eulerAngles;
                PlayerController.Instance.animator.transform.eulerAngles = new Vector3(euler.x, target.eulerAngles.y, euler.z);
            }

        }
       else {
            Ctrl_Cam_Move();
            CtrThird();
        }

       
       

        
    }

    void CtrThird() {

        var offset = SphericalToCartesian(radius, DegreeToRadian(polarDeg), DegreeToRadian(elevationDeg));
        //更新相机位置
        //transform.position = target.position + offset;
        transform.position = target.position + offset;
        //TODO:做成CAMERA NEAR OBJ,进行隐藏
        if (PlayerController.Instance.animator != null) PlayerController.Instance.animator.gameObject.SetActive(true);
        //计算完位置之后处理让镜头不会穿墙
        Vector3 direction = transform.position - target.position;
        float distance = direction.magnitude;
        direction.Normalize();
        RaycastHit hit;
        if (Physics.Raycast(target.transform.position, direction, out hit, distance, mask.value)) {
            var dstPos = hit.point - distance * distance * direction * 0.01f;
            var offsetDis = target.position - dstPos;
            CartesianToSpherical(offsetDis, out var compareRadius, out _, out _);
            if (compareRadius < 1f)  {
                if (PlayerController.Instance.animator != null)  PlayerController.Instance.animator.gameObject.SetActive(false);
            }
            transform.position = dstPos;

        }
        transform.eulerAngles = target.eulerAngles;

        if (isLookAt) {
            

            transform.LookAt(target);
            
        }
    }

    public void Ctrl_Cam_Move()
    {
        if (EditorModel.Instance.CurrentUnlock != null) { //解锁其他物体的时候,镜头不动, 要前后移动的是物体
            return;
        }

        if (Input.GetAxis("Mouse ScrollWheel") > 0)
        {
            //transform.Translate(Vector3.forward * 1f);//速度可调  自行调整
            radius = Math.Clamp(radius - 1.0f, 2, 15);
        }
        if (Input.GetAxis("Mouse ScrollWheel") < 0)
        {
            //transform.Translate(Vector3.forward * -1f);//速度可调  自行调整
           
            radius = Math.Clamp(radius + 1.0f, 2, 15);
        }
    }
}

3.相机跟随角色视角旋转:


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

public class CameraRotate : MonoBehaviour
{

    public float speedH;
    //public float speedVertical;
    public bool isMobile;
    //[Range(0f, 1f)]
    //public float damping;
    CameraFollow cameraFollow;
    private float currentHorizontal;
    private float currentVertical;
    private Vector3 lastMousePosition;
    private bool isDragging;

    float time;
    private float velocityY;
    private float velocityX;


    PointerEventData eventDataCurrentPosition;
    //private bool isDragging;
    public static CameraRotate instance;
    private void Awake() {
        instance = this;
        //UI交互要禁用玩家的控制
        
        cameraFollow = Camera.main.GetComponent<CameraFollow>();
        currentHorizontal = cameraFollow.polarDeg;
        time = Time.realtimeSinceStartup;
    }

    // Start is called before the first frame update
    void Start() {
        
    }

    public void SetPolarDeg(float degree) {
        currentHorizontal = degree;
        cameraFollow.polarDeg = degree;
    }

    private bool IsPointerOverUIObject() {//判断是否点击的是UI,有效应对安卓没有反应的情况,true为UI


        
        if (eventDataCurrentPosition == null) {
            eventDataCurrentPosition = new PointerEventData(EventSystem.current);
        }

        eventDataCurrentPosition.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
        

        

        return results.Count > 0;
    }

    // Update is called once per frame
    void LateUpdate() {

        //if (IsPointerOverUIObject()) {
        //    lastMousePosition = Input.mousePosition;
        //    return;//点击到UI不处理
        //}

        // 检查鼠标左键是否按下如果按下的那一下是在UI之上,则不让其旋转
        if (PlayerData.Instance.isRunningPC)
        {
            if (Input.GetMouseButtonDown(0) && IsPointerOverUIObject() == false)
            {
                // 记录鼠标点击位置
                lastMousePosition = Input.mousePosition;
                isDragging = true;
            }
            else if (Input.GetMouseButtonUp(0))
            {
                // 如果鼠标抬起,则需要重新按下鼠标来记录新的点击位置
                isDragging = false;
            }

        }
        else {
            if (Input.GetMouseButtonDown(0))
            {
                // 记录鼠标点击位置
                lastMousePosition = Input.mousePosition;
                isDragging = true;
            }
            else if (Input.GetMouseButtonUp(0))
            {
                // 如果鼠标抬起,则需要重新按下鼠标来记录新的点击位置
                isDragging = false;
            }

        }
        

      

        if (isDragging) {

            float speedHorizontal = speedH;
            float speedVertical = 0.1f;

            // 计算鼠标移动的增量
            Vector3 deltaMousePosition = Input.mousePosition - lastMousePosition;

            //Debug.Log("deltaMousePosition.x:" + deltaMousePosition.x + "deltaMousePosition.y:" + deltaMousePosition.y);
            // 计算水平旋转角度
            // 计算水平旋转角度
            float deltaHorizontal = speedHorizontal * deltaMousePosition.x;
            var newHorizontal = currentHorizontal - deltaHorizontal;
            //newHorizontal = Mathf.SmoothDamp(currentHorizontal, newHorizontal,ref velocityX, Time.unscaledDeltaTime);
            // 更新摄像机跟随脚本的旋转角度
            cameraFollow.polarDeg = newHorizontal;

            //Debug.Log("cameraFollow.polarDeg" + cameraFollow.polarDeg);

            currentHorizontal = newHorizontal;
            float deltaVertical = speedVertical * deltaMousePosition.y;
            var newVertical = currentVertical - deltaVertical;
            //newHorizontal = Mathf.SmoothDamp(currentVertical, newVertical, ref velocityY, Time.unscaledDeltaTime);
            // 更新摄像机跟随脚本的旋转角度
            cameraFollow.elevationDeg = Mathf.Clamp(newVertical, -90f, 89);
            currentVertical = newVertical;

            lastMousePosition = Input.mousePosition;
        }
        else {

        }
    }









}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件测试|深入理解SQL CROSS JOIN:交叉连接

简介 在SQL查询中&#xff0c;CROSS JOIN是一种用于从两个或多个表中获取所有可能组合的连接方式。它不依赖于任何关联条件&#xff0c;而是返回两个表中的每一行与另一个表中的每一行的所有组合。CROSS JOIN可以用于生成笛卡尔积&#xff0c;它在某些情况下非常有用&#xff…

服务器监控软件夜莺使用(二)

文章目录 一、采集器安装1. Categraf简介2. Categraf部署3. 测试服务器部署4. 系统监控插件5. 显卡监控插件6. 服务监控插件 二、监控仪表盘1. 机器列表2. 系统监控3. 服务监控 三、告警配置1. 邮件通知2. 告警规则3. 告警自愈 一、采集器安装 1. Categraf简介 Categraf 需要…

一种DevOpts的实现方式:基于gitlab的CICD(二)

写在之前 前文已经搭建了基于gitlab的cicd环境&#xff0c;现在我们来更近一步&#xff0c;结合官网给出的案例来详细介绍如何一步一步实现CI的过程。 基于gitlab搭建一个前端静态页面 环境依赖&#xff1a; gitlabgitlab runner&#xff08;docker版本&#xff09; 环境达吉…

【华为】IPsec VPN 实验配置(动态地址接入)

【华为】IPsec VPN 实验配置&#xff08;动态地址接入&#xff09; 注意实验需求配置思路配置命令拓扑R1基础配置配置第一阶段 IKE SA配置第二阶段 IPsec SA ISP_R2基础配置 R3基础配置配置第一阶段 IKE SA配置第二阶段 IPsec SA PCPC1PC2 检查建立成功查看命令清除IKE / IPsec…

VBA中类的解读及应用第八讲:实现定时器功能的自定义类事件

《VBA中类的解读及应用》教程【10165646】是我推出的第五套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。 类&#xff0c;是非常抽象的&#xff0c;更具研究的价值。随着我们学习、应用VBA的深入&#xff0…

Elasticsearch:Search tutorial - 使用 Python 进行搜索 (三)

这个是继上一篇文章 “Elasticsearch&#xff1a;Serarch tutorial - 使用 Python 进行搜索 &#xff08;二&#xff09;” 的续篇。在今天的文章中&#xff0c;本节将向你介绍一种不同的搜索方式&#xff0c;利用机器学习 (ML) 技术来解释含义和上下文。 向量搜索 嵌入 (embed…

图像融合论文阅读:CrossFuse: 一种基于交叉注意机制的红外与可见光图像融合方法

article{li2024crossfuse, title{CrossFuse: A novel cross attention mechanism based infrared and visible image fusion approach}, author{Li, Hui and Wu, Xiao-Jun}, journal{Information Fusion}, volume{103}, pages{102147}, year{2024}, publisher{Elsevier} } 论文…

Windows安装Rust环境(完整教程)

一、 安装mingw64(C语言环境) Rust默认使用的C语言依赖Visual Studio&#xff0c;但该工具占用空间大安装也较为麻烦&#xff0c;可以选用轻便的mingw64包。 1.1 安装地址 (1) 下载地址1-GitHub&#xff1a;Releases niXman/mingw-builds-binaries GitHub (2) 下载地址2-W…

函数战争(栈帧)之创建与销毁(c语言)(vs2022)

首先&#xff0c;什么是函数栈帧&#xff1f; C语言中&#xff0c;每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。栈帧也叫过程活动记录&#xff0c;是编译器用来实现过程函数调用的一种数据结构。 以问答的方式解释编译器与解释器-CSDN博客htt…

C++ OpenGL 3D Game Tutorial 2: Making OpenGL 3D Engine学习笔记

视频地址https://www.youtube.com/watch?vPH5kH8h82L8&listPLv8DnRaQOs5-MR-zbP1QUdq5FL0FWqVzg&index3 一、main类 接上一篇内容&#xff0c;main.cpp的内容增加了一些代码&#xff0c;显得严谨一些&#xff1a; #include<OGL3D/Game/OGame.h> #include<i…

寒假前端第一次作业

1、用户注册&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>用户注册</title> …

C++学习笔记——string类和new函数

目录 string类 1.功能增强 1.1 子字符串提取 1.2 字符串拼接 1.3 大小写转换 1.4 字符串比较 2.性能优化 3.使用示例 下面是一个简单的使用示例&#xff0c;展示了如何使用改进后的String类&#xff1a; NEW函数 2.1NEW函数的基本用法 2.2NEW函数的注意事项 2.3避…

使用lwip的perf进行测速TCP不稳定的一些相关配置项

在使用lwIP的perf工具进行TCP性能测试时&#xff0c;TCP不稳定可能涉及以下配置问题&#xff1a; 缓冲区大小&#xff08;Buffer Size&#xff09;&#xff1a;lwIP中的TCP性能受到发送和接收缓冲区大小的影响。如果缓冲区过小&#xff0c;可能导致数据包丢失或延迟增加&#x…

《BackTrader量化交易图解》第8章:plot 绘制金融图

文章目录 8. plot 绘制金融图8.1 金融分析曲线8.2 多曲线金融指标8.3 Observers 观测子模块8.4 plot 绘图函数的常用参数8.5 买卖点符号和色彩风格8.6 vol 成交参数8.7 多图拼接模式8.8 绘制 HA 平均 K 线图 8. plot 绘制金融图 8.1 金融分析曲线 BackTrader内置的plot绘图函…

Hibernate实战之操作MySQL数据库(2024-1-8)

Hibernate实战之操作MySQL数据库 2024.1.8 前提环境&#xff08;JavaMySQLNavicatVS Code&#xff09;1、Hibernate简介1.1 了解HQL 2、MySQL数据库建表2.1 编写SQL脚本2.2 MySQL执行脚本 3、Java操作MySQL实例&#xff08;Hibernate&#xff09;3.1 准备依赖的第三方jar包3.2 …

密码学:一文读懂非对称加密算法 DH、RSA

文章目录 前言非对称加密算法的由来非对称加密算法的家谱1.基于因子分解难题2.基于离散对数难题 密钥交换算法-DH密钥交换算法-DH的通信模型初始化DH算法密钥对甲方构建DH算法本地密钥乙方构建DH算法本地密钥DH算法加密消息传递 典型非对称加密算法-RSARSA的通信模型RSA特有的的…

大数据StarRocks(六) :Catalog

StarRocks 自 2.3 版本起支持 Catalog&#xff08;数据目录&#xff09;功能&#xff0c;实现在一套系统内同时维护内、外部数据&#xff0c;方便您轻松访问并查询存储在各类外部源的数据。 1. 基本概念 内部数据&#xff1a;指保存在 StarRocks 中的数据。 外部数据&#xf…

用css给宽高不固定的矩形画对角线

.kong{width: 200rpx;height: 76rpx;background: linear-gradient(to bottom right, #E5E5E5 0%, rgba(0, 0, 0, 0.1) calc(50% - 1px),#175CFF 50%, rgba(0, 0, 0, 0.1) calc(50% 1px),rgba(0, 0, 0, 0.1) 100%);}参考&#xff1a; https://blog.csdn.net/weixin_38779534/a…

1.1map

unordered_map和map的使用几乎是一致的&#xff0c;只是头文件和定义不同 #include<iostream> #include<map>//使用map需要的头文件 #include<unordered_map>//使用unordered_map需要的头文件 #include<set>//使用set需要的头文件 #include<uno…

web前端(html)练习

第一题 1. 用户名为文本框&#xff0c;名称为 UserName&#xff0c;长度为 15&#xff0c;最大字符数为 20。 2. 密码为密码框&#xff0c;名称为 UserPass&#xff0c;长度为 15&#xff0c;最大字符数为 20。 3. 性别为两个单选按钮&#xff0c;名称为 sex&#xff0c;值分…