当前位置: 首页 > news >正文

Unity3D水下场景与游泳系统开发指南

目录

最终效果

素材

将项目升级为URP

画一个水潭地形

材质升级为URP

创建水

调节水

第一人称人物移动控制

摄像机视角控制脚本 MouseLook

人物移动控制脚本 PlayerMovement

游泳

水面停留

添加水下后处理

水下呼吸

钓鱼

参考


最终效果

image.png

素材

image

将项目升级为URP

这里可以选择直接创建URP项目,也可以选择把普通项目升级为URP项目,关于如何升级,可以自行搜索,这里不赘述。

画一个水潭地形

image

材质升级为URP

image

PS:你可能会注意到即使转换材质后仍显示粉色,这其实是 Unity 的一个显示 Bug,实际上材质转换已经成功完成了。

创建水

新增空物体,添加Water Volume (Transforms)和Water Volume Helper组件配置参数

image

绑定水材质

image

添加子物体,并设置尺寸

image

调整一下水尺寸就显示出来了

image

调节水

将水调整合适大小,放置到刚才我们绘制的水潭地形上

image

可以调节水材质参数,达到自己想要的效果

image

ps:记得设置水的y轴高度,占满湖底

image

第一人称人物移动控制

image

image

image

image

摄像机视角控制脚本 MouseLook

public class MouseLook : MonoBehaviour
{public float mouseSensitivity = 1000f;  // 鼠标灵敏度设置public Transform playerBody;  // 玩家身体Transform组件private float xRotation = 0f;  // 当前X轴旋转角度void Start(){Cursor.lockState = CursorLockMode.Locked;  // 锁定并隐藏鼠标指针}void Update(){FreeLook();  // 每帧处理自由视角}void FreeLook(){// 计算平滑的鼠标移动量float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;// 更新并限制X轴旋转角度xRotation -= mouseY;xRotation = Mathf.Clamp(xRotation, -90f, 90f);// 应用摄像机旋转transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);playerBody.Rotate(Vector3.up * mouseX);  // 旋转玩家身体}
}

人物移动控制脚本 PlayerMovement

using UnityEngine;[RequireComponent(typeof(CharacterController))]
public class PlayerMovement : MonoBehaviour
{public CharacterController characterController;public float walkSpeed = 6f;  // 行走速度public float runSpeed = 9f;   // 奔跑速度public float jumpHeight = 2.5f;  // 跳跃高度private float Gravity = -19.8f;  // 重力加速度private float horizontal;private float vertical;private Vector3 moveDirection;  // 移动方向private float speed;  // 当前速度private bool isRun;  // 是否奔跑private bool isGround;  // 是否在地面private float _verticalVelocity;  // 垂直速度void Start(){speed = walkSpeed;  // 初始化速度为行走速度}void Update(){// 获取输入horizontal = Input.GetAxis("Horizontal");vertical = Input.GetAxis("Vertical");// 状态检测与更新isGround = characterController.isGrounded;SetSpeed();SetRun();SetMove();SetJump();// 应用移动characterController.Move(moveDirection * speed * Time.deltaTime + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);}void SetSpeed(){speed = isRun ? runSpeed : walkSpeed;  // 切换移动速度}void SetRun(){isRun = Input.GetKey(KeyCode.LeftShift);  // 检测是否按下奔跑键}void SetMove(){// 计算并标准化移动方向moveDirection = (transform.right * horizontal + transform.forward * vertical).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;}}
}

效果

image

游泳

