Tactics RPG Jobs

In this lesson we will show how to define a variety of jobs and store their data as a project asset. Although we have done this before (with conversation assets), this time we will be creating prefabs programmatically. By choosing prefabs over scriptable objects, we have the ability to take advantage of components such as the features which we introduced in the previous lesson.

Reference

Although I have looked at several games, I’ve been spending the most time looking at Final Fantasy Tactics Advance (FFTA). You can find plenty of FAQ’s and guides online which provide a great way to understand the overall scope of the game. For example, there are around 100 different jobs specified by FFTA – each providing a variation on gameplay:

  1. Stats (some fixed like movement range, others as growth on level up)
  2. Items (what categories can be equipped)
  3. Abilities (what can be actively used while operating as that job, what can be learned and used even outside the job)
  4. Job Tree (learn enough of one job, and there may be a secondary job which opens up to you)

There is a lot of room for complexity here, but a lot of it is really dependent on your own design. Initially, our job system will be limited to determining the starting stats and growth rates of characters, but it shouldn’t be hard to add Job features to control the categories of equippable items and usable skills in much the same way as we added features to items.

Stats

I still like the idea of being able to change jobs and so I see a great reason to define a lot of different job types. We will begin by creating spreadsheets (.csv) which contain data from which to programmatically create our project assets. Of course it’s up to you to determine how you want to organize your data. Do whatever feels the best to you in order for the data to be easy to view and balance.

Here I have created a simple example with three very generic job-types. I used values somewhere within the ranges you might see from FFTA but made it my own custom list. Ideally you will do the same and flesh out many, many more jobs, rather than cheating by directly copying data from Final Fantasy, tempting though it may be. I am starting with two different spreadsheets. The first I call JobStartingStats.csv which I have placed in the Settings folder.

Name,MHP,MMP,ATK,DEF,MAT,MDF,SPD,MOV,JMP
Warrior,43,5,61,89,11,58,100,4,1
Wizard,30,25,11,58,61,89,98,3,2
Rogue,32,13,51,67,51,67,110,5,3

Note that in this case, MOV and JMP are not merely starting stats. They will actually be implemented as StatModifierFeature components so that changing to or from a job will allow the stats to fluctuate up and down.

Next I created a spreadsheet called JobGrowthStats.csv which I also placed in the Settings folder.

Name,MHP,MMP,ATK,DEF,MAT,MDF,SPD
Warrior,8.4,0.8,8.8,9.2,1.1,7.6,1.1
Wizard,6.6,2.2,1.1,7.6,8.8,9.2,0.8
Rogue,7.6,1.1,5.6,8.8,5.6,8.8,1.8

Here I have used a floating point number for each modified stat. However, it is a special convention I saw while referencing the FAQ’s. The whole number portion of the number is a fixed amount of growth in that stat with every level-up. The fractional portion of the number is a percent chance that an additional bonus point will be awarded.

For example, using the two spreadsheets above you can deduce that a character which begins the game as a Warrior will start with 43 hit points. Upon gaining a level this character’s maximum hit points will grow by a minimum of 8 but there is a 40% chance it could grow by 9.

Job

Now let’s implement the component which holds the data from our spreadsheets, and which listens to level-ups to actually apply the stat growth, etc. Create a new script called Job in the Scripts/View Model Component/Actor folder.

using UnityEngine;
using System.Collections;

public class Job : MonoBehaviour
{
	#region Fields / Properties
	public static readonly StatTypes[] statOrder = new StatTypes[]
	{
		StatTypes.MHP,
		StatTypes.MMP,
		StatTypes.ATK,
		StatTypes.DEF,
		StatTypes.MAT,
		StatTypes.MDF,
		StatTypes.SPD
	};

	public int[] baseStats = new int[ statOrder.Length ];
	public float[] growStats = new float[ statOrder.Length ];
	Stats stats;
	#endregion

	#region MonoBehaviour
	void OnDestroy ()
	{
		this.RemoveObserver(OnLvlChangeNotification, Stats.DidChangeNotification(StatTypes.LVL));
	}
	#endregion

	#region Public
	public void Employ ()
	{
		stats = gameObject.GetComponentInParent<Stats>();
		this.AddObserver(OnLvlChangeNotification, Stats.DidChangeNotification(StatTypes.LVL), stats);

		Feature[] features = GetComponentsInChildren<Feature>();
		for (int i = 0; i < features.Length; ++i)
			features[i].Activate(gameObject);
	}

	public void UnEmploy ()
	{
		Feature[] features = GetComponentsInChildren<Feature>();
		for (int i = 0; i < features.Length; ++i)
			features[i].Deactivate();

		this.RemoveObserver(OnLvlChangeNotification, Stats.DidChangeNotification(StatTypes.LVL), stats);
		stats = null;
	}

