RPG Ability Crafting System?

So right now I’m working on a turn based RPG, in the style of the late 90s, where they would often decouple the ability system from the experience system, rather than keep them together like they did in the early-mid 90s (ie gaining abilities when you leveled up). I have most of my ideas planned out for how the ability system should work, I’m just not 100% sure how to execute some aspects of the idea.

In general, the idea is instead of the characters learning abilities, they would learn concepts, like wizards might learn elements while knights might learn sword stances. These concepts could then be mixed together on the abilities screen, for each of their ability slots, and if the concepts are compatible, an ability will result.

The way I imagine it is sorta like how Little Alchemy works, where the combination of the concepts would be commutative, like traditional addition, where, for example, if you wanted to make a mud spell, you could combine either water and earth, or earth and water, and both would yield the same result.

I assumed there would be tons of resources online for making a crafting style system, this being a post-minecraft world, but most of the stuff I’ve found online has been the more lazy crafting systems of the world, where you just have the already finished recipes, if you have the resources in your inventory, it just makes the thing. This sort of thing is useless to me, since my system is more abstract than that. I found myself unsure of what even to search for.

So far, I’ve made a bunch of abilities, in the form of Scriptable Objects with everything they need to work baked into them already. The way I figured it would work is I would need some sort of recipe class that would know the concepts required to yield a resulting ability. Each character would have an array of recipes for every ability they can possibly learn, and a list of concepts they’ve learned so far that they would have access to on the abilities screen.

I guess for me the magical black box, the missing link between these two pieces, and the part I can’t quite figure out, is the part that looks at the combinations of concepts and finds the ability in the recipe array, the part that can look at the combination that currently exists for a particular ability slot, regardless of order, and find the correct ability. Basically being able to perform a weird form of addition on things that aren’t numbers.

What are some ways I can go about doing this?

Do i understand this correctly, that the main problem is figuring out a way to look up the crafting result (if one exists), given the inputs? That should be doable pretty efficiently with MultiHashMaps, or whatever the C# version of that is called. You’d basically fill it by using one of the components as key, and assigning its values that contain the other crafting components and results (so basically the recipes). In pseudo-code you’d add your recipes like so:

Recipe r = new Recipe (Air, Mist)
map.Add(Water, r)

Since the recipe already belongs to a component (its key), we only need to save the other crafting components and the result in the recipe. When your user inputs two components, for example Water and Fire, you then use “Water” as a key to access your map and look for a recipe which combines it with fire, to find the result: steam. If “Water” has no values attached, look for Fire.
If a recipe exists, check if the character is able to use it, or do whatever else you want with it.
That way you only have to iterate over all recipes containing a specific component, instead of all recipes. If all your recipes contain exactly two components, you may want to write your own 2Key per 1Value HashMap, which would enable O(1) lookup times for recipes, using the two components as key and directly getting the resulting item back.

You’d still have to manually make up recipes and add them to the hashmap, i’m not sure if that’s something you wanted to automate or anything. I may have missed your point if that’s the case.

So there’s two basic pieces to this.

First, you need a way to look at a single recipe and decide whether it matches or does not match what the player has done.

In order to do this, you’re going to need to be mathematically rigorous about what it means to “match”. It sounds like you’re headed in the direction of set equality (the user must select every ingredient in the recipe and NONE of the ingredients that are NOT in the recipe, but order doesn’t matter). But think it through and make sure you’ve clarified the rules in your own mind. Earth+water and water+earth both make mud; do earth+water+air also make mud? How about earth+water+mud?

Once you understand your rules, write some code for:

  • a data structure that contains all the information needed to fully describe a single recipe
  • a data structure that contains all the information needed to fully describe what the player tried
  • a function that tests whether (1) and (2) match

If you’re using set equality as described above, that might look something like this:

public struct Recipe
{
    public HashSet<Concept> ingredients;
    public Ability result;
}

// If the entire contents of a craft attempt is just a single HashSet,
// you could just use HashSets directly instead of defining a struct.
// But the struct might be helpful if you think you might add more
// parameters later (e.g. if the phase of the moon affects the result)
public struct CraftAttempt
{
    public HashSet<Concept> selectedConcepts;
}


public bool CraftMatchesRecipe(CraftAttempt craft, Recipe recipe)
{
    return craft.selectedConcepts.SetEquals(recipe.ingredients);
}

