【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

目录
相关文章
|
1月前
|
C# 图形学 开发者
【Unity3D实例-功能-镜头】俯视角
本文介绍了Unity中常用的俯视角镜头实现方法,涵盖模型添加、角色Tag设置、摄像机脚本编写及测试运行,帮助开发者快速掌握俯视角在策略与模拟类游戏中的应用技巧。
181 0
|
1月前
|
人工智能 定位技术 C#
【Unity3D实例-功能-移动】通过鼠标点击进行角色移动
本文介绍了如何在Unity中实现鼠标点击控制角色移动的功能。通过安装导航网格、设置地图与角色、烘焙路径及编写脚本,带领读者一步步构建角色自动寻路系统,增强游戏交互体验。
90 0
|
2月前
|
C# 图形学 开发者
【Unity3D实例-功能-移动】复杂移动(Blend Tree方式)
本文介绍了使用Unity中Blend Tree实现角色移动控制的完整教程,涵盖动画器设置、Blend Tree创建、CharacterController组件添加及代码控制,帮助开发者实现流畅自然的角色动画效果。
70 0
|
2月前
|
图形学 开发者
【Unity3D实例-功能-移动】角色移动-通过WSAD(CharacterController方式)
本文介绍了如何在Unity中使用CharacterController组件实现角色灵活移动。内容包括模型准备、动画处理、添加组件、编写移动脚本及测试运行,帮助开发者快速掌握角色控制技巧,打造流畅的游戏体验。
115 0
|
2月前
|
C# 图形学 开发者
【Unity3D实例-功能-移动】角色移动-通过WSAD(Rigidbody方式)
本文介绍了如何在Unity中使用Rigidbody组件实现角色自由移动,包括模型准备、组件添加、脚本编写与测试运行,帮助开发者快速掌握基础角色控制技巧。
152 0
|
2月前
|
C# 图形学
【Unity3D实例-功能-移动】角色移动-通过WSAD(Transform方式)
本文介绍了如何在Unity中实现角色移动功能,使用Transform组件控制角色基础移动,并详细讲解了模型导入、动画设置、动画控制器配置及移动脚本编写等内容,适合Unity初学者学习角色控制的基础知识。
96 0
|
7月前
|
图形学
Unity 获取鼠标位置下的UGUI或3D物体
本文总结了两种检测方法,分别用于UGUI和3D物体的检测。第一种方法`GetOverUIobj`专门用于检测鼠标悬停的UGUI元素,通过`GraphicRaycaster`实现。第二种方法`GetOverWordGameObject`则同时适用于UI和3D物体检测,利用`PhysicsRaycaster`进行射线检测。两者均返回悬停对象或null。
|
7月前
|
前端开发 图形学
unity UGUI跟随3D物体的坐标转换
在 Unity 中实现 UGUI 元素跟随 3D 物体,关键是将 3D 物体的世界坐标转换为屏幕或画布坐标。通过 Camera.WorldToScreenPoint 方法,可将 3D 物体位置映射到屏幕上,再更新 UGUI 元素的位置。代码示例展示了如何使用该方法,使 UGUI 图像跟随 3D 模型,并提供文字显示、图像和线条的显示/隐藏功能。
|
7月前
|
图形学 UED
unity Tab键实现切换输入框功能
该脚本用于简化输入框之间的Tab键切换操作。只需将脚本挂载在InputField上,无需其他设置。脚本通过监听Tab键和Shift键组合,自动选择下一个或上一个可交互的InputField,提升用户体验。
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
794 6

热门文章

最新文章