【unity实战】3D水系统,游泳,潜水,钓鱼功能实现

news2024/10/1 15:23:39

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 素材
  • 将项目升级为URP
  • 画一个水潭地形
  • 材质升级为URP
  • 创建水
  • 调节水
  • 第一人称人物移动控制
  • 游泳
  • 水面停留
  • 添加水下后处理
  • 水下呼吸
  • 钓鱼
  • 参考
  • 完结

素材

https://assetstore.unity.com/packages/vfx/shaders/urp-stylized-water-shader-proto-series-187485
在这里插入图片描述

将项目升级为URP

这里可以选择直接创建URP项目,也可以选择把普通项目升级为URP项目,关于如何升级,之前我很多都讲过了,感兴趣可以回去看看:
【实现100个unity特效之1】使用Shader Graph实现动物森友会的世界弯曲效果
【制作100个unity游戏之22】3DRPG游戏开发02——天空盒、URP设置和光照

画一个水潭地形

不知道如何绘制的可以看我之前的文章:【2023Unity游戏开发教程】零基础带你从小白到超神04——地形的绘制和基础使用介绍
在这里插入图片描述

材质升级为URP

在这里插入图片描述
ps:可能你会发现材质转换了还是粉色,虽然看着还是粉色,但是其实已经转换成功了,这是unity的一个bug

创建水

新增空物体,添加Water Volume (Transforms)和Water Volume Helper组件配置参数
在这里插入图片描述
绑定水材质
在这里插入图片描述
添加子物体,并设置尺寸
在这里插入图片描述
调整一下水尺寸就显示出来了
在这里插入图片描述

调节水

将水调整合适大小,放置到刚才我们绘制的水潭地形上
在这里插入图片描述
可以调节水材质参数,达到自己想要的效果
在这里插入图片描述

ps:记得设置水的y轴高度,占满湖底
在这里插入图片描述

第一人称人物移动控制

可以看我这篇文章,直接把代码拿来用即可:
【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

摄像机视角控制代码MouseLook

public class MouseLook : MonoBehaviour
{
    // 鼠标灵敏度
    public float mouseSensitivity = 1000f;

    // 玩家的身体Transform组件,用于旋转
    public Transform playerBody;

    // x轴的旋转角度
    float xRotation = 0f;
    void Start()
    {
        // 锁定光标到屏幕中心,并隐藏光标
        Cursor.lockState = CursorLockMode.Locked;
    }

    // Update在每一帧调用
    void Update()
    {
        // 执行自由视角查看功能
        FreeLook();
    }

    // 自由视角查看功能的实现
    void FreeLook()
    {
        // 获取鼠标X轴和Y轴的移动量,乘以灵敏度和时间,得到平滑的移动速率
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
        //限制旋转角度在-90到90度之间,防止过度翻转
        xRotation = Mathf.Clamp(xRotation, -90f, 90f);

        // 累计x轴上的旋转量
        xRotation -= mouseY;

        // 应用摄像头的x轴旋转
        transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);

        // 应用玩家身体的y轴旋转
        playerBody.Rotate(Vector3.up * mouseX);
    }
}

人物移动控制代码PlayerMovement

using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class PlayerMovement: MonoBehaviour
{
    [Tooltip("角色控制器")] public CharacterController characterController;
    [Tooltip("重力加速度")] private float Gravity = -19.8f;
    private float horizontal;
    private float vertical;

    [Header("移动")]
    [Tooltip("角色行走的速度")] public float walkSpeed = 6f;
    [Tooltip("角色奔跑的速度")] public float runSpeed = 9f;
    [Tooltip("角色移动的方向")] private Vector3 moveDirection;
    [Tooltip("当前速度")] private float speed;
    [Tooltip("是否奔跑")] private bool isRun;

    [Header("地面检测")]
    [Tooltip("是否在地面")] private bool isGround;

    [Header("跳跃")]
    [Tooltip("角色跳跃的高度")] public float jumpHeight = 2.5f;
    private float _verticalVelocity;

    void Start()
    {
        speed = walkSpeed;
    }

    void Update()
    {
        horizontal = Input.GetAxis("Horizontal");
        vertical = Input.GetAxis("Vertical");

        //地面检测
        isGround = characterController.isGrounded;

        SetSpeed();

        SetRun();

        SetMove();

        SetJump();
    }

    //速度设置
    void SetSpeed()
    {
        if (isRun)
        {
            speed = runSpeed;
        }
        else
        {
            speed = walkSpeed;
        }
    }

    //控制奔跑
    void SetRun()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            isRun = true;
        }
        else
        {
            isRun = false;
        }
    }

    
    //控制移动
    void SetMove()
    {
        moveDirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向
        //将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
        // moveDirection = transform.TransformDirection(new Vector3(h, 0, v));
        moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快  
    }

    //控制跳跃
    void SetJump()
    {
        bool jump = Input.GetButtonDown("Jump");
        if (isGround)
        {
            // 在着地时阻止垂直速度无限下降
            if (_verticalVelocity < 0.0f)
            {
                _verticalVelocity = -2f;
            }

            if (jump)
            {
                _verticalVelocity = jumpHeight;
            }
        }
        else
        {
            //随时间施加重力
            _verticalVelocity += Gravity * Time.deltaTime;
        }
        characterController.Move(moveDirection * speed * Time.deltaTime + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
    }
}