	public void LoadDefaultStats ()
	{
		for (int i = 0; i < statOrder.Length; ++i)
		{
			StatTypes type = statOrder[i];
			stats.SetValue(type, baseStats[i], false);
		}

		stats.SetValue(StatTypes.HP, stats[StatTypes.MHP], false);
		stats.SetValue(StatTypes.MP, stats[StatTypes.MMP], false);
	}
	#endregion

	#region Event Handlers
	protected virtual void OnLvlChangeNotification (object sender, object args)
	{
		int oldValue = (int)args;
		int newValue = stats[StatTypes.LVL];

		for (int i = oldValue; i < newValue; ++i)
			LevelUp();
	}
	#endregion

	#region Private
	void LevelUp ()
	{
		for (int i = 0; i < statOrder.Length; ++i)
		{
			StatTypes type = statOrder[i];
			int whole = Mathf.FloorToInt(growStats[i]);
			float fraction = growStats[i] - whole;

			int value = stats[type];
			value += whole;
			if (UnityEngine.Random.value > (1f - fraction))
				value++;

			stats.SetValue(type, value, false);
		}

		stats.SetValue(StatTypes.HP, stats[StatTypes.MHP], false);
		stats.SetValue(StatTypes.MP, stats[StatTypes.MMP], false);
	}
	#endregion
}

First, I declared an array of StatTypes called statOrder – this will serve as a convenience array to help me parse data from the spreadsheets we created earlier. It is static because it wont change from job to job and this way they can all share.

Next I defined two instance arrays, one for holding the starting stat values, and one for holding the grow stat values. I was able to init them with a length equal to the length of the statOrder array from earlier. I might have decided to implement these as a Dictionary, but because Unity doesn’t serialize Dictionaries I decided to keep it as an Array.

There are three public methods. First is Employ which should be called after instantiating a job and attaching it to an actor’s hierarchy. In this method, we get a reference to the actor’s Stats component so that we can listen to targeted level up notifications as well as apply growth to the other stats in response. In addition, this method will allow any job-based feature to become active.

If you want to switch jobs, you should first UnEmploy any currently active Job. This gives the script a chance to deactivate its features and unregister from level up notifications etc.

When creating a unit for the first time, call LoadDefaultstats so that its stats will be initiated to playable values.

Job Parser

Now it’s time to create a script which can parse our spreadsheets and create project assets from them. Create a new script named JobParser in the Editor folder.

using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Collections;

public static class JobParser 
{
	[MenuItem("Pre Production/Parse Jobs")]
	public static void Parse()
	{
		CreateDirectories ();
		ParseStartingStats ();
		ParseGrowthStats ();
		AssetDatabase.SaveAssets();
		AssetDatabase.Refresh();
	}

	static void CreateDirectories ()
	{
		if (!AssetDatabase.IsValidFolder("Assets/Resources/Jobs"))
			AssetDatabase.CreateFolder("Assets/Resources", "Jobs");
	}

	static void ParseStartingStats ()
	{
		string readPath = string.Format("{0}/Settings/JobStartingStats.csv", Application.dataPath);
		string[] readText = File.ReadAllLines(readPath);
		for (int i = 1; i < readText.Length; ++i)
			PartsStartingStats(readText[i]);
	}

	static void PartsStartingStats (string line)
	{
		string[] elements = line.Split(',');
		GameObject obj = GetOrCreate(elements[0]);
		Job job = obj.GetComponent<Job>();
		for (int i = 1; i < Job.statOrder.Length + 1; ++i)
			job.baseStats[i-1] = Convert.ToInt32(elements[i]);

		StatModifierFeature move = GetFeature (obj, StatTypes.MOV);
		move.amount = Convert.ToInt32(elements[8]);

		StatModifierFeature jump = GetFeature (obj, StatTypes.JMP);
		jump.amount = Convert.ToInt32(elements[9]);
	}

	static void ParseGrowthStats ()
	{
		string readPath = string.Format("{0}/Settings/JobGrowthStats.csv", Application.dataPath);
		string[] readText = File.ReadAllLines(readPath);
		for (int i = 1; i < readText.Length; ++i)
			ParseGrowthStats(readText[i]);
	}

	static void ParseGrowthStats (string line)
	{
		string[] elements = line.Split(',');
		GameObject obj = GetOrCreate(elements[0]);
		Job job = obj.GetComponent<Job>();
		for (int i = 1; i < elements.Length; ++i)
			job.growStats[i-1] = Convert.ToSingle(elements[i]);
	}

