Character generator program

Good day!

I, a total scripting newbie, will continue to bother you about simple scripting problems, so why not open a thread where I can ask my questions, as they might become plentiful in the process of making a character generator program for pen&paper. I’m not a native speaker, so please excuse my bad English. I’m working in C#. however, as of now I spend most of the time watching tutorials and googleing stuff rather than “working”.

Right now I’m working with input fields in the character generator menu. There are multiple input fields where a player can type in a certain value for “strength”, “intelligence” and so on. The input is already restricted to integer, but I’d like to go further and restrict it to certain numbers (min value - max value). The min and max value should be variables as they can be changed by aquiring certain perks. If the input is above or below the max or min values, the integer could be set back to a standard value (which is 8) or it could show an error message or whatever. Any ideas how to do that? I was thinking about having all that stuff in a single script.

How are you restricting the input to integers? It should be pretty easy to show you how to add it there, as it’s natural to put all the input validation at the same spot.

The InputField has an option for “integer numbers” only, so that’s that, as well as a character limit. Now I want to restrict it to certain integers. The standard area would be “8, 9, 10, 11, 12, 13, 14”, but a certain perk (which allows you to push an attribute beyond 14 at character generation) could set it to “8, 9, 10, 11, 12, 13, 14”.

/ I guess I could do it with a lot of if functions (there are 8 attributes, meaning 16 if functions and 16 min/max variables), but maybe there is a more elegant way? Can I make the InputField only accept certain numbers somehow?

Right then. The easiest thing to do is to have a script hook into the input field’s onValueChanged. Whenever you get an input, parse it as int, and set it back when it’s in the correct range:

public class StatInputValidator : MonoBehaviour {

    public InputField inputField; //assign the Input field here. 
    public int minValue;
    public int maxValue;

    private void Start() {
        //You could also just assign the method in the inspector.
        inputField.onValueChanged.AddListener(ValidataStatSelection);
    }

    public void ValidateStatSelection(string statString) {
        int statValue = int.Parse(statString);
        if(statValue < minValue) {
            inputField.text = minValue.ToString();
        }
        else if(statValue > maxValue) {
            inputField.text = maxValue.ToString();
        }
    }
}

That should work. Warning - I just wrote this straight in the browser, so there might be errors!

Note that this isn’t perfect. In particular, the user will be allowed to write a wrong value, and the value will then be changed back on the next frame, which looks weird. It’s also possible that the user could cheat the system by managing to exit the character create menu before the onValueChanged runs somehow.

You could implement onValidateInput instead of onValueChanged. onValidateInput runs (I believe) before the input shows up, and allows you to change the character the user inputs with something else. It’s a bit harder to work with, though it’ll give better results.

You could also just do this like it’s done in the old D&D games. Instead of having an input field, have an up and down arrow. That’s probably a lot easier, looks better, and feels nicer for the user as they can’t try to input invalid values.

1 Like

Hmm yeah, first of all, thanks for the code. Before I’ll try it out I’m gonna give the button option a try. Earlier today I had no idea how I could implement it that way, but now, after some hours of throwing code lines around, I’m a little more confident.

Alright, so I managed to set up some buttons and put up an update function to check if the value is between the min and max values which avtivates/deactivates the buttons properly. Thing is, the buttons seem to manipulate a different variable. He’s the code:

    public void MUAdd () {
        MUCurrent = MUCurrent + 1f;
        MUInputField.text = MUCurrent.ToString ();
    }

    public void MUReduce () {
        MUCurrent = MUCurrent - 1f;
        MUInputField.text = MUCurrent.ToString ();
    }

MUCurrent is the var which is changed by the + button (MUAdd) or the minus button (MUReduce). The standard value is 8. MUMin is 8 and MUMax is 14. Now this is where it’s going crazy:
If I press +, output becomes 9. If I then press -, output becomes 7. If I then press +, output becomes 10. If I then press -, output becomes 6 and so on. What am I doing wrong?

/ Also, setting up 16 public void functions for each button is going to cause a lot of chaos. Isn’t there a way to put it all in one function and make every button execute a different line in that function?
Sorry if I’m not clear enough, don’t know if I’m using the correct terms.

First of all, MUCurrent should not be a float. Not that it matters (probably), but you should be working with integer stats here.

