-4

I'm trying to make my own video game in Unity. I have a series of biomes. They eventually evolve from bad to good, based on a float converted into an int going from 0 to 100, giving a biome Value of +1 or -1 when reaching 0 or 100, and reseting then the bar to the opposite. To that, I have a Biome.Text, which adjust itself according to the biome type.

It looks like that :

private void SetSkyBiomeType(int skyBiomeType)
{
    skyBiomeType = Mathf.Clamp(skyBiomeType, 1, 8);

    if (skyBiomeType == 1)
    {
        SkyBiomeType.text = "Dark Sky";
    }

    if (skyBiomeType == 2)
    {
        SkyBiomeType.text = "Cold Sky";
    }

    if (skyBiomeType == 3)
    {
        SkyBiomeType.text = "Dusty Sky";
    }
}

But as I wanted to have more visibility on the bar progressing from one biome to another, I added two more texts, on the left and the right of the bar, showing the previous biome and the next biome.

So I adjusted my code to :

private void SetSkyBiomeType(int skyBiomeType)
{
    skyBiomeType = Mathf.Clamp(skyBiomeType, 1, 8);

    if (skyBiomeType == 1)
    {
        SkyPreviousBiomeType.text = " ";
        SkyBiomeType.text = "Dark Sky"; ;
        SkyNextBiomeType.text = "Dusty Sky";
    }

    if (skyBiomeType == 2)
    {
        SkyPreviousBiomeType.text = "Dark Sky";
        SkyBiomeType.text = "Cold Sky";
        SkyNextBiomeType.text = "Dusty Sky";
    }
}

And so on.

And sure, it works. But I'm trying to make it more elegant by having something simple like :

 skyPreviousBiomeType.text = skyBiomeType - 1;
 skyBiomeType.text = skyBiomeType;
 skyNextBiomeType.text = skyBiomeType + 1;

Of course, that doesn't work, because the .text cannot be equal to an int without set " ". So I'm kind of of lost there. Everything I read online about how to tie a .text to an int was about making a .ToString() but that's not really what I'm looking for, and can't seem to add and remove 1 from it. I want the .text to refer itself to the value of the int.

6 Answers 6

1

I'd solve this problem using something similar to this:

private static ReadOnlySpan<string> _skyBiomes => new[]
{
    "(Empty first)",
    "Dark Sky",
    "Cold Sky",
    "Dusty Sky",
    "#4",
    "#5",
    "#6",
    "#7",
    "#8",
    "(Empty last)",
};

private void SetSkyBiomeType(int skyBiomeType)
{
    var skyBiomes = _skyBiomes;
    skyBiomeType = Mathf.Clamp(skyBiomeType, 1, skyBiomes.Length - 2);

    SkyPreviousBiomeType.text = skyBiomes[skyBiomeType - 1];
    SkyBiomeType.text = skyBiomes[skyBiomeType];
    SkyNextBiomeType.text = skyBiomes[skyBiomeType + 1];
}
Sign up to request clarification or add additional context in comments.

3 Comments

I see two issues with this solution: 1) a ReadOnlySpan in Unity isn't serializable (anyway not if the field is static either) so you have to hardcode this - which is somewhat ok but reduces the flexibility provided by Unity's Inspector .. and 2) the last empty entry is quite an uncanny and error prone hack imho .. I would rather wrap around the index .. like I said here stackoverflow.com/a/79587114/7111561
@derHugo Ad1) Yes, this sample requires direct initialisation of _skyBiomes. I wouldn't call it an issue. Might be, that's fair point, but it's not known without context and actual needs. Thanks for Unity inspector valuable tip. Ad2) OP clamped the input so we might use this knowledge to get the result without wrapping. Is it better or worse? As long as they meet expectations, both are equally good. My version would be more performant and yours will be more flexible. I'd say that's a fair way to put it. Uncanny and error prone hack are opinions; de gustibus non disputandum.
@derHugo To explain my thought process - I believe that data should be organised in a way that lets you access it with minimal operation overhead in mind. Sometimes it's tricky or you don't have context knowledge to make assumptions, but in this case I'd feel very comfortable to leverage the guarantee provided by Clamp and use my approach instead of 4x remainder+branch cost on every method invocation. I see no real value for executing these operations and my solution proves they can be simply avoided.
0

