What is the best way to add an enemy character to the story with a fight scheme?

Hello I have completed the course about the Text101 adventure and I would like to implement an element of RPG in it. Basically I would like to add an Enemy in some of the States and build an Encounter script to manage the fight and the loot. What is the best thing to do in the design process?

I was thinking to add a couple of [SerializedField] to the state script and then manage somehow…

What do you think? Is there a better and easy way still using the Text 101 structure?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "State")]
public class State : ScriptableObject
{

    [TextArea(14, 10)] [SerializeField] string storyText;
    [SerializeField] State[] nextStates;
    [SerializeField] bool enemy;
    [SerializeField] string enemyName;

    public string GetStateStory()
    {
        return storyText;
    }

    public State[] GetNextStates()
    {
        return nextStates;
    }



 

}

Thanks

Alex

Hi Alex,

Welcome to our community. :slight_smile:

The question is: How exactly is your fighting and looting supposed to work? Our AdventureGame instance is responsible for processing the “game” based on the input. The input is, in our case, a State object.

If the AdventureGame object “loads” a State object where the enemy bool is true, what is supposed to happen?

If you know that, you can surely add a bunch of if-statements to your AdventureGame to get a step closer to your goal. The best would be to draw the logic with a pen on a sheet of paper before you implement it in your code. :slight_smile:


See also:

Thank you Nina, I will sketch the idea as you suggested and I will post it. Talk you soon!

Alex

Hi Nina,

I have sketched my idea in Visio.

Basically there should be a mainstream which is the main story adventure characterised by N number of states; The story in my mind is linear, N states in series. Each state can contain an enemy that the player can decide to fight or to flee (taking life damage if he fled). When the player engages the fight, the walk control is disabled (so the state won’t change) and the fight control is enabled.
The player stats vs the enemy stats are enabled and they follow a basic RPG equation to calculate the amount of damage inflicted/taken.

Once the fight is completed if the player won the fight he can loot the body of the dead enemy and he can proceed the story to the next state.
If the player loses the fight then he steps back to the previous state (or something else which I did not realised yet!).

Do you think this is something that can be implemented based on the notions provided with Text101?

Alex

First of all, good job. Your diagram looks fine so far. What is missing is what is supposed to happen when the player enters, for example, State 2.

In your code, you already have a bool named “enemy”, which is fine. However, what can the player do to defeat the enemy? Does he need a specific item? Can he use his fists? Does the enemy have health?

In your diagram, you already have defined the Player and the Enemy stats. For simplicity, you could create a bunch of variables for the player, and a bunch of variables for the “current” enemy in AdventureGame.

In each State, you need to declare a health variable for the enemy which represents the max health. Do not change the value during runtime. This is important. Otherwise, your values will get overridden while playing your game. When you start your game next time, you will not get the original values again. Your enemy will remain “dead” forever.

When a State with enemy == true gets “loaded” in the AdventureGame instance, you assign the health value of the enemy to the enemyHealth variable in the AdventureGame instance.

You also have to keep track of the defeated enemies. That’s simple.

// Instead of 20, use the number of states you have
HashSet<int> defeatedEnemyID = new HashSet<int>(20);

int enemyHealth = 0;

void DoSomething()
{
    if (state.HasEnemy() && enemyHealth == 0)
    {
        defeatedEnemyID.Add(state.GetInstanceID());
    }
}

