Quest Debugging System

I know this one is off topic, but I had to ask because this one kept me curious for a while:

How do game developers typically debug quests whilst developing a game? Let’s say you have a mission going for you, and we can assume you have a lengthy quest to do right now. I’m sure as a developer you don’t want to do the entire quest all over again each time you play your game, and you also want to debug the quest to ensure all works as expected

How do people typically go around this, especially when the quest is deep into the game and can take tens of hours to unlock it again? I am 99.9% sure they’re doing something without having to repeat the entire quest, which can be an hour long for example, each time they want to debug their specific quest objectives

The big studios often hire teams of playtesters who run through parts of the game over and over again, noting each thing that doesn’t go quite right.

There is also Unit Testing. In this case, for each stage in the quest, you set up a scenario where you’re up to that next leg in the quest, and then through code test to see that the game responded appropriately to different actions. We don’t really cover Unit Testing (and in Unity, there are actually two categories of Unit tests, the Play Mode and Editor Mode tests).

What you can do, however, with no extra tools required, is to complete each leg of the quest to ensure it works proper, and save it… back up that save file and muck about with the next leg, etc. It’s all about setting up the proper conditions to test each section of a quest. In most cases, this is just a matter of having the QuestList’s entries set up properly. With the Json saving system, you could probably even add the completed portions of the quest to the list in the file directly.

OK umm, I’m not sure how best to bring this, but a few days ago I saw a video where someone was messing with Rockstar’s build of GTA V (it’s on YouTube, nothing illegal done. Just Take Two probably forgot to take that video down), and they had a UI on the side that can be used to access specific parts of the quest within the game itself, and I think the quest itself was a side instance of the project, I don’t know. I want to be the troublesome student who makes everyone’s life a bit worse and ask for a tutorial regarding this down the line. Not now, later, but this will probably be essential when it comes to developing the game itself

If not, can you please tell me a little more about what Unit Testing is?

If not as well, what’s the best way to go around this whole thing without being forced to invest hours into testing, especially when you’re short on time? I can see this being a problem in the future, so I want to future-proof my project :smiley:

I go into Unit Testing in the rewrite I’m making of the Json Saving System

it’s… an empty page?

Try this one:

that works. This is the brand new system?

Sort of. It is a total replacement for the binary system from the course. He wanted to make it a complete system, not just a replacement. There is a lot of good information here and it is not finished yet. I read through what he has done so far. It has a good lesson in how unit testing works in Unity. It also has a nice explanation of assembly definitions. Lots of good stuff from Brian again!

I really, REALLY don’t want to change my saving system right now. I just want to be able to develop tests for the questing system down the line, without having to modify the entire saving system, especially if I don’t know what kind of risks this carries

Any other alternative?

The system is set up as a separate project. If you go through the tutorial as it is written you don’t have to change anything in your game. Just use this as a way of learning unit testing. You should be able to apply the principals learned to testing any aspect of your game.

Fair enough, I’ll work on it as an independent project when the time is right

(For now I’m just writing and fixing my own complicated systems, xD)

I believe it’s much the same as what you already have. Brian just goes into loads more details on each json type, unit testing, assembly definitions, etc.

You can find info on ‘developer consoles’ everywhere. I made my own janky one with linked developer commands where I can open the console, type addgold 1000 and it will give me 1000 gold. It’s really just ‘editor-only’ classes (they get compiled out) that have a common interface like IDeveloperCommand and at startup commands can ‘register’ themselves with the console. Then there’s a little parser to parse what you type and execute the relevant command. Simple. It’s up to you to create the commands you may want to use when debugging, like creating a command that will list all active quests, and a command that can complete a step within a quest, etc. You can have a ‘editor-only’ flag in health to specify that you are in god-mode and make a command to set that flag. Then do a check in health to see if you should lose any. Like

public class Health : MonoBehaviour
{
    [SerializeField] float _currentHealth;
    [SerializeField] float _maxHealth;
#if UNITY_EDITOR
    private bool _godMode = false;
#endif

