如何在不使用"static"的情况下同时更改两个类中的字段值?C#.统一



我有两个类Player和PlayerUI。在Player类中,我有它的"health"值,在PlayerUI类中,有一个Slider,它的值根据Player类中的"heath"值更新每个帧。我还有一个改变"健康"值的伤害方法。在不使用static的情况下,如何在PlayerUI类和Player类中同时更改字段"health"的值?

玩家等级:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public static float health = 150;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Damage();
}
}
void Damage()
{
health -= 10;
}
}

PlayerUI类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerUI : MonoBehaviour
{
public Slider hpBar;
private void Awake()
{
hpBar.maxValue = Player.health;
}
private void Update()
{
hpBar.value = Player.health;
}
}

通常,您不希望在每帧中轮询和设置Update中的值,而是希望您的代码更受事件驱动=>仅在值实际更改时更改UI滑块。

我会使用一个属性使其成为事件驱动的。

这种结构的主要问题是:;谁应该为什么负责"以及";谁会知道谁">

选项A

例如:玩家是否应该主动控制UI,而UI甚至不知道玩家的存在?

  • 玩家"知道";播放器UI=>它有它的参考
  • 玩家主动通知UI并更新滑块值

public class Player : MonoBehaviour
{
// In this case the Player needs to know the UI
public PlayerUI ui;
private float health = 150;
public float Health
{
get => health;
set
{
health = value;
// Whenever the value of Health is changed actively update the UI
ui.hpBar.value = value;
}
}
private void Start ()
{
// initially inform the UI
Health = health;
}

...
void Damage()
{
// Important: don't change the field anymore but rather the property!
Health -= 10;
}
}

优势

  • Player获得了更多的权力和责任,并可能成为确保多个子组件(如UI和其他子组件(之间干净互操作性的核心组件。它也是API从外部访问这些子组件的中心点

的缺点

  • 由于玩家需要了解所有子组件,每次添加一个子组件时,都必须将其硬编码到类中,并配置子组件之间的引用和连接

选项2

还是应该相反,玩家甚至不知道UI的存在?

->不用知道UI,您可以在befores代码示例中添加UnityEvent(就像按钮onClick一样(,在该示例中,您可以通过Inspector或在运行时附加回调,以对运行状况属性的每一次更改做出反应。

  • 玩家只是调用他的事件,而不知道/不在乎谁在听
  • UI连接到事件->UI了解玩家

public class Player : MonoBehaviour
{
// In this case the Player doesn't know anyone
// attach listeners via the Inspector or on runtime via code
public UnityEvent<float> OnHealthChanged;
private float health = 150;
public float Health
{
get => health;
set
{
health = value;
// Whenever the value of Health is changed just raise the event
// you don't care who is listening or not, but whoever is will get informed
OnHealthChanged.Invoke(value);
}
}
private void Start ()
{
// initially inform all listeners
// Note that for timing reasons it is essential that the listeners are attached in "Awake"
// so before this "Start" is called
// Alternatively the listeners can of course also ONCE poll the value directly -> Up to you
Health = health;
}

...
void Damage()
{
// Important: don't change the field anymore but rather the property!
Health -= 10;
}
}

UI可以连接到播放器,收听事件并对其做出反应,例如

public class PlayerUI : MonoBehaviour
{
...
// In this case the UI needs to know the player
public Player player;
private void Awake ()
{
player.OnHealthChanged.AddListener(UpdateSlider);
}
private void OnDestroy ()
{
if(player) player.OnHealthChanged.RemoveListener(UpdateSlider);
}
private void UpdateSlider (float value)
{
hpBar.value = value;
}
}

优势

  • 这正好解决了选项A的缺点,并允许非常灵活地添加和删除侦听器,而播放器根本不需要关心

的缺点

  • 有了大量的子组件、事件和侦听器,它可能会很快失控,并变得难以调试和维护。同样如前所述,这对比赛条件、时间和秩序问题更为开放

Imo最好的方法是创建一个类似于;OnPlayerDamaged";当玩家受到任何伤害时,在玩家脚本中引发。然后在PlayerUI中创建一个订阅了OnPlayerDamaged事件的方法,并更改健康条。

播放器脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public static event Action<float> OnPlayerDamaged; 
private float health = 150;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Damage();
}
}
void Damage()
{
health -= 10;
OnPlayerDamaged.Invoke(health)
}
public static float GetHealth()
{
return health;
}
}

PlayerUI

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerUI : MonoBehaviour
{
public Slider hpBar;
private void Awake()
{
hpBar.maxValue = Player.GetHealth();
}

private void OnEnable()
{
Player.OnPlayerDamaged += ChangeHealth;
}
private void OnDisable()
{
Player.OnPlayerDamaged -= ChangeHealth;
}
private void Update()
{
}
public void ChangeHealth(float currentHealth)
{
hpBar.value = currentHealth;
}
}

有了这种方法,你以后可以添加任何你想要的东西,使用关于播放器损坏的信息,比如屏幕效果或音频更改。只需将该方法添加到事件中,就像我们案例中的ChangeHealth((方法一样。请记住在脚本禁用时从事件中删除该方法,以避免对同一方法进行多次订阅。

GetHealth((方法是一个快速破解方法,但你应该使用ScriptableObject来获取玩家统计信息,比如健康状况,并在你想使用它的地方引用它。然后你不需要在事件中传递currentHealth,当它被引发时,只需从可脚本化的对象中获取值。

最新更新