Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shuffling Extension (I know, not your code) returning Index out of range. #32

Open
jmeyer1980 opened this issue Mar 22, 2024 · 20 comments

Comments

@jmeyer1980
Copy link

jmeyer1980 commented Mar 22, 2024

In MapPlayerTracker I have Set up my nodes like this:

        {
            // we have access to blueprint name here as well
            Debug.Log("Entering node: " + mapNode.Node.blueprintName + " of type: " + mapNode.Node.nodeType);
            // load appropriate scene with context based on nodeType:
            // or show appropriate GUI over the map: 
            // if you choose to show GUI in some of these cases, do not forget to set "Locked" in MapPlayerTracker back to false
            switch (mapNode.Node.nodeType)
            {
            case NodeType.CommonEnemy:
	            // Load the scene or enable the UI game object for CommonEnemy
	            if (!string.IsNullOrEmpty(mapNode.Blueprint.sceneName))
		            SceneManager.LoadScene(mapNode.Blueprint.sceneName);
	            else if (mapNode.Blueprint.uiObject != null)
		            mapNode.Blueprint.uiObject.SetActive(true);
	            break;
            case NodeType.MinorEnemy:
	            // Load the scene or enable the UI game object for MinorEnemy
	            if (!string.IsNullOrEmpty(mapNode.Blueprint.sceneName))
		            SceneManager.LoadScene(mapNode.Blueprint.sceneName);
	            else if (mapNode.Blueprint.uiObject != null)
		            mapNode.Blueprint.uiObject.SetActive(true);
                    break;

MapGenerator:

public static class MapGenerator
    {
        private static MapConfig config;

        private static readonly List<NodeType> RandomNodes = new List<NodeType>
        {	
	        NodeType.CommonEnemy,
	        NodeType.MinorEnemy,
	        NodeType.EliteEnemy,
	        NodeType.RestSite,
	        NodeType.Treasure,
	        NodeType.Store,
	        NodeType.Mystery,
        };

NodeBlueprint:

namespace Map
{
    public enum NodeType
    {
	    CommonEnemy,
	    MinorEnemy,
        EliteEnemy,
        RestSite,
        Treasure,
        Store,
        Boss,
        Mystery
    }
}

For some reason, I continue to receive the error. Am I missing something?

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
System.Collections.Generic.List`1[T].get_Item (System.Int32 index) (at <dc753a1061284f8e971ee88ee4826eee>:0)
Map.ShufflingExtension.Random[T] (System.Collections.Generic.IList`1[T] list) (at Assets/STSMap/Scripts/ShufflingExtension.cs:29)
Map.MapGenerator.PlaceLayer (System.Int32 layerIndex) (at Assets/STSMap/Scripts/MapGenerator.cs:84)
Map.MapGenerator.GetMap (Map.MapConfig conf) (at Assets/STSMap/Scripts/MapGenerator.cs:41)
Map.MapManager.GenerateNewMap () (at Assets/STSMap/Scripts/MapManager.cs:41)
UnityEngine.Events.InvokableCall.Invoke () (at <6f7018b8b8c149e68c4a65a05ac289be>:0)
UnityEngine.Events.UnityEvent.Invoke () (at <6f7018b8b8c149e68c4a65a05ac289be>:0)
UnityEngine.UI.Button.Press () (at ./Library/PackageCache/[email protected]/Runtime/UI/Core/Button.cs:70)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at ./Library/PackageCache/[email protected]/Runtime/UI/Core/Button.cs:114)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at ./Library/PackageCache/[email protected]/Runtime/EventSystem/ExecuteEvents.cs:57)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at ./Library/PackageCache/[email protected]/Runtime/EventSystem/ExecuteEvents.cs:272)
UnityEngine.EventSystems.EventSystem:Update() (at ./Library/PackageCache/[email protected]/Runtime/EventSystem/EventSystem.cs:530)
@silverua
Copy link
Owner

