I am trying to make a method that gets the last item in this list. This is how I tried to do it.
public class AmmoMagazine : ScriptableObject
{
public List<AmmoCaliber> CompatibleAmmoTypes = new List<AmmoCaliber>(1);
public List<AmmoData> LoadedAmmoInMag = new List<AmmoData>(30);
public AmmoData GetNextRoundInMag()
{
Debug.Log(LoadedAmmoInMag.Count);
AmmoData NextRoundInMag = LoadedAmmoInMag[LoadedAmmoInMag.Count-1];
LoadedAmmoInMag.RemoveAt(LoadedAmmoInMag.Count);
return NextRoundInMag;
}
}
and the error is the very first (not debug.log) line of GetNextRoundInMag, when i call it for the first time. . I think that theres 2 possibilities, one is that im misremembering how indexing list elements work, the other is im not understanding something about ScriptableObjects. If its possibility #1… what am I doing wrong? If its possibility #2, i might have an idea of whats happening but dont really know how to prove it or what to do about it, but I might know a workaround, which is to not use a scriptable object to keep track of the mag, instead having it be monobehavior attached to the firearm, for reasons i understand but cant really explain, id rather use scriptableobjects for mags, and id like to know whats happening before I do that. When I test the script i see that the debug shows that i have 30 elements in the mag, so i dont know why 30-1 would be out of range of a mag that can hold 30 rounds.
I mean if the collection is empty your code will throw an error. So your code should guard against an empty collection.
And even if it wasn’t empty, line 10 would also throw an error as collection indexes start at zero. So List<T>.Count
isn’t a valid index on its own. Count - 1
is the last element, of you can use the newer [^1]
syntax.
Considering a magazine might be empty too, a TryGet pattern method might be a good option here, too.
public AmmoData GetNextRoundInMag()
{
if (LoadedAmmoInMag.Count == 0)
{
return null;
}
AmmoData NextRoundInMag = LoadedAmmoInMag[^1]; // special syntax for last element in a collection
LoadedAmmoInMag.RemoveAt(LoadedAmmoInMag.Count - 1);
return NextRoundInMag;
}
public bool TryGetNextRoundInMag(out AmmoData ammoData)
{
ammoData = GetNextRoundInMag();
return ammoData != null;
}
// usage
if (magazine.TryGetNextRoundInMag(out AmmoData ammoData) == true)
{
// shooty things
}
1 Like
I made stuff on the firearm to handle a null round in the magazine (and by extension an null round in the chamber of the weapon), which is to not fire and print to the debug log that theres no round in the chamber.
Basically the way i did it was to have an AmmoData for the round in the chamber, as i fire take the top round from the magazine and assign it to the round in the chamber, remove the round from the mag, and then check if the round in the chamber is null before firing the weapon each time. Itll be null if the magazine is empty.
That being said, the only lines of code i really needed to make it work properly, was lines 3 - 6, added to the top :
if(LoadedAmmoInMag.Count == 0)
{
return null;
}
I was about to say that i dont understand why things didnt work without this line of code, but it clicked and i get it now, and figured out what was happening, might be obvious to you but I was able to put what you said into words (in my mind) that i understand.
Turns out, it technically doesn’t need that line until the very last round, but the error was happening on the very first round because everytime i edit the script for it it, the scriptable object asset for the mag was settings its list length to 0, meaning the list was completely empty with 0 length. Adding that line of code is just goober protection. So i made sure to add that to the check.
if(LoadedAmmoInMag.Count == 0)
{
Debug.Log("Magazine Asset Capacity is 0: Dev is a goober");
return null;
}
Soooo, thanks!
Yes you’re experiencing the trait of scriptable objects - and all assets outside of scenes for that matter - where changes to them persist between play mode sessions. If you end a play mode session with an empty magazine, it will remain empty into the next session.
I wouldn’t be using scriptable objects for magazines. They are probably better off represented as regular C# classes, and potentially serializable ones so that they can be edited in the inspector still. This will also be important for save games, as Unity objects or references to Unity objects can’t be written and read to disk.
1 Like
I definitely do want to be able to edit them in the inspector, just for testing purposes for now. Once i have the mechanics for mag repacking/reloading finished I dont think theyll need to be serializable anymore but for now i need to be able to see whats going on inside. I wanted to use scriptableobjects because i had made (followed a tutorial for) an inventory system that used scriptableobjects. Thats unfortunately the only way i know how to make an inventory system, which i was probably going to have a more primitive version of for this project, so i wanted to plan ahead.
that being said, if the mags arent going to be scriptable objects, do they have to be monobehavior attached to the firearm or some other gameobject? If they arent monobehavior where would i edit them? Ive only ever worked with things that inherit (might not be the right word) from these 2.
You can mark a class or struct as [System.Serializable]
to tell Unity to serialise it, for editing in the inspector: Unity - Scripting API: Serializable
And at the end of the day, games objects and components (including monobehaviours) are what make things happen in scenes. But you can still use regular C# classes within your components to encapsulate data, to be used and passed around in various ways.
Hell a lot of my current projects are mostly regular C# stuff, with monobehaviours and scriptable objects just acting as entry points to big lumps of data. But I wouldn’t worry about doing anything like that just yet.
This topic has been very educational, but its opened up so many more questions.
Things like how scriptableobjects should be used (they seem kinda useless after the whole “unity objects and references canot be written and read to disk”), how im going to store information of these mags when they are in an inventory or just otherwise not attached to a firearm, how im gonna make the inventory system without relying on scriptableobjects.
I feel like these questions might be outside the scope of this topic and im not sure i want to keep asking stuff that arent related to the initial problem. So im not really asking them, but man. I learn so much but i still have so much more to learn. brain hurty ):
Well I think the best way to answer that is that scriptable objects are best used for immutable data (as in data that doesn’t change).
So for an inventory system, you can still use scriptable objects for the core, unchanging data for items. Mutable (changing) changing data can then be represented with plain C# objects that you generate on the fly. As we can’t write references to scriptable objects to disk, the solution is often to swap out the direct references a unique identifier for the item.
There are a few ways we can do this:
- Convert the non-serialisable data into something serialisable and back again
- Use a serialiser that lets us do this conversion as the data is being serialised, and vice versa
- Use indirect references to these objects; ergo, a wrapper object that stores the object unique ID, and internally looks up the item when needed
Roughly ordered in easiest to most complicated to implement, but also least to most convenient.
I will say, you are throwing yourself into the deep end here; inventory management stuff immediately hits the intermediate to advanced category of things. Don’t be discouraged if you find it difficult, because it is. But also don’t be afraid to post more threads, but also do spend the time to research these things on your own as well. A lot of this information is already out there.
1 Like