Merge branch 'refs/heads/feature/player.basic-combat-pt2' into dev

This commit is contained in:
Chris
2026-01-16 20:28:28 -05:00
180 changed files with 82789 additions and 2201 deletions

View File

@@ -21,7 +21,7 @@ namespace Reset {
public BBParameter<float> length;
public BBParameter<Vector3> direction;
public BBParameter<Vector3> offset;
public BBParameter<LayerMask> ignoreLayers;
public BBParameter<LayerMask> layerMask;
public BBParameter<float> width;
@@ -44,7 +44,7 @@ namespace Reset {
BBParameterEditor.ParameterField("Length", length);
BBParameterEditor.ParameterField("Direction", direction);
BBParameterEditor.ParameterField("Offset", offset);
BBParameterEditor.ParameterField("Ignore Layers", ignoreLayers);
BBParameterEditor.ParameterField("Ignore Layers", layerMask);
if (castType.value == EnvironmentObserver.CastType.SphereCast || castType.value == EnvironmentObserver.CastType.SphereOverlap) {
BBParameterEditor.ParameterField("Width", width);
@@ -88,7 +88,7 @@ namespace Reset {
length = length.value,
direction = direction.value,
offset = offset.value,
ignoreLayers = ignoreLayers.value,
ignoreLayers = layerMask.value,
width = width.value,

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace Reset.Units {
[Category("Reset/Units/Combat")]
[Description("Drag a collection of units along with the attackers movement or actions")]
public class DragUnits : ActionTask<UnitCombat>{
public BBParameter<List<Collider>> unitsToDrag;
public BBParameter<float> dragTime;
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute() {
foreach (Collider col in unitsToDrag.value) {
if (dragTime.value == 0f) {
agent.AddDragCollider(col);
} else {
agent.AddDragCollider(col, dragTime.value);
}
}
EndAction(true);
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a15fb8c5108ca6a47a12fcf6278579fd

View File

@@ -8,7 +8,7 @@ namespace Reset.Units {
[Category("Reset/Movement")]
public class ChangeRotationSettings : ActionTask<UnitMovementHandler> {
[SerializeField] public EnumValueGroup facingDirection = new EnumValueGroup("Facing Direction", PlayerFacingDirection.TowardsTarget);
[SerializeField] public EnumValueGroup facingDirection = new EnumValueGroup("Facing Direction", UnitFacingDirection.TowardsTarget);
[SerializeField] public FloatValueGroup rotationSpeed = new (newLabel: "Rotation Speed");
//Use for initialization. This is called only once in the lifetime of the task.

View File

@@ -1,14 +1,13 @@
using System;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using Unity.Netcode;
using Unity.Netcode.Components;
using Reset.Units;
using UnityEngine;
namespace Reset.Core {
[Category("Reset")]
[Description("Sends an animation trigger with network sync to networked objects")]
public class SendAnimationTrigger : ActionTask<NetworkAnimator>{
public class SendAnimationTrigger : ActionTask<UnitAnimation>{
public BBParameter<string> trigger;
//Use for initialization. This is called only once in the lifetime of the task.
@@ -21,11 +20,8 @@ namespace Reset.Core {
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute(){
try {
agent.SetTrigger(trigger.value);
} catch (Exception e) {
Debug.LogError($"Did not set Network Animator trigger <i>{trigger.name}</i> on <b>{(agent == null ? null : agent.name)}</b>: {e.Message}");
}
agent.SendAnimationTrigger(trigger.value);
EndAction(true);
}

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace Reset.Units{
public interface IUnitTargetProvider{
public GameObject UnitTarget{ get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f0c49825086c41e0990e5fa5d3f0041c
timeCreated: 1761269267

View File

@@ -15,7 +15,7 @@ using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;
namespace Reset.Units{
public class LockOnManager : MonoBehaviour{
public class LockOnManager : UnitComponent, IUnitTargetProvider {
public class ActiveLockOnTarget{
public GameObject gameObject;
public float targetWeight;
@@ -24,6 +24,9 @@ namespace Reset.Units{
}
public static LockOnManager Instance;
// IUnitTargetProvider
public GameObject UnitTarget => mainTarget.gameObject;
// Lock On settings
[Space(5)] public float lockOnRange = 40f;
@@ -32,7 +35,7 @@ namespace Reset.Units{
[FormerlySerializedAs("smoothing")] public float smoothTime = 1f;
// Lock On Tracking
[Space(10)] public ActiveLockOnTarget mainTarget;
[Space(10), ShowInInspector] public ActiveLockOnTarget mainTarget;
public List<ActiveLockOnTarget> activeTargets = new List<ActiveLockOnTarget>();
@@ -47,13 +50,13 @@ namespace Reset.Units{
private VisualElement elementRoot;
private void Awake(){
// // Register as singleton
// if (Instance == null) {
// Instance = this;
// } else {
// this.enabled = false;
// return;
// }
// Register as singleton
if (Instance == null && Unit.UnitIsLocal()) {
Instance = this;
} else {
enabled = false;
return;
}
// References from camera
targetGroup = PlayerManager.Camera.transform.Find("Target Group").GetComponent<CinemachineTargetGroup>();
@@ -267,7 +270,7 @@ namespace Reset.Units{
// Catch exception from nothing being found
if (!closestTarget) {
Debug.LogWarning("Lock-on attempted, but no lock on target was found viable.");
Debug.LogWarning($"Lock-on attempted, but no lock on target was found viable. Searched {acceptedTargets.Count} targets.");
return;
}
@@ -310,5 +313,4 @@ namespace Reset.Units{
}
}
}
}

View File

@@ -1,9 +1,10 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using Unity.Netcode;
using UnityEngine;
namespace Reset.Items{
public abstract class Item : ScriptableObject{
public abstract class Item : SerializedScriptableObject{
public string itemName;
public float permanency;

View File

@@ -1,22 +1,58 @@
using System;
using Drawing;
using NodeCanvas.StateMachines;
using NodeCanvas.Framework;
using Reset.Core;
using Reset.Units;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using Unity.Netcode;
using UnityEngine;
namespace Reset.Items{
[CreateAssetMenu(menuName = "Reset/Weapon", fileName = "New Weapon")]
public class Weapon : Item, IEquipable{
public CombatType combatType;
public GameObject weaponModel;
public NodeCanvas.Framework.Graph weaponFSM;
void Awake(){
}
public Vector3 handPositionOffset;
public Vector3 handRotationOffset;
public string actorScriptName;
[OdinSerialize, ShowInInspector] public Type actorScript;
public void AddActorScript(){
// Type actorScript = Type.GetType("ShurikenActor");
//
// if (actorScript == null) {
// Debug.LogError($"The WeaponActor supplied was not found for {itemName}. Does script named '{actorScriptName + ",Assembly-UnityScript"}' exist?");
// return;
// }
try {
if (actorScript != null) {
WeaponActor weaponActor = PlayerManager.Player.AddComponent(actorScript) as WeaponActor;
weaponActor.relatedObject = PlayerManager.Player.GetComponent<PlayerCombat>().GetCurrentWeaponItem();
weaponActor.relatedWeapon = this;
weaponActor.relatedGraph = PlayerManager.Player.GetComponent<GraphOwner>();
}
} catch (Exception e) {
Debug.LogException(e);
}
}
public override void DrawItemInfo(Vector3 position){
Debug.Log("hiuh");
Draw.ingame.Label2D(position + Vector3.up * 1.6f, "Damage goes here");
Draw.ingame.Label2D(position + Vector3.up * 1.35f, "Speed goes here");
}
public GameObject InstantiateItemObject(){
return GameObject.Instantiate(weaponModel);
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using NodeCanvas.Framework;
using UnityEngine;
using UnityEngine.UI;
namespace Reset.Items{
public abstract class WeaponActor : MonoBehaviour{
public Dictionary<string, Action> weaponEvents = new Dictionary<string, Action>();
public Dictionary<string, object> weaponVariables = new Dictionary<string, object>();
public Weapon relatedWeapon;
public GraphOwner relatedGraph;
public GameObject relatedObject;
// Recieve Weapon Catch signal from Animation
public void WeaponCatch(){
relatedGraph.SendEvent("Weapon Catch");
}
// Recieve Weapon Release signal from Animation
public void WeaponRelease(){
relatedGraph.SendEvent("Weapon Release");
}
public void RegisterWeaponEvent(string calledName, Action action){
weaponEvents.Add(calledName, action);
}
public void RegisterWeaponVariable(string variable, object value){
if (weaponVariables.ContainsKey(variable)) {
weaponVariables[variable] = value;
} else {
weaponVariables.Add(variable, value);
}
}
public object ReadWeaponVariable<T>(string variable) where T : class{
return (T)weaponVariables[variable];
}
public void DoWeaponEvent(string eventName){
if (weaponEvents.ContainsKey(eventName)) {
weaponEvents[eventName].Invoke();
return;
}
Debug.LogError($"There is no event by name of {eventName} in {name}");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7db5375a3b8f4212b407f4f8f41a8870
timeCreated: 1768000398

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 70ef6ecafdc149559e4cb6e19787e719
timeCreated: 1768000824

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Services.Relay.Models;
using UnityEngine;
using UnityEngine.Assertions.Must;
using Quaternion = UnityEngine.Quaternion;
using Random = UnityEngine.Random;
using Vector3 = UnityEngine.Vector3;
namespace Reset.Items{
public class ShurikenActor : WeaponActor{
public GameObject bladeRing;
public Transform target;
private Vector3 targetPosition;
private Transform originalParent;
private Quaternion originalRotation;
private Vector3 originalPosition;
private Vector3 originalWorldPosition;
private float rotateSpeed;
private float rotateSpeedTarget;
private float rotationAcceleration;
private Quaternion targetRotation;
void Awake(){
// Register Weapon Events
weaponEvents.Add("Set Target", SetTarget);
weaponEvents.Add("Fly To Target", FlyToTarget);
weaponEvents.Add("Fly To Hand", FlyToHand);
weaponEvents.Add("Return To Hand", ReturnToHand);
}
void SetTarget(){
target = null;
if (weaponVariables["target"] != null) {
target = (Transform)weaponVariables["target"];
}
}
void FlyToTarget(){
rotateSpeedTarget = 1200f;
rotationAcceleration = 200f;
RegisterWeaponVariable("state", "Flying To Target");
float randomRot = Random.Range(-90f, 180f);
targetRotation = transform.rotation * originalRotation * Quaternion.AngleAxis(randomRot, Vector3.up);
originalParent = relatedObject.transform.parent;
originalPosition = relatedObject.transform.localPosition;
originalRotation = relatedObject.transform.localRotation;
originalWorldPosition = relatedObject.transform.position;
relatedObject.transform.SetParent(null, true);
}
void FlyToHand(){
rotateSpeed = 3000f;
rotateSpeedTarget = 50f;
rotationAcceleration = 2f;
RegisterWeaponVariable("state", new string("Flying To Hand"));
}
void ReturnToHand(){
rotateSpeedTarget = 250f;
rotationAcceleration = 1f;
RegisterWeaponVariable("state", new string("Back In Hand"));
relatedObject.transform.SetParent(originalParent);
relatedObject.transform.localPosition = originalPosition;
relatedObject.transform.localRotation = originalRotation;
target = null;
}
void Start(){
// Save refernce to the blade outer ring
bladeRing = relatedObject.transform.GetChild(0).gameObject;
}
void Update(){
if (target) {
targetPosition = target.transform.position;
} else {
targetPosition = transform.position + transform.forward * 10f + Vector3.up;
}
rotateSpeed = Mathf.Lerp(rotateSpeed, rotateSpeedTarget, rotationAcceleration * Time.deltaTime);
bladeRing.transform.Rotate(Vector3.up * (rotateSpeed * Time.deltaTime));
Debug.Log((string)ReadWeaponVariable<string>("state"));
// Fly to the target
if ((string)ReadWeaponVariable<string>("state") == "Flying To Target") {
float offset = 9f;
Vector3 startPos = relatedObject.transform.position;
Vector3 endPos = targetPosition;
Vector3 center = (startPos + endPos) / 2f;
center -= new Vector3(0f, offset, 0f);
Vector3 relativeCenter = startPos - center;
Vector3 relativeEnd = endPos - center;
relatedObject.transform.position = Vector3.Slerp(relativeCenter, relativeEnd, 5f * Time.deltaTime) + center;
relatedObject.transform.rotation = Quaternion.Lerp(relatedObject.transform.rotation, targetRotation, 5f * Time.deltaTime);
// relatedObject.transform.position = Vector3.Slerp(relatedObject.transform.position, targetPosition, 5f * Time.deltaTime);
// When there, set next stage
if (Vector3.Distance(relatedObject.transform.position, targetPosition) < .5f) {
rotationAcceleration = 50f;
rotateSpeedTarget = 1200f;
RegisterWeaponVariable("state", new string("At Target"));
}
}
// Fly to the hand
if ((string)ReadWeaponVariable<string>("state") as string == "Flying To Hand") {
relatedObject.transform.position = Vector3.Slerp(relatedObject.transform.position, originalParent.transform.position, 5f * Time.deltaTime);
relatedObject.transform.rotation = Quaternion.Lerp(relatedObject.transform.rotation, originalParent.transform.rotation * originalRotation , 5f * Time.deltaTime);
// When there, set as finished
if (Vector3.Distance(relatedObject.transform.position, originalParent.transform.position) < .1f) {
RegisterWeaponVariable("state", new string("At Hand"));
}
}
RegisterWeaponVariable("relativePosition", relatedObject.transform.position - transform.position);
RegisterWeaponVariable("relativeOffset", new Vector3(0f, relatedObject.transform.position.y, Vector3.Distance(relatedObject.transform.position, transform.position)));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7d1d2a1a617247459542ec29b4f50785
timeCreated: 1768002409

View File

@@ -0,0 +1,6 @@
namespace Reset.Core{
public enum CombatType{
Melee = 0,
Ranged = 1
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eb3157c88aaa41f8b0b20d4690bb1459
timeCreated: 1767997846

View File

@@ -7,7 +7,8 @@ using UnityEngine;
using Random = UnityEngine.Random;
public class UnitCombat : UnitComponent {
public List<Collider> draggedUnits = new List<Collider>();
// public List<Collider> draggedUnits = new List<Collider>();
public Dictionary<Collider, float> draggedColliders = new Dictionary<Collider, float>();
private UnitMovementHandler movement;
@@ -18,9 +19,15 @@ public class UnitCombat : UnitComponent {
private float speedDelta;
private float sinAmplitude;
private float sinOffset;
private List<Collider> activelyDraggedColliders;
private List<Collider> expiredColliders;
void Awake(){
movement = GetComponent<UnitMovementHandler>();
activelyDraggedColliders = new List<Collider>();
expiredColliders = new List<Collider>();
}
void Start(){
@@ -31,7 +38,53 @@ public class UnitCombat : UnitComponent {
// Update is called once per frame
void Update(){
// Calculate movement for dragged units
DragAttackedUnits();
// Check timers to make sure an object doesn't stay dragged
CheckUnitTimers();
}
// dragTime is set as a maximum. If no time is provided then it defaults to two seconds.
public void AddDragCollider(Collider newCollider, float dragTime = 2f){
if (draggedColliders.ContainsKey(newCollider)) {
draggedColliders[newCollider] = dragTime;
} else {
draggedColliders.Add(newCollider, dragTime);
activelyDraggedColliders.Add(newCollider);
}
}
public void RemoveDragCollider(Collider colliderToRemove){
draggedColliders.Remove(colliderToRemove);
activelyDraggedColliders.Remove(colliderToRemove);
}
public void CallAttack(){
Debug.Log("Attacked!");
Unit.Graph.SendEvent("Call Attack");
}
void CheckUnitTimers(){
// Decrease the timer of the dragged colliders
foreach (Collider thisCollider in activelyDraggedColliders) {
draggedColliders[thisCollider] -= 1f * Time.deltaTime;
// Pend them for removal when ready
if (draggedColliders[thisCollider] < 0f) {
expiredColliders.Add(thisCollider);
}
}
// Remove expired colliders
for (int i = 0; i < expiredColliders.Count; i++) {
RemoveDragCollider(expiredColliders[i]);
}
// Clear list if not empty
if (expiredColliders.Count > 0f) {
expiredColliders.Clear();
}
}
void DragAttackedUnits(){
@@ -58,12 +111,15 @@ public class UnitCombat : UnitComponent {
lastPosition = transform.position;
// Apply the speed, direction, and rotation to each unit
foreach (Collider draggedUnit in draggedUnits) {
foreach (Collider draggedUnit in activelyDraggedColliders) {
UnitMovementHandler draggedUnitMovement = draggedUnit.GetComponent<UnitMovementHandler>();
if (!draggedUnitMovement) {
Debug.LogError($"No available UnitMovement on {draggedUnit.name}. Aborting drag on this unit.");
continue;
}
// Prevent units from entering a runaway situation where they constantly pull each other at high speeds across the map
speedDelta = Mathf.Min(speedDelta, draggedUnitMovement.data.moveSpeed.Value);
draggedUnitMovement.SetNewRotation(-transform.position.DirectionTo(draggedUnit.transform.position), 1f, true);
draggedUnitMovement.SetNewDirection(positionDelta.ToVector2(), 1f, true);

View File

@@ -5,19 +5,13 @@ namespace Reset.Units{
private bool enabledAsHost = true;
private Unit _unit;
internal Unit Unit{
get {
if (_unit != null) {
return _unit;
}
_unit = GetComponent<Unit>();
if (_unit == null) { _unit = GetComponent<Unit>(); }
return _unit;
}
}
void DisableComponent(){
enabledAsHost = false;
}

View File

@@ -1,7 +1,9 @@
using System;
using Reset.Units;
using UnityEngine;
using UnityEngine.InputSystem;
using Unity.Cinemachine;
using Unity.Services.Matchmaker.Models;
// This class receives input from a PlayerInput component and disptaches it
// to the appropriate Cinemachine InputAxis. The playerInput component should
@@ -32,7 +34,7 @@ class CustomInputHandler : InputAxisControllerBase<CustomInputHandler.Reader>
// We process user input on the Update clock
void Update()
{
if (Application.isPlaying){
if (Application.isPlaying && PlayerManager.Player){
UpdateControllers();
Controllers[0].Input.ProcessInput(PlayerInput);
Controllers[1].Input.ProcessInput(PlayerInput);

View File

@@ -5,7 +5,10 @@ using Unity.Netcode;
using UnityEngine;
namespace Reset.Units{
public class Enemy : Unit, ILockOnTarget, IKillable {
public class Enemy : Unit, ILockOnTarget, IKillable{
// Spawn Info
public EnemySpawn relatedSpawner;
// Lock-On
public float lockonTargetRadius{ get; set; } = 10f;
[ShowInInspector]

View File

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

View File

@@ -0,0 +1,20 @@
using Sirenix.OdinInspector;
using UnityEngine;
namespace Reset.Units{
public class EnemyCombat : UnitCombat, IUnitTargetProvider{
public GameObject target;
public GameObject UnitTarget => target;
[Button]
public void SetNewTarget(GameObject newTarget){
Unit.Graph.SendEvent("New Target Detected");
target = newTarget;
}
public void DropTarget(){
target = null;
Unit.Graph.SendEvent("Drop Target");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f1528d25d3d94df59161993cb28bcb17
timeCreated: 1761269778

View File

@@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using Drawing;
using Pathfinding;
using Sirenix.OdinInspector;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Reset.Units{
public class EnemySpawn : MonoBehaviour{
enum SpawnerState{
Idle = 0,
TargetAcquired,
TargetDropped,
InCombatUnlocked,
InCombatLocked,
Cooldown,
}
public float radius = 30f;
public int minimumEnemies = 1;
public int maximumEnemies = 5;
public Vector2 enemyCount;
// TODO: Replace this with an Enemy selector based on difficulty, random chance, etc?
public GameObject enemy;
public List<GameObject> enemies;
public GridGraph relatedGraph;
private SpawnerState spawnerState;
private float timeInState;
void Start(){
CreateAstarGraph();
SpawnEnemies();
}
void CreateAstarGraph(){
relatedGraph = AstarPath.active.data.AddGraph(typeof(GridGraph)) as GridGraph;
relatedGraph.SetDimensions(Mathf.FloorToInt(radius) * 2, Mathf.FloorToInt(radius) * 2, 1f);
relatedGraph.collision.diameter = 3f;
AstarPath.active.Scan(relatedGraph);
GetComponent<ProceduralGraphMover>().graph = relatedGraph;
}
void SpawnEnemies(){
int count = Random.Range(minimumEnemies, maximumEnemies + 1);
for (int i = 0; i < count; i++) {
Vector3 newPosition = transform.position;
float randomX = Random.Range(-(radius / 2f), radius / 2f);
float randomZ = Random.Range(-(radius / 2f), radius / 2f);
newPosition += new Vector3(randomX, transform.position.y, randomZ);
float randomRot = Random.Range(0f, 360f);
GameObject newEnemy = Instantiate(enemy, newPosition, Quaternion.AngleAxis(randomRot, Vector3.up));
newEnemy.GetComponent<Enemy>().relatedSpawner = this;
enemies.Add(newEnemy);
}
}
void Update(){
ProcessStateUpdate();
DrawSpawnStateGizmos();
}
// Set the new state of the spawner and also reset the timer for state management
void ChangeState(SpawnerState newState){
spawnerState = newState;
ProcessStateStart();
timeInState = 0f;
}
// Does initial actions on state switch
void ProcessStateStart(){
switch (spawnerState) {
case SpawnerState.TargetDropped:
foreach (GameObject thisEnemy in enemies) {
thisEnemy.GetComponent<EnemyCombat>().DropTarget();
}
break;
case SpawnerState.InCombatUnlocked:
foreach (GameObject thisEnemy in enemies) {
thisEnemy.GetComponent<Enemy>().Graph.SendEvent("Enter Combat");
}
break;
}
}
// Actively processes the state of the spawner and its assocaited enemies
void ProcessStateUpdate(){
switch (spawnerState) {
// Check if player is in range of this spawner. If so, set them as the new target
case SpawnerState.Idle:
if (PlayerIsInRange(radius, radius / 2f)) {
foreach (GameObject thisEnemy in enemies) {
thisEnemy.GetComponent<EnemyCombat>().SetNewTarget(PlayerManager.Player);
}
ChangeState(SpawnerState.TargetAcquired);
}
break;
// After grabbing a player in range, make sure they stay in range. If they aren't in range after 5 seconds, disengage. If they are or get too close, engage.
case SpawnerState.TargetAcquired:
if (PlayerIsInRange(8f, 8f) && timeInState > 1f) {
ChangeState(SpawnerState.InCombatUnlocked);
return;
}
if (timeInState > 5f) {
if (PlayerIsInRange(radius + 2f, radius * .6f)) {
ChangeState(SpawnerState.InCombatUnlocked);
return;
}
ChangeState(SpawnerState.TargetDropped);
return;
}
break;
// Wait a few seconds after dropping the target before doing anything else
case SpawnerState.TargetDropped:
if (timeInState > 3f) {
ChangeState(SpawnerState.Idle);
return;
}
break;
// Set all units to combat. Drop combat if the target gets too far
case SpawnerState.InCombatUnlocked:
if (!PlayerIsInRange(radius * 1.5f, radius * 1.5f) && timeInState > 5f) {
ChangeState(SpawnerState.TargetDropped);
return;
}
break;
// Lock the player to the spawn. Currently unused.
case SpawnerState.InCombatLocked:
break;
// Long cooldown for after all units have been killed. Currently unused.
case SpawnerState.Cooldown:
break;
}
// Increment state timer
timeInState += 1f * Time.deltaTime;
}
GameObject PlayerIsInRange(float spawnerRange, float enemyRange){
// TODO: Make compatible with all players
if (!PlayerManager.Player) {
return null;
}
Vector3 playerPos = PlayerManager.Player.transform.position;
// Skip checking and return null/false if the player is nowhere near the input range
if (Vector3.Distance(playerPos, transform.position) > spawnerRange * 2f) {
return null;
}
// If they are in range, check if the player is close enough to either an enemy or the input range
if (Vector3.Distance(playerPos, transform.position) < spawnerRange) {
return PlayerManager.Player;
}
foreach (GameObject thisEnemy in enemies) {
if (Vector3.Distance(playerPos, thisEnemy.transform.position) < enemyRange) {
return PlayerManager.Player;
}
}
return null;
}
private void DrawSpawnStateGizmos(){
Color cylinderColor = Color.blue;
switch (spawnerState) {
case SpawnerState.Idle:
break;
case SpawnerState.TargetAcquired:
cylinderColor = Color.coral;
break;
case SpawnerState.TargetDropped:
cylinderColor = Color.khaki;
break;
case SpawnerState.InCombatUnlocked:
cylinderColor = Color.red;
break;
case SpawnerState.InCombatLocked:
cylinderColor = Color.violetRed;
break;
case SpawnerState.Cooldown:
cylinderColor = Color.gray;
break;
}
Draw.WireCylinder(transform.position, transform.position + Vector3.up * 7f, radius, cylinderColor);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dd8d1c9c0ddc5c14497e74371d4b0350

View File

@@ -26,6 +26,11 @@ public class EnvironmentObserver{
BoxCast,
SphereCast
}
public enum CastOrigin{
Owner,
Location
}
[PropertySpace(0, 5), LabelWidth(60)]
public string label;
@@ -40,6 +45,8 @@ public class EnvironmentObserver{
[HideInInspector]
public bool active;
public CastOrigin castOrigin;
// Parameters for Cast cast types
[FoldoutGroup("Settings")] [HideIf("@castType == CastType.BoxOverlap || castType == CastType.SphereOverlap")]
public float length;

View File

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

View File

@@ -0,0 +1,35 @@
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace Reset.Units {
[Category("Reset/Units")]
[Description("Returns true if this unit is in the same spawn group as the provided value.")]
public class CheckIfSpawnmate : ConditionTask<Enemy>{
protected override string info{ get => $"{target} is spawnmate"; }
public BBParameter<GameObject> target;
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit(){
return null;
}
//Called whenever the condition gets enabled.
protected override void OnEnable() {
}
//Called whenever the condition gets disabled.
protected override void OnDisable() {
}
//Called once per frame while the condition is active.
//Return whether the condition is success or failure.
protected override bool OnCheck(){
return agent.relatedSpawner.enemies.Contains(target.value);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 847ff34f59879c844b597a39884f5fe8

View File

@@ -0,0 +1,60 @@
using System.Runtime.InteropServices.WindowsRuntime;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace Reset.Items{
[Category("Reset/Combat")]
[Description("Check if the provided value matches the weapon actor's variable")]
public class CheckWeaponActorVariable<T> : ConditionTask<Transform> where T : class{
public BBParameter<string> variable;
public BBParameter<T> value;
WeaponActor actor;
protected override string info{
get{ return $"weapon actor variable <b>{variable.value}</b> is <b><i>{value.value}</b></i>"; }
}
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit(){
return null;
}
//Called whenever the condition gets enabled.
protected override void OnEnable(){
actor = agent.GetComponent<WeaponActor>();
}
//Called whenever the condition gets disabled.
protected override void OnDisable(){
}
//Called once per frame while the condition is active.
//Return whether the condition is success or failure.
protected override bool OnCheck(){
if (!actor) {
Debug.LogError(
$"No weapon actor variable found on this player. Cannot check for value of {variable.value}.",
agent);
return false;
}
if (actor.weaponVariables.ContainsKey(variable.value)) {
T valueAsType = value.value;
Debug.Log((T)actor.weaponVariables[variable.value]);
Debug.Log(valueAsType);
return ((T)actor.weaponVariables[variable.value]).Equals(valueAsType);
} else {
Debug.LogError($"No variable found by name {variable.value} on the weapon actor", agent);
return false;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 33a915cdda8127941aa56ed4e845d813

View File

@@ -0,0 +1,164 @@
using System;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using Sirenix.Utilities;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Reset.Units {
[Category("Reset/Units")]
[Description("Gives the unit a new direction that causes them to circle the target.")]
public class CircleTarget : ActionTask<UnitMovementHandler>{
enum StrafeDirection{
Forward = 0, Backwards, Left, Right
}
// References
private IUnitTargetProvider targetProvider;
// Pause settings
[Description("How long will this unit wait to stop moving and pause")]
public BBParameter<Vector2> pauseIntervalRange;
[Description("How long this unit will within paused movement")]
public BBParameter<Vector2> unpauseIntervalRange;
// Speed settings
public BBParameter<Vector2> speedRange;
[Description("How long this unit will wait before switching movement speed")]
public BBParameter<Vector2> speedChangeIntervalRange;
[SliderField(0, 1)]
public BBParameter<float> speedSmoothing;
// Strafe direction settings
[Description("How long this unit will wait before switching strafe direction")]
public BBParameter<Vector2> strafeDirectionChangeInterval;
[SliderField(0, 1)]
public BBParameter<float> directionSmoothing;
// Pause state management
private bool strafePaused;
private float lastPauseEnterTime;
private float lastPauseExitTime;
private float pauseChangeTimeElapsed;
private float nextPauseChangeTime;
// Direct state management
private float lastDirectionChangeTime;
private float directionChangeTimeElapsed;
private StrafeDirection currentStrafeDirection;
private float nextDirectionChangeTime;
// Speed state management
private float lastSpeedChangeTime;
private float speedChangeTimeElapsed;
private float currentSpeed;
private float nextSpeedChangeTime;
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
if (pauseIntervalRange.value == Vector2.zero){ Debug.LogError($"Please set a setting for pause intervals on {agent.name}", agent.gameObject);}
if (unpauseIntervalRange.value == Vector2.zero){ Debug.LogError($"Please set a setting for unpause intervals on {agent.name}", agent.gameObject);}
if (speedRange.value == Vector2.zero){ Debug.LogError($"Please set a setting for speed range on {agent.name}", agent.gameObject);}
if (speedChangeIntervalRange.value == Vector2.zero){ Debug.LogError($"Please set a setting for speed change intervals on {agent.name}", agent.gameObject);}
if (strafeDirectionChangeInterval.value == Vector2.zero){ Debug.LogError($"Please set a setting for strafe direction change intervals on {agent.name}", agent.gameObject);}
// Get reference to target provider
targetProvider = agent.gameObject.GetComponent<IUnitTargetProvider>();
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute(){
// Reset timers
lastPauseEnterTime = 0f;
lastPauseExitTime = 0f;
pauseChangeTimeElapsed = 0f;
lastDirectionChangeTime = 0f;
directionChangeTimeElapsed = 0f;
// Initialize with direction
currentStrafeDirection = (StrafeDirection)Random.Range(0, 3);
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
// Set a new direction
agent.SetNewDirection(GetVector3Direction().ToVector2(), directionSmoothing.value, true);
// Set paused or set new speed if unpaused
if (strafePaused) {
agent.SetNewSpeed(0f, speedSmoothing.value, true);
} else {
agent.SetNewSpeed(currentSpeed, speedSmoothing.value, true);
}
// Check the timers
CheckForChange();
}
// Method to quickly transform a strafe direction into a Vector3
Vector3 GetVector3Direction(){
switch (currentStrafeDirection) {
case StrafeDirection.Forward:
return agent.transform.position.DirectionTo(targetProvider.UnitTarget.transform.position);
case StrafeDirection.Backwards:
return -agent.transform.position.DirectionTo(targetProvider.UnitTarget.transform.position);
case StrafeDirection.Left:
return Quaternion.AngleAxis(-90f, Vector3.up) * agent.transform.position.DirectionTo(targetProvider.UnitTarget.transform.position);
case StrafeDirection.Right:
return Quaternion.AngleAxis(90f, Vector3.up) * agent.transform.position.DirectionTo(targetProvider.UnitTarget.transform.position);
default:
return Vector3.zero;
}
}
void SetNewPauseChangeTime(){
if (strafePaused) {
nextPauseChangeTime = elapsedTime + Random.Range(unpauseIntervalRange.value.x, unpauseIntervalRange.value.y);
} else {
nextPauseChangeTime = elapsedTime + Random.Range(pauseIntervalRange.value.x, pauseIntervalRange.value.y);
}
}
void SetNewSpeedChangeTime(){
nextSpeedChangeTime = elapsedTime + Random.Range(speedChangeIntervalRange.value.x, speedChangeIntervalRange.value.y);
}
void SetNewDirectionChangeTime(){
nextDirectionChangeTime = elapsedTime + Random.Range(strafeDirectionChangeInterval.value.x, strafeDirectionChangeInterval.value.y);
}
void CheckForChange(){
if (nextPauseChangeTime > elapsedTime) {
strafePaused = !strafePaused;
SetNewPauseChangeTime();
}
if (nextSpeedChangeTime > elapsedTime) {
currentSpeed = Random.Range(speedRange.value.x, speedRange.value.y);
SetNewSpeedChangeTime();
}
if (nextDirectionChangeTime > elapsedTime) {
currentStrafeDirection = (StrafeDirection)Random.Range(0, 3);
SetNewDirectionChangeTime();
}
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9f9afac70b34c5e44b747e86f7491aa3

View File

@@ -0,0 +1,57 @@
using System;
using System.Runtime.CompilerServices;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace Reset.Units {
[Category("Reset")]
[Description("Get the current target and save it to a graph variable")]
public class GetCurrentTarget : ActionTask<LockOnManager>{
public BBParameter<Transform> target;
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute(){
if (agent.mainTarget == null) {
Debug.LogWarning("There is no LockOnTarget to save as current target");
EndAction(true);
return;
}
try {
target.value = agent.mainTarget.gameObject.transform;
} catch (Exception e) {
Debug.LogError($"Failed to save the current LockOnTarget target: {e.Message}");
EndAction(false);
return;
}
EndAction(true);
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 13713276d513eea4186f28ea39cbf302

View File

@@ -0,0 +1,50 @@
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace Reset.Items {
[Category("Reset/Combat")]
[Description("Retrieve the valule of a weapon actor variable")]
public class GetWeaponActorVariable<T> : ActionTask<Transform>{
public BBParameter<string> variable;
public BBParameter<T> saveValueTo;
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute() {
WeaponActor actor = agent.GetComponent<WeaponActor>();
if (!actor) {
Debug.LogError($"No weapon actor variable found on this player. Cannot check for value of {variable.value}.", agent);
EndAction(false);
}
saveValueTo.value = (T)actor.weaponVariables[variable.value];
EndAction(true);
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f379bc09216af524290d9b72067031d5

View File

@@ -0,0 +1,56 @@
using System;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using Reset.Items;
using UnityEngine;
namespace Reset.Items {
[Category("Reset/Combat")]
[Description("Sends a named event as a string to the current weapon's WeaponActor.")]
public class SendWeaponActorEvent : ActionTask<Transform>{
public BBParameter<string> weaponEvent;
protected override string info{
get{ return $"Send weapon event <b>{weaponEvent.value}</b>"; }
}
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute(){
WeaponActor actor = agent.GetComponent<WeaponActor>();
if (actor == null) {
Debug.LogError("No WeaponActor was found on this player.");
EndAction(false);
}
actor.DoWeaponEvent(weaponEvent.value);
EndAction(true);
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 567e649f23e3ba44ea1339e928d17b83

View File

@@ -0,0 +1,65 @@
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using Pathfinding;
using UnityEngine;
namespace Reset.Units {
[Category("Reset/Units")]
[Description("Set a new path towards a provided target")]
public class SetNewPathfindingPath : ActionTask<UnitPathfinding>{
public BBParameter<GameObject> target;
public bool useTimer;
public BBParameter<float> timeToRepath;
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
if (target.isNoneOrNull) {
Debug.Log($"There is no target set for {agent.name} to pathfind to.", agent.gameObject);
return $"There is no target set for {agent.name} to pathfind to.";
}
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute() {
agent.seeker.StartPath(agent.transform.position, target.value.transform.position, OnPathComplete);
if (useTimer) {
return;
}
EndAction(true);
}
public void OnPathComplete(Path p){
if (!p.error) {
agent.AssignNewPath(p);
}
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
if (useTimer && elapsedTime > timeToRepath.value) {
agent.seeker.StartPath(agent.transform.position, target.value.transform.position, OnPathComplete);
EndAction();
}
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 71ce4953752dde3449673b8931aefc00

View File

@@ -0,0 +1,64 @@
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using Pathfinding;
using UnityEngine;
namespace Reset.Units {
[Category("Reset/Pathfinding")]
[Description("Set the agent's current path to a wandering path.")]
public class SetWanderingPath : ActionTask<UnitPathfinding> {
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute(){
Enemy thisEnemy = agent.Unit as Enemy;
Vector3 pathTargetPos = agent.transform.position;
if (thisEnemy.relatedSpawner) {
pathTargetPos = thisEnemy.relatedSpawner.transform.position;
}
Vector3 randomizedDestination = new Vector3(
Random.Range(-thisEnemy.relatedSpawner.radius, thisEnemy.relatedSpawner.radius),
0f,
Random.Range(-thisEnemy.relatedSpawner.radius, thisEnemy.relatedSpawner.radius)
);
pathTargetPos += randomizedDestination;
agent.seeker.StartPath(agent.transform.position, pathTargetPos, OnPathComplete);
EndAction(true);
}
public void OnPathComplete(Path p){
if (!p.error) {
agent.AssignNewPath(p);
}
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a8b7661016b47ae4e9137833e7111460

View File

@@ -0,0 +1,61 @@
using System;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
using UnityEngine;
namespace Reset.Items {
[Category("Reset/Combat")]
[Description("Set an existing or new weapon actor variable to the defined value.")]
public class SetWeaponActorVariable : ActionTask<Transform>{
public BBParameter<string> variable;
public BBParameter<object> value;
protected override string info{
get{ return $"Set weapon variable <b>{variable.value}</b> == <i>{value.name}</i>"; }
}
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute() {
WeaponActor actor = agent.GetComponent<WeaponActor>();
if (actor == null) {
Debug.LogError("No WeaponActor was found on this player.");
EndAction(false);
}
try {
actor.RegisterWeaponVariable(variable.value, value.value);
} catch (Exception e) {
Debug.LogError($"Failed to set weapon variable '{variable.value}': {e.Message} ");
EndAction(false);
}
EndAction(true);
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ff6862d725648584c89631637986f89c

View File

@@ -0,0 +1,41 @@
using NodeCanvas.Framework;
using ParadoxNotion.Design;
namespace Reset.Units {
[Category("Reset/Units")]
[Description("Set whether this unit can use pathfinding or not.")]
public class TogglePathfinding : ActionTask<UnitPathfinding>{
public BBParameter<bool> value;
//Use for initialization. This is called only once in the lifetime of the task.
//Return null if init was successfull. Return an error string otherwise
protected override string OnInit() {
return null;
}
//This is called once each time the task is enabled.
//Call EndAction() to mark the action as finished, either in success or failure.
//EndAction can be called from anywhere.
protected override void OnExecute(){
agent.pathfindingEnabled = value.value;
EndAction(true);
}
//Called once per frame while the action is active.
protected override void OnUpdate() {
}
//Called when the task is disabled.
protected override void OnStop() {
}
//Called when the task is paused.
protected override void OnPause() {
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e7bebfffaed94604db733c72cde6be7d

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace Reset.Units{
public interface IUnitDirectionProvider{
public Vector2 Direction{ get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ba6af7dc2e654035a720449447af3fa8
timeCreated: 1761268431

View File

@@ -5,12 +5,12 @@ using UnityEngine.InputSystem;
namespace Reset.Units{
public class InteractionHandler : MonoBehaviour{
private PlayerEnvironmentManager envManager;
private Inventory inventory;
private IInventory inventory;
private EnvironmentObserver observer;
void Awake(){
envManager = GetComponent<PlayerEnvironmentManager>();
inventory = GetComponent<Inventory>();
inventory = GetComponent<IInventory>();
observer = envManager.FindObserverFromString("itemdrop");

View File

@@ -1,47 +1,13 @@
using System.Collections.Generic;
using NodeCanvas.Tasks.Actions;
using System.Collections.Generic;
using Reset.Items;
using UnityEngine;
namespace Reset.Units{
public class Inventory : MonoBehaviour{
public Weapon rangedWeapon;
public Weapon meleeWeapon;
public Ability spellAbility1;
public Ability spellAbility2;
public Ability toolAbility1;
public Ability toolAbility2;
public interface IInventory{
public List<Item> storedItems{ get; set; }
public List<Item> storedItems = new List<Item>(15);
void Start(){
}
public void AddToInventory(Item newItem){
storedItems.Add(newItem);
}
public void Equip(Item item){
if (item is not IEquipable) {
Debug.LogError("This item is not equippable.", item);
return;
}
if (item is Weapon thisWeapon) {
if (meleeWeapon != null) {
storedItems.Add(meleeWeapon);
}
meleeWeapon = thisWeapon;
}
}
// Update is called once per frame
void Update(){
}
}
}
}

View File

@@ -1,2 +1,3 @@
fileFormatVersion: 2
guid: d57ab8ec1cbbdcf46813f268625d3494
fileFormatVersion: 2
guid: daf46ad8d27d4133a55f13a0c8a4efd3
timeCreated: 1767808174

View File

@@ -8,6 +8,7 @@ using Unity.Netcode;
namespace Reset.Units{
public class Player : Unit, IKillable, IInteractable{
// IKillable
[ShowInInspector]
public float maxHealth{ get; set; }
public float currentHealth{ get; set; }
@@ -32,7 +33,7 @@ namespace Reset.Units{
public float lastKnownReviveTime;
void Awake(){
maxHealth = 20f;
}
void AttachToGame(){
@@ -82,21 +83,28 @@ namespace Reset.Units{
// Tell every unit to set the new health value
if (UnitIsNetworked()) {
SetHealthRpc(newHealth);
SetHealthRpc(newHealth);
} else {
SetHealth(newHealth);
}
}
[Rpc(SendTo.Everyone)]
public void SetHealthRpc(float health){
// Set health to new value, clamped to 0
health = Mathf.Max(health, 0f);
currentHealth = health;
SetHealth(health);
// For local players, run things based on health value.
// This Rpc is global but only the owner checks health
CheckHealth();
}
private void SetHealth(float health){
health = Mathf.Max(health, 0f);
currentHealth = health;
}
void CheckHealth(){
if (UnitIsLocal()){
if (currentHealth <= 0f) {

View File

@@ -0,0 +1,8 @@
using UnityEngine;
namespace Reset.Units{
public class PlayerAnimation : UnitAnimation{
public Transform rightHand;
public Transform leftHand;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6974bcad59fb407296da5a7ed7f7a79c
timeCreated: 1767815017

View File

@@ -0,0 +1,92 @@
using System;
using Reset.Core;
using Reset.Items;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Profiling;
namespace Reset.Units{
public class PlayerCombat : UnitCombat{
public CombatType currentCombatType;
private IEquipable currentWeapon;
private GameObject currentWeaponItem;
public void DrawWeapon(){
if (Unit.UnitIsNetworked()) {
CreatePlayerWeaponRpc();
} else {
CreatePlayerWeapon();
}
}
[Rpc(SendTo.Everyone)]
public void CreatePlayerWeaponRpc(){
CreatePlayerWeapon();
}
public void CreatePlayerWeapon(){
// Remove a current weapon
DisposeCurrentWeapon();
// Remove a current weapon
if (currentWeapon != null) {
Destroy(currentWeaponItem);
currentWeaponItem = null;
}
// Switch which weapopn gets pulled out
Weapon weaponType = null;
switch ((Unit.Combat as PlayerCombat).currentCombatType) {
case CombatType.Melee:
weaponType = ((PlayerInventory)Unit.Inventory).meleeWeapon;
break;
case CombatType.Ranged:
weaponType = ((PlayerInventory)Unit.Inventory).rangedWeapon;
break;
default:
throw new ArgumentOutOfRangeException();
}
// Add weapon to status and hand
currentWeapon = weaponType;
currentWeaponItem = weaponType.InstantiateItemObject();
// Move item to hand
currentWeaponItem.transform.SetParent((Unit.Animation as PlayerAnimation).rightHand);
currentWeaponItem.transform.localPosition = weaponType.handPositionOffset;
currentWeaponItem.transform.rotation = (Unit.Animation as PlayerAnimation).rightHand.rotation * Quaternion.Euler(weaponType.handRotationOffset);
// Add the weapon script for this weapon
(currentWeapon as Weapon).AddActorScript();
}
public GameObject GetCurrentWeaponItem(){
return currentWeaponItem;
}
public void HolsterWeapon(){
DisposeCurrentWeapon();
}
public void DisposeCurrentWeapon(){
// Return if no weapon active
if (currentWeapon == null) {
return;
}
// Destroy physical mesh
Destroy(currentWeaponItem);
// Destroy weapon actor
if ((GetComponent<WeaponActor>()) != null) {
Destroy(GetComponent<WeaponActor>());
}
// Remove references
currentWeaponItem = null;
currentWeapon = null;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e2b74fc5d4eb483bb4e637c3be6353b2
timeCreated: 1767998040

View File

@@ -13,14 +13,17 @@ using Sirenix.OdinInspector;
using Unity.Cinemachine;
using Object = UnityEngine.Object;
public class PlayerControls : MonoBehaviour{
public class PlayerControls : MonoBehaviour, IUnitDirectionProvider{
// References
private Player thisPlayer;
private PlayerInput input;
private SignalDefinition inputSignal;
private SignalDefinition blockSignal;
// IUnitDirectionProvider
public Vector2 Direction => rawMoveInput;
// TODO: Turn these into accessors
public Vector2 rawMoveInput;
public Vector2 rawLookInput;

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using Reset.Items;
using UnityEngine;
namespace Reset.Units{
public class PlayerInventory : UnitComponent, IInventory{
public Weapon rangedWeapon;
public Weapon meleeWeapon;
public Ability spellAbility1;
public Ability spellAbility2;
public Ability toolAbility1;
public Ability toolAbility2;
public List<Item> storedItems{ get; set; }
public void EquipToCharacter(Item item){
if (item is not IEquipable) {
Debug.LogError("This item is not equippable.", item);
return;
}
if (item is Weapon thisWeapon) {
if (meleeWeapon != null) {
storedItems.Add(meleeWeapon);
}
meleeWeapon = thisWeapon;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d57ab8ec1cbbdcf46813f268625d3494

View File

@@ -22,8 +22,16 @@ public class SettingValue<T> : IResettableSettingValue{
public T Value{
get => currentValue;
set => targetValue = value;
set{
if (IsSmoothable()) {
targetValue = value;
} else {
currentValue = value;
targetValue = value;
}
}
}
[HorizontalGroup("Settings"), VerticalGroup("Settings/Smoothing"), BoxGroup("Settings/Smoothing/Smoothing"), LabelText("Current"), ShowIf("@IsSmoothable()")]
public float currentSmoothing;
[HorizontalGroup("Settings"), VerticalGroup("Settings/Value"), BoxGroup("Settings/Value/Value"), LabelText("Default")]

View File

@@ -24,6 +24,30 @@ namespace Reset.Units{
get{ if (!_graph) { _graph = GetComponent<GraphOwner>(); } return _graph; }
}
private UnitCombat _combat;
internal UnitCombat Combat{
get {
if (_combat == null) { _combat = GetComponent<UnitCombat>(); }
return _combat;
}
}
private UnitAnimation _animation;
internal UnitAnimation Animation{
get {
if (_animation == null) { _animation = GetComponent<UnitAnimation>(); }
return _animation;
}
}
private IInventory _inventory;
internal IInventory Inventory{
get {
if (_inventory == null) { _inventory = GetComponent<IInventory>(); }
return _inventory;
}
}
// Debug and Gizmos
public NetworkVariable<FixedString64Bytes> graphStateAsString;
@@ -94,13 +118,18 @@ namespace Reset.Units{
private void DrawStateGizmo(){
// Get state from FSM
if (UnitIsLocal()) {
string stateString = "";
if (UnitIsNetworked() && UnitIsLocal()) {
graphStateAsString.Value = FSM.currentRootStateName;
stateString = graphStateAsString.Value.ToString();
} else {
stateString = FSM.currentRootStateName;
}
// Draw state gizmo, regardless of if local or not
try {
Draw.ingame.Label2D(transform.position + Vector3.up * 2.7f, graphStateAsString.Value.ToString(),
Draw.ingame.Label2D(transform.position + Vector3.up * 2.7f, stateString,
Color.red);
} catch (Exception e) {
Debug.LogError(e.Message);

View File

@@ -1,25 +1,94 @@
using System;
using Unity.Netcode.Components;
using UnityEngine;
using UnityEngine.Rendering;
namespace Reset.Units{
public class UnitAnimation : UnitComponent{
public Animator modelAnimator;
public Transform headBone;
public Transform neckBone;
public Vector3 headRotOffset;
public Vector3 neckRotOffset;
public Vector3 rotationLimit;
public float moveSpeed;
// Temporary
private float inputMagnitude;
private NetworkAnimator netAnimator;
void Update(){
// Temporary
try {
inputMagnitude = Mathf.MoveTowards(inputMagnitude, GetComponent<PlayerControls>().rawMoveInput.magnitude * 2f, 6f * Time.deltaTime);
// try {
// inputMagnitude = Mathf.MoveTowards(inputMagnitude, GetComponent<PlayerControls>().rawMoveInput.magnitude * 2f, 6f * Time.deltaTime);
//
// modelAnimator.SetFloat("Move Direction X", Unit.Movement.GetResolvedDirectionLocal().x * inputMagnitude);
// modelAnimator.SetFloat("Move Direction Y", Unit.Movement.GetResolvedDirectionLocal().y * inputMagnitude);
// } catch (Exception e) {
// Debug.LogError($"Failed in setting X and Y move direction floats: {e.Message}");
// }
modelAnimator.SetFloat("Move Direction X", moveSpeed);
modelAnimator.SetFloat("Gravity", Unit.Movement.resolvedMovement.gravity);
modelAnimator.SetBool("Grounded", Physics.Raycast(transform.position, Vector3.down, .2f));
}
public void SendAnimationTrigger(string trigger){
if (Unit.UnitIsNetworked()) {
try {
netAnimator.SetTrigger(trigger);
} catch (Exception e){
Debug.LogError($"Failed to send network animation trigger: {e.Message}");
}
modelAnimator.SetFloat("Move Direction X", Unit.Movement.GetResolvedDirectionLocal().x * inputMagnitude);
modelAnimator.SetFloat("Move Direction Y", Unit.Movement.GetResolvedDirectionLocal().y * inputMagnitude);
} catch (Exception e) {
Debug.LogError($"Failed in setting X and Y move direction floats: {e.Message}");
} else {
modelAnimator.SetTrigger(trigger);
}
}
}
private void LateUpdate(){
// TurnHead();
}
void TurnHead(){
var targetObject = (Unit.Combat as EnemyCombat).UnitTarget;
if (targetObject) {
Vector3 headDirToTarget = headBone.position.DirectionTo(targetObject.transform.position);
Vector3 neckDirToTarget = neckBone.position.DirectionTo(targetObject.transform.position);
Quaternion newHeadRot = Quaternion.LookRotation(headDirToTarget);
Quaternion newNeckRot = Quaternion.LookRotation(neckDirToTarget);
Quaternion offsetHeadRot = Quaternion.Euler(headRotOffset);
Quaternion offsetNeckRot = Quaternion.Euler(neckRotOffset);
Debug.Log(offsetNeckRot.eulerAngles);
Vector3 clampedHeadRot = new Vector3(
Mathf.Clamp(newHeadRot.eulerAngles.x, -rotationLimit.x, rotationLimit.x),
Mathf.Clamp(newHeadRot.eulerAngles.y, -rotationLimit.y, rotationLimit.y),
Mathf.Clamp(newHeadRot.eulerAngles.z, -rotationLimit.z, rotationLimit.z)
);
Vector3 clampedNeckRot = new Vector3(
Mathf.Clamp(newNeckRot.eulerAngles.x, -rotationLimit.x, rotationLimit.x),
Mathf.Clamp(newNeckRot.eulerAngles.y, -rotationLimit.y, rotationLimit.y),
Mathf.Clamp(newNeckRot.eulerAngles.z, -rotationLimit.z, rotationLimit.z)
);
Quaternion outputHeadRot = Quaternion.Lerp(headBone.transform.rotation, Quaternion.Euler(clampedHeadRot), .8f);
Quaternion outputNeckRot = Quaternion.Lerp(neckBone.transform.rotation, Quaternion.Euler(clampedNeckRot), .2f);
headBone.transform.rotation = offsetHeadRot * headBone.transform.rotation * outputHeadRot;
neckBone.transform.rotation = (offsetNeckRot * neckBone.transform.rotation * outputNeckRot);
}
}
}
}

View File

@@ -1,4 +1,13 @@
namespace Reset.Units{
public enum UnitFacingDirection{
TowardsTarget = 0,
MatchInput,
MatchCamera,
Static,
Momentum,
SpecifiedDirection
}
public enum PlayerFacingDirection{
TowardsTarget = 0,
MatchInput,

View File

@@ -31,7 +31,7 @@ namespace Reset.Units{
[Title("Gravity Scale"), HideLabel, InlineProperty] public SettingValue<float> gravityScale = new SettingValue<float>(1f);
// Rotation
[Title("Rotate Facing"), HideLabel, InlineProperty] public SettingValue<PlayerFacingDirection> facingDirection = new SettingValue<PlayerFacingDirection>(initValue: PlayerFacingDirection.Momentum);
[Title("Rotate Facing"), HideLabel, InlineProperty] public SettingValue<UnitFacingDirection> facingDirection = new SettingValue<UnitFacingDirection>(initValue: UnitFacingDirection.Momentum);
[Title("Rotation Speed"), HideLabel, InlineProperty] public SettingValue<float> rotationSpeed = new SettingValue<float>(5f);

View File

@@ -5,7 +5,7 @@ using Sirenix.OdinInspector;
using Unity.Netcode;
namespace Reset.Units{
public class UnitMovementHandler : UnitComponent{
public class UnitMovementHandler : UnitComponent {
[ShowInInspector, InlineProperty, HideLabel, FoldoutGroup("Resolved Movement", expanded: true)]
public ResolvedMovement resolvedMovement;
@@ -19,8 +19,8 @@ namespace Reset.Units{
// References
private CharacterController controller;
private PlayerControls controls;
private LockOnManager lockOnManager;
private IUnitDirectionProvider directionProvider;
private IUnitTargetProvider targetProvider;
// Movement Data
[ShowInInspector, PropertyOrder(2), FoldoutGroup("Movement Data", expanded: true), InlineProperty, HideLabel] public UnitMovementData data = new();
@@ -30,8 +30,8 @@ namespace Reset.Units{
void Awake(){
controller = GetComponent<CharacterController>();
controls = GetComponent<PlayerControls>();
lockOnManager = GetComponent<LockOnManager>();
directionProvider = GetComponent<IUnitDirectionProvider>();
targetProvider = GetComponent<IUnitTargetProvider>();
InitAllSettings();
}
@@ -52,6 +52,12 @@ namespace Reset.Units{
// Apply movement
DoMovement(resolvedMovement.moveDirection.World, resolvedMovement.gravity, resolvedMovement.moveSpeed, data.gravityScale.Value);
// Apply movespeed to the Animator
if (transform.gameObject == PlayerManager.Player){ // temp
Unit.Animation.moveSpeed = resolvedMovement.moveSpeed * resolvedMovement.moveDirection.Local.magnitude / data.moveSpeed.currentValue;
}
DebugOverlayDrawer.ChangeValue("Movement", "Move Direction (Local)", resolvedMovement.moveDirection.Local);
DebugOverlayDrawer.ChangeValue("Movement", "Move Direction (World)", resolvedMovement.moveDirection.World);
}
@@ -59,11 +65,13 @@ namespace Reset.Units{
// Update the direction, called every frame
private void UpdateCurrentDirection(){
// Get input value
Vector2 targetDirection = new Vector2(controls.rawMoveInput.x, controls.rawMoveInput.y);
Vector2 targetDirection = new Vector2(directionProvider.Direction.x, directionProvider.Direction.y);
// Rotate input by camera rotation (instead of rotating the output direction by camera rotation)
targetDirection = (Camera.main.transform.rotation * targetDirection.ToVector3()).ToVector2();
if (GetComponent<Player>()) {
targetDirection = (Camera.main.transform.rotation * targetDirection.ToVector3()).ToVector2();
}
// Deadzone
if (targetDirection.magnitude < .1f) {
targetDirection = resolvedMovement.moveDirection.RawWorld;
@@ -97,7 +105,7 @@ namespace Reset.Units{
newDirection = Vector2.SmoothDamp(currentDirection, targetDirection, ref refVelocityDirectionChangingHardness, data.directionChangingSoftness.Value * data.airDirectionDecay.Value * Time.deltaTime);
}
newDirection = Vector3.Slerp(resolvedMovement.moveDirection.World, newDirection, controls.rawMoveInput.magnitude);
newDirection = Vector3.Slerp(resolvedMovement.moveDirection.World, newDirection, directionProvider.Direction.magnitude);
// Commit the new direction
resolvedMovement.moveDirection.World = newDirection;
@@ -108,7 +116,7 @@ namespace Reset.Units{
// ""Smooth"" the speed
float smoothedSpeed;
if (resolvedMovement.moveDirection.Local.magnitude < controls.rawMoveInput.magnitude) {
if (resolvedMovement.moveDirection.Local.magnitude < directionProvider.Direction.magnitude) {
smoothedSpeed = Mathf.MoveTowards(resolvedMovement.moveSpeed, data.moveSpeed.Value, data.acceleration.Value * Time.deltaTime);
} else {
smoothedSpeed = Mathf.MoveTowards(resolvedMovement.moveSpeed, 0f, data.deacceleration.Value * Time.deltaTime);
@@ -135,42 +143,42 @@ namespace Reset.Units{
// Update the rotation, called every frame
private void UpdateCurrentRotation(){
// Get input value
Vector3 inputMovement = new Vector3(controls.rawMoveInput.x, 0f, controls.rawMoveInput.y);
Vector3 inputMovement = new Vector3(directionProvider.Direction.x, 0f, directionProvider.Direction.y);
Quaternion targetRotation = Quaternion.identity;
// Switch the desired rotation based on current movement setting
switch (data.facingDirection.Value) {
// Just look at target
case PlayerFacingDirection.TowardsTarget:
case UnitFacingDirection.TowardsTarget:
// Look directly at the target
if (lockOnManager.mainTarget == null) {
if (targetProvider.UnitTarget == null) {
Debug.LogError("Trying to rotate towards a target but there is no target. Forcing rotation to Static and continuing");
data.facingDirection.Value = PlayerFacingDirection.Static;
data.facingDirection.currentValue = PlayerFacingDirection.Static;
data.facingDirection.Value = UnitFacingDirection.Static;
data.facingDirection.currentValue = UnitFacingDirection.Static;
targetRotation = transform.rotation;
break;
}
targetRotation = Quaternion.LookRotation(transform.position.DirectionTo(lockOnManager.mainTarget.gameObject.transform.position));
targetRotation = Quaternion.LookRotation(transform.position.DirectionTo(targetProvider.UnitTarget.transform.position));
break;
case PlayerFacingDirection.Momentum:
case UnitFacingDirection.Momentum:
// Look towards the current direction the agent is moving
if (inputMovement.magnitude > .05f){
targetRotation = Quaternion.LookRotation(resolvedMovement.moveDirection.RawWorld.ToVector3(), Vector3.up);
}
break;
case PlayerFacingDirection.MatchInput:
case UnitFacingDirection.MatchInput:
// Look towards the input direction- similar to Momentum but snappier
if (controls.rawMoveInput.magnitude < 0.05f) { break; }
if (directionProvider.Direction.magnitude < 0.05f) { break; }
targetRotation = Camera.main.transform.rotation * Quaternion.LookRotation(inputMovement);
break;
case PlayerFacingDirection.MatchCamera:
case UnitFacingDirection.MatchCamera:
// Look the same direction as the camera
targetRotation = Quaternion.Euler(Camera.main.transform.rotation.eulerAngles.Flatten(0, null, 0));
break;
case PlayerFacingDirection.Static:
case UnitFacingDirection.Static:
// Don't change
targetRotation = resolvedMovement.rotation;
break;
@@ -179,9 +187,9 @@ namespace Reset.Units{
DebugOverlayDrawer.ChangeValue("Rotation", "Target Rotation", targetRotation.eulerAngles);
// Add the current input into the created rotation
if (data.facingDirection.Value == PlayerFacingDirection.MatchCamera || data.facingDirection.Value == PlayerFacingDirection.TowardsTarget) {
if (data.facingDirection.Value == UnitFacingDirection.MatchCamera || data.facingDirection.Value == UnitFacingDirection.TowardsTarget) {
resolvedMovement.rotation = targetRotation;
} else if (controls.rawMoveInput.sqrMagnitude > .1){
} else if (directionProvider.Direction.sqrMagnitude > .1){
resolvedMovement.rotation = targetRotation;
}
@@ -353,6 +361,10 @@ namespace Reset.Units{
public Quaternion GetResolvedRotation(){
return resolvedMovement.rotation;
}
public bool GetGrounded(){
return controller.isGrounded;
}
[Button("Initialize Settings", ButtonHeight = 30), PropertySpace(10,5 )]
void InitAllSettings(){
@@ -391,6 +403,7 @@ namespace Reset.Units{
public void OverwriteDirectionFromInput(Vector2 value, float priority, float speed = Mathf.Infinity){ // Old
Debug.LogError("Using an old movement command! Switch to one of the new alternatives!");
}
}
}

View File

@@ -0,0 +1,93 @@
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using Pathfinding;
using Pathfinding.Drawing;
using UnityEngine;
using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;
namespace Reset.Units{
[RequireComponent(typeof(Seeker))]
public class UnitPathfinding : UnitComponent, IUnitDirectionProvider{
public Vector2 Direction{ get; set; }
public bool pathfindingEnabled;
public Seeker seeker;
public float nextWaypointDistance = 3;
private int currentWaypoint;
public Path path;
public bool reachedEndOfPath;
public void Start(){
seeker = GetComponent<Seeker>();
Enemy thisEnemy = (Unit as Enemy);
if (thisEnemy.relatedSpawner){
seeker.graphMask = GraphMask.FromGraph(thisEnemy.relatedSpawner.relatedGraph);
} else {
Debug.LogWarning("Creating a graph for a singular enemy is not yet implemented. Graph mask not set on this unit.", transform);
}
}
public void AssignNewPath(Path p){
if (!p.error) {
path = p;
// Reset the waypoint counter so that we start to move towards the first point in the path
currentWaypoint = 0;
}
}
public void Update(){
FollowCurrentPath();
}
private void FollowCurrentPath(){
if (path == null) {
// We have no path to follow yet, so don't do anything
return;
}
reachedEndOfPath = false;
// The distance to the next waypoint in the path
float distanceToWaypoint;
while (true) {
// If you want maximum performance you can check the squared distance instead to get rid of a
// square root calculation. But that is outside the scope of this tutorial.
distanceToWaypoint = Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]);
if (distanceToWaypoint < nextWaypointDistance) {
// Check if there is another waypoint or if we have reached the end of the path
if (currentWaypoint + 1 < path.vectorPath.Count) {
currentWaypoint++;
} else {
// Set a status variable to indicate that the agent has reached the end of the path.
// You can use this to trigger some special code if your game requires that.
reachedEndOfPath = true;
break;
}
} else {
break;
}
}
// Pass the direction to the direction handler interface
if (currentWaypoint + 1 == path.vectorPath.Count) {
Direction = (path.vectorPath[currentWaypoint] - transform.position).ToVector2();
Direction = Vector3.ClampMagnitude(Direction, 1f);
} else {
Direction = (path.vectorPath[currentWaypoint] - transform.position).normalized.ToVector2();
}
// Draw an indicator for the path
Draw.ingame.SolidCircle(path.vectorPath[currentWaypoint] + Vector3.up * .02f, Vector3.up, .3f);
Draw.ingame.Arrow(transform.position, transform.position + Direction.ToVector3(), Vector3.up, .3f);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1a72dabd280fe71488a78596bd852afc

View File

@@ -14,7 +14,9 @@
"GUID:f2d49d9fa7e7eb3418e39723a7d3b92f",
"GUID:5540e30183c82e84b954c033c388e06c",
"GUID:fe25561d224ed4743af4c60938a59d0b",
"GUID:37e17ffe38d86ae48bc3207e83ffef88"
"GUID:37e17ffe38d86ae48bc3207e83ffef88",
"GUID:efa45043feb7e4147a305b73b5cea642",
"GUID:f4059aaf6c60a4a58a177a2609feb769"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: 3c37fe734c185fc48b50bb4fc19e7537
AssemblyDefinitionImporter:
guid: a4ea9f82528f3024da71454257341643
DefaultImporter:
externalObjects: {}
userData:
assetBundleName: