欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第25篇中,我们将探索如何用unity制作一个3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱等功能,我会附带项目源码,以便你更好理解它。
具体可以看我这篇文章:
【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用
这里我就直接贴出代码了
人物移动控制
[RequireComponent(typeof(CharacterController))]
public class MovementScript : 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 = 8f;
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 = 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);
}
}
视角控制
public class MouseLook : MonoBehaviour
{
// 鼠标灵敏度
public float mouseSensitivity = 500f;
// 玩家的身体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);
}
}
效果
对UI知识还不太懂的小伙伴可以看这篇基础篇文件:【Unity游戏开发教程】零基础带你从小白到超神30——UI组件和布局的使用
物品插槽背景框
物品插槽,可以把物品插槽做出预制体,后面好修改
物品信息脚本
public class Item : MonoBehaviour
{
public new string name = "New Item";//物品名称
[TextArea]
public string description = "New Description";//物品描述
public Sprite icon;//物品图标
public int currentQuantity = 1;//物品当前数量
public int maxQuantity = 16;//物品最大堆叠数量
}
背包插槽脚本
public class Slot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public bool hovered; // 鼠标是否悬停在该槽位上的标志
private Item heldItem; // 当前槽位持有的物品
private Color opaque = new Color(1, 1, 1, 1); // 不透明颜色
private Color transparent = new Color(1, 1, 1, 0); // 透明颜色
private Image thisSlotImage; // 该槽位的图像组件
public TMP_Text thisSlotQuantityText; // 用于显示物品数量的文本组件
// 初始化槽位
public void initialiseSlot()
{
thisSlotImage = gameObject.GetComponent<Image>();
thisSlotQuantityText = transform.GetChild(0).GetComponent<TMP_Text>();
thisSlotImage.sprite = null;
thisSlotImage.color = transparent;
setItem(null);
}
// 设置槽位中的物品
public void setItem(Item item)
{
heldItem = item;
if (item != null)
{
thisSlotImage.sprite = heldItem.icon;
thisSlotImage.color = opaque;
updateData();
}
else
{
thisSlotImage.sprite = null;
thisSlotImage.color = transparent;
updateData();
}
}
// 获取当前槽位持有的物品
public Item getItem()
{
return heldItem;
}
// 当前槽位是否持有的物品
public bool hasItem()
{
return heldItem ? true : false;
}
// 更新槽位显示的数据
public void updateData()
{
if (heldItem != null) // 如果持有物品
thisSlotQuantityText.text = heldItem.currentQuantity.ToString(); // 显示物品的数量
else // 如果不持有物品
thisSlotQuantityText.text = "";
}
// 当鼠标指针进入槽位区域时调用
public void OnPointerEnter(PointerEventData pointerEventData)
{
hovered = true;
}
// 当鼠标指针离开槽位区域时调用
public void OnPointerExit(PointerEventData pointerEventData)
{
hovered = false;
}
}
库存系统脚本
public class Inventory : MonoBehaviour
{
[Header("UI")]
public GameObject inventory; // 游戏中的背包界面
public List<Slot> allInventorySlots = new List<Slot>(); // 所有的槽位列表
public List<Slot> inventorySloats = new List<Slot>();//背包的的槽位列表
public Image crosshair; // 准星图像
public TMP_Text itemHoverText; // 当中心悬停在物品上时显示物品名称的文本
[Header("射线检测")]
public float raycastDistance = 5f; // 射线检测的距离
public LayerMask itemLayer; // 射线检测的目标层,用于识别物品
public void Start()
{
toggleInventory(false); // 初始时关闭背包界面
//合并槽位
allInventorySlots.AddRange(inventorySloats);
foreach (Slot uiSlot in allInventorySlots) // 初始化所有槽位
{
uiSlot.initialiseSlot();
}
}
public void Update()
{
itemRaycast(Input.GetKeyDown(KeyCode.E)); // 显示物品名称和按E拾取物品
if (Input.GetKeyDown(KeyCode.Tab)) // 按下tab键切换背包界面的显示状态
toggleInventory(!inventory.activeInHierarchy);
}
private void itemRaycast(bool hasClicked = false)
{
itemHoverText.text = ""; // 默认不显示任何物品名称
Ray ray = Camera.main.ScreenPointToRay(crosshair.transform.position); // 从准星位置发出射线
RaycastHit hit;
if (Physics.Raycast(ray, out hit, raycastDistance, itemLayer)) // 如果射线检测到物品层的对象
{
if (hit.collider != null)
{
if (hasClicked) // 如果是按了操作,尝试捡起物品
{
Item newItem = hit.collider.GetComponent<Item>();
if (newItem)
{
addItemToInventory(newItem); // 将物品添加到背包中
}
}
else // 否则,仅获取物品名称以显示
{
Item newItem = hit.collider.GetComponent<Item>();
if (newItem)
{
itemHoverText.text = newItem.name; // 显示物品名称
}
}
}
}
}
//将物品添加到背包中
private void addItemToInventory(Item itemToAdd)
{
int leftoverQuantity = itemToAdd.currentQuantity; // 剩余需要添加到背包的物品数量
Slot openSlot = null; // 记录一个空的槽位
for (int i = 0; i < allInventorySlots.Count; i++) // 遍历所有槽位
{
Item heldItem = allInventorySlots[i].getItem();
if (heldItem != null && itemToAdd.name == heldItem.name) // 如果槽位中有相同名称的物品
{
int freeSpaceInSlot = heldItem.maxQuantity - heldItem.currentQuantity; // 计算槽位中的剩余空间
if (freeSpaceInSlot >= leftoverQuantity) // 如果剩余空间足够
{
heldItem.currentQuantity += leftoverQuantity; // 添加物品到该槽位
Destroy(itemToAdd.gameObject); // 销毁场景中的物品对象
allInventorySlots[i].updateData(); // 更新槽位显示的数据
return;
}
else // 如果剩余空间不足
{
heldItem.currentQuantity = heldItem.maxQuantity; // 填满当前槽位
leftoverQuantity -= freeSpaceInSlot; // 更新剩余需要添加的物品数量
}
}
else if (heldItem == null) // 如果槽位为空
{
if (!openSlot)
openSlot = allInventorySlots[i]; // 记录第一个空槽位
}
allInventorySlots[i].updateData(); // 更新槽位显示的数据
}
if (leftoverQuantity > 0 && openSlot) // 如果还有剩余物品且找到了空槽位
{
openSlot.setItem(itemToAdd); // 将物品添加到空槽位
itemToAdd.currentQuantity = leftoverQuantity; // 更新物品的数量
itemToAdd.gameObject.SetActive(false); // 隐藏场景中的物品对象
}
else
{
itemToAdd.currentQuantity = leftoverQuantity; // 更新物品的数量
}
}
private void toggleInventory(bool enable)
{
//关闭背包时,关闭所有鼠标悬停在该槽位上的标志
if (!enable)
{
foreach (Slot curSlot in allInventorySlots)
{
curSlot.hovered = false;
}
}
inventory.SetActive(enable); // 根据参数显示或隐藏背包界面
Cursor.lockState = enable ? CursorLockMode.None : CursorLockMode.Locked; // 根据背包界面的状态锁定或解锁鼠标指针
Cursor.visible = enable; // 设置鼠标指针的可见性
// 禁用或启用相机的旋转控制
Camera.main.GetComponent<MouseLook>().enabled = !enable;
}
}
物品挂载Item脚本,配置参数,记得添加碰撞体并修改图层为Item
背包插槽挂载Slot脚本
角色上挂载Inventory脚本
拾取
源码不出意外的话我会放在最后一节
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~