void SomeOtherMethod()
{
    // You can proceed to the next state if the enemy is dead or if there isn't an enemy
   if (!enemy || defeatedEnemyID.Contains(state.GetInstanceID())
   {
        // proceed with the next state
   }
   else
   {
       // allow player to attack
   }
}

While you are on it, you could also define actual game states in your game with an enum. That’ll help you control the user input / user interface. For example:

enum GameState
{
    Inventory,
    LoadEnemy,
    Attack,
    SelectState,
    Lose
}

GameState gameState = GameState.SelectState;

void Update()
{
   if (gameState == GameState.SelectState)
   {
         if (Input.GetKeyDown(KeyCode.A)) { // code }
   }
   else if (gameState == GameState.Lose)
   {
         if (Input.GetKeyDown(KeyCode.Enter)) { // code for restarting the game }
   }
}

All you need to do is to wire up your logic with enums and if-statements. With the GameState value, you can define states in which something specific is supposed to happen. For example:

if the State has an enemy => gameState = GameState.LoadEnemy => “load” the enemy values => gameState = GameState.Attack

Without the enums, the player could “attack” even if there isn’t anything. He could reload the game even though he has not lost or won. And so on.

Does that make sense?

Hi Nina,

thank you for the advise. I will start writing some code and let see how much far I will go :slight_smile:

Can you point me to some details about the declaration you used in your script, see below. I am not familiar with the Handset declaration.

HashSet<int> defeatedEnemyID = new HashSet<int>(20);

Should I incorporate the functions above in the main AdventureGame script or keep in separate classes?

HashSet allows you to add integers but only one integer. This way, you do not risk to add the same integer multiple times. Furhtermore, the HashSet is very fast when checking its content via the Contain method. The alternative would be to use a List or an array.

You can keep everything in the AdventureGame class for now. Once your solution is working, you can start refactoring your code. Since AdventureGame handles the actual game, it makes sense to have defeatedEnemyID in that class because you do not want to override anything in your State instances/objects.

1 Like

I was trying to replace the keyboard keys 1-2-3 with some buttons but I got stuck. I have added the main story object in the button “On click” and selected as function “ManageState”…the algorithm is looping in the state2 without even clicking any button… :cry:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class AdventureGame : MonoBehaviour
{

    [SerializeField] TextMeshProUGUI textComponent;
    [SerializeField] TextMeshProUGUI enemyStory;
    [SerializeField] State startingState;
    [SerializeField] Button [] DynamicButton;

    State state;



    // Use this for initialization
    void Start()
    {
        state = startingState;
        textComponent.text += state.GetStateStory(); // + "\n    Alex" + state.GetEnemyText();
                                                     //enemyStory.text += state.GetEnemyText();

        DynamicButton[0].interactable = true; // deactivate the attack buttons at the beginning of the game
        DynamicButton[1].interactable = true;
        DynamicButton[2].interactable = true;
        //DynamicButton[3].interactable = false;

    }

    // Update is called once per frame
    void Update()
    {
        ManageState();

        ManageEnemy();

       
    }

    public void ManageState()
    {
        var nextStates = state.GetNextStates();
       // if (Input.GetKeyDown(KeyCode.Alpha1))
       if (DynamicButton[0])
        {
            Debug.Log("state0" + DynamicButton[0].enabled);
            state = nextStates[0];
        }
        else if (DynamicButton[1])// (Input.GetKeyDown(KeyCode.Alpha2))
        {   Debug.Log("state1");
            state = nextStates[1];
            
        }
        else if (DynamicButton[2])//(Input.GetKeyDown(KeyCode.Alpha3))
        {   Debug.Log("state2");
            state = nextStates[2];
            
        }

I think the problem is the following: The if/else if conditions check whether there is an object at a specific index in the DynamicButton array. Since there is one at index 0 and since ManageState() gets executed each frame, the if-block gets executed each frame.

What you need is some kind of an event system. I suggested game states.

Furthermore, how are your buttons supposed to work? Are they clickable? If so, create a public method for each button and use the OnClick() field. This is covered in the Number Wizard UI section. The best would be to watch the videos and apply the knowledge you gained there to your adventure game.

Hi Nina,

I followed your suggestion and I found the solution. Now I have basically added to each state a string vector containing what I want each of my button to display every time the player access a different state.

All the main story game buttons are clickable. Each state can contain as many buttons as I want so the player can take multiple decisions leading to multiple states.
The text of the buttons depends on the state where the player is.

I am going now to work on the fight method and the inventory method. I got how to create a list of items for the inventory, but I still didn’t get how can I make the player to use or drop the items.

Little by little I am learning and sorting it out…

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class AdventureGame : MonoBehaviour
{

    [SerializeField] TextMeshProUGUI textComponent;
    [SerializeField] TextMeshProUGUI enemyStory;
    [SerializeField] State startingState;
    [SerializeField] Button [] DynamicButton;
    [SerializeField] TextMeshProUGUI[] buttonText;
    //[SerializeField] TextMeshProUGUI buttonText2;
    //[SerializeField] TextMeshProUGUI buttonText3;
    public string[] buttonString;

    State state;
    


    // Use this for initialization
    void Start()
    {
        state = startingState;
        textComponent.text += state.GetStateStory(); // + "\n    Alex" + state.GetEnemyText();
                                                     //enemyStory.text += state.GetEnemyText();

        DynamicButton[0].interactable = true; // deactivate the attack buttons at the beginning of the game
        DynamicButton[1].interactable = true;
        DynamicButton[2].interactable = true;
        //DynamicButton[3].interactable = false;

    }

    // Update is called once per frame
    void Update()
    {
        //ManageState();

        ManageEnemy();

       
    }

    public void Button1Down()
    {
        var nextStates = state.GetNextStates();
        Debug.Log("state0" + DynamicButton[0].enabled);
        state = nextStates[0];
        textComponent.text = state.GetStateStory() + state.GetEnemyText();
        //buttonText[0].text = state.GetButtonLabel.;
        buttonString = state.GetButtonLabel();
        buttonText[0].text = buttonString[0];

        

    }

Good job so far! :slight_smile:

Make the buttons work first. Then chose your next idea, and do not work on multiple ideas simultaneously because that’ll make debugging more difficult.

I would probably start with the fight method because you can reuse a part of it for your (more complex) inventory. In the fight method, all you have to do is to decrease the current enemy’s health and have the enemy perform an action. Start with logging a message into your console. Then proceed with decreasing the enemy’s health.

I completed the buttons, at least the one dedicated to the state transitions and to the fight.
I tested them and they are working…I am sure the code is not as efficient as an the one that an expert would write but it looks like it is doing its job.
The states are getting very busy and full of data. Is there a way to make them more readable? For example a way to replace the “Element” with my custom wording

Next step is now the inventory (for which I was opting for a list of items). Every item can be “used”, “dropped”, “examined”.

That’s absolutely fine. For now, you want to focus on making your idea work. Once everything is working, you can start refactoring your code.

The states are getting very busy and full of data. Is there a way to make them more readable?

Well, that’s the problem of scriptable objects, and everything that’s complex. Your scriptable object looks fine so far in my opinion. It’s something you can work with.

What you could do is to create a new type of scriptable object. Name it Enemy and “design” your enemy as you need it. Then create Enemy objects like you did with your story texts.

In your State class, replace the enemyStatsArray with a variable of type Enemy. Drag your Enemy scriptable object into it. (Yes, this works like the nextStates array but instead of a “next state”, you access an “enemy”.)

The Enemy class could also contain the enemy text, the max energy and whatever the enemy needs.

You also do not need a bool anymore. If there is an enemy assigned, you execute your code. And if there is no enemy assigned (== null), you don’t.

1 Like

That’s great Nina! it is working fine and it looks more smart solution.

Looking now at the dynamic of the game, I would like to add a sort of fading/blur effect on the buttons and the text. Let me see if I can explain what I mean.

When the player decides to hit a “Story control button” (different from the “Fight Control Button”) I would like the text to appear on the canvas with a sort of fade/blur effect…at the moment everything is instantaneous: the player hits a button and the text immediately appear on the canvas. Or the player wins a fight with a Boss and the Story control button immediately appear on the canvas.

Is there a way to add such a delay plus blur/fade effect to the text and to the objects in general?

Is there a way to add such a delay plus blur/fade effect to the text and to the objects in general?

You could fade in/out the text (the alpha value of the text colour) with a coroutine. That’s simple.

From my experience, the blur effect is relatively difficult to implement because you need to write a shader, and you need to control it via code to fade in/our the blurriness. Depending on the complexity of your game, this can have a noticeable negative impact on the performance of your game, so it is definitely advisable to dive into performance optimisation.

However, you could check if TextMeshPro had got a “blur” option for its texts. If that’s the case, you could try to control that value via code in your coroutine. TMP features are very performant.

1 Like

Thank you Nina.

Now I am facing a new challenge. I linked together all the scenes following the lesson with the Wizard GUI but when I thought that everything was under my control then I realised that I need something to pass the information of my character across different scenes…After hours I got stuck!

For example:

The inventory Scene needs to know what is the current status of the character, the current State and the status of the enemy ,if for example I want to use a spell from the inventory to hit the enemy.
The script below provides me an error:NullReferenceException: Object reference not set to an instance of an objectInventoryScene.GatherInventory () (at Assets/Scripts/InventoryScene.cs:26)
InventoryScene.Update () (at Assets/Scripts/InventoryScene.cs:18)

This is what I have done so far and then I literally stuck…I was trying to pass to the inventory scene the current value of the player energy. Nothing is displayed a part my debug message and also when I try to go back to the main game scene it loads me the state zero as I would start a new game.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class InventoryScene : MonoBehaviour
{

    [SerializeField] TextMeshProUGUI playerEnergy;
    AdventureGame Player;

    [SerializeField] AdventureGame adventureGame;



    public void Update()
    {
        GatherInventory();
    }
    public void GatherInventory()

    {

        playerEnergy.text = "Hello I am here";

        playerEnergy.text =  Player.character.Energy.ToString();

        





    }





}

I think I sorted out the issue.
I don’t think I need a new scene for the game menus. I think I can manage everything with different canvas activated in the Main game adventure using:

What do you think?

    public void DisableCoreGame()
    {
        canvasCoreGame.SetActive(false);



    }

    public void EnableCoreGame()
    {
        canvasCoreGame.SetActive(true);



    }

Looks fine. Does it work? If so, you could either create a “toggle”, or you could make use of a technique called method overloading to refactor your solution a bit. It depends on the purpose of the two methods. Whatever your idea is, instead of two, you could probably get the job done with one method.


The following could be a potential problem:

playerEnergy.text = "Hello I am here";
playerEnergy.text =  Player.character.Energy.ToString();

“Hello I am here” will get overridden within milliseconds by Player.character.Energy.ToString();. What was your idea?


NullReferenceException means that a reference (“link”) to an instance is missing. Double click on the error message to see to which line in your code it is referring. If you exposed a field in the Inspector, make sure that it’s not empty.

Unless I’m wrong, nothing was assigned to AdventureGame Player;.

Hi Nina,

all works fine so far. I also sorted out that error due to the Null reference as you suggested.

I am moving on with the inventory which it is working as expected. When I kill a Boss I automatically loot his body and get all the items he drops. The items are stored in a list. The items so far do not override each other, so if I proceed in a new state and kill a second Boss and loot his body the items are stored in bottom of the list.


Next steps:

-create methods to USE, DROP, EXAMINE and COMBINE items (sort of crafting).
I was thinking to manage them as follow: the player can click on each button.
The system will ask what he wants to USE, DROP, EXAMINE or COMBINE.
I will probably manage the actions with a Switch/Case or with IFs. Unless I get some other idea.

  • create an EQUIP method to use weapons
  • create a Gold method so the player can loot gold from the boss.
  • create Chests in the game where the player can open the chest and find:
    • gold
    • an enemy
    • an item etc.

Talking about the list of string: if there are two strings with the same name, for example if a Boss drops two keys. How can I display on the screen something like:

INVENTORY:
-gun
-2 * keys

  • Sword

That’s impressive. I’m happy to see that you made your inventory work. :slight_smile:

Regarding your question, I would suggest to create an enum with all your items, and use a Dictionary. The keys in dictionaries are unique. With an enum, you do not need to be afraid of typos which could ruin your solution.

For the visual representation of your inventory, you can simply convert the enum keys to strings. See here. And for the amount, you look up the value of the respective key in the Dictionary.

And if you want to format your string, look up “string concatenation”. Depending on your idea, you can also use if-statements.

1 Like

Privacy & Terms