Agreed, I find regions very useful as well.
Especially in unity where breaking out into several classes isn’t always the best scenario for some tasks.
For example, an animation controller I might write could have several nested classes to handle states of the animation controller. Now I COULD break them out into their own code files, but why? They’re VERY specific to this one class, and breaking them out clutters up the folder where the animation controllers exist, which makes the designers job very annoying seeing 7 code files, for which only 1 is used, for a type of entities animations. A lot of my design is wrestling with designers in this case. They don’t understand why this “simple thing that just animates the player” requires 10 different classes… or to them, 10 different oddly named files like ‘PlayerAnimationController’, ‘PlayerAnimIdleState’, ‘PlayerAnimMovementState’, ‘PlayerAnimDeathState’, etc, etc.
Here’s an example of what I mean:
using UnityEngine;
using System.Collections.Generic;
using com.spacepuppy;
using com.spacepuppy.Anim;
using com.spacepuppy.Collections;
using com.spacepuppy.Scenario;
using com.spacepuppy.Utils;
namespace com.mansion.Entities.Actors.Player
{
public class PlayerAnimator : SPComponent, IEntityAwakeHandler
{
#region Anim Constants
public const int ANIMLAYER_DEFAULT = 0;
public const int ANIMLAYER_WALK = 5;
public const int ANIMLAYER_ACTION = 10;
public const int ANIMLAYER_DEATH = 100;
#endregion
#region Fields
[SerializeField()]
[DefaultFromSelf(UseEntity = true)]
private SPAnimationController _controller;
[SerializeField()]
private DefaultMovementAnimationInfo _defaultMovementAnimations;
[SerializeField()]
private UndeadMovementAnimationInfo _undeadAnimations;
[SerializeField()]
private RangeWeaponAnimationInfo _rangeWeaponAnimations;
[SerializeField()]
private StruckAnimationInfo _struckAnimations;
[Header("Event Triggers")]
[SerializeField()]
private Trigger _onFireWeapon;
[SerializeField()]
private Trigger _onReloadWeapon;
[SerializeField()]
private Trigger _onStruck;
[SerializeField()]
private Trigger _onDeath;
[SerializeField()]
private Trigger _onRebirth;
[SerializeField()]
private Trigger _onUndeadAttack;
[System.NonSerialized()]
private IEntity _entity;
#endregion
#region CONSTRUCTOR
protected override void Awake()
{
base.Awake();
var entity = SPEntity.Pool.GetFromSource<IEntity>(this);
if (entity != null && entity.IsAwake) this.OnAwake(entity);
}
void IEntityAwakeHandler.OnEntityAwake(SPEntity entity)
{
this.OnAwake(entity as IEntity);
}
private void OnAwake(IEntity entity)
{
_entity = entity;
this.InitAnims();
}
#endregion
#region Properties
public IEntity Entity
{
get { return _entity; }
}
public SPAnimationController Controller
{
get { return _controller; }
}
public DefaultMovementAnimationInfo DefaultMovementAnimations
{
get { return _defaultMovementAnimations; }
}
public UndeadMovementAnimationInfo UndeadAnimations
{
get { return _undeadAnimations; }
}
public RangeWeaponAnimationInfo RangeWeaponAnimations
{
get { return _rangeWeaponAnimations; }
}
public StruckAnimationInfo StruckAnimations
{
get { return _struckAnimations; }
}
#endregion
#region Methods
private void InitAnims()
{
_defaultMovementAnimations.Init(this);
_undeadAnimations.Init(this);
_rangeWeaponAnimations.Init(this);
_struckAnimations.Init(this);
}
#endregion
#region Special Types
[System.Serializable()]
public class DefaultMovementAnimationInfo
{
public const string ANIM_MOVE_IDLE = "Idle";
public const string ANIM_MOVE_IDLEACTION = "IdleAction";
public const string ANIM_MOVE_WALK = "Walk";
public const string ANIM_MOVE_STRAFE_F = "StrafeForward";
public const string ANIM_MOVE_STRAFE_B = "StrafeBackward";
public const string ANIM_MOVE_STRAFE_L = "StrafeLeft";
public const string ANIM_MOVE_STRAFE_R = "StrafeRight";
public const string ANIM_MOVE_IDLE_CRIT = "Idle_Crit";
public const string ANIM_MOVE_IDLEACTION_CRIT = "IdleAction_Crit";
public const string ANIM_MOVE_WALK_CRIT = "Walk_Crit";
public enum DefaultMovementState
{
IdleAction = -1,
Idle = 0,
Walk = 1
}
#region Fields
[SerializeField()]
private float _moveAnimSpeedRatio = 1;
[SerializeField()]
private float _critMoveAnimSpeedRatio = 1;
[SerializeField()]
[SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_DEFAULT)]
[SPAnimClipCollection.StaticCollection(ANIM_MOVE_IDLE,
ANIM_MOVE_IDLEACTION,
ANIM_MOVE_WALK,
ANIM_MOVE_STRAFE_F,
ANIM_MOVE_STRAFE_B,
ANIM_MOVE_STRAFE_L,
ANIM_MOVE_STRAFE_R,
ANIM_MOVE_IDLE_CRIT,
ANIM_MOVE_IDLEACTION_CRIT,
ANIM_MOVE_WALK_CRIT)]
private SPAnimClipCollection _animations;
[System.NonSerialized()]
private PlayerAnimator _owner;
[System.NonSerialized()]
private DefaultMovementState _state;
[System.NonSerialized()]
private SPAnimClip _currentWalk;
#endregion
#region CONSTRUCTOR
#endregion
#region Properties
public DefaultMovementState State { get { return _state; } }
#endregion
#region Methods
internal void Init(PlayerAnimator owner)
{
_owner = owner;
_animations.Init(_owner.Controller, "*move");
this.PlayIdle();
}
public SPAnim Play(string id, QueueMode queuMode = QueueMode.PlayNow, PlayMode playMode = PlayMode.StopSameLayer)
{
return _animations.Play(id, queuMode, playMode);
}
public void PlayIdle()
{
if (_currentWalk != null)
{
_currentWalk.Stop();
_currentWalk = null;
}
SPAnimClip clip;
if (_owner._entity.HealthMeter.Status == HealthMeter.StatusType.Critical)
clip = _animations[ANIM_MOVE_IDLE_CRIT];
else
clip = _animations[ANIM_MOVE_IDLE];
if (clip == null) return;
clip.Layer = ANIMLAYER_DEFAULT;
clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
_state = DefaultMovementState.Idle;
}
public void PlayIdleAction()
{
SPAnimClip clip;
if (_owner._entity.HealthMeter.Status == HealthMeter.StatusType.Critical)
clip = _animations[ANIM_MOVE_IDLEACTION_CRIT];
else
clip = _animations[ANIM_MOVE_IDLEACTION];
if (clip == null) return;
_state = DefaultMovementState.IdleAction;
clip.WrapMode = WrapMode.Once;
clip.Layer = ANIMLAYER_DEFAULT + 1;
clip.CrossFade(Constants.DEFAULT_CROSSFADE_DUR).Schedule((a) =>
{
if (_state == DefaultMovementState.IdleAction)
{
_state = DefaultMovementState.Idle;
this.PlayIdle();
}
});
}
public void PlayWalk(float spd = 1f)
{
SPAnimClip clip;
float ratio;
if (_owner._entity.HealthMeter.Status == HealthMeter.StatusType.Critical)
{
clip = _animations[ANIM_MOVE_WALK_CRIT];
ratio = _critMoveAnimSpeedRatio;
}
else
{
clip = _animations[ANIM_MOVE_WALK];
ratio = _moveAnimSpeedRatio;
}
if (clip == null) return;
clip.WrapMode = WrapMode.Loop;
clip.Speed = spd * ratio;
clip.Layer = ANIMLAYER_WALK;
clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
_currentWalk = clip;
_state = DefaultMovementState.Walk;
}
public void PlayStrafe(float offAngle, float spd = 1f)
{
SPAnimClip clip;
if (Mathf.Abs(offAngle) >= 135f)
clip = _animations[ANIM_MOVE_STRAFE_B];
else if (offAngle < -45f)
clip = _animations[ANIM_MOVE_STRAFE_L];
else if (offAngle > 45f)
clip = _animations[ANIM_MOVE_STRAFE_R];
else
clip = _animations[ANIM_MOVE_STRAFE_F];
if (clip == null) return;
clip.WrapMode = WrapMode.Loop;
clip.Speed = spd * _moveAnimSpeedRatio;
clip.Layer = ANIMLAYER_WALK;
clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
_currentWalk = clip;
_state = DefaultMovementState.Walk;
}
#endregion
}
[System.Serializable()]
public class UndeadMovementAnimationInfo
{
public const string ANIM_UNDEAD_IDLE = "Idle";
public const string ANIM_UNDEAD_IDLEACTION = "IdleAction";
public const string ANIM_UNDEAD_WALK = "Walk";
public const string ANIM_UNDEAD_MELEE = "Melee";
public enum DefaultMovementState
{
IdleAction = -1,
Idle = 0,
Walk = 1
}
#region Fields
[SerializeField()]
private float _moveAnimSpeedRatio = 1;
[SerializeField()]
[SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_DEFAULT)]
[SPAnimClipCollection.StaticCollection(ANIM_UNDEAD_IDLE,
ANIM_UNDEAD_IDLEACTION,
ANIM_UNDEAD_WALK,
ANIM_UNDEAD_MELEE)]
private SPAnimClipCollection _animations;
[System.NonSerialized()]
private PlayerAnimator _owner;
[System.NonSerialized()]
private DefaultMovementState _state;
[System.NonSerialized()]
private SPAnimClip _currentWalk;
#endregion
#region CONSTRUCTOR
#endregion
#region Properties
public DefaultMovementState State { get { return _state; } }
#endregion
#region Methods
internal void Init(PlayerAnimator owner)
{
_owner = owner;
_animations.Init(_owner.Controller, "*undead");
this.PlayIdle();
}
public SPAnim Play(string id, QueueMode queuMode = QueueMode.PlayNow, PlayMode playMode = PlayMode.StopSameLayer)
{
return _animations.Play(id, queuMode, playMode);
}
public void PlayIdle()
{
if (_currentWalk != null)
{
_currentWalk.Stop();
_currentWalk = null;
}
var clip = _animations[ANIM_UNDEAD_IDLE];
if (clip == null) return;
clip.Layer = ANIMLAYER_DEFAULT;
clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
_state = DefaultMovementState.Idle;
}
public void PlayIdleAction()
{
var clip = _animations[ANIM_UNDEAD_IDLEACTION];
if (clip == null) return;
_state = DefaultMovementState.IdleAction;
clip.WrapMode = WrapMode.Once;
clip.Layer = ANIMLAYER_DEFAULT + 1;
clip.CrossFade(Constants.DEFAULT_CROSSFADE_DUR).Schedule((a) =>
{
if (_state == DefaultMovementState.IdleAction)
{
_state = DefaultMovementState.Idle;
this.PlayIdle();
}
});
}
public void PlayWalk(float spd = 1f)
{
var clip = _animations[ANIM_UNDEAD_WALK];
if (clip == null) return;
clip.WrapMode = WrapMode.Loop;
clip.Speed = spd * _moveAnimSpeedRatio;
clip.Layer = ANIMLAYER_WALK;
clip.CrossFadeDirectly(Constants.DEFAULT_CROSSFADE_DUR);
_currentWalk = clip;
_state = DefaultMovementState.Walk;
}
public IRadicalWaitHandle PlayMelee()
{
var clip = _animations[ANIM_UNDEAD_MELEE];
if (clip == null) return RadicalWaitHandle.Null;
_owner._onUndeadAttack.ActivateTrigger();
clip.WrapMode = WrapMode.Once;
clip.Layer = ANIMLAYER_ACTION;
var anim = clip.CrossFade(Constants.DEFAULT_CROSSFADE_DUR, QueueMode.PlayNow);
return anim;
}
#endregion
}
[System.Serializable()]
public class RangeWeaponAnimationInfo
{
public const string ANIM_RANGE_DRAW = "Draw";
public const string ANIM_RANGE_AIM = "Aim";
public const string ANIM_RANGE_HOLSTER = "Holster";
public const string ANIM_RANGE_FIRE = "Fire";
public const string ANIM_RANGE_RELOAD = "Reload";
public enum RangeWeaponState
{
Holstering = -1,
None = 0,
Drawing = 1,
Aim = 2,
Firing = 3,
Reload = 4
}
#region Fields
[SerializeField()]
[SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_ACTION)]
[SPAnimClipCollection.StaticCollection(ANIM_RANGE_DRAW,
ANIM_RANGE_AIM,
ANIM_RANGE_HOLSTER,
ANIM_RANGE_FIRE,
ANIM_RANGE_RELOAD)]
private SPAnimClipCollection _animations;
[System.NonSerialized()]
private PlayerAnimator _owner;
[System.NonSerialized()]
private RangeWeaponState _state;
[System.NonSerialized()]
private SPAnim _drawAnim;
#endregion
#region CONSTRUCTOR
#endregion
#region Properties
public RangeWeaponState State
{
get { return _state; }
}
#endregion
#region Methods
internal void Init(PlayerAnimator owner)
{
_owner = owner;
_animations.Init(_owner.Controller, "*range");
}
public SPAnim Play(string id, QueueMode queuMode = QueueMode.PlayNow, PlayMode playMode = PlayMode.StopSameLayer)
{
return _animations.Play(id, queuMode, playMode);
}
public bool DrawWeapon()
{
switch (_state)
{
case RangeWeaponState.Holstering:
case RangeWeaponState.None:
{
var clip1 = _animations[ANIM_RANGE_DRAW];
var clip2 = _animations[ANIM_RANGE_AIM];
if (clip1 == null || clip2 == null) return false;
_state = RangeWeaponState.Drawing;
clip1.WrapMode = WrapMode.Clamp;
clip1.Layer = ANIMLAYER_ACTION;
_drawAnim = clip1.Play(QueueMode.PlayNow);
_drawAnim.Schedule((a) =>
{
_drawAnim = null;
_state = RangeWeaponState.Aim;
});
clip2.WrapMode = WrapMode.Loop;
clip2.Layer = ANIMLAYER_ACTION;
clip2.Play(QueueMode.CompleteOthers);
return true;
}
case RangeWeaponState.Drawing:
case RangeWeaponState.Aim:
case RangeWeaponState.Firing:
case RangeWeaponState.Reload:
default:
return false;
}
}
public bool HolsterWeapon()
{
switch(_state)
{
case RangeWeaponState.Holstering:
case RangeWeaponState.None:
return false;
case RangeWeaponState.Drawing:
{
var clip = _animations[ANIM_RANGE_DRAW];
_state = RangeWeaponState.Holstering;
clip.WrapMode = WrapMode.Clamp;
clip.Layer = ANIMLAYER_ACTION;
clip.Time = _drawAnim.Time;
var anim = clip.Play(QueueMode.PlayNow);
anim.Speed = -1f;
anim.Schedule((a) =>
{
_state = RangeWeaponState.None;
});
_drawAnim = null;
return true;
}
case RangeWeaponState.Aim:
{
var clip = _animations[ANIM_RANGE_HOLSTER];
if (clip == null) return false;
_state = RangeWeaponState.Holstering;
clip.WrapMode = WrapMode.Once;
clip.Layer = ANIMLAYER_ACTION;
clip.CrossFade(Constants.DEFAULT_CROSSFADE_DUR, QueueMode.PlayNow).Schedule((a) =>
{
_state = RangeWeaponState.None;
});
return true;
}
case RangeWeaponState.Firing:
case RangeWeaponState.Reload:
default:
return false;
}
}
public bool FireWeapon()
{
switch (_state)
{
case RangeWeaponState.Holstering:
case RangeWeaponState.None:
case RangeWeaponState.Drawing:
return false;
case RangeWeaponState.Aim:
{
var clip = _animations[ANIM_RANGE_FIRE];
if (clip == null) return false;
_state = RangeWeaponState.Firing;
clip.WrapMode = WrapMode.Once;
clip.Layer = ANIMLAYER_ACTION + 1;
clip.Play(QueueMode.CompleteOthers).Schedule((a) =>
{
_state = RangeWeaponState.Aim;
});
_owner._onFireWeapon.ActivateTrigger();
return true;
}
case RangeWeaponState.Firing:
case RangeWeaponState.Reload:
default:
return false;
}
}
public bool ReloadWeapon()
{
switch (_state)
{
case RangeWeaponState.Holstering:
case RangeWeaponState.None:
case RangeWeaponState.Drawing:
return false;
case RangeWeaponState.Aim:
{
var clip = _animations[ANIM_RANGE_RELOAD];
if (clip == null) return false;
_state = RangeWeaponState.Reload;
clip.WrapMode = WrapMode.Once;
clip.Layer = ANIMLAYER_ACTION + 1;
clip.Play(QueueMode.CompleteOthers).Schedule((a) =>
{
_state = RangeWeaponState.Aim;
});
_owner._onReloadWeapon.ActivateTrigger();
return true;
}
case RangeWeaponState.Firing:
case RangeWeaponState.Reload:
default:
return false;
}
}
#endregion
}
[System.Serializable()]
public class StruckAnimationInfo
{
public const string ANIM_STRUCK_DEATH = "Death";
public const string ANIM_STRUCK_REBIRTH = "Rebirth";
#region Fields
[SerializeField()]
[SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_ACTION)]
private SPAnimClipCollection _struckAnimations;
[SerializeField()]
[SPAnimClipCollection.Config(DefaultLayer = PlayerAnimator.ANIMLAYER_DEATH)]
[SPAnimClipCollection.StaticCollection(ANIM_STRUCK_DEATH,
ANIM_STRUCK_REBIRTH)]
private SPAnimClipCollection _deathAnimations;
[System.NonSerialized()]
private PlayerAnimator _owner;
#endregion
#region Methods
internal void Init(PlayerAnimator owner)
{
_owner = owner;
_struckAnimations.Init(_owner.Controller, "*struck");
_deathAnimations.Init(_owner.Controller, "*death");
}
public SPAnim PlayStruck()
{
var clip = _struckAnimations.PickRandom();
if (clip == null) return null;
_owner._onStruck.ActivateTrigger();
return clip.Play();
}
public SPAnim PlayDeath()
{
var clip = _deathAnimations[ANIM_STRUCK_DEATH];
if (clip == null) return null;
_owner._onDeath.ActivateTrigger();
clip.Layer = PlayerAnimator.ANIMLAYER_DEATH;
clip.WrapMode = WrapMode.ClampForever;
return clip.CrossFade(0.5f);
}
public SPAnim PlayRebirth()
{
var clip = _deathAnimations[ANIM_STRUCK_REBIRTH];
if (clip == null) return null;
_owner._onRebirth.ActivateTrigger();
clip.Layer = PlayerAnimator.ANIMLAYER_DEATH;
return clip.CrossFade(1f);
}
#endregion
}
#endregion
}
}
Note my ‘special tpes’ region. I have no reason to break this thing out into their own classes. While at the same time, grouping them into a region does let me close them up and not have to pay attention to them while working with other code in the class.
Call me ADD, but I find all the extra code distracting while I happen to be working in one specific area. I like to get out of my sight for a moment. It’s not like I forget its there… I KNOW its there. My regions follow a standard (it’s my standard, but still a standard), I don’t shrink the Properties region and go “Ope, I don’t have any properties in this class!”.
Others are like the Fields. Sure, they follow naming conventions that make them distinct… I’m not going to confuse my fields with my constants with my properties. And they’re grouped appropriately. Which that stack-exchange or whatever says should be enough.
But… look at all those attributes? I need them! But they’re really distracting to look at. So yeah, I hide them away when I don’t need them.
And yeah, sure, I could rewrite this damn thing in so many different ways that gets rid of the regions. But why… why do that? It works! And it’s readable for most with any reasonable programming skill. I get saying a region inside a method is bad… your method might be too big (is that even allowed in C#?), but screw you that the mere use of them is bad. pffffff yep, and goto’s and singletons are evil, and oop design patterns set back programming 30 years, and so on and so forth.
These are the same sorts who think all code should be written in Haskell, in ONLY Debian Linux (or insert some other distribution), while typing upside down in pig latin. Why? Because that’s what they do, and you should do it too. Nothing’s wrong with these tools, but using them and only them, while excluding others just as viable, is stupid.