feat: Event & Teleporter

This commit is contained in:
2026-11-03 22:58:10 +01:00
parent a7e8ef1a1f
commit 9fd4e13d34
34 changed files with 5877 additions and 25 deletions

View File

@@ -1,4 +1,5 @@
using Arena.Combat;
using System;
using UnityEngine;
using UnityEngine.InputSystem;
@@ -18,6 +19,7 @@ namespace Arena.Characters
{
public CharacterController Controller;
public InputActionReference MoveAction;
public InputActionReference InteractAction;
}
[System.Serializable]
@@ -37,8 +39,12 @@ namespace Arena.Characters
[SerializeField] private StateContainer _state;
[SerializeField] private Health _health;
public StateContainer State => _state;
public Action OnInteract;
public StateContainer State => _state;
public Health Health => _health;
private bool _isInteracting;
private Vector2 _moveInput;
private Camera _camera;
@@ -65,12 +71,16 @@ namespace Arena.Characters
void OnEnable()
{
_references.MoveAction.action.Enable();
_references.InteractAction.action.Enable();
}
void OnDisable()
{
if (Instance == this)
{
_references.MoveAction.action.Disable();
_references.InteractAction.action.Disable();
}
}
void OnDestroy()
@@ -88,20 +98,47 @@ namespace Arena.Characters
GetInputs();
Move(t);
Interact();
}
#endregion
#region Public Methods
#region PUBLIC METHODS
public void TakeDamage(int amount)
{
_health.TakeDamage(amount);
}
// <summary>
/// Teleports the player.
/// </summary>
/// <param name="pose">x, y, z: position. w: angle around the vertical axis, in degrees.</param>
public void Teleport(Vector4 pose)
{
// An enabled CharacterController would overwrite the new position
_references.Controller.enabled = false;
Vector3 position = new Vector3(pose.x, pose.y, pose.z);
Quaternion rotation = Quaternion.Euler(0f, pose.w, 0f);
transform.SetPositionAndRotation(position, rotation);
_references.Controller.enabled = true;
}
/// <summary>
/// Teleports the player to the position and heading of a target, such as a spawn point.
/// </summary>
public void Teleport(Transform target)
{
Vector3 position = target.position;
Vector4 pose = new Vector4(position.x, position.y, position.z, target.eulerAngles.y);
Teleport(pose);
}
#endregion
#region Private Methods
#region PRIVATE METHODS
#if UNITY_EDITOR
[ContextMenu("Debug/Take 25 Damage")]
@@ -114,6 +151,7 @@ namespace Arena.Characters
private void GetInputs()
{
_moveInput = _references.MoveAction.action.ReadValue<Vector2>();
_isInteracting = _references.InteractAction.action.WasPressedThisFrame();
}
private void Move(float t)
@@ -126,6 +164,14 @@ namespace Arena.Characters
_references.Controller.Move(velocity * t);
}
private void Interact()
{
if (_isInteracting)
{
OnInteract?.Invoke();
}
}
private Vector3 ToWorldDirection(Vector2 input)
{
// Only the camera rotation around the vertical axis is kept

View File

@@ -1,16 +1,89 @@
using UnityEngine;
using UnityEngine.Events;
public class PlayerEvent : MonoBehaviour
namespace Arena.Characters
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
public class PlayerEvent : MonoBehaviour
{
public enum Trigger
{
OnPlayerEnter,
OnPlayerInteract
}
}
[System.Serializable]
public class Settings
{
[Tooltip("OnPlayerEnter: as soon as the player enters. OnPlayerInteract: when the player interacts inside the zone.")]
public Trigger Trigger;
}
// Update is called once per frame
void Update()
{
[SerializeField] private Settings _settings;
[SerializeField] private UnityEvent _onPlayer;
private bool _isListening;
#region UNITY LIFECYCLES
private void OnDisable()
{
// The object can be hidden while the player is inside
StopListening();
}
void OnTriggerEnter(Collider other)
{
if (!IsPlayer(other))
return;
if (_settings.Trigger == Trigger.OnPlayerEnter)
_onPlayer.Invoke();
else
StartListening();
}
void OnTriggerExit(Collider other)
{
if (IsPlayer(other))
StopListening();
}
#endregion
private bool IsPlayer(Collider other)
{
return Player.Instance != null && other.gameObject == Player.Instance.gameObject;
}
private void StartListening()
{
Player.Instance.OnInteract += HandleInteract;
_isListening = true;
}
private void StopListening()
{
if (!_isListening)
return;
if (Player.Instance != null)
Player.Instance.OnInteract -= HandleInteract;
_isListening = false;
}
private void HandleInteract()
{
_onPlayer.Invoke();
}
public void Teleport(Transform target)
{
if (!Player.Instance)
return;
StopListening();
Player.Instance.Teleport(target);
}
}
}