I think, the line where you get the error must be this one:
var blueprintName = config.nodeBlueprints.Where(b => b.nodeType == nodeType).ToList().Random().name;
Have you created the NodeBlueprint scriptable objects for CommonEnemy, MinorEnemy and have you added those scriptable objects into the nodeBlueprints list in your MapConfig scriptable object?

@jmeyer1980
Copy link
Author

Minor enemy is a part of the default nodes that came with the zip I downloaded from here. The only node "type" I added and created is CommonEnemy. The nodes that belong to the types common and minor are the even numbered tiers and odd numbered tiers respectively. I honestly thought I covered everything.
image

@jmeyer1980
Copy link
Author

I know I have to be missing something because this happens every time I try to add any additional node types. For my game, I T10-T0 are difficulty level scenes and I am trying to launch those scenes using the different node types. I think I am explaining correctly. When I use the default node types, I can launch A scene without issue. I am not good enough with scriptable objects to easily change which enemies spawn based on a difficulty curve... but I am going to figure it out eventually. But I need to learn how to successfully add new node types first.

@silverua
Copy link
Owner

Do you have any node blueprints of type EliteEnemy in Default Map Config / Node Blueprints?
Asking because you have the EliteEnemy node type and it's featured in Random Nodes.
It might throw errors if the randomization lands on EliteEnemy for the random node and the config does not have any EliteEnemy node bluepritnts.

A quick test that may help with this - you can try setting all the Randomize Nodes sliders to 0 and seeing if it eliminates the errors.

@jmeyer1980
Copy link
Author

I have errors from Oneline as well. I only mention this because I can't adjust the randomize node values in the default map config?
image

@jmeyer1980
Copy link
Author

jmeyer1980 commented Mar 22, 2024

To the best of my knowledge, the only things I changed were the code in the three scripts to add the scene/gui loading stuff and to add the ONE node type: CommonEnemy.

Edit: I gotta take a nap. Might be a while before I can reply again.

@silverua
Copy link
Owner

silverua commented Mar 22, 2024

Have a good nap!
Something to try later: I'd try breaking it down further and checking what the logs say before you get this error. For example:

var nodeType = Random.Range(0f, 1f) < layer.randomizeNodes ? GetRandomNode() : layer.nodeType;
var list = config.nodeBlueprints.Where(b => b.nodeType == nodeType).ToList();
Debug.Log($"{list.Count} nodes of type {nodeType} found in config");
var blueprintName = list.Random().name;

This way before the error happens, you would get a log that hints which type of node was causing an issue and how many of them you have in your MapCofig. It it says, 0 nodes, you have to fix that by making and adding a NodeBlueprint of that type into your MapConfig.

@jmeyer1980
Copy link
Author

Wasn't able to get back to it today. Will try again tomorrow.

@jmeyer1980
Copy link
Author

I'm sorry, I have autism and ADHD. If what I am doing isn't working and I feel like I am doing it correctly, I get tunnel vision and can't see what I am missing. I have to keep trying over and over until whatever I am missing clicks.

I reimported the files and made a video where I retrace my steps and reproduce the error. It is eight minutes because it takes me a few to remember what I am doing again. lol. I am praying that you notice what I am missing. I couldn't figure out exactly where I needed to add that debug line, so that hasn't been tried yet.

https://youtu.be/_OBtwaMvfpI

@silverua
Copy link
Owner

silverua commented Mar 23, 2024

Thanks for sending the vid.
Critical things that I am seeing:

  • When you create the Node Blueprint scriptable object for the Common Enemy node in the video at about 1:25 into the video, you do it by copying the Minor Enemy node blueprint, which is okay. But you are forgetting to set the Node Type enum value on the NodeBlueprint that you have created there to Common Enemy. It is very important that all the Node Blueprint scriptable objects have the Node Type value set to corresponding type.
  • To do this correctly, I think, you should edit the code first and add the CommonEnemy entry into the enum, so that you can set it correctly on the Common Enemy NodeBlueprint object when you create it.
  • Lastly, it is important how you add the entry to the enum. In the video you've added it in the beginning. In Unity to avoid mistakes, you should always add new enum entries in the end. Unity saves enum values based on how they are ordered, not based by their names. When you add a new entry in the beginning of the enum, in every place of the project where you had public fields of this enum type, everything shifts by 1 entry, so everything you've been setting up is completely lost.