[RequireComponent(typeof(CharacterController))]
public class PlayerMovement : MonoBehaviour
{[Header("Components")][Tooltip("角色控制器")] public CharacterController characterController;[Header("Movement")][Tooltip("角色行走速度")] public float walkSpeed = 6f;[Tooltip("角色奔跑速度")] public float runSpeed = 9f;private float currentSpeed;private bool isRunning;private Vector3 moveDirection;[Header("Gravity")]private float gravity;private float verticalVelocity;[Tooltip("地面重力")] public float groundGravity = -19.8f;[Header("Jump")][Tooltip("跳跃高度")] public float jumpHeight = 5f;private bool isGrounded;[Header("Swimming")][Tooltip("是否在水中")] public bool isSwimming;[Tooltip("是否在水下")] public bool isUnderWater;[Tooltip("水中重力")] public float swimmingGravity = -0.5f;[Header("Camera")]public Transform cameraTransform;void Start(){currentSpeed = walkSpeed;}void Update(){float horizontal = Input.GetAxis("Horizontal");float vertical = Input.GetAxis("Vertical");isGrounded = characterController.isGrounded;UpdateSpeed();UpdateRunning();UpdateMovement(horizontal, vertical);}void UpdateSpeed(){currentSpeed = isRunning ? runSpeed : walkSpeed;}void UpdateRunning(){isRunning = Input.GetKey(KeyCode.LeftShift);}void UpdateMovement(float horizontal, float vertical){// 跳跃控制bool jump = Input.GetButtonDown("Jump");if (isGrounded){if (verticalVelocity < 0.0f){verticalVelocity = -2f;}if (jump){verticalVelocity = jumpHeight;}}// 水中移动逻辑if (isSwimming){gravity = swimmingGravity;verticalVelocity = gravity;moveDirection = transform.right * horizontal + cameraTransform.forward * vertical;}else // 地面移动逻辑{gravity = groundGravity;verticalVelocity += gravity * Time.deltaTime;moveDirection = transform.right * horizontal + transform.forward * vertical;}moveDirection = moveDirection.normalized;characterController.Move(moveDirection * currentSpeed * Time.deltaTime + new Vector3(0.0f, verticalVelocity, 0.0f) * Time.deltaTime);}
}

 

记得配置角色标签

image

编辑水的触发器碰撞体积

image

效果

水面停留

当角色离开水面时,需要停止其上下浮动行为。可以在角色离开水面时将Y轴速度归零来实现这一效果。以下是修改后的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);
}

我们只要检测摄像机是否在水下即可,给我们的摄像机添加触发器和刚体

image

修改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

添加水下后处理

image

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

image

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

image

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

image

提升伽马增益

image

视野模糊效果(Depth OF Field)

image

全屏屏幕光圈效果

image

胶片颗粒感

image

效果

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

image

效果

image.png

钓鱼