效果
在这里插入图片描述

游泳

实现游泳的逻辑大概就是,修改PlayerMovement人物脚本,控制人物在两个重力直接切换,并修改水里的移动方向为视角方向

[RequireComponent(typeof(CharacterController))]
public class PlayerMovement : MonoBehaviour
{
    [Tooltip("角色控制器")] public CharacterController characterController;
    [Tooltip("重力加速度")] private float Gravity;//当前重力
    private float horizontal;
    private float vertical;

    [Header("移动")]
    [Tooltip("角色行走的速度")] public float walkSpeed = 6f;
    [Tooltip("角色奔跑的速度")] public float runSpeed = 9f;
    [Tooltip("角色移动的方向")] private Vector3 moveDirection;
    [Tooltip("当前速度")] private float speed;
    [Tooltip("是否奔跑")] private bool isRun;

    [Header("地面检测")]
    [Tooltip("是否在地面")] private bool isGround;

    [Header("跳跃")]
    [Tooltip("角色跳跃的高度")] public float jumpHeight = 5f;
    private float _verticalVelocity;

    [Header("水")]
    public bool isSwimming;//是否在水里
    //是否在水面
    public bool isUnderWater;//是否被水淹没
    public float swimmingGravity = -0.5f; //水里的重力
    public float groundGravity = -19.8f;//地面的重力
    public Transform Camera;

    void Start()
    {
        speed = walkSpeed;
    }

    void Update()
    {
        horizontal = Input.GetAxis("Horizontal");
        vertical = Input.GetAxis("Vertical");

        //地面检测
        isGround = characterController.isGrounded;

        SetSpeed();

        SetRun();

        SetJump();
    }

    //速度设置
    void SetSpeed()
    {
        if (isRun)
        {
            speed = runSpeed;
        }
        else
        {
            speed = walkSpeed;
        }
    }

    //控制奔跑
    void SetRun()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            isRun = true;
        }
        else
        {
            isRun = false;
        }
    }

    //控制跳跃
    void SetJump()
    {
        bool jump = Input.GetButtonDown("Jump");

        if (isGround)
        {
            // 在着地时阻止垂直速度无限下降
            if (_verticalVelocity < 0.0f)
            {
                _verticalVelocity = -2f;
            }

            if (jump)
            {
                _verticalVelocity = jumpHeight;
            }
        }

        //水里处理
        if (isSwimming)
        {
            
            Gravity = swimmingGravity;
            _verticalVelocity = Gravity;
            
            moveDirection = transform.right * horizontal + Camera.forward * vertical; //水里往相机的前方移动
        }
        else
        {
            Gravity = groundGravity;
            //随时间施加重力
            _verticalVelocity += Gravity * Time.deltaTime;
            moveDirection = transform.right * horizontal + transform.forward * vertical;
        }
        moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快

        characterController.Move(moveDirection * speed * Time.deltaTime + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
    }
}

记得配置角色标签
在这里插入图片描述

编辑水的触发器碰撞体积
在这里插入图片描述
效果
在这里插入图片描述

水面停留

我们不希望人物出了水面,还是在上下浮动,可以选择在任务离开水面时把y轴速度设置为0即可,修改PlayerMovement

public bool isUnderWater;//是否被水淹没

//。。。

void SetJump()
{
    //控制跳跃
    bool jump = Input.GetButtonDown("Jump");
    if (isGround)
    {
        // 在着地时阻止垂直速度无限下降
        if (_verticalVelocity < 0.0f)
        {
            _verticalVelocity = -2f;
        }

        if (jump)
        {
            _verticalVelocity = jumpHeight;
        }
    }

    //水里处理
    if (isSwimming)
    {
        if (isUnderWater)
        {
            Gravity = swimmingGravity;
            _verticalVelocity = Gravity;
        }
        else
        {
            _verticalVelocity = 0;
        }
        moveDirection = transform.right * horizontal + Camera.forward * vertical; //水里往相机的前方移动
    }
    else
    {
        Gravity = groundGravity;
        //随时间施加重力
        _verticalVelocity += Gravity * Time.deltaTime;
        moveDirection = transform.right * horizontal + transform.forward * vertical;
    }
    moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快

    characterController.Move(moveDirection * speed * Time.deltaTime + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
}

我们只要检测摄像机是否在水下即可,给我们的摄像机添加触发器和刚体
在这里插入图片描述

修改SwimAra

public class SwimAra : MonoBehaviour
{
    private void OnTriggerEnter(Collider other) {
        if(other.CompareTag("Player")){
            other.GetComponent<PlayerMovement>().isSwimming = true;
        }
        if(other.CompareTag("MainCamera")){
            other.GetComponentInParent<PlayerMovement>().isUnderWater = true;
        }
    }

    private void OnTriggerExit(Collider other) {
        if(other.CompareTag("Player")){
            other.GetComponent<PlayerMovement>().isSwimming = false;
        }
        if(other.CompareTag("MainCamera")){
            other.GetComponentInParent<PlayerMovement>().isUnderWater = false;
        }
    }
}

效果
在这里插入图片描述

添加水下后处理

在这里插入图片描述
修改模式为局部,碰撞体积设置和水体一样大
在这里插入图片描述

简单配置后处理,添加通道混色器
在这里插入图片描述

你会发现看不到效果,因为我们还需要开启摄像机的后处理效果,记得所有相机都要开启,记得把人物放进水里
在这里插入图片描述
提升伽马增益
在这里插入图片描述

视野模糊效果(Depth OF Field)
在这里插入图片描述

全屏屏幕光圈效果
在这里插入图片描述

胶片颗粒感
在这里插入图片描述
效果
在这里插入图片描述

水下呼吸

新增PlayerHealth,控制人物状态

public class PlayerHealth : MonoBehaviour
{
    public static PlayerHealth Instance;
    public float maxHealth = 100;//最大生命值
    public float currentHealth;

    //---玩家氧气----/
    public float currentOxygenPercent; // 当前氧气百分比
    public float maxOxygenPercent = 100; // 最大氧气百分比
    public float oxygenDecreasedPerSecond = 1f; // 每次减少的氧气百分比
    private float oxygenTimer = 0f; // 氧气计时器
    private float decreaseInterval = 1f; // 减少间隔
    public GameObject oxygenBar;//氧气条

    private void Awake() {
        Instance = this;
    }

    void Start()
    {
        currentHealth = maxHealth;
        currentOxygenPercent = maxOxygenPercent;
    }

    void Update()
    {
        if (GetComponent<PlayerMovement>().isUnderWater)
        {
            oxygenBar.SetActive(true);
            oxygenTimer += Time.deltaTime;
            if (oxygenTimer > decreaseInterval)
            {
                DecreaseOxygen();
                oxygenTimer = 0;
            }
        }else{
            oxygenBar.SetActive(false);
            currentOxygenPercent = maxOxygenPercent;
        }
    }

    private void DecreaseOxygen()
    {
        currentOxygenPercent -= oxygenDecreasedPerSecond;

        // 没有氧气了
        if (currentOxygenPercent < 0)
        {
            currentOxygenPercent = 0;

            //扣血
            currentHealth -= 1f;
        }
    }
}

新增OxygenBar,控制人物氧气条UI

public class OxygenBar : MonoBehaviour
{
    private Slider slider; // 氧气条的滑动条
    public TextMeshProUGUI oxygenCounter; // 氧气计数器文本
    private float currentOxygen, maxOxygen; // 当前氧气值和最大氧气值

    void Awake()
    {
        slider = GetComponent<Slider>(); // 获取滑动条组件
    }

    void Update()
    {
        currentOxygen = PlayerHealth.Instance.currentOxygenPercent; // 获取当前氧气百分比
        maxOxygen = PlayerHealth.Instance.maxOxygenPercent; // 获取最大氧气百分比

        float fillValue = currentOxygen / maxOxygen; // 计算填充值
        slider.value = fillValue; // 更新滑动条的值

        oxygenCounter.text = (fillValue * 100).ToString("0") + "%"; // 更新氧气计数器文本显示
    }
}

配置
在这里插入图片描述
在这里插入图片描述
效果
在这里插入图片描述

钓鱼

待续

参考

https://www.youtube.com/watch?v=vX5AOF4Wdgo&list=PLtLToKUhgzwnk4U2eQYridNnObc2gqWo-&index=44

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

在这里插入图片描述

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

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

相关文章

06 数据结构之树

引言&#xff1a; 数的代码实现&#xff0c; 先序遍历、中序、后序、层次遍历 /* binary_tree.h */ #ifndef _BINARY_TREE_H #define _BINARY_TREE_H#include <stdio.h> #include <stdlib.h> #include <string.h>#define DEBUG(msg) \printf("--%s--, %…

【工作】如何写好一份工作自评/总结 述职报告

文章目录 一、述职与工作汇报1、述职是什么&#xff1f;2、述职的目标&#xff08;表扬/体谅/资源&#xff09;3、述职的对象&#xff08;挑战/规划/方法&#xff09; 二、如何做好一份述职报告1、述职内容2、述职PPT制作3、述职试讲练习 三、附工作自评 一、述职与工作汇报 1…

18个惊艳的可视化大屏(第21辑):环境监测与污染治理

hello&#xff0c;我是贝格前端工场老司机&#xff0c;这是第21期了&#xff0c;本次分享环境监测与污染治理场景下&#xff0c;可视化大屏的意义和案例&#xff0c;喜欢文章的别忘点赞关注&#xff0c;文章底部也有其他行业的案例。 可视化大屏在环境监测污染治理中发挥着重要…

【C++】list模拟实现list迭代器失效问题

list模拟实现&list迭代器失效问题 一&#xff0c;list模拟实现1. list的主要框架接口模拟2. list构造&拷贝构造&析构3. list迭代器3.1 普通迭代器3.2 const迭代器 4. 增删查改 二&#xff0c;迭代器失效问题1. list的迭代器失效原因2. 解决办法 一&#xff0c;list…

【动态规划】【前缀和】【和式变换】100216. K 个不相交子数组的最大能量值

本文涉及知识点 动态规划汇总 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode 100216. K 个不相交子数组的最大能量值 给你一个长度为 n 下标从 0 开始的整数数组 nums 和一个 正奇数 整数 k 。 x 个子数组的能量值定义为 stren…

九州金榜|孩子厌学的因素及解决办法

孩子在学习的过程中&#xff0c;遇到厌学这种情况非常容易见到&#xff0c;这也是孩子在成长的过程中经常遇到的烦恼。面对孩子的厌学&#xff0c;作为家长这时候不要慌乱&#xff0c;要做到分析孩子产生厌学的原因&#xff0c;在去寻找解决孩子厌学的办法。下面九州金榜家庭教…

C++单例模式、工厂模式

一、单例模式 (一) 什么是单例模式 1. 是什么&#xff1f; 在系统的整个生命周期内&#xff0c;一个类只允许存在一个实例。 2. 为什么&#xff1f; 两个原因&#xff1a; 节省资源。方便控制&#xff0c;在操作公共资源的场景时&#xff0c;避免了多个对象引起的复杂操作…

【Python】成功解决ModuleNotFoundError: No module named ‘seaborn’

【Python】成功解决ModuleNotFoundError: No module named ‘seaborn’ &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; …

win11家庭版docker和milvus

docker 1、官网下载docker文件Get Started | Docker&#xff0c;选择download for windows下载。 2、双击打开下载好的文件Docker Desktop Installer.exe&#xff0c;add shortcut to desktop选择√代表同意添加快捷键到桌面&#xff0c;如果不勾选就说明不创建快捷键&#x…

软件报错提示缺少D3DCompiler_47.dll文件怎么解决

许多用户在运行游戏或电脑软件时&#xff0c;遇到了一个提示“找不到d3dcompiler_47.dll”的错误消息。这个问题相当普遍&#xff0c;这个错误通常是由于系统中缺少关键的d3dcompiler_47.dll文件所导致的&#xff0c;而这个文件是很多应用程序运行的必要条件&#xff0c;特别是…

浮点数的前世今生

文章目录 浮点数问题浮点数赋值和打印不同0.1累加100次&#xff0c;得到的不是10 计算机如何存储整数计算机如何存储浮点数二进制小数表示法浮点数表示小数和浮点数的转换十进制小数转换成浮点数二进制float二进制转换成十进制小数 问题解决方法参考资料 浮点数问题 浮点数赋值…

《深度学习风暴:掀起智能革命的浪潮》

在当今信息时代,深度学习已经成为科技领域的一股强大力量,其应用领域涵盖了从医疗到金融再到智能交互等方方面面。随着技术的不断进步和应用的不断拓展,深度学习的发展势头愈发迅猛,掀起了一股智能革命的浪潮。本文将从基本原理、应用实例、挑战与未来发展方向、与机器学习…

makedowm文本居中、首行缩进、回车换行

文章目录 1. 居中2. 首行缩进3. 回车换行3.1 段落中<br />换行3.2 句子中 \Enter 换行3.3 句子中 空格Enter 换行 1. 居中 由于Markdown本身不支持字体居中&#xff0c;所以采取HTML语法。如下&#xff1a; <center>这一行需要居中</center>注意: <cent…

基于PBS向超算服务器队列提交任务的脚本模板与常用命令

本文介绍在Linux服务器中&#xff0c;通过PBS&#xff08;Portable Batch System&#xff09;作业管理系统脚本的方式&#xff0c;提交任务到服务器队列&#xff0c;并执行任务的方法。 最近&#xff0c;需要在学校公用的超算中执行代码任务&#xff1b;而和多数超算设备一样&a…

仿牛客项目Day3:开发社区登录模块

发送邮件 邮箱设置 springEmail properties spring.mail.hostsmtp.qq.com spring.mail.port465 spring.mail.username spring.mail.password spring.mail.protocolsmtps spring.mail.properties.mail.smtp.ssl.enabletrue MailClient Component public class MailClient {…

计算机网络——OSI网络层次模型

计算机网络——OSI网络层次模型 应用层表示层会话层传输层TCP和UDP协议复用分用 网络层数据链路层物理层OSI网络层次模型中的硬件设备MAC地址和IP地址MAC地址IP地址MAC地址和IP地址区别 OSI网络层次模型通信过程解释端到端点到点端到端和点到点的区别 我们之前简单介绍了一下网…

数组:初始化,访问某一个,遍历

文章目录 静态初始化数组数组的访问&#xff1a;遍历数组案例 动态初始化数组总结案例 静态初始化数组 定义数组的时候直接给数组赋值。 简化格式&#xff1a; int[] ages {12,52,96}; 完整格式&#xff1a; int[] ages new int[]{12,16,26};数组变量名中存储的是数组在内存…

LINUX ADC使用

监测 ADC ,使用CAT 查看&#xff1a; LINUX ADC基本使用 &adc {pinctrl-names "default";pinctrl-0 <&adc6>;pinctrl-1 <&adc7>;pinctrl-2 <&adc8>;pinctrl-3 <&adc9>;pinctrl-4 <&adc10>;pinctrl-5 …

xxl-job学习记录

1、应用场景 例&#xff1a; 某收银系统需要在每天凌晨统计前一天的财务分析、汇总 某银行系统需要在信用卡还款日前三天发短信提醒等 2、为什么需要使用任务调度 spring中提供了注解Scheduled的注解&#xff0c;这个注解也可以实现定时任务的执行 我们只需要在方法上使用这…

安全加速SCDN在网站运营中的重要作用

SCDN&#xff08;Secure Content Delivery Network&#xff09;是一种安全加速技术&#xff0c;对于网站运营起到非常重要的作用。它能够提升用户体验&#xff0c;保护网站安全&#xff0c;提高网站的性能和可靠性。本文将详细介绍SCDN在网站运营中的作用。 首先&#xff0c;SC…