So, if you want to do this without introducing any errors, you do it in this order:

  1. You add an enum entry for CommonEnemy in code in the end of the enum. Adding in the end is very important.
  2. You create a Node Blueprint object for Common Enemy. It's okay to do it the way you did by copying Minor Enemy or any other existing Node Blueprint.
  3. Also very important: right after you create the Common Enemy node blueprint, you select it and in the Inspector set the Node Type to Common Enemy enum value that you have created in step 1.
  4. Then you go to your map config and add the Common Enemy node blueprint to the list.
  5. Then you adjust the node Types in layers whichever way you want.

And it should work. Adding new NodeTypes into MapGenerator.RandomNodes is optional and not super important. You can do it later.

@silverua
Copy link
Owner

As for the code that I've mentioned above, I think in your case adding it is a good idea because if you forget to do some bits of the config, it will remind you and steer you in the right direction.

You can replace this line in MapGenerator.PlaceLayer:

var blueprintName = config.nodeBlueprints.Where(b => b.nodeType == nodeType).ToList().Random().name;

With these lines:

var nodeType = Random.Range(0f, 1f) < layer.randomizeNodes ? GetRandomNode() : layer.nodeType;
var list = config.nodeBlueprints.Where(b => b.nodeType == nodeType).ToList();
Debug.Log($"{list.Count} nodes of type {nodeType} found in config");
var blueprintName = list.Random().name;

Then if you ever get an error you will be warned about which NodeType caused it.

@jmeyer1980
Copy link
Author

So I was doing it backwards, taking stuff from where it belonged and rearranging it when I shouldn't have. What if I wanted to scrap them all and re-add the nodes I want in the order I want? Would Unity accept that? Or is it just best that I leave the current things where they are cause it aint broke and adding new ones will just work if I continue following your instructions?

I wish I could say that I am new to all of this, but I've been putzing around with Unity since 2017. My biggest issue is that I don't know enough C# to write code from scratch. I am utilizing Bing's new notepad feature for AI generated code. I could have used it to try and figure this out... but the couple of times I tried, it told me I was doing it right. lol. That's when I came back here again.

I also have the Single Player CCG kit. I was able to use it's map generator as a stand-alone feature, but it doesn't have the UI based version like yours does and when I add it to my project, the boss location is shifted too far out of the camera view no matter what I try. So I use yours. Hopefully, I am able to continue adding things to the end of the enums here and get things working. If I understood things better... I could use a single level scene and have a difficulty curve set up the scene for me. For now, I will use the multiple scenes method. yuck. lol.

BTW. Thank you.

@silverua
Copy link
Owner

Glad that you made some progress!

You can add enums in the order that you prefer, but then literally everywhere in the Unity project you have to go and set them manually. Which is: on all the Node Blueprint objects and in the Map Config object.

If you ever want to add more enum entries, remember that adding anywhere besides the end of the enum messes up the setup that you did in Unity and forces you to re-check everything again.

That's why the least intrusive way is to add new entries to the end of the list.

AI is still not great for coding games unfortunately. I would not fully rely on it.
If you need to write code for your game, I think, it's still a great idea to learn how to code yourself and not rely on AI too much. It's making a lot of mistakes and often suggests solutions that are just wrong.

@jmeyer1980
Copy link
Author

I have learned more in the last six months than I have in the last five years and that is all thanks to AI. I understand the limitations and the risks, but I would never have gotten as far as I have with this shmup than without it. It just works for my learning disability.

I do my own code when I can and when I can recall what I need for the occasion and that is happening way more often than it used to. So I will respectfully continue to use AI as a crutch until I don't need to any more.

Right now, what I am unsure of, as I am writing this, is how I am going to handle victories. Like how to return to the map and start act 2. I'm sure I have the right idea: set victory bool, load map scene, confirm victory bool, trigger map gen while referencing the Act2 config, and then (I think) lock the nodes to force the player to click the first one?