Create a dictionary with key int and value of string and store it via some ScriptableObject that is accessible to your scripts. Key should be biome ID and value should be BiomeText.

See: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=net-9.0

Then access the dictionary instead.


int currBiome = i
...
skyPreviousBiomeType.Text = biomes[currBiome-1];
skyBiomeType.text = biomes[currBiome];
skyNextBiomeType.text = biomes[skyBiomeType+1];

Obviously, this is not a bulletproof solution.
You would preferably need to check if the key is in the dictionary and if not, apply some other custom logic of your choice to select the Biome.
Example problem could occur when your currBiome is 1; what would be the previous Biome then? Do you stay at 1 or does it underflow into 8?

Comments

0

You can simply use a string array for the biome types. By inserting extra empty strings at the beginning and at the end, we ensure that we always get a result for the previous and next biome. This also ensures that the first real biome is at index = 1.

private static readonly string[] _biomTypes = { "", "Dark Sky", "Cold Sky", "Dusty Sky", "five more types...", "" };

private void SetSkyBiomeType(int skyBiomeType)
{
    skyBiomeType = Mathf.Clamp(skyBiomeType, 1, 8);

    SkyBiomeType.text = _biomTypes[skyBiomeType];
    SkyPreviousBiomeType.text = _biomTypes[skyBiomeType - 1];
    SkyNextBiomeType.text = _biomTypes[skyBiomeType + 1];
}

No if-statements required.

By making the array static, we ensure that it will be instantiated only once.

4 Comments

Thanks, it seems to be the way to deal with my issue that makes the most sense to me for now, and I'll try to learn from that input !
Isn't that basically what I said? stackoverflow.com/a/79587114/7111561 .. the thing is here though that clamping to index 1 and a hard coded max of 8 seems odd to me ... For my answer I rather assumed a wrap around index so that next doesn't fail for the max index
Yes, your solution may be "basically the same" dear derHugo, but I struggled to understand yours (because of my lack of knowledge), while this one seems much more straightforward. Don't worry, I'll keep re-reading it until it's printed up there ! Anyways, all answers are much appreciated as there is plenty of "solution" to solve this out. Thanks a lot.
@user30342766 I've added a bit more explanation to my answer hoping this will spread some light into the what and why ;)
0

Try wrapping the sky biome and use a - and + override. This way you can have different biome types such as LandBiome : IBiomeType and WaterBiome : IBiomeType

public interface IBiomeType
{
    public string Name { get; }
}
public struct SkyBiome : IBiomeType
{
    public readonly string Name
    {
        get => BiomeNames[_value];
    }

    //Add all of your Sky Biome Names here
    private static readonly List<string> BiomeNames 
        = ["Dusty Sky", "Sunny Sky", "Clear Sky", "Cloudy Sky"];
    private int _value;
    public static readonly SkyBiome Dusty = new() {_value = BiomeNames.IndexOf("Dusty Sky")};
    public static readonly SkyBiome Sunny = new() { _value = BiomeNames.IndexOf("Sunny Sky") };
    public static readonly SkyBiome Clear = new() { _value = BiomeNames.IndexOf("Clear Sky") };
    public static readonly SkyBiome Cloudy = new() { _value = BiomeNames.IndexOf("Cloudy Sky") };
    public override readonly int GetHashCode()
    {
        return HashCode.Combine(Name, _value);
    }
    public override readonly bool Equals([NotNullWhen(true)] object? obj)
    {
        return obj is SkyBiome other && other.Name == Name;
    }
    public override readonly string ToString()
    {
        return $"{nameof(SkyBiome)}[ {Name} ]";
    }
    public static SkyBiome operator -(SkyBiome skyBiome)
    {
        --skyBiome._value;
        if (skyBiome._value < 0) skyBiome._value = BiomeNames.Count - 1;            
        return skyBiome;            
    }

