I’m getting big performance spikes in GUIUtility.BeginGUI(). But I have no clue what that is and what contributes to it generating so much garbage.
Info #1: I have a big gameobject with all my UI and I disabled it, so that’s not the reason.
Info #2: This also occurs in built game.
There might be many sources of GC allocations. To identify those, you can try the following techniques:
When profiling in the Editor you can enable allocation callstacks and watch in Timeline view for red bars - those are GC.Alloc samples which might happen on any thread.
In a Player build deep profiler might help to resemble the callstack - you can enable it for mono scripting backend with -deepprofiling cmd line argument.
Use Profiling.Recorder.sampleBlockCount API for GC.Alloc marker to see if you have any allocations in a frame (in Development Players).
Also there is pretty comprehensive guide which explains how to minimize managed allocations in Unity.
I found out what is the reason but it doesn’t make any sense: I had a function OnGUI() in one of my scripts, it doesn’t contain anything yet it creates garbage. It has to be a bug right?! EDIT: I have this bug on other components too it’s not just the script I pasted. Removing the OnGUI() function entirely fixed it.
private void OnGUI()
{
}
Here’s the full script:
using System;
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
[RequireComponent(typeof(Collider2D)), DisallowMultipleComponent]
public class Hurtbox : MonoBehaviour, IPausable
{
// private void OnGUI()
// {
// }
public Entity Entity { get; private set; }
[DisableInEditorMode, DisableInPlayMode]
public Unit Owner;
public Alliance StartType
{
get
{
if (Owner != null)
return GetStartTypeFor(Owner.Name);
else
{
// Debug.LogError("doesn't have an owner! Alliance = not unit");
return Alliance.NotUnit;
}
}
}
public static Alliance GetStartTypeFor(ID _unit)
{
if (_unit == ID.Player)
return Alliance.Player;
else if (_unit == ID.Demon)
return Alliance.Ethereal;
// else if (_unit == Entity.Crossbowman || _unit == Entity.Guardian)
// return Alliance.Empire;
else return Alliance.Creatures;
}
private Alliance AllianceOverwritten;
private bool IsAllianceOverwritten;
public Alliance CurrentType
{
get
{
if (IsAllianceOverwritten)
{
return AllianceOverwritten;
}
return StartType;
}
}
public void UseOverwriteAlliance(Alliance _newAlliance)
{
AllianceOverwritten = _newAlliance;
IsAllianceOverwritten = true;
}
public void UseOverwriteAllianceStop()
{
IsAllianceOverwritten = false;
}
public static bool DEBUG_PLAYER_INVINCIBLE = false;
public static bool DEBUG_ENEMIES_INVINCIBLE = false;
public static bool DEBUG_ONE_HIT_KILL = false;
public SkinnedMeshRenderer AutoFit;
// private Collider2D Col;
public bool IsPaused { get; set; }
private Controller Control;
public void OnPaused()
{
enabled = false;
}
public void OnUnpaused()
{
enabled = true;
}
public bool IsUnit => Owner != null;
/// <summary>
/// OnGotHit's status can be null depending on what we were hit from.
/// </summary>
public Action<HurtboxHitInfo> OnPreHit;
/// <summary>
/// OnGotHit's status can be null depending on what we were hit from.
/// </summary>
public Action<HurtboxHitInfo> OnPostHit;
public struct HurtboxHitInfo
{
public HurtboxHitInfo(Damage _dmg, Hitbox _hit, Hurtbox _hurtbox, HitType _hitResult, float _damage)
{
Damage = _dmg;
Hitbox = _hit;
Hurtbox = _hurtbox;
HitType = _hitResult;
Expulsion = _hit.ComplexExpulsion;
DamageDealt = _damage;
IsEmpty = false;
}
public HurtboxHitInfo(bool _isEmpty)
{
IsEmpty = true;
Damage = null;
Hitbox = null;
Hurtbox = null;
DamageDealt = 0;
HitType = HitType.Empty();
Expulsion = null;
}
public static HurtboxHitInfo Empty()
{
return new HurtboxHitInfo(true);
}
public float DamageDealt;
public bool IsEmpty;
public Damage Damage;
public Hitbox Hitbox;
public Hurtbox Hurtbox;
public HitType HitType;
public AdvancedExpulsion Expulsion;
}
// public Action<Hitbox, AdvancedExpulsion> OnHitboxHit;
// public Action OnHit;
public Action<Damage, Hitbox> OnDying;
/// <summary>
/// When the character blocks the attack, with a bad timing.
/// </summary>
public Action<Hitbox> OnBlocked;
/// <summary>
/// When the character blocked the attack at the right moment, with a perfect timing.
/// </summary>
public Action<Hitbox> OnPerfectBlock;
/// <summary>
/// We got countered when during our attack
/// </summary>
public Action OnYouGotCountered;
private static GameObject DamageIndicator;
public SpriteRenderer SpriteToFlicker;
// private ColorAnimator DamageFlicker;
private Color DamagedFlickerColor = Color.red;
/// <summary>
/// A status is not required to make Interactible work.
/// </summary>
[HideInInspector] public Status Status;
// [Tooltip("If you want the interactible to move when it's hit, you need a Controller2")]
// public Controller2 Control;
[Tooltip("Once you get hit, for how long are you invincible?")]
public float InvincibilityTimeAfterHit = -1f;
public bool CantBeExpulsedByHit;
public bool HasRecovered => DisabledTime <= 0f;
/// <summary>
/// List of all the hitboxes that have hit us along with a timer that represent when these hitboxes can hit us again.
/// </summary>
private readonly List<HitboxHit> HitboxHits = new List<HitboxHit>();
//private readonly List<ExpulsionData> ExpulsionDatas = new List<ExpulsionData>();
// [Header("Health Bar")] public bool ActivateHealthBar;
// private HorizontalBar HealthBar;
// public float HealthBarHeight = 5f;
// HITBOX AVOIDANCE
public bool IsInvincible => InvincibilityTime > 0f;
public float InvincibilityTime;
public bool IsDefending { get; private set; }
public float DefendTime { get; private set; }
public Vector2 DefendAngleMinMax;
public float DisabledTime;
private bool InvincibleDefense;
private bool OmniAngleDefense;
public GameObject[] OnGotHitFx;
/// <summary>
/// from 0 to 360, with 0dgr and 360dgr == right
/// 90
/// 180 0
/// 270
/// </summary>
public void UpdateDefend(float _fromAngle, float _toAngle, bool _invicibleDefend = false)
{
IsDefending = true;
OmniAngleDefense = false;
DefendAngleMinMax.x = _fromAngle;
DefendAngleMinMax.y = _toAngle;
InvincibleDefense = _invicibleDefend;
}
public void UpdateDefend(bool _invicibleDefend = false)
{
IsDefending = true;
OmniAngleDefense = true;
InvincibleDefense = _invicibleDefend;
DefendAngleMinMax.x = 0f;
DefendAngleMinMax.y = 0f;
}
// public void UpdateDefend(float _fromAngle, float _toAngle)
// {
// DefendTime += Time.deltaTime;
// IsDefending = true;
// DefendAngleMinMax.x = _fromAngle;
// DefendAngleMinMax.y = _toAngle;
// }
public void StopDefend()
{
OmniAngleDefense = false;
DefendTime = 0f;
IsDefending = false;
}
void Awake()
{
PauseManager.AddMe(this);
Owner = GetComponentInParent<Unit>();
Entity = GetComponentInParent<Entity>();
if (DamageIndicator == null)
DamageIndicator = Resources.Load<GameObject>(ResourcesPath.DAMAGE_CANVAS);
Status = GetComponent<Status>();
if (Status != null) Status.HurtboxInit(this);
Control = GetComponentInParent<Controller>();
}
void OnDestroy()
{
PauseManager.RemoveMe(this);
}
#if UNITY_EDITOR
private void OnValidate()
{
if (gameObject.layer != GConstants.Hurtbox_Layer)
gameObject.layer = GConstants.Hurtbox_Layer; // Hurtbox
var owner = GetComponentInParent<Unit>();
if ( /*Owner != null && */Owner != owner)
Owner = owner;
if (SpriteToFlicker == null)
{
var sr = GetComponentInChildren<SpriteRenderer>();
if (sr != null)
SpriteToFlicker = sr;
}
// if (Control == null) Control = GetComponentInParent<Controller2>();
var rb2D = GetComponent<Rigidbody2D>();
if (rb2D == null)
{
rb2D = gameObject.AddComponent<Rigidbody2D>();
}
if (rb2D != null)
{
if (rb2D.isKinematic == false) rb2D.isKinematic = true;
if (rb2D.collisionDetectionMode != CollisionDetectionMode2D.Continuous)
rb2D.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
}
var col2d = GetComponent<Collider2D>();
if (col2d.isTrigger == false)
col2d.isTrigger = true;
}
#endif
void Update()
{
if (IsPaused) return;
if (IsDefending)
DefendTime += Time.deltaTime;
if (AutoFit != null)
{
var scaleAdjBounds = AutoFit.bounds;
scaleAdjBounds.size /= transform.lossyScale.x;
Control.Col2D.FitToBounds(scaleAdjBounds, Collider2DExt.FitToBoundsTransformStyle.Center);
transform.position = Control.Col2D.transform.position;
}
if (Status)
{
// TODO: Not really being used...
DisabledTime -= Time.deltaTime * Status.Stats.HitRecoveryMult.Current;
}
InvincibilityUpdate();
UpdateHitboxHits();
}
private void UpdateHitboxHits()
{
for (var i = HitboxHits.Count - 1; i >= 0; i--)
{
var hh = HitboxHits[i];
hh.Timer -= Time.deltaTime;
// HitboxHits[i] = new HitboxHit(hh.Id, hh.ActivatedCount, hh.Timer);
// Remove when finished
if (hh.Timer <= 0f)
{
HitboxHits.RemoveAt(i);
}
}
}
public void SetLastHitboxThatHitMe(int _id, int _activatedCount, float _resetTime)
{
if (IsInvincible) return;
HitboxHits.Add(new HitboxHit(_id, _activatedCount, _resetTime));
}
public void SetDisabledTime(float _disabledTime)
{
if (DisabledTime < _disabledTime)
DisabledTime = _disabledTime;
}
public bool HasBeenHitByHitboxId(int _id, int _activatedCount)
{
foreach (var hitboxHit in HitboxHits)
{
// Have I been hit by this hitbox's id + activatedCount before??
if (hitboxHit.Id == _id
&& hitboxHit.Timer > 0f
&& hitboxHit.ActivatedCount == _activatedCount)
{
//Debug.Log("Not accepting new hitbox yet, timer left: " + hitboxHit.Timer);
return true;
}
}
return false;
}
public int HasBeenHitByLinkedHitbox(int _mainId, int _mainIdActivatedCount, int[] _linkedIds)
{
foreach (var hitboxHit in HitboxHits)
{
// Have I been hit by this hitbox's id + activatedCount before??
if (hitboxHit.Id == _mainId
&& hitboxHit.Timer > 0f
&& hitboxHit.ActivatedCount == _mainIdActivatedCount)
{
//Debug.Log("Not accepting new hitbox yet, timer left: " + hitboxHit.Timer);
return 1;
}
}
if (HitboxHits.Count > 0)
{
for (var i = 0; i < _linkedIds.Length; i++)
{
var linkedId = _linkedIds[i];
var lastId = HitboxHits.Count - 1;
if (HitboxHits[lastId].Id == linkedId)
{
// Debug.Log("recently linked hitbox hit target, skipping");
return 2;
}
}
}
return 0;
}
public void ReceiveEnchantments(Damage _damage, Hitbox _hitbox, HitType _hitResult)
{
// var gotHit = _hitResult.HasHit == HitResult.TargetInvincible || _hitResult.HasHit == HitResult.TargetCountered;;
if (IsUnit == false) return;
if (_hitbox.EnchantsOnHit != null)
{
foreach (var ench in _hitbox.EnchantsOnHit)
{
if (ench.Prefab.GetEnchantmentName() == EnchantID.Frozen && Status.IsFrozen)
{
Status.EnchantmentManager.StopEnchantment(EnchantID.Frozen);
continue;
}
EnchantmentStock.InflictEnchantment(_hitbox.EnchantsOnHit, this.Owner);
}
}
if (_hitbox.EnchantsOnHitInspector != null)
{
foreach (var ench in _hitbox.EnchantsOnHitInspector)
{
if (ench.ID == EnchantID.Frozen && Status.IsFrozen)
{
Status.EnchantmentManager.StopEnchantment(EnchantID.Frozen);
continue;
}
EnchantmentStock.InflictEnchantment(ench.ID, null, this.Owner, ench.Time);
}
}
}
public bool ReceiveDamage(Damage _damage, Hitbox _hitbox, AdvancedExpulsion _advancedExpulsion,
HitType _hitResult)
{
// IMPORTANT: We don't need the attacker status to calculate the dmg.
// The damage source is already calculated in the Damage object.
// Also the status can be null depending on if it's not a unit, but simply a trap...
// So we probably need to introduce pure dmg for traps and such...
var gotHit = _hitResult.HasHit == HitResult.TargetInvincible || _hitResult.HasHit == HitResult.TargetCountered;
Status attackerStatus = null;
if (_hitbox != null)
{
if (_hitbox.Owner != null)
attackerStatus = _hitbox.Owner.Status;
}
// Debug.Log("got hit");
// Debug.Log("InvincibilityTime: " + InvincibilityTime );
// Debug.Log("Status.IsDead: " + Status.IsDead);
// Debug.Log("_hitResult: " + _hitResult);
var damageDealt = CalculateDamage(_damage, _hitResult);
var hitInfo = new HurtboxHitInfo(_damage, _hitbox, this, _hitResult, damageDealt);
if (OnPreHit != null)
OnPreHit.Invoke(hitInfo);
if (_damage == null)
{
Debug.LogError("The attack was null!");
return gotHit;
}
if (_hitResult.HasHit != HitResult.TargetCountered && IsInvincible == false)
{
// // Freeze time to give a better impact.
// // TODO: Might not be ok.
// if (_damage.IsMelee && IsInvincible == false)
// GameTime.FreezeTime(GameScriptable.CombatFeel.OnHitFreezeTime);
if (this.Status == null)
{
// For interactibles without status ( rocks ... )
if (CantBeExpulsedByHit == false)
{
ExpulseBehaviour(_advancedExpulsion);
}
}
else if (Status.IsDead == false)
{
// var shakePreset = _hitbox.OnHitPreset;
// if (shakePreset != null)
// ProCamera2DShake.Instance.Shake(shakePreset);
// // Enemy regains stamina on hits
// if (_hitbox.Damage.IsMelee && attackerStatus)
// {
// attackerStatus.AddRage(-dmg * Status.CurrRageGainMult);
// }
if (CantBeExpulsedByHit == false)
{
// Debug.Log("expulsing: " + (_advancedExpulsion.Magnitude) + " Angle: " +
// _advancedExpulsion.ProcessedAngle + " for " + Owner.UnitName);
ExpulseBehaviour(_advancedExpulsion);
}
foreach (var fx in _hitbox.OnHitFx)
{
Instantiate(fx, transform.position, Quaternion.identity);
}
foreach (var fx in OnGotHitFx)
{
Instantiate(fx, transform.position, Quaternion.identity);
}
// Flicker to show damage has been dealt
if (Owner != null)
{
Owner.Visuals.ShaderManager.SetHit(.15f);
}
if (_damage.DealsDamage)
{
StartCoroutine(SpawnDamageIndicator(_damage, damageDealt,
_advancedExpulsion.GetProcessedDirection(), transform,
_hitResult.IsCritical));
}
if (Status.ShieldHp > 0 && damageDealt < 0)
{
// We need to remove from the shield
if (Status.ShieldHp >= damageDealt)
{
Status.SetShieldHp(Status.ShieldHp + damageDealt);
damageDealt = 0;
}
else
{
Status.SetShieldHp(0);
damageDealt += Status.ShieldHp;
}
}
if (Math.Abs(damageDealt) > 0.01f)
{
if (IfFrozenOnThisAttack(_damage) == false)
// DamageFlicker.StartFadeInOutColor(DamagedFlickerColor,
// GameScriptable.CombatFeel.OnHitWhiteFadeInTime,
// GameScriptable.CombatFeel.OnHitWhiteFadeMaintainTime,
// GameScriptable.CombatFeel.OnHitWhiteFadeOutTime, IsCharacterFlicker);
Status.ChangeHp(damageDealt, false);
}
if (attackerStatus != null && _damage.IsVampirism)
{
var gainedHp = Mathf.Clamp(Mathf.Abs(damageDealt) * attackerStatus.Stats.RevengeDrainMult.Current,
0,
attackerStatus.Stats.CurrentLostHealth.Value);
// Debug.Log(attackerStatus.name + " just revenged " + gainedHp + " hp back! RevengeMult: " +
// attackerStatus.Stats.RevengeDrainMult.Current * 100 + "%");
attackerStatus.ChangeHp(gainedHp, false);
// TODO: think about mana regen
var manaGained = Mathf.Clamp(
Mathf.Abs(damageDealt) * attackerStatus.Stats.ManaRegenOnHitMult.Current, 0,
attackerStatus.Stats.MaxMana.Current);
manaGained += attackerStatus.Stats.ManaRegenOnHit.Current;
attackerStatus.Stats.CurrentMana.Value += manaGained;
}
if (Status.IsDead)
{
if (OnDying != null)
{
OnDying.Invoke(_damage, _hitbox);
}
if (attackerStatus != null)
{
Status.EnchantmentManager.ActivateDeathEnchantmentsOn(_hitbox.Owner);
// Reduce Player's Shrine fate kill count
if (attackerStatus.IsPlayer)
{
foreach (var f in attackerStatus.EnchantmentManager.Enchantments)
{
if (f.Setup.Erase.HasAFlagOf(Enchantment.EraseType.EnemiesKilled))
{
f.NrEnemyLeftToKill--;
}
}
}
}
if (Status.Owner != null)
{
if (Owner.Control.Expulsed)
{
// Debug.Log("died being expulsed");
Status.Owner.Kill(Unit.KillingWays.Expulsed);
}
else
{
// Debug.Log("died not expulsed!");
Status.Owner.Kill(Unit.KillingWays.Hit);
}
}
}
}
}
if (OnPostHit != null && IsInvincible == false)
{
var hitInfoPost = new HurtboxHitInfo(_damage, _hitbox, this, _hitResult, damageDealt);
OnPostHit.Invoke(hitInfoPost);
}
if (InvincibilityTimeAfterHit > 0f && IsInvincible == false)
{
StartInvincibilityFor(InvincibilityTimeAfterHit);
}
return gotHit;
}
private bool IfFrozenOnThisAttack(Damage _attack)
{
bool freezeAttack = false;
// foreach (var fate in _attack.Enchantments)
// {
// if (fate.Info.EnchName == EnchName.Frozen)
// {
// freezeAttack = true;
// }
// }
return freezeAttack;
}
private IEnumerator SpawnDamageIndicator(Damage _dmg, float _dmgDealt, Vector2 _direction,
Transform _targetToFollow, bool _isCriticalHit)
{
// Can spawn 2 damage indicators at the same time with an offset
// To spawn the 3rd damage indicator we must wait some time.
// var damageType = _dmg.GetMostDamagingType();
// Color damageColor;
// switch (damageType)
// {
// case DamageType.Physical:
// damageColor = Color.grey;
// break;
// case DamageType.Fire:
// damageColor = Color.red;
// break;
// case DamageType.Ice:
// damageColor = Color.blue;
// break;
// case DamageType.Lightning:
// damageColor = Color.white;
// break;
// case DamageType.Earth:
// damageColor = new Color(0.71f, 0.34f, 0f);
// break;
// case DamageType.Magical:
// damageColor = Color.magenta;
// break;
// default:
// damageColor = Color.black;
// break;
// }
var damageIndic = Instantiate(DamageIndicator, transform.position, Quaternion.identity);
damageIndic.GetComponent<RectTransform>().position = transform.position;
var indicator = damageIndic.GetComponent<DamageIndicator>();
indicator.Init(_targetToFollow);
indicator.DamageText.text = "";
if (_isCriticalHit) indicator.DamageText.text = "CRIT!\n";
indicator.DamageText.text += _dmgDealt.ToString("#");
indicator.Animator.SetBool("Left", !(_direction.x > 0));
indicator.DamageText.color = _dmg.IdName == ID.Player ? Color.white : Color.red;
yield return null;
}
private float CalculateDamage(Damage _attack, HitType _hit)
{
//Debug.Log("dmg calc: " + _attack.CurrPhysDmg);
//Debug.Log(" Status.CurrPhysicalRes: " + Status.CurrPhysicalRes);
float resultDamage = 0;
if (Status == null || Status.IsDead || IsInvincible || _hit.HasHit == HitResult.TargetCountered)
{
return 0f;
}
// Debug.Log("_attack.CurrPhysDmg: " + _attack.CurrPhysDmg);
// Debug.Log("_attack.CurrFireDmg: " + _attack.CurrFireDmg);
// Debug.Log("_attack.CurrLightningDmg: " + _attack.CurrLightningDmg);
// Debug.Log("_attack.CurrIceDmg: " + _attack.CurrIceDmg);
// Debug.Log("_attack.CurrEarthDmg: " + _attack.CurrEarthDmg);
// Debug.Log("_attack.CurrMagicDmg: " + _attack.CurrMagicDmg);
var stats = Status.Stats;
resultDamage += _attack.PhysicalDamage.Current * (1 - stats.PhysicalResistance.Current);
resultDamage += _attack.FireDamage.Current * (1 - stats.FireResistance.Current);
resultDamage += _attack.LightningDamage.Current * (1 - stats.LightningResistance.Current);
resultDamage += _attack.IceDamage.Current * (1 - stats.IceResistance.Current);
resultDamage += _attack.EarthDamage.Current * (1 - stats.EarthResistance.Current);
resultDamage += _attack.MagicDamage.Current * (1 - stats.MagicResistance.Current);
float totalDamage =
-resultDamage *
(_hit.IsCritical ? _attack.CritDamageMult.Current : 1f) /* * (_hit.IsBackstab ? 1.2f : 1f)*/;
// if (_hit.HasHit == HitResult.TargetBlocked) totalDamage *= .5f; // only 50 % of dmg.
// Debug.Log("totalDamage: " + totalDamage);
//Debug.Log("totalDmg: " + totalDamage);
// Debug.Break();
if (DEBUG_ONE_HIT_KILL)
totalDamage = -9999999999f;
if (Owner != null)
{
if (Owner.Name == ID.Player && DEBUG_PLAYER_INVINCIBLE) totalDamage = 0;
if (Owner.Name != ID.Player && DEBUG_ENEMIES_INVINCIBLE) totalDamage = 0;
}
return Mathf.RoundToInt(totalDamage);
}
public void ExpulseBehaviour(AdvancedExpulsion _ae)
{
if (Control == null || _ae.Enabled == false)
{
return;
}
if (Owner != null)
{
Owner.FlipMaster.BeforeHitFlipX = Owner.FlipMaster.IsFlippedX;
Owner.FlipMaster.BeforeHitFlipY = Owner.FlipMaster.IsFlippedY;
// Debug.Log("Facing dir before hit: " + Owner.Control.FacingDirection);
// Owner.FlipMaster.BeforeHitFaceDir = Owner.Control.FacingDirection;
Owner.FlipMaster.HitDirX = _ae.GetProcessedDirection().x < 0 ? false : true;
// Owner.FlipMaster.StayFlippedLikeBeforeHit();
}
Control.ExpulsionStart(_ae);
}
public const float PLAYER_PERFECT_BLOCK_TIMING_RANGE = .23f; //.18f
// SHIELDING LOGIC
public HitType GetHitType(float _hitAngle, Hitbox _hitbox)
{
var result = new HitType {IsBackstab = false};
// the angle right now it's like this:
// 90
// +-180 0
// -90
var counterable = _hitbox.Definition.IsCounterable;
// Debug.Log("hit angle: " + _hitAngle);
// Debug.Log("counterable: " + counterable);
// Debug.Log("IsDefending: " + IsDefending);
bool perfectBlock = false;
bool failedBlock = false;
if (IsDefending && counterable)
{
bool damaged = _hitAngle.IsAngleWithinMinMax(DefendAngleMinMax.x, DefendAngleMinMax.y) == false;
if (OmniAngleDefense) damaged = false;
// Debug.Log("damaged: " + damaged);
if (damaged == false)
{
// Debug.Log("Blocked!");
var blockTimedRight = DefendTime < PLAYER_PERFECT_BLOCK_TIMING_RANGE;
// Debug.Log("blockTimedRight: " + blockTimedRight);
// Debug.Log("InvincibleDefense: " + InvincibleDefense);
if (blockTimedRight || InvincibleDefense)
{
// Debug.Log("Perfect block!");
if (OnPerfectBlock != null)
OnPerfectBlock.Invoke(_hitbox);
if (_hitbox.Owner.Hurtbox.OnYouGotCountered != null)
_hitbox.Owner.Hurtbox.OnYouGotCountered.Invoke();
perfectBlock = true;
}
else
{
if (OnBlocked != null)
OnBlocked.Invoke(_hitbox);
failedBlock = true;
}
}
}
if (perfectBlock) result.HasHit = HitResult.TargetCountered;
else if (failedBlock) result.HasHit = HitResult.TargetBlocked;
else result.HasHit = HitResult.TouchesTarget;
if (IsInvincible) result.HasHit = HitResult.TargetInvincible;
result.IsCritical = _hitbox.Damage.TriggerCritChance();
if (this.Owner != null && this.Owner.FlipMaster != null)
{
var flip = this.Owner.FlipMaster;
var looksToTheLeftBackstab = flip.IsFlippedX && _hitbox.transform.position.x > this.transform.position.x;
var looksToTheRightBackstab = !flip.IsFlippedX && _hitbox.transform.position.x < this.transform.position.x;
if (looksToTheLeftBackstab || looksToTheRightBackstab) // looks to the left
{
result.IsBackstab = true;
}
}
return result;
}
// INVINCIBILITY LOGIC
public void StartInvincibilityFor(float _time)
{
//Debug.Log("Starting invincibility for : " + _time + "s");
InvincibilityTime = _time;
}
public void StopInvincibility()
{
InvincibilityTime = 0f;
}
private void InvincibilityUpdate()
{
if (IsInvincible)
{
// Debug.Log("Invincible " + " for: " + InvincibilityTime);
InvincibilityTime -= Time.deltaTime;
if (InvincibilityTime < 0)
{
InvincibilityTime = 0;
}
}
}
/// <summary>
/// Use this when someone died and that he must be reset.
/// </summary>
public void ResetEverything()
{
// Interactible stuff
InvincibilityTime = 0;
HitboxHits.Clear();
// if (DamageFlicker != null)
// DamageFlicker.StopFlicker();
DisabledTime = 0f;
if (Owner.Control != null && Owner.Control.Rb2D != null)
Owner.Control.Rb2D.velocity = Vector2.zero;
if (Status != null)
{
// Status stuff
Status.EnchantmentManager.RemoveAllEnchantments();
Status.RejuvenateAll();
}
}
public struct HitType
{
public HitResult HasHit;
public bool IsBackstab;
public bool IsCritical;
public static HitType Empty()
{
return new HitType() {HasHit = HitResult.TargetBlocked, IsBackstab = false, IsCritical = false};
}
}
public enum HitResult
{
TargetInvincible,
TouchesTarget,
TargetBlocked,
TargetCountered
}
}
public class HitboxHit
{
public HitboxHit(int _id, int _activatedCount, float _timer)
{
Id = _id;
ActivatedCount = _activatedCount;
Timer = _timer;
GameTimeAtCreation = Time.time;
// Debug.Log("HitGameTime: " + GameTimeAtCreation);
}
public int Id;
public int ActivatedCount;
public float Timer;
public float GameTimeAtCreation;
}
Here’s a video to prove that the profiler stops detecting garbage as soon as I comment the function OnGUI() or that I disable the Hurtbox component:
Using OnGUI is the “old way” of creating in-game GUI. The replacement (Canvas-based UI elements) was released in November 2014. All the components supporting OnGUI have been deprecated at this point.
In short, fixes for OnGUI allocating will probably not happen.
Oh I see, the Profiler could be maybe use a little more work anyway on that specific issue: so that people know in which script OnGUI() generates garbage.
Anyway I still find it weird that when there is nothing being done in OnGUI it generates garbage. I think that’s new because I don’t recall having this garbage in previous versions
With “Callstack” enabled you can get the callstack information for the GC.Alloc sample - which should give you information about the script. (Also works in players starting from 2019.3 - see this beta doc).
Just replying back to ask – does anyone know what replaces OnGUI’s Event class in the new UI system? I’m hoping to get event information from the new UI system without OnGUI, which allocates.
A good rule of thumb that works for most things in Unity (at least right now): If it’s in a namespace more specific than UnityEngine or UnityEditor, it’s most likely current. Namespaces like UnityEngine.EventSystems, UnityEngine.UI, etc. Unity didn’t get in the habit of making namespaces for specific systems until 2014-ish, and to my knowledge, so far none of the classes they’ve made since then have become deprecated.
That doesn’t mean that if it doesn’t have a namespace it is outdated (not all pre-2014 code is or ever will be replaced/deprecated), so this only really works for determining which of two alternative systems is the current one.
GUILayouUtility.tBegin is what allocates even with empty on GUI because it re-creates objects every time OnGUI makes a pass. You can re-use cached but this might lead to some issue, albeit I havent encountered any yet.
Problem is, as you fix this part - another one will surface with IMGUI.
I was generating a constant 368B of GC with GUILayoutUtility.Begin and it only went away when I disabled the CinemachineBrain component on my MainCamera.
Not sure if your CinemachineBrain is producing that much (or if you still care about it almost 2 years later) but hope this might point people in the right direction for their project
If you’re using cinemachine you can’t really turn of the cinemachinebrain, but when searching through the code of cinemachine the OnGUI is only called in the editor.
I have never used Cinemachine, but if it really uses OnGUI at runtime, you may be able to set useGUILayout of that MonoBehaviour to false. It disables the layout system for the IMGUI system for this MonoBehaviour. That means no layout events will be generated and all GUILayout controls can not be used. This is also true for GUI.Window calls. Though all other events should still work fine. I haven’t tested it but if they disabled it properly, it should not generate any garbage. Of course if you actually draw things inside OnGUI, most likely there will still be some garbage generated