当前位置:   article > 正文

Unity2D游戏开发——玩家/场景交互组件的实现(独立解耦的组件,附详细流程和代码)_unity简单场景交互

unity简单场景交互

效果演示

2D游戏当中,基本都会有玩家和场景物体交互的功能,简单的可以表示成下面几张图的样子(红色方块是玩家对象):

  • 玩家与场景物体不接触
    玩家与宝箱
  • 玩家与场景物体接触后触发提示
    提示信息
  • 玩家与场景物体发生交互
    交互之后的效果
    上述演示效果很直观的体现了,玩家/场景交互功能大概有以下的几点需求:
  • 场景物体与玩家物体发生碰撞后会触发提示,解除碰撞后提示消失;
  • 交互之后,提示消失;
  • 为了组件的复用,场景物体,玩家物体和提示框之间应相互独立,代码中不允许获得对方的引用。

一、构造物体对象

我们首先创建提示框、场景物体和玩家对象。

1. 创建一个提示框对象

我们的需求是,玩家对象与场景物体接触后才会触发提示,那么提示框就应当作为Prefab对象被动态的生成出来,所以我们先构造一个提示框Prefab对象。如下图所示,修改渲染的层级顺序 (我这里没改Layer,而是把同级Layer下的序号改成了2,同级别的Layer下,序号大的会覆盖序号小的)
在这里插入图片描述

2. 创建场景物体对象

场景物体可以根据需求直接预设在世界坐标系中,所以我们直接在场景中新建一个Sprite,命名为“Chest”,并添加BoxCollider2D组件。如下图修改对应组件的参数,将此物体的渲染序号设为0,并设置碰撞盒为触发型。另外,再新建两个脚本:Interlocutor和Chest;
Chest的组件面板
3. 创建玩家对象

与创建场景物体类似,我们直接在场景中新建一个Sprite,命名为“Player”,并添加Rigidbody2D和BoxCollider2D两个组件。修改渲染的序号,使之渲染顺序介于提示框和场景对象之间,并将物体的碰撞盒设置为触发型(实际上不设置不影响结果,两个碰撞体中只要有一个设置了Trigger就可以了)。

在这里插入图片描述
之后,新建一个名为“Player Controller”的脚本文件(代码在最后一节),并绑定跟随相机。

二、提示框触发

我们先回顾一下Unity的生命周期,这里有几个碰撞相关的重要方法:

  • OnTriggerEnter2D 。碰撞体设置为Trigger时,物体检测到碰撞时触发;
  • OnTriggerStay2D。碰撞体设置为Trigger时,物体处于碰撞状态时触发;
  • OnTriggerExit2D。碰撞体设置为Trigger时,物体解除碰撞时触发;
  • OnCollisionEnter2D。碰撞体不是Trigger时,物体检测到碰撞时触发;
  • OnCollisionStay2D。碰撞体不是Trigger时,物体处于碰撞状态时触发;
  • OnCollisionExit2D。碰撞体不是Trigger时,物体解除碰撞时触发;

很显然,我们要实现接触时显示提示框,不接触时隐藏提示框的需求,只需要调用OnTriggerEnter2D和OnTriggerExit2D两个方法就可以了。部分代码如下:

	/// <summary>
    /// 当触发器检测到物理接触时触发。
    /// </summary>
    /// <param name="collision">碰撞物体对象。</param>
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (Finished)
            return;
        if (collision.tag == reactTag)
            tip.SetActive(true);
    }

    /// <summary>
    /// 当触发器解除物理接触时触发。
    /// </summary>
    /// <param name="collision">碰撞物体对象。</param>
    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.tag == reactTag)
            tip.SetActive(false);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

三、使用UnityEvent触发方法

第一节中,我们为场景物体新建了一个“Interlocutor”组件类,它的功能就是要触发交互方法,改变物体属性,且能够在不改代码的前提下能给多个不同的物体使用。当然,很多策略都可以改变一个物体的属性,只不过当物体的数量和种类不断增加的时候,代码复用性会极大下降。所以,我们必须找到一种独立、高效的手段实现物体交互的功能。好在,Unity已经为我们提供了这样一个神器——UnityEvent。UnityEvent相当于Android里面绑定的Listener或者是.NET里面的事件委托,只不过使用UnityEvent时经常不需要用代码绑定事件,因为UnityEditor已经帮我们做完了这部分工作。

UnityEvent中有一个非常重要的方法——Invoke(),它的作用就是直接触发已经绑定好了的Unity事件(UGUI里,Button的触发就是这样实现的)。我们可以这样定义一个UnityEvent对象:

private UnityEvent keyDownEvent = null;
  • 1

当我们需要触发这个对象中的委托方法时,只需要如下操作即可:

