- Home /
Listen to changes on attached by serializefield script?
I have weapon switch script that detects finger swipe on screen but I feel I designed it the wrong way.
Now the weapon switch has serializefield of type PlayerAttack to switch weapon on and I want to get it the other way as it will be multiplayer game and each player will get own weapon switch swipe canvas - so - serializefield on playerAttack to set the weapon switch to listen to is what I want to achieve.
I hope it makes sense lol
public class WeaponSwitchDetector : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
{
[SerializeField] PlayerAttack playerAttack;
float _startXPos = 0f;
float _endXPos = 0f;
//TODO redo this, it's the player that should set it's weapon switcher ui, not the other way around.
public void SetPlayerToSwitchWeaponOn(PlayerAttack thisPlayerAttack)
{
playerAttack = thisPlayerAttack;
}
public void OnBeginDrag(PointerEventData eventData)
{
_startXPos = eventData.position.x;
}
public void OnDrag(PointerEventData data)
{
}
public void OnEndDrag(PointerEventData eventData)
{
_endXPos = eventData.position.x;
if (_endXPos < _startXPos)
{
playerAttack.SwitchWeapon(0);
}
else
{
playerAttack.SwitchWeapon(1);
}
}
}
Teraz mój WeaponSwitchDetector ma pole SerializeField w którym ustawiam który PlayerAttack ma uruchamiać funkcję od zmiany broni
Ja chcę to zrobić odwrotnie, usunąć SerializeField z WeaponSwitchDetector, żeby w PlayerAttack zrobić SerializeField i dopiero tu ustawić który WeaponSwitchDetector mam nasłuchiwać (chciałbym zwróconą pozycję kliknięcia onBeginDrag i OnEndDrag a zmieniać broń prosto z PlayerAttack na podstawie tych dwóch) Nie wiem nawet czy tak się da zrobić ale chciałbym mieć lepszy design i strukturę a nie pomieszanie między ui a 'nie ui'
Warstwę UI od obiektów sceny można rozdzielić za pomocą eventów (tzw. observer pattern
). Polega to na tym, że klasa UI wystawia publiczne pole event do którego inne klasy mogą dodawać swoje callbacki (tylko dodawana metoda musi pasować wzorem do wzoru eventu tj. mieć takie same argumenty). Czyli wtedy jedyną odpowiedzialnością klasy WeaponSwitchDetector
staje się uruchamiania eventu onSwitchWeapon
i nic więcej. Każda klasa może wtedy podpiąć swój callback pod zdarzenie WeaponSwitchDetector.onSwitchWeapon
(zmiana broni, uruchomienie dźwięku, wysłanie analityki etc), jeśli tylko ma ku temu powód. Gdyby coś było niejasne to pytaj.
Answer by andrew-lukasik · Aug 12, 2020 at 12:40 PM
Case: multiple WeaponSwitchDetector instances:
///
/// RESPONSIBILITY: Raise weapon switch event. Allow other scripts to subscribe.
///
public class WeaponSwitchDetector : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
{
float _startXPos = 0f;
public event System.Action<int> onSwitchWeapon = (dir) =>
{
#if UNITY_EDITOR
Debug.Log($"{nameof(onSwitchWeapon)}( {dir} ) event raised");
#endif
};
void IBeginDragHandler.OnBeginDrag ( PointerEventData eventData )
=> _startXPos = eventData.position.x;
void IDragHandler.OnDrag ( PointerEventData data ) {}
void IEndDragHandler.OnEndDrag ( PointerEventData eventData )
=> onSwitchWeapon( eventData.position.x<_startXPos ? 0 : 1 );
}
///
/// RESPONSIBILITY: Switch weapons I guess ¯\_(ツ)_/¯
///
public class PlayerAttack : MonoBehaviour
{
[SerializeField] WeaponSwitchDetector _switchDetector = null;
void OnEnable () => _switchDetector.onSwitchWeapon += SwitchWeapon;// subscribe
void OnDisable () => _switchDetector.onSwitchWeapon -= SwitchWeapon;// unsubscribe
public void SwitchWeapon ( int i ) => Debug.Log($"Weapon {i} selected!");
}
Case: single WeaponSwitchDetector instance:
///
/// RESPONSIBILITY: Raise weapon switch event. Allow other scripts to subscribe.
///
public class WeaponSwitchDetector : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
{
float _startXPos = 0f;
public static event System.Action<int> onSwitchWeapon = (dir) =>
{
#if UNITY_EDITOR
Debug.Log($"{nameof(onSwitchWeapon)}( {dir} ) event raised");
#endif
};
void IBeginDragHandler.OnBeginDrag ( PointerEventData eventData )
=> _startXPos = eventData.position.x;
void IDragHandler.OnDrag ( PointerEventData data ) {}
void IEndDragHandler.OnEndDrag ( PointerEventData eventData )
=> onSwitchWeapon( eventData.position.x<_startXPos ? 0 : 1 );
}
///
/// RESPONSIBILITY: Switch weapons I guess ¯\_(ツ)_/¯
///
public class PlayerAttack : MonoBehaviour
{
void OnEnable () => WeaponSwitchDetector.onSwitchWeapon += SwitchWeapon;// subscribe
void OnDisable () => WeaponSwitchDetector.onSwitchWeapon -= SwitchWeapon;// unsubscribe
public void SwitchWeapon ( int i ) => Debug.Log($"Weapon {i} selected!");
}
If you find my code syntax somewhat confusing here is a vanilla, 1:1, 100% equivalent:
///
/// RESPONSIBILITY: Raise weapon switch event. Allow other scripts to subscribe.
///
public class WeaponSwitchDetector : $$anonymous$$onoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
{
float _startXPos = 0f;
public event System.Action<int> onSwitchWeapon = onSwitchWeapon_InitialCallback;
public void OnBeginDrag ( PointerEventData eventData )
{
_startXPos = eventData.position.x;
}
public void OnDrag ( PointerEventData data )
{
}
public void OnEndDrag ( PointerEventData eventData )
{
if( eventData.position.x<_startXPos ) onSwitchWeapon( 0 );
else onSwitchWeapon( 1 );
}
static void onSwitchWeapon_InitialCallback ( int dir )
{
#if UNITY_EDITOR
Debug.Log( nameof(onSwitchWeapon) + "( " + dir + " ) event raised");
#endif
}
}
///
/// RESPONSIBILITY: Switch weapons I guess ¯\_(ツ)_/¯
///
public class PlayerAttack : $$anonymous$$onoBehaviour
{
[SerializeField] WeaponSwitchDetector _switchDetector = null;
void OnEnable ()
{
_switchDetector.onSwitchWeapon += SwitchWeapon;// subscribe
}
void OnDisable ()
{
_switchDetector.onSwitchWeapon -= SwitchWeapon;// unsubscribe
}
public void SwitchWeapon ( int dir )
{
Debug.Log( "Weapon " + dir + " selected!" );
}
}
No właśnie nie wiedziałem jak działa większość tych '=>' , nigdy nie miałem okazji używać takiej konstrukcji. Dzięki za pomoc choć mam kilka pytań
public event System.Action<int> onSwitchWeapon = onSwitchWeapon_DefaultCallback;
To przypisanie do onSwitchWeapon_DefaultCallback faktycznie musi mieć jakąś domyślną funkcję? $$anonymous$$yślałem, że masz pomyłkę i to powinno być _InitialCallback ale wtedy metoda musi być statyczna i przy każdej próbie zmiany dostawałem info z konsoli o ustawieniu eventu.
Rozumiem więc, że można po prostu dodać dodatkową funkcję, która będzie wykonywana przy każdym wykonaniu eventu? (Ustawiłem na null i działa.)
Jak działa ta część przy triggerowaniu eventu ? 0 : 1)
? To 0 i 1 to wartości do zwrócenia dla podpiętego listenera zależnie od spełnienia warunku, a czy ten pytajnik i dwukropek na coś wpływają czy taki po prostu jest syntax?
"Czy onSwitchWeapon musi mieć jakąś domyślną funkcję?"
Nie musi, możesz dać spokojnie null. Ale z tego względu że null
może prowadzić do czasami do NullReferenceException
to zainicjowanie go jakąś metodą po prostu pozwala się tym nie przejmować i tyle. Plus skoro ta metoda wykonywana jest przy każdym uruchomieniu eventu to czasami się przydaje przy debuggowaniu, wystarczy (jak tu) wrzucić do niej Debug.Log
żeby móc wiedzieć kiedy ten event jest uruchamiany (łatwo taką linijkę wykomentować żeby wyłączyć loga jak nie potrzebny aktualnie).
"onSwitchWeapon( eventData.position.x<_startXPos ? 0 : 1 );"
Ah przeoczyłem. To jest tzw. conditional operator ?: i jest to po prostu skrót od:
if( eventData.position.x<_startXPos )
onSwitchWeapon( 0 );
else
onSwitchWeapon( 1 );