Spawn enemies when quest start

We’ve chat a lot on the discord RPG channel about it, and at the end I realize that is a real mess to spit-out the enemy that I need when quest start.
It would be very nice if anyone could help to unravel this tangled skein!
@sampattuzzi @Brian_Trotter @Aionlasting :sweat_smile:
(sorry if I summon you but for this circle of hell we need real demons :smiling_imp:)

So I figured out this method which takes Quest SO as a parameter and will spit out the victim of the quest, so later we can set the NPC as needed.

By digging on this rambling logic we should try to get through each Objectives via a foreach loop and inside I guess we’ll need a switch (or an if statemet) for the EPredicate and when the result is something like EPredicate.HasKilled or EPredicate.HasKilledClass then we could/should be able to know who is the victims that must be killed.

Am I on the right path? :thinking:
Because my first issue is how to reach the EPricate evaluation.

foreach (var item in quest.GetObjectives())
{
    if(item.completionCondition...*here I miss something* == EPredicate.HasKilledClass)

:pray: Hope someone could help :slight_smile: :vulcan_salute:

Wait, I don’t understand exactly what you want. Are you trying to have someone give the player a quest to kill someone else, but then spawn that ‘victim’ when the quest is given?

It shouldn’t be too difficult. Dialogs already trigger a UnityEvent that could be used to spawn a ‘victim’ when the quest is given. NPCs have a health component that triggers a UnityEvent when the NPC dies that could be used to complete the quest.

Nope, I just want to spawn the object of the quest.
So when you talk with someone and he’s tells you that you have to kills skeletons, a function will spawn that skeletons at runtime.

Problem is that I wish to pass some parameters to the “SpanwnerManager” so he will know what kind of mob he have to spawn, also SpawnerManger could set the appropriate parameters on the mob’s components too.

I though that I could just use the Reference parameter but that’s not enough, and also I will loose the option to use HasKilled or HasKilledClass :unamused:

Maybe the easiest way is to add another field with the mob prefab… it would be redundant but could a shortcut.

I don’t know what your SpawnerManager looks like, but the DialogueTrigger could be extended do that for you.

First, create an interface so that it’s easier to work with

public interface IDialogueTrigger
{
    void Trigger(string actionToTrigger);
}

Now we’ll need to update PlayerConversant to look for interfaces instead

// PlayerConversant.cs
private void TriggerAction(string triggerAction)
{
    if (triggerAction == null) return;
    // find all the monobehaviours on this game object that implement the interface
    foreach (var trigger in _currentConversant.GetComponents<MonoBehaviour>().OfType<IDialogueTrigger>())
    {
        trigger.Trigger(triggerAction);
    }
}

Next, our old DialogueTrigger class must inherit from the interface. It is already implemented so nothing more is needed

public class DialogueTrigger : MonoBehaviour, IDialogueTrigger
{
    [SerializeField] string action;
    [SerializeField] UnityEvent onTrigger;

    public void Trigger(string actionToTrigger)
    {
        if (actionToTrigger != action) return;
        onTrigger.Invoke();
    }
}

Now we make a new trigger.
Make a struct to hold the details

public struct SpawnEnemyDetails
{
    public GameObject EnemyToSpawn;
    public int NumberToSpawn;
    public SpawnEnemyDetails(GameObject enemyToSpawn, int numberToSpawn)
    {
        EnemyToSpawn = enemyToSpawn;
        NumberToSpawn = numberToSpawn;
    }
}

and a new unity event to pass the details to listeners

[Serializable]
public class SpawnEnemyEvent : UnityEvent<SpawnEnemyDetails> { }

And lastly, the new dialogue trigger

// New dialog trigger
public class SpawnEnemyDialogueTrigger : MonoBehaviour, IDialogueTrigger
{
    [SerializeField] string action;
    [SerializeField] GameObject enemyPrefab;
    [SerializeField] int numberToSpawn;
    [SerializeField] SpawnEnemyEvent onTrigger;

    public void Trigger(string actionToTrigger)
    {
        if (actionToTrigger != action) return;
        onTriggerSpawn.Invoke(new SpawnEnemyDetails(enemyPrefab, numberToSpawn));
    }
}

You will set this up like any other DialogueTrigger, but it includes fields for the enemy you want to spawn, etc.
Now you just need your SpawnManager to have a public method that takes the SpawnEnemyDetails, Add it to the new SpawnEnemyEvent and use it to do what it needs to do

public class SpawnManager : MonoBehaviour
{
    public void SpawnEnemies(SpawnEnemyDetails details)
    {
        for (var i = 0; i < details.NumberToSpawn; i++)
        {
            Instantiate(details.EnemyToSpawn);
        }
    }
}

You could add your EPredicate.HasKilled or EPredicate.HasKilledClass to the SpawnEnemyDetails as well. The downside here is that the configuration lives on the trigger.

My own implementation uses scriptable objects for the actions, but it will look something like this
image


Edit
I had a quick think about it and it’s not too difficult to put the spawn configuration on the quest instead.
Disregarding all the previous stuff:
Create a new Quest type

[CreateAssetMenu(menuName = "RPG/Quests/New Spawner Quest")]
public class SpawnerQuest : Quest
{
    [SerializeField] List<SpawnDetails> spawnDetails = new();

    public IEnumerable<SpawnDetails> GetSpawnDetails() => spawnDetails;

    [Serializable]
    public class SpawnDetails
    {
        [SerializeField] GameObject objectToSpawn;
        [SerializeField] int numberToSpawn;

        public GameObject GetObjectToSpawn() => objectToSpawn;
        public int GetNumberToSpawn() => NumberToSpawn;
    }
}

Then create a QuestGiver component that expects this type of quest

public class SpawnerQuestGiver : MonoBehaviour
{
    [SerializeField] SpawnerQuest questToGive = null;

    public void GiveQuest()
    {
        var questList = GameObject.FindWithTag("Player").GetComponent<QuestList>();
        questList.AddQuest(questToGive);

        var spawner = GetComponent<TestSpawner>();
        foreach (var detail in questToGive.GetSpawnDetails())
        {
            spawner.SpawnEnemies(new SpawnEnemyDetails(detail.GetObjectToSpawn(),
                detail.GetNumberToSpawn()));
        }
    }
}

It’s the same as any other quest giver (I omitted the ‘items to give’ bits, but you don’t have to), but now the quest can tell it what type of object (can be a sword, too) to spawn, etc.

This is the new quest
image
image

1 Like

This is gorgeous!! :grin:
I just returned from work, so I haven’t tested it, but it looks great and I’ll work on it soon as possible
In meantime thank you and cheers! :beers: :smiley:

Ok I’m back…

I’ve a doubt, because in predicate we specify the target name:
image
If we didn’t set the target name in predicate, how the Quest known if we’ve complete the step? :thinking:

sorry but I’m a bit dumb this days, prolly caused by the hot weather of this days :sweat_smile:

Oh. @Brian_Trotter has a post about it here
Tracking kills for Quests?
And there is a long post that we did without the improved conditions here
Tracking kills without implementing the “Improved Conditions”

2 Likes

I’ve search a lot for both questions but looks like I’ve used the wrong terms :thinking:
Thanks a lot, I’ll study on it :beers:

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

Privacy & Terms