keyDownEvent.Invoke();
  • 1

我们也不需要使用代码手动绑定事件,只需要在Inspector面板里使用传参的方式,就可以完成动态的交互事件绑定了:

UnityEvent的面板

完整代码

Interlocutor类

using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 交互者组件类。
/// </summary>
public class Interlocutor : MonoBehaviour
{
    #region 可视变量
    [SerializeField] [Tooltip("可交互的标签。")] private string reactTag = "Player";
    [SerializeField] [Tooltip("交互提示模板对象。")] private GameObject tipTemplate = null;
    [SerializeField] [Tooltip("是否可以重复交互。")] private bool repeat = false;
    [SerializeField] [Tooltip("交互事件按键。")] private KeyCode keyDownCode = KeyCode.F;
    [SerializeField] [Tooltip("交互事件列表。")] private UnityEvent keyDownEvent = null;
    #endregion

    #region 成员变量
    private GameObject tip = null;
    #endregion

    #region 属性控制
    /// <summary>
    /// 是否已经进行了交互。
    /// </summary>
    public bool Finished { get; set; } = false;
    #endregion

    #region 基础私有方法
    /// <summary>
    /// 第一帧调用之前触发。
    /// </summary>
    private void Start()
    {
        tip = Instantiate(Resources.Load(tipTemplate.name) as GameObject);
        tip.SetActive(false);
    }

    /// <summary>
    /// 帧刷新时触发。
    /// </summary>
    private void Update()
    {
        if (!tip.activeSelf || Finished)
            return;
        if (Input.GetKeyUp(keyDownCode))
        {
            // 触发方法
            keyDownEvent.Invoke();
            // 运行重复触发将不回收对象
            if (!repeat)
                Finished = true;
            tip.SetActive(false);
        }
    }

    /// <summary>
    /// 当触发器检测到物理接触时触发。
    /// </summary>
    /// <param name="collision">碰撞物体对象。</param>
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (Finished)
            return;
        if (collision.tag == reactTag)
            tip.SetActive(true);
    }

    /// <summary>
    /// 当触发器解除物理接触时触发。
    /// </summary>
    /// <param name="collision">碰撞物体对象。</param>
    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.tag == reactTag)
            tip.SetActive(false);
    }
    #endregion
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

Chest类

Chest类是与“Chest”物体绑定的功能类,专门用来操纵箱子的状态,我们这里的功能是简单的改变箱子的Sprite,其完整代码如下:

using UnityEngine;

/// <summary>
/// 宝箱控制器脚本类。
/// </summary>
public class Chest : MonoBehaviour
{
    #region 可视变量
    [SerializeField] [Tooltip("开宝箱后的贴图。")] private Sprite opened = null;
    #endregion

    #region 基础公有方法
    /// <summary>
    /// 打开宝箱。
    /// </summary>
    public void Open()
    {
        gameObject.GetComponent<SpriteRenderer>().sprite = opened;
    }
    #endregion
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Player Controller类

using UnityEngine;

/// <summary>
/// 玩家控制器脚本类。
/// </summary>
public class PlayerController : MonoBehaviour
{
    #region 可视变量
    [SerializeField] public Camera playerCamera = null; // 角色跟随相机
    #endregion

    #region 成员变量
    [HideInInspector] private float cameraDepth = -10;  // 相机深度
    #endregion

    #region 基础私有方法
    /// <summary>
    /// 脚本实例化后立即触发。
    /// </summary>
    private void Awake()
    {
        // 配置相机
        cameraDepth = playerCamera.transform.position.z;
    }

    /// <summary>
    /// 帧刷新时触发。
    /// </summary>
    private void Update()
    {
        // 移动物体
        if (Input.GetKey(KeyCode.W))
            gameObject.transform.Translate(2 * Vector2.up * Time.deltaTime);
        else if (Input.GetKey(KeyCode.S))
            gameObject.transform.Translate(2 * Vector2.down * Time.deltaTime);
        else if (Input.GetKey(KeyCode.D))
            gameObject.transform.Translate(2 * Vector2.right * Time.deltaTime);
        else if (Input.GetKey(KeyCode.A))
            gameObject.transform.Translate(2 * Vector2.left * Time.deltaTime);
        // 重定位相机
        SetCameraPosition(gameObject.transform.position);
    }

    /// <summary>
    /// 设定相机位置。
    /// </summary>
    /// <param name="position">新的相机位置。</param>
    private void SetCameraPosition(Vector3 position)
    {
        playerCamera.transform.position = new Vector3(position.x, position.y, cameraDepth);
    }
    #endregion
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/118286
推荐阅读
相关标签
  

闽ICP备14008679号