    public void Awake()
    {
#if UNITY_EDITOR
        DeveloperConsole.Instance.RegisterCommand(new GodModeCommand(this));
#endif
    }

    public void TakeDamage(float damage)
    {
#if UNITY_EDITOR
        if (_godMode) return;
#endif
        _currentHealth = Mathf.Max(0f, _currentHealth - damage);
// ... etc.
    }

#if UNITY_EDITOR
    public class GodModeCommand : IDeveloperCommand
    {
        private Health _health;
        public GodModeCommand(Health health) => _health = health;
        public void Execute(string[] args)
        {
            if (!bool.TryParse(args[0], out var godMode)) return;
            _health._godMode = godMode;
        }
    }
#endif
}

It’s just an example. Probably not exactly how you’d do it but that’s the idea. I didn’t bother to check how I did mine.


EDIT
Here’s a video Hugo (Code Monkey) made a while back using a paid console asset

I have no affiliation to Code Monkey or the asset. It’s just a video showing you how these things could work

Ahh this asset will be extremely useful. I will probably get it on the Black Friday deals as well, along with some GPU acceleration stuff, like GPU Instancer (I already own that), Bakery (own that too), Mesh Baker and Mesh Combine Studio 2 being next on the list (and I got GAIA for World Streaming and Terrains). I think this should be enough to get the FPS to run at fair speed on older machines as well, only time will tell (if anyone has any suggestions to boost performance further, let me know)

As long as I can program my own functions to make them work as expected, I believe this can significantly boost the time of testing when it comes to the game’s storyline. Thank you @bixarrio for bringing this asset to my attention

@Brian_Trotter @bixarrio this is probably an easy bug to fix, it’s a bit of a side question, but I am trying to fix a bug with my system that gets NPCs to follow the player if he’s invited to a team through dialogue. The problem is, sometimes I get an NRE regarding ‘SetDestination’ not working because the Agent is not on the floor. Anyone knows how to fix this? (I can’t tell if my solutions are working or not, because this is one of these ‘hit or miss’ kinda bugs. In other words, sometimes it happens, sometimes it doesn’t). Here’s the troublesome function:

        private void FollowTargetPoint(float deltaTime)
        {
            if (!stateMachine.Agent.enabled) stateMachine.Agent.enabled = true;

            // Get the current target point position
            Vector3 targetPosition = stateMachine.GetCurrentTargetPoint().transform.position;

            // Calculate the distance to the target point
            float sqrDistanceToTarget = Vector3.SqrMagnitude(stateMachine.transform.position - targetPosition);

            // If the enemy is close enough to the target point, switch to idle state
            if (sqrDistanceToTarget < 0.1f * 0.1f)
            {
                stateMachine.Agent.velocity = Vector3.zero;
                stateMachine.Animator.SetFloat(FreeLookSpeedHash, 0f);
                FaceTarget(stateMachine.Player.GetComponent<PlayerStateMachine>().transform.position, deltaTime);
                stateMachine.SwitchState(new EnemyIdleState(stateMachine));
                return;
            }

            // Set the target point's position as the destination
            stateMachine.Agent.destination = targetPosition;

            // Calculate the desired velocity for movement
            Vector3 desiredVelocity = stateMachine.Agent.desiredVelocity.normalized;

            // Move the enemy towards the target point
            Move(desiredVelocity * stateMachine.MovementSpeed, deltaTime);
            stateMachine.Agent.velocity = desiredVelocity * stateMachine.MovementSpeed;
            stateMachine.Agent.nextPosition = stateMachine.transform.position;
        }

That problem usually happens when they respawn either when they die or when the player dies and respawns

I’m not going to try and figure out what’s happening here, but I can tell you that there are loads of posts here about the agent not being on the navmesh and Brian has probably answered all of them.

Also, perhaps you shouldn’t just give the agent any position. That position may not be on the navmesh. You can ask the navmesh to give you a valid position close to the one you supply that is, in fact, on the navmesh. It’s something like

