Procedurally Spawned Characters

Ok I figured out why it fails on restore. The RestoreState method (below) gives it the transform of the parent, which is overwritten by the Mover. But the “bad” guard position stays. It seems now the correct fix is to modify the AIController to implement ISaveable

        public void RestoreState(object state)
        {
            if (state is List<KeyValuePair<string, object>> stateDict)
            {
                foreach (KeyValuePair<string, object> pair in stateDict)
                {
                    DynamicCharacterConfiguration config = DynamicCharacterConfiguration.GetFromID(pair.Key);
                    if (config == null) continue;
                    var character = CreateDynamicEntity(config, transform.position, transform.eulerAngles);
                    if (character)
                    {
                        character.RestoreState(pair.Value);
                    }
                }
            }
        }

The mover should only be modifying the transform.position property, not the transform itself, and the Mover should have saved the position of the character at the time it was saved… so I’m not sure what’s going on with that.
If maiking AIController as an ISaveable/IJsonSaveble is doing the trick, though, I’d stick with it.

The mover is modifying the transform.position correctly, but what is happening is in this line of the code on RestoreState.

var character = CreateDynamicEntity(config, transform.position, transform.eulerAngles);

The position is being set to the DynamicSaving’s associated gameobject.
Then what happens is this code in AIController.Awake executes and sets the guardPosition to be the same as the DynamicSaving’s transform. This was a change late in the SAB course.

            guardPosition = new LazyValue<Vector3>(GetGuardPosition);
            guardPosition.ForceInit();

So when the game loads, the Dynamic Entities are in their correct starting locations thanks to the Mover script but they end up drifting towards a guard position that is the location of the Dynamic Saving’s gameobject.

Yeah I think this is the fix. It was in this commit that I think broke it. Sam in the video mentions he’s putting in a hack that he would have preferred if it was implementing ISaveable. So in short, I think the RPG.Dynamic is fine, it’s just exhibiting weird behavior as a result of a late change in SAB. Moving the ForceInit to Start() fixes the issue (but breaks respawn).

Interesting. This is one of the trickier aspects of programming. Sometimes, what seems like an insignificant change in one place turns out to break something else. I think what Sam did made sense at the time, but it was set up for a different purpose (actually, sort of a way to manage respawns, if you’re creative with it, but I actually prefer NOT to use Reset. I prefer restoring to clean objects with a known state).

Yes. It seemed like an innocuous change at the time. Fighting the bug was also an awesome way to hammer home the point of transforms get set on instantiated objects.

Is this the relevant discussion? How to instantiate a new & unique ScriptableObject during run-time

Does Instantiate on an SO at runtime still work as expected in the Unity 2022.3 LTS? If so, this would massively simplify some stuff I’ve been working on.

I’m using them in the 2022.3.8 for Spellborne Hunter. I would be screaming bloody murder in the Unity Support Forum if they did away with that functionality.

Here’s the code repo for Spellborne Hunter. Check out RandomDropper and Shop for creation, and the Inventory, Equipment, and ActionStore for saving/restoring the instantiated stats. In the StasEquipableItem itself is the code where the random stats are generated.

1 Like

Spellborn GitLab

1 Like

You have no idea how useful this is. I was in the middle of creating parallel objects (non SO) to my SOs to store instance-level state. It started to turn into a mess. Then I figured I would come back to this thread and your repo. Glad I did.

SOs are a wonderful tool in the Unity arsenal. Many people just think of them as pure data containers, but they are so much more than that, and even what I do with them in Spellborne Hunter is just a drop in the bucket to their true power.

1 Like

I just wanted to say, that I got procedurally generated with quests with quest objectives and rewards that are also procedurally determined AND that also save their own state working in less than a day after you responded to me. Thank you so much.

I tried to do this back in August and forget about saving state, I couldn’t even figure out the proper data structure for playing a live game. I spent days and I kept confusing myself. Using this guidance with instantiating scriptable objects at run time and marking them as “instances” was a HUGE help. Again - done AND with state saving in less than a day when prior attempts took days with nothing to show for it.

This builds on top of the procedurally spawned enemies from RPG.Dynamic. They are spawned as part of these procedural quests. I also have enemy levels that are procedurally determined and based off a delta of the player’s current level.

Since my game is educational in nature, I will now using these techniques to dynamically create educational content (not just quests/enemies) based on how well the player is doing. The strategy pattern I learned in SAB is very helpful here.

In the course of figuring this out, I noticed something interesting about how you are serializing and deserializing in JSON and posted a separate question.

For anyone reading this and new to Unity/C#, my recommendation is to take all 4 RPG courses and also do a few of Brian’s tips and tricks before trying to go too far in customizing your game. The stuff you learn doing that will make later customizations so much easier and faster.

Again - thank you so much!

1 Like

Privacy & Terms