View File

@@ -4,7 +4,7 @@ using UnityEngine;
namespace Arena.Combat
{
/// <summary>
/// Hit points of an entity. Serialized inside the component that owns it (Player, Enemy),
/// Health points of an entity. Serialized inside the component that owns it (Player, Enemy),
/// which calls Initialize in its Awake.
/// </summary>
[System.Serializable]
@@ -12,19 +12,19 @@ namespace Arena.Combat
{
#region Fields
[Tooltip("Hit points when full.")]
[Tooltip("Health points when full.")]
[Min(1)] public int Max = 100;
[Tooltip("Current hit points, visible for debugging.")]
[Tooltip("Current health points, visible for debugging.")]
[SerializeField] private int _current;
/// <summary>
/// Raised with the current and max hit points each time they change.
/// Raised with the current and max health points each time they change.
/// </summary>
public event Action<int, int> OnChange;
/// <summary>
/// Raised once, when the hit points reach zero.
/// Raised once, when the health points reach zero.
/// </summary>
public event Action OnDied;
@@ -36,7 +36,7 @@ namespace Arena.Combat
#region Public Methods
/// <summary>
/// Fills the hit points. Called by the owner in its Awake.
/// Fills the health points. Called by the owner in its Awake.
/// </summary>
public void Initialize()
{
@@ -44,7 +44,7 @@ namespace Arena.Combat
}
/// <summary>
/// Removes hit points and raises OnDied the first time they reach zero.
/// Removes health points and raises OnDied the first time they reach zero.
/// </summary>
public void TakeDamage(int amount)
{
@@ -58,7 +58,7 @@ namespace Arena.Combat
}
/// <summary>
/// Gives hit points back without exceeding Max. Has no effect once dead.
/// Gives health points back without exceeding Max. Has no effect once dead.
/// </summary>
public void Heal(int amount)
{

View File

@@ -0,0 +1,68 @@
using UnityEngine;
namespace Arena.Combat
{
/// <summary>
/// Moves straight ahead and damages the first IDamageable it touches.
/// Needs a trigger collider and a kinematic Rigidbody.
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class Projectile : MonoBehaviour
{
[System.Serializable]
public class Settings
{
[Tooltip("Speed, in meters per second.")]
[Min(0f)] public float Speed = 20f;
[Tooltip("Time before an unused projectile is destroyed, in seconds.")]
[Min(0.1f)] public float Lifetime = 2f;
}
#region Fields
[SerializeField] private Settings _settings;
private int _damage;
private GameObject _owner;
#endregion
#region Unity Lifecycle
private void Update()
{
transform.position += transform.forward * _settings.Speed * Time.deltaTime;
}
private void OnTriggerEnter(Collider other)
{
// Ignore the shooter and the other trigger zones
if (other.isTrigger || other.gameObject == _owner)
return;
if (other.TryGetComponent(out IDamageable target))
target.TakeDamage(_damage);
Destroy(gameObject);
}
#endregion
#region Public Methods
/// <summary>
/// Starts the projectile. Called right after Instantiate by whoever fires it.
/// </summary>
/// <param name="damage">Damage dealt to the IDamageable it touches.</param>
/// <param name="owner">Shooter, ignored by the projectile.</param>
public void Launch(int damage, GameObject owner)
{
_damage = damage;
_owner = owner;
Destroy(gameObject, _settings.Lifetime);
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 875f6e6257f7027489dceee7bdfec3cb

View File

@@ -0,0 +1,16 @@
using UnityEngine;
public class ProjectileLauncher : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4bb53e2fd0884a746883755fca366194

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 88dab0d441585ab4cbae1aa9191deffe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using UnityEngine;
using Arena.Characters;
namespace Arena.Interactions
{
/// <summary>
/// Heals the player. Apply is meant to be linked to a PlayerEvent.
/// </summary>
public class HealEffect : MonoBehaviour
{
[System.Serializable]
public class Settings
{
[Tooltip("Hit points given to the player.")]
[Min(1)] public int Amount = 25;
}
#region Fields
[SerializeField] private Settings _settings;
#endregion
#region Public Methods
public void Apply()
{
if (Player.Instance != null)
Player.Instance.Health.Heal(_settings.Amount);
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5e30510fdd4a8b64da1115732d8ee972