November 2019 W1: Advanced Crafting Pass (but not really)
Nov 4-8 Week 1 Update - Not what it was supposed to be
Unfortunately, this will be the first week that I've not managed to hit my feature targets. At the end of last week's blog post I had stated that this week was going to be an advanced pass over the crafting system, and while you could stretch the meaning to make that true, the work I accomplished this week was not at all what I set out to do.
Be warned there will be very few screenshots and no gifs this week, because frankly I have absolutely nothing but code to show, though I do have an interesting experience to share, especially for anyone focusing or learning C#.
What was supposed to be achieved
This week, to finish off the skills from last week I had hoped to fully implement an item subtype feature. That is, based on the values of modules the user puts into a crafted item, it's assigned a discrete subtype. For example, were the player to create a 'Melee' type weapon with a 1m handle/grip module, and a 20cm blade, it would automatically be classified as a 'polearm'.
This functionality is important to the proper functioning of the skills system, as the skills directly tie into the various weapon and armour subtypes, so without the subtypes there is no linking skills to items.
Honestly, I thought this task would be fairly simple, and the task itself is. Unfortunately, the backend behind item classification that I wrote several weeks/months ago was more rigid than I expected, and proved mentally difficult to keep track of when writing in new content such as modules. This system itself is also intrinsically linked to both the crafting and equipment systems, meaning there was a little bit of a necessary code webbing occurring.
Thus, almost nothing of the original goal was achieved this week.
What actually was achieved
It's not all doom and gloom though, far from it! This week has been both an amazing opportunity to refactor the older parts of the game's code and one to learn. My knowledge has increased substantially more this week than most weeks, and I was ecstatic to realise that when reflecting on my work.
Almost all of the work done this week went towards refactoring the item classifications, class hierarchies, module classifications, the way crafting interacts with the classifications, and the way equipment does. Overall, it was a lot of work. Approximately half the week was spent porting the old, rather poor flag based system over to a full class hierarchy, where every item type has a class rather than a dynamically set type in a single class.
The refactor has neatened the code substantially, and made it far more readable and workable.
System.Attribute and Enums
I want to go a little more technically in depth in this blog specifically, because I learnt how to leverage C#'s custom attributes and enums to create really neat, low maintenance, low memory type linking. Essentially what I set out to achieve while building the system that now incorporates these was to take an arbitrary enum, parse it somehow and return with a type that can be instantiated.
This is particularly useful for items and modules, as I can pass an enum straight from the GUI (For example, ItemTypes.Firearm) to the crafting class, which simply pulls the enum's assigned type and instantiates it. With a few type checks, we can keep it relatively safe and reliable.
The Basics
This part is particularly for those who are interested, but not necessarily versed in programming, OOP, or C#. I'll explain relatively succinctly, but hopefully clearly, what the two words above mean, as well as what the problem was.
There are three relevant terms to break down (Two above, and another for the standard solution I would have used prior):
Enum - An enum is essentially a collection of strings and attached ints. They start at 0, and count up by one for every value. The 'string' is actually a member of the enum itself, and is used to reference the int. It makes a lot more sense when it's laid out in an example:
In this example, Value 1 = 0, Tree2 = 1, ArbitraryName3 = 2, and Horses4 = 3. Each is accessed via the following: EnumName.ValueName. For example, ThisIsAnEnum.Tree2 will return the values of Tree2 (Being the enum itself, Tree2, and its int: 1).
This is incredibly useful for two reasons: The first is that ItemTypes.Firearm is far more human readable than "13". The second is that it stops silly errors arising from things like accidentally writing "31" instead of 13.
Attribute - In C#, an attribute is essentially like a tag attached to another object. The .Net standard attributes include things like [Serializable], which does just about what it says on the tin. It allows the tagged class to be serialised. They're used by simply writing the attribute in square brackets ([]) above the object you want to assign it to.
In this case, the Value1 has the attribute "Type", with a type of Weapon, while Horses4 has the same attribute with the type of Armour.
Dictionary - A dictionary is pretty simple. It works like a normal language dictionary's Word/Definition layout. You look up a word, and get a definition. In C# they store two generic types (They can be anything), and contain a key (Word), and a value (Definition). By looking for the key in the dictionary, you can find the value.
The 'standard' solution
I'll point out first that is is the solution I used as standard, and may not be by any means a standard industry convention. Originally I would create an Enum and a Dictionary<Enum, Class> as a way of referencing objects.
So if I needed to find the class of ItemTypes.Firearm, I'd simply look up ClassDict[ItemTypes.Firearm] and get out a class of the type I needed, then I could perform a deep copy of the class and use that as the new item.
This has two downsides: the first, and one which annoyed me the most, was that you now have two distinct places where you now need to write new modules in when you add one. Instead of simply adding a new ItemTypes.NewItem, you need the enum and a dictionary entry for it.
The Attribute solution
I essentially spoiled this solution above, you assign attributes to a set of enums. The attribute assigned should hold a variable of type System.Type, which means it'll take the typeof() any object you throw into it, in this case classes.
With this, we assign the Types of the classes we want to the enums, and then when our relevant functions receive a call with an attached enum, they can grab from the enum's attribute, and use either Activator.CreateInstance(Type) (The .Net library method), ScriptableObject.CreateInstance(Type) (The Unity Scriptable Object method), or finally a GameObject.AddComponent(Type) call, which handles instantiation for Monobehaviour classes.
Through this, I pioneered into System.Attribute usage, and learnt a couple of things which should help anyone looking to use attributes for whatever reason:
Attributes will cause stack overflow errors if their value is set via property (Ie. var x { get; set;}). - I have absolutely no clue why this is the case, nor could I find anything on Google. However, my attribute continually crashed every time it was accessed via property. I tried it with different types, with and without explicit/implicit casting. Nada.
Attribute constructors are called every time the attribute is accessed; the attribute values are not cached. - Unlike most other objects within C#, which have their values and contents cached after instantiation, attributes are created fresh every single time you attempt to access them. This is not documented anywhere, as far as I can see. This is a double edged sword, if you call it too much, it'll probably be quite slow, but if you're only accessing it every now and again, you save that little bit of memory that caching would require.
Moving forward
Next week, I'll be hopefully continuing and quickly finishing the subtypes system, and then move on to the medical/health system. If I can squeeze the full medical system into whatever remains of the week, I would be ecstatic, however the current plan is just to continue a week 'behind', as the deadlines are relatively arbitrary.
Comments
Post a Comment