Then the second piece is that you need to search for a matching recipe out of all possible recipes. The simplest possible way to do this is to create a collection of all recipes (e.g. a List), and iterate over that entire collection testing each recipe one at a time until you find a match. List even has a function specifically for doing that, called Find().

List<Recipe> allRecipes;

Recipe matchingRecipe = allRecipes.Find((recipe) => CraftMatchesRecipe(craft, recipe));

If the number of recipes isn’t ridiculously huge and you don’t do this search more than once a second, this is probably a fine option, so you could just stop with that if you want.

But this means you might need to check every single recipe in the universe to find a match. There are probably a lot of ways you could optimize the search.

One way would be to use a Dictionary that has CraftAttempts as keys. You’d need to write some additional functions to be able to use CraftAttempts as keys in a dictionary (IIRC you’d need to write an equality function and a hash function–it should be easy to find tutorials online of what you’d need). Also, this only works as long as you are testing CraftAttempts for exact equality. If you wanted to use different crafting rules–for instance, if including “extra” unneeded ingredients does not spoil the recipe–then using a Dictionary might be a bad idea.

Another way would be to put your recipes into some sort of numerical order in a way where you can compare a CraftAttempt to a Recipe and know whether you should look higher or lower in the recipe book to find the right recipe (if it exists). This would allow you to do a binary search instead of a linear search to find the right recipe.

You could also just break your recipes down into categories so that you can quickly jump to the right “chapter” of the recipe book based only on looking at the craft attempt. For instance, you could organize recipes based on the number of ingredients, and then if the craft attempt uses 3 ingredients you know you only need to look at the recipes that also use 3 ingredients. There are lots of potential ways to do this depending on the details of your system.

But again, just searching the entire recipe book one recipe at a time is probably a totally workable plan.

Yeah, basically what you said is what I want to do, looking up if a combination has a result, and if so, loading the character’s ability slot with the resulting ability.

So I’m not sure I fully understand what you mean by the multihashmap thing. The explanation feels like it goes a little over my head, I’ve never heard of this and sorta have no idea where I would even start.

And I ultimately plan on having more than 2 components for some of these abilities, some having 3 or 4. I eventually wanna build on this system to make concepts you can add onto abilities to modify their properties in some way in addition to the core concepts which are used to make the ability, but thats down the line, I want to get the core system working first before getting more advanced.

I like the idea of the structure here, knowing what pieces I might need.

While I do want the core match to be exact, I do eventually intend to add concepts to the game that are like modifiers which slightly alter the resulting ability, but I feel like those wouldn’t really be factored into the actual matching process, so I’m not sure I would need to worry about those in this process, but I’m not totally sure. I guess I should just get the core system working first before getting fancy though.

I like this chapter idea, searching only recipes that match the number of concepts used in the current combination.

Theres a lot to go through here. I guess part of what I was wondering was what goes where. I guess this covers some of it though. But I’m still not sure I fully understand it all

I’ll try and break it down for you, to get a simple overview of the basic components involved.
The bare minimum of what you need to look at should be:

  • A way to define and save recipes
  • A way to store all defined recipes
  • A way to compare equality of some user input and some recipe
  • A way to figure out if the user inputs (components) fit one of your recipes or not

As you said yourself, for 1) you would create a “Recipe” class, that stores the components and the result. For 2) you could add a method to your Recipe class that checks whether the user inputs are the same as its components. For 3) you could store all your recipes in an array / list. For 4) you could iteratre over your list of recipes and compare the user inputs with each recipe using the method defined in 3).

That’s about it what you need to do, conceptually. However, there are other things you need to think about, or more efficient approaches to storing and comparing the data - which is what Antistone and I focussed on in our answers. Seemingly that was a bit much, so with the above as simple example in mind, let’s discuss some problems and solutions from scratch:

First and foremost, as Antistone said, you need to be sure what your game / comparison rules actually are supposed to be, before thinking about 3). These game / comparison rules may influence the way you save your data in 1).
For example, since you said that order of your inputs is irrelevant, it could make sense saving them in a Set, instead of a list. In case you are unfamiliar, a Set is basically an unordered List that does not allow duplicates. Arrays / Lists, Maps, Sets and Trees are probably (more or less in that order) the most common datastructures you’ll come across, so it’s always good taking a look at them at least once imho.
Anyways, if other than the order the input recipes must be identical, then your comparison method (3) may be as simple as recipeComponents.SetEquals(userInputs), in Antistones’ example, which returns true if the recipes match, or false if they dont.