	static StatModifierFeature GetFeature (GameObject obj, StatTypes type)
	{
		StatModifierFeature[] smf = obj.GetComponents<StatModifierFeature>();
		for (int i = 0; i < smf.Length; ++i)
		{
			if (smf[i].type == type)
				return smf[i];
		}

		StatModifierFeature feature = obj.AddComponent<StatModifierFeature>();
		feature.type = type;
		return feature;
	}

	static GameObject GetOrCreate (string jobName)
	{
		string fullPath = string.Format("Assets/Resources/Jobs/{0}.prefab", jobName);
		GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(fullPath);
		if (obj == null)
			obj = Create(fullPath);
		return obj;
	}

	static GameObject Create (string fullPath)
	{
		GameObject instance = new GameObject ("temp");
		instance.AddComponent<Job>();
		GameObject prefab = PrefabUtility.CreatePrefab( fullPath, instance );
		GameObject.DestroyImmediate(instance);
		return prefab;
	}
}

Because this is a pre-production script, I didn’t put a lot of effort into it. There are hard coded strings, repeated bits of code, etc that could all be cleaned up, but this is not at all re-usable, and doesn’t need to be performant, so I felt no need to waste time on it. As long as it works, I am happy.

In order to make this script work its magic, we added a MenuItem tag. As the name implies, this adds a new entry into Unity’s menu bar. You should see a new entry called “Pre Production” and under that an option called “Parse Jobs”. Select that and our Job assets will be created in the project.

You can easily delete and recreate these assets at any time. Because of this, you might choose to ignore these assets in your source control repository, not that it hurts to keep them. All you truly need to version is the spreadsheet and parser, not the result of using them together.

It is possible to “listen” for changes to your spreadsheets and have the assets re-created automatically. See my post on Bestiary Management and Scriptable Objects for an example of this.

Init Battle State

Now that movement range and jump height stats are able to be driven by a job, let’s change our SpawnTestUnits code to create one of each of the three sample job types. The code to create and configure our units is getting a bit long, and is an indication that we will probably need some sort of factory class soon.

void SpawnTestUnits ()
{
	string[] jobs = new string[]{"Rogue", "Warrior", "Wizard"};
	for (int i = 0; i < jobs.Length; ++i)
	{
		GameObject instance = Instantiate(owner.heroPrefab) as GameObject;

		Stats s = instance.AddComponent<Stats>();
		s[StatTypes.LVL] = 1;

		GameObject jobPrefab = Resources.Load<GameObject>( "Jobs/" + jobs[i] );
		GameObject jobInstance = Instantiate(jobPrefab) as GameObject;
		jobInstance.transform.SetParent(instance.transform);

		Job job = jobInstance.GetComponent<Job>();
		job.Employ();
		job.LoadDefaultStats();

		Point p = new Point((int)levelData.tiles[i].x, (int)levelData.tiles[i].z);

		Unit unit = instance.GetComponent<Unit>();
		unit.Place(board.GetTile(p));
		unit.Match();

		instance.AddComponent<WalkMovement>();

		units.Add(unit);

//		Rank rank = instance.AddComponent<Rank>();
//		rank.Init (10);
	}
}

Movement

We will also need to convert our Movement component into a wrapper much like the Rank component was. For this, add a field to store a reference to the Stats component, and then turn range and jumpHeight into properties as follows:

public int range { get { return stats[StatTypes.MOV]; }}
public int jumpHeight { get { return stats[StatTypes.JMP]; }}
protected Stats stats;

Have the component get its reference to the Stats component in the Start method:

protected virtual void Start ()
{
	stats = GetComponent<Stats>();
}

Demo

Open the main Battle scene and play it. There should be three units as there were before, but we removed the variation on movement types – everyone walks for now. See that the range of the units is different depending on the job they began with.

Switch the inspector to Debug mode so that you can see the private stat data in the Stats component. You should see the values have been set according to the starting stats which we had specified for the job.

Stop the scene and go back to the SpawnTestUnits method of the InitBattleState then uncomment the two lines where we add the Rank component and init the starting level to 10. Play the scene a few times and look at the stats of each hero. You should see slight differences in the stats thanks to the random bonus portion of the Job.

Summary

In this lesson we discussed the various purposes of a Jobs system and looked at references from the Final Fantasy series. Then we began implementing our game via spreadsheets so that it would be easy to see and balance the data. We created an editor script which could then parse our spreadsheets and create prefabs as project assets. Finally, we tied these systems back into the main game so that our demo units have stats (including movement stats) which are driven by their job.

Don’t forget that the project repository is available online here. If you ever have any trouble getting something to compile, or need an asset, feel free to use this resource.