using UnityEngine;
using System.Collections;public class FishingManager : MonoBehaviour
{public Transform hookTransform; // 鱼钩的Transformpublic float throwForce = 10f; // 抛竿的力public float waterLevel = 0f; // 水平面的高度public float biteWaitTimeMin = 2f; // 鱼咬钩的最小等待时间public float biteWaitTimeMax = 10f; // 鱼咬钩的最大等待时间public float biteReactionTime = 2f; // 咬钩后玩家反应的时间(超过这个时间则鱼逃跑)public KeyCode throwKey = KeyCode.Space; // 抛竿按键public KeyCode reelKey = KeyCode.Space; // 收竿按键private bool isThrown = false; // 是否已经抛竿private bool isInWater = false; // 鱼钩是否在水中private bool isFishBiting = false; // 鱼是否正在咬钩private bool isReeling = false; // 是否正在收竿private float biteTimer = 0f; // 咬钩计时器private float reactionTimer = 0f; // 反应计时器void Update(){// 抛竿if (!isThrown && Input.GetKeyDown(throwKey)){ThrowHook();}// 如果鱼钩在水中且没有鱼咬钩,则等待咬钩if (isInWater && !isFishBiting && !isReeling){// 等待随机时间后鱼咬钩biteTimer -= Time.deltaTime;if (biteTimer <= 0){StartBite();}}// 如果鱼正在咬钩,等待玩家收竿if (isFishBiting){reactionTimer -= Time.deltaTime;if (reactionTimer <= 0){// 鱼逃跑FishEscaped();}else if (Input.GetKeyDown(reelKey)){// 玩家收竿ReelFish();}}}// 抛竿方法void ThrowHook(){// 这里简单模拟抛竿:给鱼钩一个向前的力Rigidbody hookRb = hookTransform.GetComponent<Rigidbody>();if (hookRb != null){hookRb.AddForce(transform.forward * throwForce, ForceMode.Impulse);isThrown = true;isInWater = false; // 刚抛出时不在水中}}// 当鱼钩进入水中(触发器)void OnTriggerEnter(Collider other){if (other.CompareTag("Water")){isInWater = true;// 设置一个随机的咬钩等待时间biteTimer = Random.Range(biteWaitTimeMin, biteWaitTimeMax);}}// 开始咬钩void StartBite(){isFishBiting = true;reactionTimer = biteReactionTime; // 重置反应计时器// 这里可以触发咬钩的视觉效果或声音Debug.Log("鱼咬钩了!快收竿!");}// 收竿钓到鱼void ReelFish(){isReeling = true;isFishBiting = false;// 钓到鱼后的处理,比如增加鱼的数量,播放动画等Debug.Log("恭喜!钓到一条鱼!");// 重置状态,准备下一次抛竿ResetFishing();}// 鱼逃跑void FishEscaped(){isFishBiting = false;Debug.Log("鱼逃跑了...");ResetFishing();}// 重置钓鱼状态void ResetFishing(){isThrown = false;isInWater = false;isReeling = false;// 将鱼钩拉回原位(实际游戏中可能需要一个动画过程)// 这里简单重置位置hookTransform.position = transform.position;Rigidbody hookRb = hookTransform.GetComponent<Rigidbody>();if (hookRb != null){hookRb.velocity = Vector3.zero;hookRb.angularVelocity = Vector3.zero;}}
}

  1. 将脚本挂载在玩家角色上(或一个管理物体上)。
  2. 将鱼钩物体拖拽到hookTransform变量上。
  3. 确保鱼钩有刚体组件(Rigidbody)和碰撞器(Collider)。
  4. 创建一个代表水的物体,设置Tag为"Water",并添加Collider(设置为触发器Trigger),调整到合适的位置和大小。

参考

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

http://www.lryc.cn/news/611834.html

相关文章:

  • Scrapy(一):轻松爬取图片网站内容​
  • 安宝特方案丨工业AR+AI质检方案:致力于提升检测精度与流程效率
  • linux-系统性能监控
  • Python爬虫实战:研究spiderfoot工具,构建网络情报收集系统
  • python每日一题 贪心算法
  • 线程-线程池篇(二)
  • 基于Hadoop的木鸟民宿数据分析与可视化、民宿价格预测模型系统的设计与实现
  • 使用 gptqmodel 量化 Qwen3-Coder-30B-A3B-Instruct
  • MyBatis基础操作完整指南
  • smart-water表设计方案
  • 百度华为硬件笔试机试题-卷4
  • 希赛《华为 HCIA-Datacom 》核心考点之 NAT 技术解析
  • 解决远程连接云服务器mysql编号1130问题
  • 文本编码扫盲及设计思路总结
  • 应急响应排查(windows版)
  • JAVA高级编程第七章
  • 【Linux系统】进程间通信:命名管道
  • 嵌入式处理器指令系统:精简指令集RISC与复杂指令集CISC的简介,及区别
  • Chrontel 昆泰【CH7107B-BF】CH7107B ——HDMI to CVBS Converter
  • 【arcmap中shp图层数据导入到postgresql数据库中,中文出现乱码,怎么办?】
  • 使用 Maxwell 和 RabbitMQ 监控 Mysql Flowable 表变更
  • 医学影像PACS系统的设计与实现,PACS系统源码
  • LMS/NLMS最小均值算法:双麦克风降噪
  • python中的推导式
  • YOLOv5 上使用 **labelImg** 标注并训练自己的数据集
  • PyTorch生成式人工智能——Hugging Face环境配置与应用详解
  • 【32】C++实战篇—— m行n列的坐标点,求每行相邻点X差值dX,每列相邻点y差值dY,并以矩阵形式左端对齐
  • 远程连接----ubuntu ,rocky 等Linux系统,WindTerm_2.7.0
  • Spring选择哪种方式代理?
  • 阿里云DMS Data Copilot——高效智能的数据助手,助力企业实现数据驱动的未来