When you plan on having LOTS of recipes (4) comparing the user inputs to them can take quite a while. Iterating over a list of n-many items, takes O(n) time, which basically means it scales linearly with the amount of elements. That’s why i suggested storing the recipes in a key/value relation in a HashMap (called Dictionary in C#), which would get you all recipes involving, for example, Water in O(1), meaning constant, time. This would heavily reduce the amount of items you need to iterate over. AntiStone made a similar suggestion in his post further down.
However, in hindsight i’d agree with Antistone in that this wont become a problem unless you plan on having thousands of recipes. And even then, you could compare this super efficiently / fast with the new Burst Jobs system (if performance ever becomes a problem) since it’s a very easily parallelized task. So i wouldnt focus on optimizations like these before they become an actual problem.

I hope this helps in better understanding the previous posts. Since i focussed mainly on efficiently storing and lookup of the recipes, i’d suggest following Antistones’ post for implementation details.

OK, so that did answer some of my questions. I wasn’t quite sure what should go where, like if I wanted all of these methods and stuff in the menu, on the character, in the concepts/recipes, or in their own class entirely.

So with these Sets, can they be used in the inspector like arrays and lists? My plan was to add the recipe book as it were to the character class and just drop everything in via the inspector, to make putting all of this together as easy as possible. One of my goals with setting this up is to make it as easy to work with as humanly possible, so down the line I’m not stressing myself out if I have to make any tweaks or modifications to the system later.

That being said, is there any reason not to make the recipes into scriptable objects? I did that with the abilities because previously I was using CSV files to load in these really cumbersome abilities, that had a ton of really hardcoded stuff in the battle system to make them work. Tweaking the abilities in the inspector with nice sliders and such makes life so much easier. My goal is to just use the ability as a blueprint, and instantiate a copy of it into the ability slot on the character when a valid combination is made. Would it make sense to make the recipes themselves scriptable objects too, so I can just assemble them ahead of time, and just drop them in on my characters? Or will some aspect of how scriptable objects work mess that up? I had some issues before with these scriptable abilities before, because of the way they are saved and such.

Looking at how I have it all designed right now, I’m not expecting characters to have more than 20-30 recipes a piece, I’m assuming that should be pretty easy? Any optimization is good though, so if I can figure out how to work some of that efficient stuff in now, it would save me time fixing it later on if it does become a problem.

I assume concepts should just be a name, icon, and whatever I need on them mechanically? (I intend for the player to level up the concepts, and having to learn them over time, meaning not all of them will be accessible to the player initially, but I suspect in terms of mixing, this won’t matter a whole lot). I’m not sure if I need anything on the concepts to make them comparable, or if that is all just handled by the method I’m supposed to put in the recipe itself. Though I guess if I intend to have those modifier concepts, I might need some sort of way to tell the system to ignore the modifier concepts in the check? Or maybe just handle those as an entirely separate thing? something you add once the ability is already created?

The way I understand it so far, is that the recipe will need a list of concepts necessary to make it, and the resulting ability, and a method to handle the check against what the player currently has in a given ability slot. On the ability slot itself in the menu, it will have to have some sort of way of making that check, like I guess when a new concept is added, it check to see if theres an ability that matches the current combination of concepts. I suspect for this reason I might need to make some sort of “ability slot” class that is sorta like a recipe, but it’s concept list starts empty, and whenever a concept is added, it uses one of those things @Antistone mentioned, to make like a craft check, to make that comparison, and add the ability to the slot if a match exists. We can just assume that the match between the core concepts (ignoring any sort of modifiers) has to be perfect, because it seems like that will be easier.

I dont think Sets have a default inspector representation, but i never tested (and cant right now). If they dont, you could always write your own custom editor to show them, or simply use a list instead. Wouldnt change the approach that much.
When you say you plan on adding the recipe book to your character, do you mean the list of all recipes, or the list of the recipes learned by the player? The latter makes sense, since that is different for each character, but list of all recipes should rather be stored in a static or singleton manager class that’s accessible from everywhere.

I cant really help you decide for or against ScriptableObjects as i have little experience with them. However, to my knowledge scriptable objects are generally used more as an optimization than anything else, so it shouldnt make or break a deal if used correctly. Somebody else can probably give better advice on this.

As a rule of thumb i wouldnt optimize stuff unless you profiled a performance problem, or know ahead of time that it will definitely become a problem in the future. Iterating 50 or so items in a list takes literally less time than the blink of an eye, so i wouldnt worry about it too much. Get your system to work, and if you want to optimize it, or see a performance problem after some time, then implement optimizations.

Concepts would be your components, or the “Water, Earth, …” equivalent in Little Alchemy, right? If so, then how you define them is more of an implementation detail / subjective preference and depends on how you intend to handle them in other systems. You could make them as lightweight as you want, up to the point where you create an enum called Concepts and put your concept types into it. Your character would, in this example, keep track of the level of each learned concept that he is able to use. Nothing speaks against making the concepts themselves contain more data either, as this all comes down to how you intend to use them down the road. Just implement it in a way that makes sense now. Adjusting it to work better with systems you implement later shouldnt be a huge deal, and you will know when you need to change them.

As for the modifiers, i dont think it makes sense to call them Concepts as well. To my current understanding they are something different, so they also should have a different type. Do they also have a visual representation and can get used in crating recipes? If so it may make sense to have two crafting systems. You’d determine which one to use based on whether or not the user inputs contain a modifier, or only concepts. I’d asume anything other than one concept plus one modifier would not be a valid recipe anyways?
Make sure to make up your mind for the rules involved here, as that determined the way you implement it. As always, there is tons of different ways to implement anything. And just like above it’s fine to start with something that makes sense now and adjust it to fit systems you add later. Unless you plan on planning out every little detail of your project before writing the first line of code, this is bound to happen anyways. And if that’s actually something you want to do, i’d suggest using UML-diagrams to plan out all your class relations and so on.

However, sometimes it’s just a good idea to start programming. You already have a decent idea of what it is you want to do. You often only realise after starting that some things are easier than you thought, and others dont work the way you waned them to. But it’s really easy to overthink things and procrastinate actually writing code too hehe. When you start writing code that also makes it easier for us to help you, since we could focus on fixing specific problems, or suggesting how to change existing structures to fit some other system you plan on adding.
That said, i hope this helps. And in case you jump into it: good luck and have fun :slight_smile:

OK, so I spent the last few days writing some of the pieces and planning some things, and I ran into something that could potentially be a problem, maybe?

The way I wrote the Concept class includes experience and a description and stuff. I don’t think the recipes really need this part, so with the recipes, what I thought I could do is instead just give them a list of strings that is just the names of the concepts required.

Will this work? will it be possible to check just the name of the concept against the strings in the recipe?

That’s the thing when you put a lot of data into a class that’s not always required. You need this data when you are talking about a concept known to the player (so he can gain experience on that), but not when you are talking about a concept as the recipe knows it. It’s basically two different types, so i’d split it into two as well.

As i said, the concept itself may be something like an enum entry, so concepts are fully defined by Concept.Water or Concept.Fire - sort of like what you planned on doing with strings. In order to store additional information like experience, i’d wrap it into some other type, like PersonalConcept, which would contain a concept, experience, … and so on and belongs to a player. When the player inputs one of his known concepts, you obviously only care about the concept part of it. Does that make sense?
You could also make Concept a class, but make PersonalConcept inherit from Concept. That way you can use it both ways; like a Concept or a PersonalConcept, depending on which you need. Either way i’d still save your “names” in an enum, since working with strings for this seems like a hassle. If you need some code examples of what i mean feel free to ask.

Generally it’s possible to compare two things based on any criteria you want if you’re willing to write your own comparison code, but you’d probably be better off either:

  • Identify your Concepts based on an index number (optionally with an enum to give that index number a friendly name) so that you only need to pass around and compare that index number. If you need details on a particular Concept (e.g. its name), you can look it up in an array of objects based on that index number.
  • If you never need to serialize your references to Concepts, you can just pass around handles to the full objects. Classes are a reference type in C#, so passing them around and comparing them is easy and efficient as long as you’re only copying and comparing references rather than the entire content of the object.

The reason the enum thing might not work is that each character has their own unique set of concepts, and each one has between 10 and 30 concepts, which I feel like would make that messy really quickly.

The concepts track their own growth, because eventually I would like to have the level of the ability being generated be dependent on the level of the concepts used. Each time the ability is used in battle, every concept involved in it’s creation will gain experience.

So I tried doing the setEquals thing, and it seems like Lists maybe can’t do that? Is list.Equals the same thing? The answers online are conflicting and confusing.

When you say that players have Concepts that each contain 10-30 Concepts, do you mean Recipes which contain other Concepts or are you talking about something different? If we are talking about Recipes, then a hierarchy based approach could work. Example:

    public enum ConceptNames
    {
        WATER,
        FIRE,
        STEAM,
        DENSESTEAM,
        // Whatever concept names you have
    }

    public abstract class AbstractConcept
    {
        public ConceptNames concept;
        internal int level = 0;

        public AbstractConcept(ConceptNames concept)
        {
            this.concept = concept;
        }

        public abstract void LevelUp();
    }

    public class BasicConcept : AbstractConcept
    {
        public BasicConcept(ConceptNames concept) : base(concept)
        {
        }

        public override void LevelUp()
        {
            // Some example code
            level++;
        }
    }

    public class Recipe : AbstractConcept // <-- Recipes now are also concepts!
    {
        // Containing an array of concepts, which can also be recipes again!
        // I used an array for this example, but it can also be a list, set, .. with slight adjustments
        AbstractConcept[] concepts;

        public Recipe(ConceptNames concept) : base(concept)
        {
            concepts = new AbstractConcept[4]; // you said something about 3-4. can be a list for dynamic size
        }

        public override void LevelUp()
        {
            level++; // dont forget to level up this concept too
            for (int i = 0; i < concepts.Length; i++)
            { // level up all components we used
                if (concepts[i] != null)
                {
                    // Here we call LevelUp() on each concept, which may be a basic concept
                    // or a Recipe itself, in which case it recursively calls LevelUp() on
                    // all of its concepts and so on!
                    concepts[i].LevelUp();
                }
            }
        }
    }

For convenience we save our concept names in an enum, instead of working with strings.
We have an abstract class AbstractConcept, which contains the basic data structure for what a concept is.
An abstract class can not be instantiated and is only supposed to be inherited by other classes.
We have two classes that inherit the abstract class: BasicConcent and Recipe.
This means that Recipe is not actually a concept as well, which makes sense the way you intend to use it.
A BasicConcept is a “level 0” concept that required no components to craft. Example: Water, Fire, …
A Recipe is a higher level concept that required some components to craft. Example: Steam.

A Recipe contains a list of AbstractConcept references it can be crafted by. Since a Recipe itself is also an AbstractConcept, it can contain references to other Recipes as Components. Example, Steam and Steam making DenseSteam or something (i’m really bad with these examples lol).
When you use a spell (or in my horrible example: DenseSteam) you can level that spell up by calling LevelUp. If it’s a BasicConcept it will just level itself up. If it’s a Recipe, it will level up itself and all components it is made of, and the components its components are made of.

Now, this has a hidden “feature” that may as well be a bug, depending on how you want to handle the leveling of components. When you call LevelUp on DenseSteam, then it will call LevelUp on both Steam components, thus it will basically level up Steam twice, and Fire and Water twice. This can be intended, since that’s actually how often you’d use these basic components, but depending on how you want to handle it, you may not want that.
If you dont want that you could make LevelUp take a random ID as parameter, make each concept save the last random ID it was accessed by, and only level up if the new ID is different from the last. That way Steam, Water and Fire should only level up once when calling LevelUp on DenseSteam.

Somewhere you’d save a list of AbstractConcepts to which you add all BasicConcepts and Recipes that exist in your game. Each character needs to get its own (deep-)copy of this list in order to have different concept levels per character.

Would this solve your problems? Did i miss anything?

As for your other question: SetEquals can be used on Sets. The Equals on Lists does the same, however “equality” for lists takes element order into consideration, which is something you do not want.
According to this post: https://stackoverflow.com/questions/22173762/check-if-two-lists-are-equal
You should be able to use: var a = list1.All(list2.Contains);

Hope this helps!

Edit: Rereading your comment, i’m not sure which way around you meant the level thing.
In my example, all component concepts are levelled up when a higher-level concept is used that uses them. I believe you also want the level of the higher-level concept to be determined by the sum of levels of all of its components? In that case, simply return the level when calling LevelUp() and add them up in the Recipe-version of LevelUp to get the sum of all involved levels. The same rules apply. However, going with this approach higher-level concepts should not increment their level when calling LevelUp, since the level is getting calculated by the sum of levels of its components. Otherwise this may result in unintended behavior when you have recipes of recipes of basicconcepts (calling LevelUp on DenseSteam).

I’m saying each character has 10-30 concepts a piece, which would mean each character would probably need their own Enum? Which just feels like it would get messy fast.

Each character has their own unique concepts and abilities, it’s not a shared list of skills between all characters.

I think there might be some confusion about how I intend for the levels to work. I have ScriptableAbility for each level of an ability, so like Fireball Lv 1, Fireball Lv 2, and so on. Those are all in the recipe right now. What I intend to do is check the level of each Concept involved in the creation of the ability, and use the level of the lowest level concept involved as the level of the ability, and grab the corresponding ability from the recipe. This part I have MOSTLY figured out, at least theoretically. I made an “Ability Slot” class that can handle some of the interactions between the concepts and abilities, it’s basically the crafting table for the most part. I’m gonna have a method in there that will just iterate though the concepts and give them a point of experience whenever an ability in that slot is used.

Theres also a piece here for learning the concepts from special equipment, but for now I’m just worrying about getting the core system working at all.

The only part left thats really throwing me off is the comparing the 2 lists. I just tried list.All, and apparently that isn’t a thing either? Is there something I need to do to make that work?

Edit: Also, your example does remind me of one of the rules I want to make sure is enforced, at least at some point, and that is no duplicate concepts in an ability, and no duplicate abilities. But I think for now, lets worry about getting the crafting working at all first.

Ok so you meant that there are 10-30 specific concepts per character class, like swordstances vs spells?

The enum would contain all concepts, no matter which character they belong to.
Think of it as a list keeping track of “this exists at all”. I’d keep track of which concepts and characters belong together, by adding a list of compatible characters (also being an enum) to AbstractConcept. That way, when you create a character and give him his own copy of the available concepts, simply only give them concepts that are compatible with his class. That way you would only give Water to the wizard, and StanceX to the warrior. However, it would also allow you to have a shared pool between classes in case you ever want to do that.

Done the same way i incremented all involved concept levels recursively in the example above. For what you wrote, instead of incrementing them you’d want to calculate the Min() of them.

You are right, .All is a function from IEnumerable, not List. So either use that, or… what speaks against using Set? It’s basically a list without order, and a reasonably efficient SetEquals method that takes care of all your problems. Is the representation in the inspector that important? You can write your own too if you have to.

SetEquals is a method of HashSet; you can’t call it on Lists. (You can pass any IEnumerable, including a List, as the argument; but the first object needs to be a HashSet.)

List.Equals is just the inherited Object.Equals, which for classes will test reference equality (i.e. not whether two lists have the same content, but whether they are two references to one single object). But even if there were a list equality method, that wouldn’t do what you want, because lists care about the order of elements, not just what elements are present.

You can either copy the contents of your lists into HashSets and then use SetEquals, or you can write your own function to test two lists for set equality. (If you are using a non-standard concept of “equality” in order to compare Concepts, then you might want to write your own function anyway.)

Testing two Lists for set equality is reasonably straightforward, though if there’s a possibility your lists contain duplicate entries then it gets a little bit tricky.

There is absolutely nothing wrong with having hundreds of entries in a single enum if that turns out to be your most convenient route. I don’t see any advantages to having hundreds of magical strings instead.

If each player-character in the game has their own personal version of a Concept that includes their level of expertise (and possibly other data), then obviously you’ll need to store some per-character data on that Concept, rather than just having a single universal list.

However, if your recipes only care about the “kind” of concept and not individual character data like level, then you still need some way of encoding that particular piece of information. An enum is likely the best way to do that.

You could have a separate enum for each type of character, if the concepts and recipes are completely non-overlapping between characters (in which case you will also need a separate recipe type for each character, though you can probably use generics to make that easy). But you could also use a single enum for ALL concepts in the game, and that’s totally fine, too. Even if it’s a really large number of concepts.

However, some part of your code will probably need to know which concepts are available to which characters, so you’ll probably need to make class-specific lists somewhere. The main advantage of using a single enum for all concepts would be that you could have some concepts that are shared between multiple character types, if you wanted.

So just an update on this, I’ve spent the last few days mocking up a UI to test all of this in, I wrote out a thing I think MIGHT work, based on both of your suggestions, along with some of my own ideas, but I can’t test it until this is complete. Once thats done, I’ll report back on whether it worked or not, with any questions I might have based on the outcome.

Your help up to this point has been instrumental though, so thanks. I will return with the results

1 Like

So the latest on this situation.

In the process of setting up my UI, I’ve run into some issues, and I have yet to even fully test the actual mixing part, though that is the piece I’m currently working on setting up, the code in the UI that lets me do just that.

Attached is an image of what I have so far in the UI. It successfully pulls all the necessary information for the concepts a character has into the scroll area on the left without issue (right now it just displays them all, rather than it being dependent on equipment in some way, a problem I’ll want to address after I have the core system working).

The goal is to be able to click the icon for the concept on the left, then click a slot in the window on the right and place it there. When the concept gets placed, it should perform the mix check, then if that results in an ability, the abilities name and stats should show up in the relevant textboxes.

Right now I’ve got a coroutine that starts when you click the button for the icon in the concepts list that walks thought the steps one at a time, waiting for input from the player.

But right now I’m getting really weird Null Reference Exceptions that are sorta baffling me. The first one happens when I run the method I use to update the ability screen. I know up to a certain point the code is working perfectly, since the mini character pod and concepts window are working perfectly fine. It’s when I get to drawing the ability slots that the first one goes

//Draw Ability Window
        for (int j = 0; j < toDraw.abilitySlots.Length; j++) {
            if (toDraw.abilitySlots [j].slot != null) {
                //Draw Created Ability Parameters
                AbilitySlotNames [j].text = toDraw.abilitySlots[j].slot.name;
                AbilitySlotPowers [j].text = "Power: " + toDraw.abilitySlots[j].slot.GetPower();
                AbilitySlotForces [j].text = "Force: " + toDraw.abilitySlots[j].slot.GetForce();
                AbilitySlotPreps [j].text = "Prep Time: " + toDraw.abilitySlots[j].slot.GetPrepTime() + "s";
                AbilitySlotCasts [j].text = "Cast Time: " + toDraw.abilitySlots[j].slot.GetCastTime() + "s";
                AbilitySlotCooldowns [j].text = "Cooldown: " + toDraw.abilitySlots[j].slot.GetCooldown() + "s";
                AbilitySlotStamina [j].value = 1f - (toDraw.abilitySlots[j].slot.GetStaminaCost() / 100);
                AbilitySlotDamageType [j].gameObject.SetActive (true);
                AbilitySlotTargeting [j].gameObject.SetActive (true);

            } else {
                //default state
                AbilitySlotNames [j].text = "Empty";
                AbilitySlotPowers [j].text = "Power: 0";
                AbilitySlotForces [j].text = "Force: 0";
                AbilitySlotPreps [j].text = "Prep Time: 0s";
                AbilitySlotCasts [j].text = "Cast Time: 0s";
                AbilitySlotCooldowns [j].text = "Cooldown: 0s";
                AbilitySlotStamina [j].value = 1f;
                AbilitySlotDamageType [j].gameObject.SetActive (false);
                AbilitySlotTargeting [j].gameObject.SetActive (false);

            }
            for (int k = 0; k < 4; k++) {
                //Draw Concept Slot Contents
                Vector2 iterator = new Vector2((float)j, (float)k);
                if (GetConceptSlotButton(iterator) != null) {
                    GetConceptSlotButton(iterator).image.enabled = true;
                    GetConceptSlotButton(iterator).image.sprite = toDraw.abilitySlots [j].GetConcept(k).Icon;
                } else {
                    GetConceptSlotButton(iterator).image.enabled = false;
                    GetConceptSlotButton(iterator).image.sprite = null;
                }
            }
        }

This line in particular…

if (toDraw.abilitySlots [j].slot != null) {

Is the line that it says is throwing the error.

The other issue is happening in my previously mentioned coroutine, when I try to click the button hidden in the ability slot, where the actual adding to the ability slot is supposed to happen.

public IEnumerator ConceptPlacingAndMixing()
    {
        Character toDraw = MCP.instance.GetCharacterFromParty(primaryCharacterSelection);
        for (int i = 0; i < mainMenuButtons.Length; i++) {
            //loop though buttons to hide concept buttons
            ConceptIcons [i].interactable = false;
        }
        subMode = 1;
        while (subMode != 0) {
            //At this point, the concept to be placed has already been selected, it's just a matter of placing it,
            //then attempting to mix the contents of the slots, to see if an ability results.
            Debug.Log("Starting Concept Placement Process");
            if (subMode == 1) {
                //a nested loop to activate all of the concept slot buttons
                for (int j = 0; j < 6; j++) {
                    for (int k = 0; k < 5; k++) {
                        Vector2 iterator = new Vector2((float)j, (float)k);
                        GetConceptSlotButton(iterator).interactable = true;
                        GetConceptSlotButton(iterator).image.enabled = true;
                    }
                }
                Debug.Log ("Successfully Set all concept buttons to be interactive");
                ChangeInformation ("Select a Concept slot to place the selected concept in");
                conceptLocation = -Vector2.one;
                while (conceptLocation == -Vector2.one && subMode == 1) {
                    yield return null;
                }
                if (conceptLocation != -Vector2.one) {
                    subMode = 2;
                }
                Debug.Log ("Successfully Selected a concept location");
                continue;
            }
            if (subMode == 2) {
                //set the concept into the slot, then perform a Mix check, to see if an ability
                //should also be added.
                Debug.Log ("Now Placing concept, Attempting to place in slot #" + (int)conceptLocation.x);
                toDraw.abilitySlots[(int)conceptLocation.x].AddConcept(toDraw.GetConceptToDraw(secondaryCharacterSelection));
                GetConceptSlotButton(conceptLocation).image.sprite = toDraw.GetConceptToDraw (secondaryCharacterSelection).Icon;
                toDraw.MixAttempt ((int)conceptLocation.x);
                UpdateSingleAbilitySlot ((int)conceptLocation.x);
                subMode = 0;
            }
        }
        //Return all buttons to normal.
        for (int i = 0; i < mainMenuButtons.Length; i++) {
            //loop though buttons to hide concept buttons
            ConceptIcons [i].interactable = true;
        }
        for (int j = 0; j < 5; j++) {
            for (int k = 0; k < 4; k++) {
                Vector2 iterator = new Vector2((float)j, (float)k);
                GetConceptSlotButton(iterator).interactable = false;
            }
        }
        yield return null;
    }

In this instance, the line…

toDraw.abilitySlots[(int)conceptLocation.x].AddConcept(toDraw.GetConceptToDraw(secondaryCharacterSelection));

…is the one throwing the error, the line literally adding the concept to the slot.

In both cases, I’m not sure what even could be having a problem, I’m pretty sure everything exists, because it’s all visible in the inspector, and you can see my debug.logs where I tested the ability slot and it always wrote something out that I was expecting to see. The common denominator seems to be the actual ability slot on the ability slot class, but I have no idea why this would be a problem.

If that line is throwing an error, then either “toDraw” itself, or “abilitySlots[j]” is null. Make sure you instantiated all of them. You can try to find out more about the problem by printing some Debug.Log()s. This often helps when it’s unclear why something is null.

That line is too long to say something conclusive, so break it into multiple smaller parts that you can check for null more easily. Judging from what appears to be the first problem, i’d bet my money on some index of the abilitySlots array being null tho.

At some point you are instantiating every abilitySlot, right? If so, afterwards iterate over it and print something if it’s null at some index. If one is null, then figure out which one, and from there why. If not, then it may become null at some later point, for some reason. Or it may be something else entirely.
But, while i’m guilty of doing the same, as a general piece of advice: prevent long lines of code. It’s more readable and faster to debug when you separate them into smaller steps. Shouldnt make a difference for performance either.

Hope this helps.

Well, I know ToDraw works, because I use it in the previous part of the drawing method, and it works for everything else. And given that the ability slot is the common denominator, it’s probably that.

Usually when I have a null problem involving stuff like arrays and lists, it’s because I didn’t set them up in the inspector correctly (ie setting their size and such). Isn’t that essentially initializing it? In this case, I did do this with the AbilitySlots, but theres still some sort of problem. I can see all the slots in the inspector.

What really baffles me about this is that first if statement is explicitly looking to see if abilityslot is null or not, why would just checking if the slot is null throw an exception like that?