67 thoughts on “Tactics RPG Jobs

  1. A quick question for you: Why, in the updated Movement class, did you put the line where you connect the stats component in the Start method, instead of the awake method, where you have the other connectors (unit and jumper)?

    I tried it with the connector in the Start method, and the stat line was never run, causing an error when range or jumpHeight tried to reference stats- the entire Start method wasn’t run, which is strange to me. Thinking I had mistyped something, I copied the movement class from your repository, and experienced the same error. Putting the connector line in the Awake function fixed the problem.

    1. I figured it out- I had left a blank Start method in WalkMovement that was overriding the Movement.Start method.

      I am still curious about the reasoning for placing it in start vs awake, however ๐Ÿ™‚ Is it to make sure the Stats are loaded before connecting it?

      1. Good question Jordan, I don’t remember any particular reason why I put it in Start instead of Awake. Perhaps just an oversight. Sometimes there are reasons – like if I was manually adding components instead of including them with the prefab, but in this case both Awake and Start would work fine.

  2. Alright, I got prefab generation based on .asset files to work. Basically each prefab has an ‘item’ component attached, but one of the variables in item is ‘sprite’.

    I don’t think there is a way to assign a sprite in the CSV file that generates the .asset, unless I reference the image name/path directly. This seems like bad practice.

    Is there a good method for data-driving images onto a prefab?

    1. I don’t think I would reference a whole path in the csv, but referencing the name of a sprite doesn’t seem bad to me. You can also try naming conventions so that the names of prefabs have similarly named assets which can be loaded, though in many cases the simple rules end up with too many exceptions and you might as well do it all.

      1. Do you think it’s best for me to fill the item data at runtime or to do it in editor similar to the .asset file generation?

        So for example, I have a bunch of prefabs with an ‘Item’ class attached but that class doesn’t have values assigned for its variables yet. Should I assign the variables at runtime only, or would you do it automagically through Editor scripts?

        1. It depends. If you reuse the same prefab with different item data sets then I would do it at runtime. If you always pair exactly the same prefab with exactly the same data, then I would do it at Editor time.

      2. I’ve been looking for an example resource but I’m having trouble finding one. Do you know of any good resources where the name of a file is referenced in an external data file and then that file is linked to the prefab?

  3. Okay i love your tutorial so far, but now i get an error saying “ArgumentException: The thing you want to instantiate is null.” and it brings me to the line GameObject jobInstance = Instantiate(jobPrefab) as GameObject; from SpawnUnits, but i don’t understand what could be wrong here :

    1. Glad you are enjoying it. Did you remember to run the “Pre Production/Parse Jobs” menu command? You won’t be able to instantiate a job prefab if you didn’t create it first.

      1. I wish I could edit my posts here, but the problem was I had misspelled “Rogue” in the stat.csv as “Rouge”. When that was fixed it could find everything fine.

  4. Hey Men

    ive got a confusing error.

    Assets/Scripts/Exceptions/ValueChangeException.cs(39,25): error CS1501: No overload for method `Modify’ takes `1′ arguments

    and also all my skripts wont loading. (Skript can not be loaded) everywhere

    do you know what i could do to solve this mistake ?

    could it be: when i have everywehre ( using system; and using System.Collections.Generic;

    that this send me the errormessage

  5. Hi Jon!
    I just completed this part, and I couldn’t figure out where I could see the stats growth after uncommenting the lines for Rank in the Demo.
    I ended up just doing a debug.log so I could see it. Was I supposed to see those stats somewhere else?

    1. Setting up a simple Debug Log is a perfect way to test for now. The stat growth wouldn’t mean much until you had some other sort of data that persisted outside of a battle, and I only built the battle portion of this project. I felt that the non-battle portions were less complicated, and would also be more varied, so I hoped that my readers would be able to implement it based on their own design.

  6. First of all, I am loving these tutorials. I appreciate how much effort you put into automating processes like putting jobs together. Seems really smart for balancing purposes to quickly change a value in a .csv and then pushing a button in unity to update all the objects.

    I am using this idea and putting it towards creating a skill tree type system but I am running into an issue where the created prefab is being cleared (all values set to NULL) when I push play in Unity. Any idea why this may be happening?

    Thanks again!

    1. I’m glad you’re enjoying them! Regarding your issue, do you experience a similar problem when using the Job prefabs? Do the skill prefabs hold values before you push play? Do they also regain their values after you exit play mode? If so, it might be that something in your code is clearing the values.

      1. Yeah, the skill prefabs are holding their values before pushing play and after pushing play. Upon exiting play the list that the skills are contained in is set to Null along with all the skills in my resource folder. Upon reparsing the values are restored.

        I’m going to make some tweeks to the skill tree and skill classes so that the data is private and see if that works. I’ll let you know how it goes. Thanks.

      2. After playing around with the classes I am still running into the issue where the assest values are set to NULL after exiting play as well as when reopening the project.

        I find this strange because I am not encountering this issue with the job assets created from this tutorial.

        Here is my set up; I have a Skill class which holds several variables. The variables are private and are set through the parsing command. After the Skill is made it is added to the skill tree class. The skill tree class is simply a private list.

        I really wonder what could be causing the values to be set to NULL when exiting play and reopening the project. If you have any idea hit me up. Thanks again for sharing your knowledge!

        1. Perhaps if you could share your project – or at least a simplified version with the Skill class, Skill Tree class, parser and sample data to parse I could recreate what you are seeing and might be able to help you out.

          1. You could zip your project and share with a link via something like drive.google.com or if you want to just copy and paste the relevant code, perhaps you could start a new thread on my forum since it is a bit off topic for this post.

          2. I created a post on the forum and added a zip with the scripts in question. Let me know what you think!

  7. I started your tutorial here and I like what I see so far. I noticed that you mentioned a job tree kind of system. I got excited and tried to skip ahead to find it, but it seems that you did not actually get into it.
    Any chance you will do that at some point? Or give some direction on how to make it with this one? For example, add another experience stat like a job point stat? Keep job levels like fftactics did?

    1. I didn’t actually implement the Job Tree in this project, and only hinted about the possibility. You seem to have picked up on the idea just like I’d hoped. I had been imagining something much like what you have suggested – creating new job related stats such as a string for a job name to use for evolution and an integer for the level required to upgrade. Beyond that you would just need to create a system to either observe the level up notifications or to provide a menu that could control the activation etc.

      1. I see, thank you for confirming my thoughts! I ended up making some extra logic to the onlevelup/changed parts, making a new separate joblevellistener script. Attached to a extra GO in my scene in battles to record. Still debating putting a stat on the unit for it or not, as that would be a lot of extra code. Right now I save it to an external file and read from that in a unit selection stage to change the jobs based on level.

        1. I finally got my system working all the way! I took a closer look at what you had on scriptable objects.
          I started by making a scrIptable object with the name, and a list of required jobs, then a list of required levels.
          I can add any new job, then simply add base requirements of other job levels to unlock it.
          I read all the jobs in the resource folder, then check them for the job subreqs. This way I only show currently useable jobs, or soon to be attained jobs for characters in the job change view. Made a simple overlay that when I finish will display the requirements if not met, or be disabled if the job can be selected already.

          Now I am looking to improve on the abilities system. I hope to make different skills unlock at different levels in the class. Any suggestions on how to do this? I was thinking I could just read the job level, and have a case statement in my character factory. Check the current job level, find the case for that level range, then add a number to the ability file to load maybe. Lvl 5 mage only knows missile attacks. Lvl 10 knows aoe elemental magic as well. Have to ability SO for the unit. What do you think?

          Also am looking at using the same job level to check for a mastered class, and if a class is mastered, be able to equip a second list of abilities. ( No idea what I will do here yet.)

          1. Awesome, congrats! Regarding other tips, I’d suggest opening a new thread in my forums. It’s too much for a nested comment to handle well.

  8. Any reason this wouldn’t work for the LevelUp function?

    void LevelUp ()
    {
    for (int i = 0; i < statOrder.Length; ++i)
    {
    StatTypes type = statOrder[i];
    int growth = Mathf.FloorToInt(growStats[i] + UnityEngine.Random.value);
    int value = stats[type];
    value += growth;
    stats.SetValue(type, value, false);
    }
    stats.SetValue(StatTypes.HP, stats[StatTypes.MHP], false);
    stats.SetValue(StatTypes.MP, stats[StatTypes.MMP], false);
    }

    Basically instead of juggling variables and doing an if statement I just added the number to the stats and truncated it.

    Also I'm still having trouble getting errors at the top of the website. I thought this one was fine, but it's just the home page that isn't having errors.

    Warning: count(): Parameter must be an array or an object that implements Countable in ….. on line 284

    1. It seems like there was a purpose to the fractional part of the stat, but to be perfectly honest, I don’t remember why that code is written that way. I know that I was closely following a mechanics guide and that they did things a little weird due to platform limitations etc. Mostly I did this so I didn’t have to put any effort into designing my own systems and could focus merely on getting something working. I would encourage you to feel free to redesign any or all of the systems as you see fit!

      I’m not sure why you’re getting errors on the website, but I will try to look into it a bit.

  9. Hi, How many elements should I be seeing for the stats (script) and should they be named yet in the debug window? (I think no for the second question, but just need to double check. Across all of the hero(clone)s, I see Element 10 and Element 11 at 0, with Element 12, 13, and 14 representing (presumably): speed, mov, and jump. All the other stats also seem accounted for from Element 0 – 9. Everything other than element 10 and 11 are non-zero values that seem to make sense and match the csv at level 1 at least.

    Should I be seeing those two elements at a value of 0? they seem extra to me, but nothing else is broken. I’m afraid I left some code snippet in or something that I wasn’t supposed to, but I don’t want to start a grand search just yet since everything else is fine, and I plan to move to the next lesson for now.

    1. There are 15 total elements, indexed from 0 to 14. They are not named when viewed through the Debug inspector other than being listed as “Element 0” etc. The elements at 10 and 11 currently hold the value of 50 for each of the heroes. They are configured on the Jobs prefabs via “Stat Modifier Feature” components – one for each with the Type specified as EVD and RES.

      1. Thanks for the quick response! I’m afraid I must still not be understanding something, as I don’t know where the value 50 gets added to this. From your guidance, and a little backtracking, I now get that those elements came from the StatTypes enumeration, but im still unsure where the value of 50 gets defined or set. it looks like I could manually change the stat in the inspector for an instance on the StatModifierFeature component, but I’m trying to figure out where in the code the value for those two stats of the generated prefab is produced. (The two attributes are not in the CSV’s either from what I am seeing.) I also don’t think I see EVD or RES mentioned in this lesson, so I’m unsure where to look right now.

        1. No, I think you are on top of things. It has been a long time since I made this, so I had to look a little deeper. If I delete the job prefab objects and recreate them in the repository project then they will end up with 0 for those two stats. I probably created the jobs, then added the stats manually (which in the long run is not an ideal way to do it). Good catch!

          1. Ah thanks again for the response and explanation! Everything has been really helpful for me on getting a grasp of how to create my own projects.

  10. So apprantly you cant add components to prefabs anymore… YOu have to do load prefab contents or something. Nobody ran into this problem?

    1. You can still add components to prefabs, they just modified how you view them in the interface. After selecting a prefab click the “Open Prefab” button in the inspector to view its first level contents like normal.

      1. Oh I meant on the code. In the JobParcer script. CreatePrefab I think is the one that is obsolete.

          1. I saw a forum it was like saveassettprefab and loadasassetprefab. I don’t know I couldn’t figure it out. What I have been trying to do is just create the jobs as scriptable objects but I am having trouble getting it to work. Your coding skills are WAY more advanced than mine! I am having trouble learning. Is the whole liquid fire you? I have tried to back up and get better. I am thinking about the CCG tutorial. Any suggestions as to where to start on your website? I read through all of the beginner stuff, I understand most of it.

          2. Yep, pretty much everything on this site is mine (I did borrow simple scripts in some places and give credit then). As far as where to start, that’s a good question. Generally I would have suggested the Tactics RPG because the architectural choices I made (which rely heavily on Unity) are very simple for beginners to understand. It is pretty old by now though and it’s not surprising that some of the content is now obsolete. The Unofficial Pokemon Board Game project was also pretty simple as I made it with my son to help him learn. I can’t comment on whether it will have the same issues.

            Each of my tutorial projects use different IDE’s, languages, and even different architectural styles, so you may want to favor whichever topic is most important or interesting to you. Unless you truly grasp all of the material you may find it difficult to apply concepts between the projects because of those differences.

          3. Hello. While going through this excellent tutorial I’ve run into this same issue. I made a few changes to the JobParser script to work around that. I can share my version of the script with you if you like.

          4. For some reason I was unable to reply to the poster below, but would love to see your script @matteo how you changed the job parser.

            Perhaps if you have it in google drive or such, but I would like to request you give a link to the methods needed perhaps first. Would be more beneficial to try and learn my way to a useable example and then compare with what you came up with after.

  11. for anyone that’s getting this error:

    Assertion failed on expression: ‘!go.TestHideFlag(Object::kNotEditable)’
    UnityEngine.GameObject:AddComponent()

    I have made minor alterations to the job parser script. TLF’s approach was deprecated which is causing the issue, you can read more here: https://forum.unity.com/threads/bug-problem-with-indirectly-adding-components.544168/.

    Script changes the PartsStartingStats function to look like this:
    static void PartsStartingStats (string line) {
    string[] elements = line.Split (‘,’);
    string path = “Assets/Resources/Jobs/” + elements[0].ToString () + “.prefab”;
    GameObject obj = null;
    if (File.Exists (path)) {
    obj = PrefabUtility.LoadPrefabContents (path);
    } else {
    Debug.Log (string.Format (“load prefab contents did not run. checked location {0}”, path));
    obj = GetOrCreate (elements[0]);
    }

    Job job = obj.GetComponent ();
    for (int i = 1; i < Job.statOrder.Length + 1; ++i)
    job.baseStats[i – 1] = Convert.ToInt32 (elements[i]);
    StatModifierFeature move = GetFeature (obj, StatTypes.MOV);
    move.amount = Convert.ToInt32 (elements[8]);
    StatModifierFeature jump = GetFeature (obj, StatTypes.JMP);
    jump.amount = Convert.ToInt32 (elements[9]);
    }

    1. Made some more changes to the script so it was stable:

      static void PartsStartingStats (string line) {
      string[] elements = line.Split (‘,’);
      string path = “Assets/Resources/Jobs/” + elements[0].ToString () + “.prefab”;

      GameObject obj = null;
      if (File.Exists (path)) {
      obj = PrefabUtility.LoadPrefabContents (path);
      } else {
      obj = PrefabUtility.SaveAsPrefabAsset (new GameObject (), path);
      obj = PrefabUtility.LoadPrefabContents (path);
      }

      Job job = obj.GetComponent () ? obj.GetComponent () : obj.AddComponent ();
      Debug.Log (string.Format (“object is: {0}. job is {1}”, obj, job));

      for (int i = 1; i < Job.statOrder.Length + 1; ++i)
      job.baseStats[i – 1] = Convert.ToInt32 (elements[i]);
      StatModifierFeature move = GetFeature (obj, StatTypes.MOV);
      move.amount = Convert.ToInt32 (elements[8]);
      StatModifierFeature jump = GetFeature (obj, StatTypes.JMP);
      jump.amount = Convert.ToInt32 (elements[9]);

      PrefabUtility.UnloadPrefabContents (obj);
      }

      static void ParseGrowthStats (string line) {
      string[] elements = line.Split (',');
      string path = "Assets/Resources/Jobs/" + elements[0].ToString () + ".prefab";
      GameObject obj = PrefabUtility.LoadPrefabContents (path);
      Job job = obj.GetComponent () ? obj.GetComponent () : obj.AddComponent ();
      for (int i = 1; i < elements.Length; ++i)
      job.growStats[i – 1] = Convert.ToSingle (elements[i]);
      }

      cheers

  12. First off: thanks for putting in all the effort with such a cohesive and easy to understand tutorial series!

    As someone coming from more simple scripting in GMS 2.0, I started this series when I swapped to Unity, to help build out a concept for a squad tactics game I’ve always wanted to make. It wouldn’t be an exaggeration to say that I’m learning at least one new concept about programming an entry. Keep up the good work!

    Second: are there any architectural changes you would make to this setup if rather than a Final Fantasy style job system, you were modeling something more like a MOBA or the XCOM games, where character and abilities are intrinsic to one another?

    Currently I’ve adapted the job components from this tutorial to hold character-specific stats, but before I take it further and start modeling abilities I’m wondering if there isn’t a better method or pattern I’m overlooking.

    My specific case is that I’d like each character to have a generic basic attack derived from an equipped weapon, but other than that, a unique name, art, stats, and a pool of abilities, giving each different tactical value in different situations.

    1. Glad you’re enjoying the series. It may be a surprise, but I haven’t played XCOM or similar MOBA games, so I am not familiar with the mechanics you want. I imagine that I would almost certainly make large architectural changes that were specific to the game I wanted to make. I rarely reuse game specific code, and will usually only reuse things like my notification or animation libraries. You will see that is the case if you follow along with my other projects – they all feel very different from each other. If you need specific help, feel free to post in my forums and I’ll do my best.

      1. Ah, I see. I’ll definitely check out the other projects to see how you approached their design challenges once I’m done reading through this one.

        As for characters, I’m finding the notification center, exceptions, and component-based design are already excellent patterns for the kind of mechanics I’m looking for.

        The only considerable difference between FFT and what I have in mind is that characters have a single unique “job”. That is, they each have a predetermined set of abilities they can unlock. Because of the narrower scope, these abilities can be crafted to synergize tightly with one another, using special resources, applying unique buffs or debuffs, etc. Exceptions seem like a perfect tool for this kind of design.

        Anyhow, If I run into any difficulties I’ll be sure to drop by and post!

  13. This might seem like a simple question, but currently I am trying to add a new stat (CRIT) into the current list of stats, however the program refuses to comply, throwing this error as a result:

    IndexOutOfRangeException: Array index is out of range.
    Job.LoadDefaultStats () (at Assets/Scripts/View Model Component/Actor/Job.cs:58)

    FYI, the only pieces of code that I changed are adding the CRIT stat into:
    1. StatTypes script
    2. the statorder array in Job script
    3. JobStartingStats with the necessary values

    HOWEVER, I found out that the program runs normally if one of the current existing stat types in the statorder array is replaced by my new CRIT stat.

    Any ideas why this is happening?

    1. First let me make sure you understand the error. Let’s say you have an array of integers named “foo” that looks like this: ‘[4, 2, 7]’. The Length of the array is ‘3’ because there are three integers. The index values of each are { 0, 1, 2 }, and I refer to elements in the array by that index, so if I say ‘foo[2]’ I would get the last number in the array which is ‘7’. If I say ‘foo[3]’ I would get an IndexOutOfRangeException because there are no values at that index – we have tried to get a value outside the range of the array.

      What that means is that the array that is referenced by the Job script on line 58 is probably smaller than you think it is and you need to understand why. It sounds like you’ve already added the necessary values to most of the necessary places, but don’t forget ‘JobGrowthStats’ and then make sure you don’t forget to run the ‘Pre Production -> Parse Jobs’ menu action as well.

        1. if you don’t mind me asking how did you get the Create function to spit out prefabs properly, I am currently attempting things but I have now officially hit a road block

          1. Can you be a bit more specific? Are you able to create prefabs, but there is something wrong with them? Or are you unable to create prefabs at all? Are you simply following along with the lesson and something isn’t working, or are you trying to modify the project? When you try to create them, do you get any errors printed in the console?

  14. Hi, I’ve run into an issue:

    “No overloard for method ‘LoadAssetAtPath’ takes ‘1’ arguments”

    static GameObject GetOrCreate(string jobName)
    {
    string fullPath = string.Format(“Assets/Resources/Jobs/{0}.prefab”, jobName);
    GameObject obj = AssetDatabase.LoadAssetAtPath(fullPath);
    if (obj == null)
    obj = Create(fullPath);
    return obj;
    }

    Looking at the definition of the LoadAssetAtPath method it takes a second parameter of ‘Type type’ but I’ve got no idea what that would be.

    Version of Unity is 5.0.0f4 Personal.

    Thanks for your help.

    1. Wow, that’s an old version of Unity. I guess you went back that far cause that’s how old the tutorial was when I started writing it! I dont know if you are aware, but you can change the version of Unity’s documentation. Though unfortunately even that only goes back to Unity version 5.2.

      The non-generic version of LoadAssetAtPath would need you to give type information. In this case, the type of object we are trying to load is a GameObject. So, the line might look like one of these for the second parameter:
      GameObject obj = AssetDatabase.LoadAssetAtPath(fullPath, GameObject);
      GameObject obj = AssetDatabase.LoadAssetAtPath(fullPath, typeof(GameObject));

      And may also need either a prefix or post fix cast like one of these:
      GameObject obj = (GameObject)AssetDatabase.LoadAssetAtPath(fullPath, typeof(GameObject));
      GameObject obj = AssetDatabase.LoadAssetAtPath(fullPath, typeof(GameObject)) as GameObject;

      Hope that helps!

      1. Thanks for the response. I’d actually found a workaround using Resources.Load, so
        GameObject obj = Resources.Load(fullPath, typeof(GameObject)) as GameObject;

        but it looks like
        GameObject obj = (GameObject)AssetDatabase.LoadAssetAtPath(fullPath, typeof(GameObject));
        works as well, so I’ll go with that.

        Thanks for your help.

        Oh and yeah I’ve gotten stuck with tutorials before due to version changes, so I figured I’d go with the same version of Unity you did the tutorial in.

  15. Hi. I’m not sure if this has happened before and has already been answered, but I’ll ask anyway. Whenever I play the level, the board along with a single hero unit spawn, but I am unable to move the tile selector in the game and get the error: The object you want to instantiate is null. I may have missed something though I am unsure what it is.

    Thank you for the tutorial so far it has been extremely helpful. ๐Ÿ™‚

    1. Sorry for the delay Joshua, I was enjoying a little vacation. For this particular lesson, some have forgotten to run the โ€œPre Production/Parse Jobsโ€ menu command. If you can share which script is causing the error and at what line, I would have a better idea. It may be that you have forgotten to assign a reference in the inspector for one of the scripts.

  16. Yes, that is correct. I somehow forgot to assign the ability menu controller to the battle controller in the inspector, so that issue is fixed now.

Leave a Reply to admin Cancel reply

Your email address will not be published. Required fields are marked *