In Unity C#, I’m trying to make this gun customization system that’s really advanced and unique.
when you attach mods to the gun like suppressors, optics, foregrips, etc. it’ll change the stats for damage, accuracy, recoil, etc. all perfectly fine.
however; I’m trying to add a new feature where you can preview or see the stats of the attachment you’re hovering your mouse over. how I have it setup is, I have a float assigned to a slider value, if a button is pressed to assign the attachment, it’ll equip the attachment, and change the stats accordingly.
the main issue I’m having with this is that when I take my mouse cursor off the button to preview the stats of an attachment, it just gives me a completely wrong value to revert the value of the stats back to what it originally was before previewing the stats of the new attachment.
You will need to show some code (please use code tags on the forums to submit code, no screenshots or simple text), because you clearly are doing something wrong there, but we can’t guess what since we aren’t familiar with your code.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.UIElements;
using UnityEditor.Build.Content;
using static UnityEngine.Rendering.DebugUI;
using Image = UnityEngine.UI.Image;
public class MK18stats : MonoBehaviour
{
//Scripts
public MK18 MainMK18Script;
//Stats
public float DamageValue;
public UnityEngine.UI.Slider DamageStatSlider;
public TextMeshProUGUI DamageStatValueText;
public GameObject DamageSliderBackground;
// Start is called before the first frame update
void Start()
{
//Stats
DamageValue = 22.30f;
DamageStatValueText.text = DamageValue.ToString();
DamageStatSlider.value = (float)DamageValue;
}
// Update is called once per frame
void Update()
{
//Stats
DamageStatValueText.text = DamageValue.ToString();
DamageStatSlider.value = (float)DamageValue;
if (DamageValue < 22.3)
{
DamageSliderBackground.SetActive(true);
}
else
{
DamageSliderBackground.SetActive(false);
}
}
public void AttachSurefireSOCOMRC2stats()
{
DamageValue -= 2.40f;
}
public void AttachAACRANGER5stats()
{
DamageValue -= 2.25f;
}
public void UnequipMuzzleStats()
{
if (MainMK18Script.isSUREFIRESOCOMRC2active)
{
DamageValue += 2.40f;
MainMK18Script.isSUREFIRESOCOMRC2active = false;
}
if (MainMK18Script.isAACRANGER5active)
{
DamageValue += 2.25f;
MainMK18Script.isAACRANGER5active = false;
}
}
}
public class MK18 : MonoBehaviour
{
//Scripts
public MK18stats MK18statsScript;
//Arrays
public GameObject[] suppressors;
public GameObject[] toggles;
//Suppressors
#region
public GameObject AACRANGER5;
public GameObject DEADAIRSIERRA5;
public GameObject ENERGETICARMAMENTARX;
public GameObject GEMTECHHALOGMT;
public GameObject HUXWRXFLOW556K;
public GameObject RUGGEDRAZOR556;
public GameObject SILENCERCOVELOSLBP;
public GameObject SOUNDGUARDSG556;
public GameObject SUREFIRESOCOMRC2;
public GameObject AACRANGER5Toggle;
public GameObject DEADAIRSIERRA5Toggle;
public GameObject ENERGETICARMAMENTARXToggle;
public GameObject GEMTECHHALOGMTToggle;
public GameObject HUXWRXFLOW556KToggle;
public GameObject RUGGEDRAZOR556Toggle;
public GameObject SILENCERCOVELOSLBPToggle;
public GameObject SOUNDGUARDSG556Toggle;
public GameObject SUREFIRESOCOMRC2Toggle;
#endregion
//Booleans
#region
public bool isSUREFIRESOCOMRC2active;
public bool isAACRANGER5active;
#endregion
private void Start()
{
//Script
MK18statsScript = GameObject.FindObjectOfType<MK18stats>();
//Arrays
suppressors = new GameObject[] { AACRANGER5, DEADAIRSIERRA5, ENERGETICARMAMENTARX, GEMTECHHALOGMT,
HUXWRXFLOW556K, RUGGEDRAZOR556, SILENCERCOVELOSLBP, SOUNDGUARDSG556,
SUREFIRESOCOMRC2 };
// Initialize toggle array
toggles = new GameObject[] { AACRANGER5Toggle, DEADAIRSIERRA5Toggle, ENERGETICARMAMENTARXToggle, GEMTECHHALOGMTToggle,
HUXWRXFLOW556KToggle, RUGGEDRAZOR556Toggle, SILENCERCOVELOSLBPToggle, SOUNDGUARDSG556Toggle,
SUREFIRESOCOMRC2Toggle };
}
public void Update()
{
isSUREFIRESOCOMRC2active = SUREFIRESOCOMRC2.activeSelf;
isAACRANGER5active = AACRANGER5.activeSelf;
}
#region
// Method to deactivate all suppressors except the one being activated
void DeactivateAllSuppressorsExcept(GameObject suppressorToActivate)
{
foreach (GameObject suppressor in suppressors)
{
if (suppressor != null && suppressor != suppressorToActivate)
{
suppressor.SetActive(false);
}
}
}
// Method to deactivate all toggles except the one being toggled
void DeactivateAllTogglesExcept(GameObject toggleToActivate)
{
foreach (GameObject toggle in toggles)
{
if (toggle != null && toggle != toggleToActivate)
{
toggle.SetActive(false);
}
}
}
// Method to deactivate all suppressors at once
void DeactivateAllSuppressors()
{
foreach (GameObject suppressor in suppressors)
{
if (suppressor != null)
{
suppressor.SetActive(false);
}
}
}
public void DeactivateAllToggles()
{
foreach (GameObject toggle in toggles)
{
if (toggle != null)
{
toggle.SetActive(false);
}
}
}
#endregion
public void AttachSUREFIRESOCOMRC2()
{
UnequipAttachedMuzzle();
if (!isSUREFIRESOCOMRC2active)
{
if (SUREFIRESOCOMRC2 != null)
{
MK18statsScript.AttachSurefireSOCOMRC2stats();
SUREFIRESOCOMRC2.SetActive(true);
SUREFIRESOCOMRC2Toggle.SetActive(true);
DeactivateAllSuppressorsExcept(SUREFIRESOCOMRC2);
DeactivateAllTogglesExcept(SUREFIRESOCOMRC2Toggle);
}
}
}
public void AttachAACRANGER5()
{
UnequipAttachedMuzzle();
if (!isAACRANGER5active)
{
if (AACRANGER5 != null)
{
MK18statsScript.AttachAACRANGER5stats();
AACRANGER5.SetActive(true);
AACRANGER5Toggle.SetActive(true);
DeactivateAllSuppressorsExcept(AACRANGER5);
DeactivateAllTogglesExcept(AACRANGER5Toggle);
}
}
}
public void UnequipAttachedMuzzle()
{
MK18statsScript.UnequipMuzzleStats();
DeactivateAllSuppressors();
DeactivateAllToggles();
}
}
I don’t see handling any kind of hover here. No cursor of any kind. You said the Equip is ok, the hover and the subsequent leave is problematic, but maybe I misunderstood something?
This is just the script before I tried making the functions. So the goal here is that I make a public void for an event trigger on a button to just alter the stats to as if the attachment is actually attached, and then a void to revert the stats back before. I don’t know if event triggers are even good in the first place but that’s how I do it. So when i tried to do it, hovering my mouse over a button would decrease the damage by 2.4 of course, and off the button would increase by 2.4, but when I have an attachment actually attached, and then I go on to hover the button of another attachment I want to see the stats change of, it properly shows it, and when I take the mouse off it would just give me completely faulty numbers that didn’t even make sense like it would be a dozen or a thousand numbers higher.
the first 30 seconds of this video is a really good example of what I’m looking to make, this guy has it so that when he hovers his mouse over a button, it just changes the stats of the gun so that you get an idea of what the changes will be like. And then he takes his mouse off, it just reverts it back to what it originally was.
It is possible (and often made) mistake to subscribe to an event but never unsubscribe and then just subscribe over and over multiple times (sometimes between runs in the editor) so some calculations can be thrown off with this.
But this is just blind guess, I don’t even understand why you’re lowering the damage stat when you equip an item and adding to it when unequip.
Anyway, I also recommend to refactor this whole thing into data-driven architecture. Make some ScriptabeObjects for some equipment and drive it by its data. There are great tutorials both on Youtube and on the Learn site if you aren’t familiar with the SO-driven architectures. The main point is that you should not have kilometer long exact class member variables and magic numbers in your code.
One thing I noticed, OP, is that you’re using MonoBehaviors for storing state. MonoBehaviors are things that are attached to in-game objects and exist in world space .Weapon Stats certainly do not sound like that.
It would be better idea to make them C# classes or ScriptableObjects. ScriptableObjects are supported by inspector and can be stored as in-project assets.
i would go for Scriptable object for each weapon like so. M16 could have:
int Damage 15;
List Attachments;
then another scriptable objects for Attachments
and it would be calculated like so
Silencer example
DamageMult 0.9;
finally take Damage and loop through attachments * their damage mult.
thats the point, but you would need to calculate it the way you want it. For example multiplicative or additive and so on.
and of course function GetDamage in the Weapon scriptable object so you don’t overwrite all SO weapon data.
(15 + 10 * 0.2) * 1.25 * 1.05 * 0.6848 = 369 ( 368.5 ) ← this kind of craziness in the end, each number representing multiplication to base damage 15. ( got this from totally unrelated thing, but it shows what i mean)
It’s not that difficult with SOs and some basic clean architecture:
public enum AttachmentModifierType
{
Damage,
Range,
Silence
}
// Attachments data SO, 1 per attachment
// It contains a list of "Modifiers" to apply/remove when attached/detached
[CreateAssetMenu())
public class AttachmentDataSO : ScriptableObject
{
public string Name;
public string Description;
public Sprite Icon;
public List<AttachmentModifier> Modifiers;
}
[Serializable]
public class AttachmentModifier
{
public AttachmentModifierType ModifierType;
public float Amount;
}
// Attachment script, attach to each attachment GO
// and set its AttachmentData
public class WeaponAttachment : MonoBehaviour
{
public AttachmentDataSO AttachmentData;
public Transform AttachmentPoint;
private void Awake()
{
if (!AttachmentData)
Debug.LogWarning("This Attachment has no AttachmentData assigned!", this);
if (!AttachmentPoint)
AttachmentPoint = transform;
}
}
// Weapon script
public class Weapon : MonoBehaviour
{
private readonly List<WeaponAttachment> _attachments = new();
private readonly Dictionary<AttachmentModifierType, float> _attachmentModifiers = Enum.GetValues<AttachmentModifierType>().ToDictionary(x => x, x => 0f);
public void AddAttachment(WeaponAttachment attachment)
{
if (_attachments.Contains(attachment))
return;
_attachments.Add(attachment);
attachment.Transform.parent = this;
attachment.Transform.localPosition = attachment.AttachmentPoint.localPosition;
AddAttachmentStats(attachment);
}
public void RemoveAttachment(WeaponAttachment attachment)
{
if (!_attachments.Remove(attachment))
return;
RemoveAttachmentStats(attachment);
Destroy(attachment.gameObject);
}
public void Fire()
{
// Fire your weapon, take into consideration _attachmentModifiers values...
}
private AddAttachmentStats(WeaponAttachment attachment)
{
foreach (var modifier in attachment.AttachmentData.Modifiers)
{
_attachmentModifiers[modifier.StatType] += modifier.Amount;
}
private RemoveAttachmentStats(WeaponAttachment attachment)
{
foreach (var modifier in attachment.AttachmentData.Modifiers)
{
_attachmentModifiers[modifier.StatType] -= modifier.Amount;
}
}