if (NavMesh.SamplePosition(targetPosition, out var hit, 1f, NavMesh.AllAreas))
{
    targetPosition = hit.position;
}

This specific example will look for a valid position on the navmesh within 1 unit from the position you specify. See the documentation here: Unity - Scripting API: AI.NavMesh.SamplePosition

they’re just calculating the distance to a target point attached to the player and the AI gets them to go there. Once they’ve arrived, they face the player and that’s it :stuck_out_tongue:

I’ll search these up. Unless I mark my last comment as [SOLVED], then it’s unsolved :sweat_smile:

I’m not sure what I did with the Respawn Manager or other stuff, but something in there ensures this never fails that I probably forgot about here. Probably a good spot to start searching within

Well considering there’s a notification, I still didn’t solve it…

@Brian_Trotter sorry to disturb you again, but I have a bit of a funky problem. I’m trying to make a pirate system, and in my ‘BoatAttackingState.cs’, which is used to get the AI Boat to RAM the players’ boat when it gets too close, I have this function:

    private void RamLastAttackerBoat(float deltaTime)
    {
        var targetBoat = stateMachine.GetBoatLastAttacker().GetComponentInParent<BoatStateMachine>();

        if (!stateMachine.Agent.enabled) stateMachine.Agent.enabled = true; // If you turn this off, the code below won't work

        if (targetBoat != null)
        {
            var playerOnBoat = targetBoat.GetComponentInChildren<PlayerStateMachine>();
            if (playerOnBoat != null)
            {
                stateMachine.Agent.SetDestination(targetBoat.transform.position); // If you turn this off, the boat won't even try to reach you

                Vector3 direction = stateMachine.Agent.desiredVelocity; // The direction the NavMeshAgent prefers to use to reach its goal
                direction.y = 0; // Eliminate the y-axis (no flying stuff for boats, PLEASE!)
                Move(direction.normalized * stateMachine.MovementSpeed, deltaTime); // If you turn this off, the boat will hug you (don't want that)
                stateMachine.Agent.velocity = direction * stateMachine.MovementSpeed; // The velocity of the boat each frame
                stateMachine.Agent.nextPosition = stateMachine.transform.position; // The next position for the NavMeshAgent to go towards (if it's not itself, it'll teleport. Don't want that!)
                Debug.Log($"{stateMachine.gameObject.name} is moving towards {stateMachine.GetBoatLastAttacker().gameObject.name}, BoatAttackingState");
            }
            else
            {
                stateMachine.Agent.ResetPath(); // Don't try to move, because the player is not on the opponent boat (i.e: his allies are fighting you, so you should stop moving too)
                Debug.Log($"{stateMachine.GetBoatLastAttacker().gameObject.name} is off the boat, resetting NavMeshAgent path, BoatAttackingState");
            }
        }
        else
        {
            stateMachine.Agent.ResetPath(); // Don't try to move, because the player is not on the opponent boat (i.e: his allies are fighting you, so you should stop moving too)
            Debug.Log($"TargetBoat not found. Resetting NavMeshAgent path, BoatAttackingState");
        }
    }

This function is supposed to get the NPC boat to RAM the players’ boat, but if the player mounts and dismounts his boat way too frequently, I am guessing the mathematics gets messy because the AI boat just either zooms its way into the players’ boat position, regardless of whether there’s colliders or not, and sometimes it literally zooms through the players’ boat to a completely random location

I also need to mention that this mostly happens when the boat gets too close, which is when it switches to the attacking state

Any idea how to fix this?

(Even the chasing state, which is the exact same formula, suffers the same problem)

Edit 1: And just disabling the NavMeshAgent does not fix the problem either…

The code to actually move to the Player boat looks correct, so the issue is likely in the decision making, possibly even the call to RamLastAttackerBoat in the first place. Is this called every frame on a boat?

The boat thing seems to really be introducing more bugs than it is adding features…

Privacy & Terms