@silverua
Copy link
Owner

By all means! Whatever works for you!

So, when you win, while you are in a different scene, you can do something like:

PlayerPrefs.DeleteKey("Map");
PlayerPrefs.Save();

This ensures that:

  • the map is deleted, so it will try to generate a new one next time
  • the player position is also deleted, so it will prompt the player to select 1 of the first nodes next time

Another thing that you can do before going into the map scene is set the act number, so you could do something like:

PlayerPrefs.SetInt("ActIndex", 1);

Then you load the map scene.

And in the map scene you can have a new script that would set the MapConfig based on the value in PlayerPrefs.
For example:

public class ActSetter : MonoBehaviour
{
    public MapManager mapManager;
    public MapConfig[] acts;

    private void Awake()
    {
        var index = PlayerPrefs.HasKey("ActIndex") ? PlayerPrefs.GetInt("ActIndex") : 0;
        if (index < 0 || index >= acts.Length) index = 0;
        mapManager.config = acts[index];
    }
}

Add it to an object in the Map scene, link the MapManager and populate the acts array in the inspector.

And when you lose the game, you can also delete the map, but set the act to 0:

PlayerPrefs.DeleteKey("Map");
PlayerPrefs.Save();
PlayerPrefs.SetInt("ActIndex", 0);

Hope it helps.

@jmeyer1980
Copy link
Author

jmeyer1980 commented Mar 25, 2024

I don't know if it will let me comment on a closed topic, but here we go: Would you mind explaining to me how to unlock the next nodes after victory on the current scene? I have tried using SetAttainableNodes() but it seems to want an object reference? Which I assume are the next nodes on our path?

Edit: cool. It let me re-open. I felt this was better than creating a new topic since we already covered new act and failed attempt.

@jmeyer1980 jmeyer1980 reopened this Mar 25, 2024
@silverua
Copy link
Owner

Personally, I think, it is better to create new issues if we're discussing a different topic.
It makes it easier for people in a similar situation to find answers to their questions.
But we can carry on here for now.

By design the method SetAttainableNodes() should be called automatically when the player moves or when the map is loaded. It is based on the path that player has covered on the map so far. So, unlocking next nodes should happen automatically. If it is not happening, something must be going wrong and we should investigate why it's happening.

Can you describe what you're doing and what the expectation is:

  • is the gameplay in a different scene or is it in the same scene?
  • when you're returning to the map, are you loading the map scene or are you re-enabling the object with the map?
  • do you get any errors in the console?
  • when you return to the map, what's happening exactly? Is the player in the correct position, but the next nodes are not highlighted? Something else?

@jmeyer1980
Copy link
Author

Personally, I think, it is better to create new issues if we're discussing a different topic. It makes it easier for people in a similar situation to find answers to their questions. But we can carry on here for now.

By design the method SetAttainableNodes() should be called automatically when the player moves or when the map is loaded. It is based on the path that player has covered on the map so far. So, unlocking next nodes should happen automatically. If it is not happening, something must be going wrong and we should investigate why it's happening.

Can you describe what you're doing and what the expectation is:

  • is the gameplay in a different scene or is it in the same scene?
  • when you're returning to the map, are you loading the map scene or are you re-enabling the object with the map?
  • do you get any errors in the console?
  • when you return to the map, what's happening exactly? Is the player in the correct position, but the next nodes are not highlighted? Something else?

This was a pre-emptive question. I was still setting up my win/loss UI and the question was weighing on my mind. Thank you.

Here's the most recent vid I took of what I have done so far. It's not much, but it is something.
https://youtu.be/Bw_DYrVZP6Y

@silverua
Copy link
Owner

That's looking pretty good! Congrats! Keep it up!

@jmeyer1980
Copy link
Author

jmeyer1980 commented Mar 25, 2024

That's looking pretty good! Congrats! Keep it up!

Thank you very much. The ship selection screen was actually a lot of fun to work on. Getting the selected ship through the map screen and into the level was also, sarcastically, fun. When it finally worked, though, I scared my dog. lol. I literally jumped out of my seat.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants