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

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

最终效果

image.png


素材

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;
        }
    }
}

效果

image.png

添加水下后处理

修改模式为局部,碰撞体积设置和水体一样大

简单配置后处理,添加通道混色器

你会发现看不到效果,因为我们还需要开启摄像机的后处理效果,记得所有相机都要开启,记得把人物放进水里

提升伽马增益

视野模糊效果(Depth OF Field)

全屏屏幕光圈效果

胶片颗粒感

效果

image.png

水下呼吸

新增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") + "%"; // 更新氧气计数器文本显示
    }
}

配置

效果

image.png

钓鱼

待续

参考

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

目录
相关文章
|
3月前
|
定位技术 C# 图形学
从零开始的unity3d入门教程(二)----基本功能讲解
这是一篇Unity3D入门教程,详细介绍了Unity界面操作、游戏物体创建修改、场景搭建、玩家控制、音效添加以及游戏测试和导出的全过程。
从零开始的unity3d入门教程(二)----基本功能讲解
|
3月前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
79 1
|
3月前
|
图形学
小功能⭐️Unity UnityEvent实现代码的选择
小功能⭐️Unity UnityEvent实现代码的选择
|
3月前
|
机器学习/深度学习 图形学 Windows
小功能⭐️unity3d KeyCode各键值说明
小功能⭐️unity3d KeyCode各键值说明
|
2月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
202 0
|
2月前
|
图形学 开发者 UED
Unity游戏开发必备技巧:深度解析事件系统运用之道,从生命周期回调到自定义事件,打造高效逻辑与流畅交互的全方位指南
【8月更文挑战第31天】在游戏开发中,事件系统是连接游戏逻辑与用户交互的关键。Unity提供了多种机制处理事件,如MonoBehaviour生命周期回调、事件系统组件及自定义事件。本文介绍如何有效利用这些机制,包括创建自定义事件和使用Unity内置事件系统提升游戏体验。通过合理安排代码执行时机,如在Awake、Start等方法中初始化组件,以及使用委托和事件处理复杂逻辑,可以使游戏更加高效且逻辑清晰。掌握这些技巧有助于开发者更好地应对游戏开发挑战。
121 0
|
3月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
123 0
|
3月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
85 0
|
3月前
|
图形学 C# 开发者
Unity粒子系统全解析:从基础设置到高级编程技巧,教你轻松玩转绚丽多彩的视觉特效,打造震撼游戏画面的终极指南
【8月更文挑战第31天】粒子系统是Unity引擎的强大功能,可创建动态视觉效果,如火焰、爆炸等。本文介绍如何在Unity中使用粒子系统,并提供示例代码。首先创建粒子系统,然后调整Emission、Shape、Color over Lifetime等模块参数,实现所需效果。此外,还可通过C#脚本实现更复杂的粒子效果,增强游戏视觉冲击力和沉浸感。
187 0
|
3月前
|
开发者 图形学 前端开发
绝招放送:彻底解锁Unity UI系统奥秘,五大步骤教你如何缔造令人惊叹的沉浸式游戏体验,从Canvas到动画,一步一个脚印走向大师级UI设计
【8月更文挑战第31天】随着游戏开发技术的进步,UI成为提升游戏体验的关键。本文探讨如何利用Unity的UI系统创建美观且功能丰富的界面,包括Canvas、UI元素及Event System的使用,并通过具体示例代码展示按钮点击事件及淡入淡出动画的实现过程,助力开发者打造沉浸式的游戏体验。
95 0