    /// <summary>
    /// Gets the next <see cref="SkyBiome"/>
    /// </summary>        
    public SkyBiome GetNextSkyBiome()
    {
        SkyBiome skyBiome = new() { _value = _value };
        return +skyBiome;
    }

    /// <summary>
    /// Gets the previous <see cref="SkyBiome"/>
    /// </summary>        
    public SkyBiome GetPreviousSkyBiome()
    {          
        //the - and + operators directly effect the current SkyBiome
        SkyBiome skyBiome = new() { _value = _value};
        return -skyBiome;
    }

    public static SkyBiome operator +(SkyBiome skyBiome)
    {
        ++skyBiome._value;
        if (skyBiome._value >= BiomeNames.Count) skyBiome._value = 0;
        return skyBiome;            
    }

    public static bool operator ==(SkyBiome left, SkyBiome right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(SkyBiome left, SkyBiome right)
    {
        return !(left == right);
    }
}

Comments

0

People have already posted the best solutions that applies to your problems, and solves it. But let's analyze whey they work.

So what you want is a biomeType which is a string in your case, that can be represented by a number aka int. Following this logic you would have below list of biomes.

0. " ",
1. "Dark Sky",
2. "Cold Sky",
3. "Dusty Sky",
.
.
n. "Nth Biome"

This is a classic requirement for Dictionary where you can have int as a key which represents one of your biomes, and type of the biome a string can be set as a value represented by a unique int key.

But notice one thing that all your keys or int values are sequential and starts from 0. Here using an array makes much more sense as arrays are much simpler and faster then a Dictionary. Arrays are also seializable by unity and can be updated directly from an inspector window. Using incidences from 0 to n as your key you can represent as much biomes as you need.

Well now you can follow Olivier Jacot-Descombes's answer

You can also use a List<T> if you want to make your biome callection more dynamic. And everything I saied about an array also applies for a List<T> as well.

1 Comment

The problem with a Dictionary is that Unity can't serialize it so you will have to fill it in code .. doable of course but since this is something purely index based a Dictionary is actually a bit overkill. A simple array or list is enough here and would have the advantage of being serializable by Unity so you can modify the content directly via the Inspector
0

A lot of solutions here like using a switch case or dictionary are a bit overkill. Also Unity can't serialize the dictionary and other collection types out of the box.

This is basically purely index-based so no need for a Dictionary, a simple array or list can already handle this and these are the two collection types that Unity can serialize out of the box.

=> Simply have and fill a string[] accordingly

Then I would prefer to not Clamp but rather wrap around the index so if the index goes beyond the maximum index of the array it simply starts from 0. And the other way round if it falls below 0 it starts at the end of the array. This way you don't even have to worry about disabling the next and previous functionality in case the index is at one of the limits.

I would recommend to do something like

// This gets exposed so you can modify it in the Inspector of the component
[SerializeField] private string[] skyBiomTypes;

// Wraps around the index at the ends
// meaning if the index becomes larger than the array allows it starts again from 0
// and the same for the other direction if falling below 0 it starts from the end
private int IndexWrapAround(int index) 
{
    int r = index % skyBiomeTypes.Length;
    return r < 0 ? r + skyBiomeTypes.Length : r;
}

private void SetSkyBiomeType(int skyBiomeType)
{
    // at this point either already wrap around the index 
    skyBiomeType = IndexWrapAround(skyBiomeType);
    // or stick to clamp according to your needs
    // In the case of clamp though make sure the Indices are actually valid
    //skyBiomeType = Mathf.Clamp(0, skyBiomeTypes.Length - 1);
    
    // Use wrap around indices in both directions so that next or previous 
    // will not fail if index is at min or max
    SkyPreviousBiomeType.text = skyBiomeTypes[IndexWrapAround(skyBiomeType - 1)];
    SkyBiomeType.text = skyBiomeTypes[skyBiomeType];
    SkyNextBiomeType.text = skyBiomeTypes[IndexWrapAround(skyBiomeType + 1)];
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.