Secondly, that should work, if both the up and down button is hooked up to the same script. It sounds like each button has it’s own version of the script - that would explain why you’ve got two different MUCurrent variables.

The idea here is not to have 16 different functions. You have two functions in the script - one for moving the value up, and one for moving it down. Then you add one version of the script to each status field, and hook the plus button up to that script’s plus method and the minus button up to the same script’s minus method.

The script should not need to know what stat it is assigned to.

It has to be a float because of some calculations done with it. When it was an integer, there were problems with rounding.
I attached the same script to the buttons’ onClick functions as well as to the InputField. I set up a function for add and one for reduce. Could you give me some example code for what those functions should look like?

/ Wow, I thinks I got it. Thanks so much! As soon as I’m done I’ll post the new code here.

Just as an option. Wouldn’t it be easier to change the inputField into a slider? That way the user can never change the value between the min or max value.

1 Like

Alright, I’ve tested the system and tried some things in the code. Nearly everything is working fine. Nearly. I think I’ve managed to creat my first “bug”! :3
This is the code for the input validation in update ():

        if (StatValue <= MinValue) {
            StatMinus.interactable = false;
        }
        else if ((StatValue >= MaxValue) || (EigenschaftenCurrent <= 0)) {
            StatPlus.interactable = false;
        }
        else if ((StatValue <= MinValue) && (EigenschaftenCurrent <= 0)) {
            StatMinus.interactable = false;
            StatPlus.interactable = false;
        }
        else {
            StatMinus.interactable = true;
            StatPlus.interactable = true;
        }

The first if statement disables the - button if the stat equals MinValue.
The second if statement disables the + button if the stat equals MaxValue OR the user has no more points left to spend.
The third statement SHOULD disable both buttons if the stat equals MinValue AND the user has no more points left to spend. This is what ain’t working. Looks like this:

Any idea what might be the problem?

@Timelog : That’s a neat idea, but I already managed the button thing and I think it looks awesome! What do you think?

It looks pretty neat. Though the arrows look rather plain against the rest of the font :open_mouth:

Yup. This here is the problem:

else if ((StatValue >= MaxValue) || (EigenschaftenCurrent <= 0)) {
            ...
}
else if ((StatValue <= MinValue) && (EigenschaftenCurrent <= 0)) {
     ...
}

If EigenschaftenCurrent is <= 0(don’t mix languages like that holy moly!), then one that checks || on that will kick in. This means that your third else-if can’t possibly happen.

You could change the order, but it’d be much prettier to write it without the if’s altogether:

StatMinus.interactable = StatValue > MinValue;
StatPlus.interactable = StatValue < MaxValue && EigenschaftenCurrent > 0;

Then you should have the number as an integer, and then cast it to a float when you need it to be a float.

Rounding of floating point numbers are inaccurate, and surprising. In particular, taking a whole value and adding 1f to it doesn’t neccessarilly give you a whole value. You might end up with something like x.0000001 or x.9999999. If you expand the max stat to be higher, you will end up with a situation where you don’t have a whole number, which will not look good!

1 Like

Alright, I’m gonna keep the stuff about floats in mind and try to stick to ints whenever possible. And thanks for your code, it works perfectly! I would have never thought I could set up the conditions that way!

Alright, next problem. I already talked about perks that can increase certain values. The player can choose such a perk by clicking a toggle checkbox. If the toggle isOn, the value should increase by 10. If it set to off, the value should decrease by one. However, I’m getting a little confused about how to properly add a listener to a toggle.

    void Start () {
        Ausbildung.onValueChanged.AddListener ((value) => {AusbildungToggle(value);});
    }

    void AusbildungToggle (bool value) {
        if (Ausbildung.isOn) {
            IPMax = IPMax + 10;
            IPValue = IPValue + 10;
        }

        else {
            IPMax = IPMax - 10;
            IPValue = IPValue - 10;
        }
    }

The listener itself seems to work (I can put a print above the if statement and that works); however, the if statement is not.

bump Anyone?

First of all, you don’t need to create the lambda. Your AusbildungToggle (don’t mix languages like that!) fits into the type of onValueChanged. So you can just do:

Ausbildung.onValueChanged.AddListener(AusbildungToggle);

Other than that… it should work. The common way to do this is to use the argument sent to the listener:

private void AusbildungToggle(bool value) {
    if (value) {
        IPMax = IPMax + 10;
        IPValue = IPValue + 10;
    }
    else {
        IPMax = IPMax - 10;
        IPValue = IPValue - 10;
    }
}

but other than that there isn’t any real problem.

This is something you should be able to debug yourself. The easiest is to just debug all the values:

private void AusbildungToggle(bool value) {
    Debug.Log("Before: " + IPMax + ", " + IPValue + ", toggle value: " + toggle);
    if (value) {
        IPMax = IPMax + 10;
        IPValue = IPValue + 10;
    }
    else {
        IPMax = IPMax - 10;
        IPValue = IPValue - 10;
    }
    Debug.Log("After: " + IPMax + ", " + IPValue);
}

And figure out what’s happening from there. I’d guess you’re simply not updating the drawing of the values properly, but there’s no way to know from here.

Again, your help is much appreciated. It works perfectly now! I also had some issues with updating the inputfields… those are the things I tend to overlook when looking for the problem. Thanks!

Alright, I’ve got the feeling the next steps will be a lot more complicated. I’m trying to do the following:
For skill selection I’d like to use a system where there are available skills on the left side and added skills on the right side. Using the buttons in the middle the user can add or remove skills. If a skill appears on the right side, they should be equipped with an inputfield (which shows the current stat level) as well as + and - buttons for leveling up.

I already added some scroll fields and buttons. I can think of some ways to achieve what I wanna do, but I’m looking for a straight-forward one which includes sorting in alphabetical order. Any ideas?

Hi! It’s been a while and I’ve come quite far until now. I’ve been putting a lot of work in polishing the project and fixing early “mistakes” lately.
However, right now I’m facing a big challende: serialization.
During character generation, everything is saved to a script which is attached to a GameObject:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GenerationMainScript : MonoBehaviour {

    // Personal stuff
    public List<string> personalData;
    public string charName;
    public string charSex;
    public string charAge;
    public string charSize;
    public string charWeight;
    public string charLooks;
    public string charFamily;
    public string charSO;
    public string charOrigin;

    // primary attributes
    public List<int> primaryAttributes;
    public int muCurrent;
    public int muStart;
    public int muMax;
    public int klCurrent;
    public int klStart;
    public int klMax;
    public int inCurrent;
    public int inStart;
    public int inMax;
    public int chCurrent;
    public int chStart;
    public int chMax;
    public int geCurrent;
    public int geStart;
    public int geMax;
    public int ffCurrent;
    public int ffStart;
    public int ffMax;
    public int koCurrent;
    public int koStart;
    public int koMax;
    public int kkCurrent;
    public int kkStart;
    public int kkMax;

    // secondary attributes
    public List<int> secondaryAttributes;
    public int lepCurrent;
    public int lapCurrent;
    public int iniCurrent;
    public int natCurrent;
    public int paCurrent;
    public int fatCurrent;
    public int deCurrent;
    public int piCurrent;
    public int gsCurrent;

    // perks
    public List<string> perksList;

    // handicaps
    public List<string> handicapsList;

    // skills
    public List<Skill> skillsList = new List<Skill>();

    // abilities
    public List<string> abilitiesList;
}

In Unity, that looks like this:

I’d like to save the contents of that script to .xml. I read that is a good format if you still want to be able to edit stuff manually (if ever neccessary).
I’ve been trying to do that stuff using documentation and several tutorials, but I just can’t figgure it out.

This is what I have so far:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;

public class cgmSaveData : MonoBehaviour {
    public GenerationMainScript gms;

   // Use this for initialization
   public void GenerateCharacter ()
    {
      
        // path check and create directory if not already there
        if (!Directory.Exists("Saves"))
            Directory.CreateDirectory("Saves");

        // create xml serializer
        var serializer = new XmlSerializer(typeof(GenerationMainScript));

        // create file stream (charName_datetime.xml)
        FileStream saveFile = File.Create("Saves/" + gms.charName + " " + System.DateTime.Now.ToString() + ".xml");
        print(saveFile);

        // serialize data and close stream
        serializer.Serialize(saveFile, gms);
        saveFile.Close();

        print("success!");

    }

}

Unity says there’s an error in the xml serializer. It says:

I’d be very happy if someone could tell me what I’m doing wrong - and tell me a proper way to do it right.