Increase enemies attributes when round finishes

I am developing a Top down shooter where the enemies will become harder each round. In order to do this I am multiplying their attributes in the constructor. Is this the right way or should I do it in the class where I manage the rounds?

    public GiantSpider(Vector2 _position,int _id) : base("2D\\Enemies\\Spider", _position, new Vector2(100, 100), _id)
        {
            
            speed = 1.0f + 0.2f * (GameGlobals.currentRound - 1);
            attackArea = 15.0f;
            healthMax = 120+10 * (GameGlobals.currentRound - 1);
            scoreAwarded = 175;
            damage = 60+5* (GameGlobals.currentRound - 1);
            currentHealth = healthMax;
        }

seams ok, but i would have it as a separate thing and not hard coded. That why the GiantSpider class becomes more versatile.

like so:

public GiantSpider(int difficulty, Vector2 _position,int _id) : base("2D\\Enemies\\Spider", _position, new Vector2(100, 100), _id)
        {
            
            speed = 1.0f + 0.2f * (difficulty - 1);
            attackArea = 15.0f;
            healthMax = 120+10 * (difficulty - 1);
            scoreAwarded = 175;
            damage = 60+5* (difficulty - 1);
            currentHealth = healthMax;
        }

I didn´t understand ,sorry :sweat_smile:

you mean like JSON?

No just look at the difference between my code and yours.

in my example i control the GiantSpider class by the int difficulty when i create it. That way if i want to create a Giant spider with the same difficulty as the level you do:

GiantSpider enemy = new GiantSpider(GameGlobals.currentRound, position, id);

it will be the same result as your code but you can now control the Giant spiders difficulty form outside the class. Making the class more reusable.

Can I have another opinion? I have this class to store all the values relatable with the gameplay. Is it okay to have it? Should I make the attributes protected?

{
    class GameGlobals
    {
        public static bool paused;
        public static int score;
        public static PassObject PassProjectile, PassEnemy,PassSpawnPoint,CheckScroll;
        public static int monstersKilled;
        public static int currentRound;
        public static int roundSpawns;
        public static int roundKills;
    }
}

This is a relay hard question to answer as all projects look different and demands different solutions. So this might be a very good solution or a bad one :slight_smile:

but… personally i try not to use globals in my code just as a general rule and try to pass things around insted like in my example with the GiantSpider above.

But if this is your first project i wouldn’t worry so much about it. If it works than you are fine! There are so many rules structuring code i doubt you can follow them all anyways… hmm that may upset some one if so a retract everything and state that i know nothing =)

I asked because this is my final project for university so I want to make this as good as I can

Well whatever the reason we should always strive for that :slight_smile:

i have sent this link to you in another topic its from someone that actually knows what he is talking about unlike me. https://gameprogrammingpatterns.com/contents.html maybe it will help you out.

Thanks :grin:

If you make them protected they will no longer be accessible. Protected means that they can be accessible by classes that inherit from this class. You want these value accessible everywhere.

As @Gronk suggested inheriting and using a formula makes more sense. This way if you have more then one type of enemy, like a giant rat and a giant spider, you can have them behave differently. It is important that they inherit from the same base class though. An abstract class would be best. You can then track all objects based on the base class. Something along the lines of:

public abstract class Enemy 
{
    protected int health;
    public abstract void SetHealth();
     public abstract int GetHealth(); 
 }
public class GiantSpider : Enemy 
{
    public override void SetHealth()
    {
           health = GameGlobals.currentRound * 6;
    }
    public override int GetHealth()
    {
        return health;    
    }
 }
public class GiantRat : Enemy 
{
    public override void SetHealth(int value)
    {
        health = GameGlobals.currentRound * 4;
    }
 }

If you need to add new values to your creatures, say magic, add that to the base class and implement it in your child class if they are common to all creatures. If it is specific to one creature add it to just that creature. You can even change behaviors. For example, if you added another creature ghost that stays away from the player add a new class Ghost that inherits from Enemy and add logic that it stays away and maybe fires ectoplasm at the player.

1 Like

What you have is fine for a one-off project. For stuff like that, as long at it works, you’re good. If you need to worry about future maintenance of extension, though… that’s about when you wanna start trying to follow “good” practices. In this case, how you get the data to your class (global or injection) is, ultimately, up to you; however, you may want to consider decoupling any given class from data that it doesn’t care about.

In your example, GiantRat maybe doesn’t care about some of the data in GameGlobals, so why does it need access to that? Thinking this way maybe helps you organize your data more tightly, and save you from passing around giant objects (global or otherwise).

I would argue (in my limited experience) that if you made up your own rules around how to use your globals and you follow them! Using a static class with a colection of varibals can be “good” practise.

Im probobly wrong but hear me out. if i start thinking about the all the formal “good” practticis i get nothing done. I understand that if we are more than one person on a project everything changes and its better to conforme to some standard. But as long as its just me i feal that it restricts me from making logic and forces me to think about structure.

2 Likes

If you are being graded on how object oriented your code is, they are not going to like the Global variables.

You are tying your GiantSpider class to the global variable. One problem with this is, if you want to reuse your class, then you will only be able to reuse it in projects that have the Global variable.

Another more general problem with Global variables is, it makes the code more difficult to understand, particularly in large projects with multiple programmers. If you have to change code that uses the global variable, you will have to scan the entire project to understand how it is used, and how your changes will affect the rest of the code. This can be a nightmare.

Gronk’s suggestion of passing in the difficulty as a parameter to the constructor solves this. The class is now reusable as it doesn’t have any external dependencies (such as the Global variable).

There are other benefits such as allowing you to have different difficulties on the same level.
Maybe the queen spider is twice as difficult so you can pass in 2*difficulty. She can then be on the same level as other giant spiders.

If you google “Why global variables are bad” you will get an idea of why your tutors might frown when grading your project.

Hope this helps.

I do have base classes because I intended to make different types of enemies from the begginig.Can I have a feedback on this? Also, I am trying to implement a cooldown to my characters because the game is to easy. Can I use time passed?

 public class Unit : Basic2D
    {
        protected bool isDead;
        protected float speed;
        protected float attackArea;           
        protected int damage;
        protected int currentHealth, healthMax;
        protected int scoreAwarded;
        protected int ownerId;
        protected int projectilesOnScreen;
        protected int maxProjectilesOnScreen;
        protected float range;          //how long can the unit shoot projectiles

        public Unit(string _path, Vector2 _position, Vector2 _dimensions,int _ownerId) : base(_path, _position, _dimensions)
        {
            isDead = false;
            ownerId = _ownerId;
        }

        public int GetDamage()
        {
            return damage;
        }

        public float GetAttackArea()
        {
            return attackArea;
        }

        public bool IsDead()
        {
            return isDead;
        }

        public int GetHealth()
        {
            return currentHealth;
        }
        public int GetHealthMax()
        {
            return healthMax;
        }

        public float GetSpeed()
        {
            return speed;
        }
        public int GetScoreAwarded()
        {
            return scoreAwarded;
        }

        public int GetProjectilesOnScreen()
        {
            return projectilesOnScreen;
        }

        public void SetProjectilesOnScreen(int _projectiles)
        {
            projectilesOnScreen = _projectiles;
        }

        public float GetRange()
        {
            return range;
        }

        public int GetOwnerId()
        {
            return ownerId;
        }

        public void SetOwnerId(int _ownerId)
        {
            ownerId = _ownerId;
            
        }
        public virtual void Update(Vector2 _offset,Player _player)
        {
            base.Update(_offset);
        }
        
        public virtual void GetHit(int _damageTaken)
        {
           
            if (_damageTaken >= currentHealth)
            {
                currentHealth = 0;
                isDead = true;
            }
            else
            {
                currentHealth -= _damageTaken;
            }
        }
        public override void Draw(Vector2 _offset)
        {
            base.Draw(_offset);
        }
    }


    public class Enemy : Unit
    {
        public Enemy(string _path, Vector2 _position, Vector2 _dimensions, int _id) : base(_path, _position, _dimensions,_id)
        {
            
        }

       
        public override void Update(Vector2 _offset, Player _player)
        {
            ArtificialIntelligence(_player.GetCharacter());
            base.Update(_offset);
        }


        //the enemy will move to the point where the player currently is
        public virtual void ArtificialIntelligence(Character _character)
        {
            SetPosition(GetPosition() + Globals.RadialMovement(_character.GetPosition(), this.GetPosition(), this.speed));
            SetRotation(Globals.RotateTowards(this.GetPosition(), _character.GetPosition()));
            
            if(Vector2.Distance(this.GetPosition(), _character.GetPosition()) < this.GetAttackArea())
            {
                _character.GetHit(GetDamage());
                isDead = true;
            }

        }
        public override void Draw(Vector2 _offset)
        {
            base.Draw(_offset);
        }
    }
public class Character : Unit
    {
        public Character(string _path, Vector2 _position, Vector2 _dimensions,int _ownerId) : base(_path, _position, _dimensions,_ownerId)
        {
            projectilesOnScreen = 0;   
        }

        public override void Update(Vector2 _offset)
        {
            bool checkScroll = false;

            if (Globals.keyboard.GetPress("A"))
            {
                SetPosition(new Vector2(GetPosition().X - speed, GetPosition().Y));
                checkScroll = true;
            }
            if (Globals.keyboard.GetPress("D"))
            {
                SetPosition(new Vector2(GetPosition().X + speed, GetPosition().Y));
                checkScroll = true;
            }
            if (Globals.keyboard.GetPress("W"))
            {
                SetPosition(new Vector2(GetPosition().X, GetPosition().Y - speed));  //Y is inverted, this means that 0 is on top .So if the player wants to go up Y has do decrease 
                checkScroll = true;
            }

            if (Globals.keyboard.GetPress("S"))
            {
                SetPosition(new Vector2(GetPosition().X, GetPosition().Y + speed));   //Again, Y is inverted
                checkScroll = true;
            }

            if (checkScroll)
            {
                GameGlobals.CheckScroll(GetPosition());
            }


            SetRotation(Globals.RotateTowards(GetPosition(), new Vector2(Globals.mouseControl.newMousePos.X, Globals.mouseControl.newMousePos.Y) - _offset));
        }

        public override void Draw(Vector2 _offset)
        {
            base.Draw(_offset);
        }
    }

This is good advice! Especially if you are planing on braking the “rules”. If you can explain the the dangers to your professor’s will know that you, at least, thought about the problem.

1 Like

Yes, you can use elapsed time. I’m guessing your firing code is in your player class. Can you share that code?

I think I am seeing a lot of “errors” in the tutorial I am following. For example , I have this Player classes , but I really think they are unnecessary because I can do the updates in World

 public class User : Player
    {
        

        public User(int _id) : base(_id)
        {
            character = new Archer(new Vector2(300, 300),id);
        }

        public override void Update(Player _player, Vector2 _offset)
        {
            base.Update(_player, _offset);
        }

      
    }
  public class AIPlayer : Player
    {
        

        public AIPlayer(int _id) : base(_id)
        {
            spawnPoints.Add(new SpawnPoint(new Vector2(50, 50)));
            spawnPoints.Add(new SpawnPoint(new Vector2(Globals.screenWidth / 2, 50)));
            spawnPoints[spawnPoints.Count - 1].GetSpawnTimer().AddToTimer(500);
            spawnPoints.Add(new SpawnPoint(new Vector2(Globals.screenWidth - 50, 50)));
            spawnPoints[spawnPoints.Count - 1].GetSpawnTimer().AddToTimer(1000);
        }

        public override void Update(Player _player ,Vector2 _offset)
        {
            base.Update(_player, _offset);
        }

        public override void ChangeScore(int _score)
        {
            GameGlobals.score += _score;
        }

        
    }


    public class User : Player
    {
        

        public User(int _id) : base(_id)
        {
            character = new Archer(new Vector2(300, 300),id);
        }

        public override void Update(Player _player, Vector2 _offset)
        {
            base.Update(_player, _offset);
        }

      
    }
 public class World
    {
        private Vector2 offset;
        private UI ui;
        private List<Projectile> projectiles;
        private PassObject resetWorld,changeGameState;
        private User user;
        private AIPlayer aiPlayer;


        public World(PassObject _resetWorld, PassObject _changeGameState)
        {
            projectiles = new List<Projectile>();
            resetWorld = _resetWorld;
            changeGameState = _changeGameState;
            user = new User(1);
            aiPlayer = new AIPlayer(2);
            GameGlobals.currentRound = 1;
            GameGlobals.roundSpawns = 0;
            GameGlobals.roundKills = 0;

            GameGlobals.PassProjectile = AddProjectile;
            GameGlobals.PassEnemy = AddEnemy;
            GameGlobals.CheckScroll = CheckScroll;
            GameGlobals.PassSpawnPoint = AddSpawnPoint;
            GameGlobals.paused = false;

            offset = new Vector2(0, 0);

            offset = new Vector2(0, 0);

            ui = new UI(resetWorld);
        }

        public User GetUser()
        {
            return user;
        }

        public void Update()
        {
            if (!user.GetCharacter().IsDead() && !GameGlobals.paused)
            {
                user.Update(aiPlayer, offset);
                aiPlayer.Update(user, offset);

               

                for (int i = 0; i < projectiles.Count; i++)
                {
                    projectiles[i].Update(offset, aiPlayer.GetUnits().ToList<Unit>());
                    if (projectiles[i].IsDone())
                    {
                        projectiles.RemoveAt(i);
                        i--;
                    }
                }

                //change round
                if (GameGlobals.roundKills == 10 + 5 * GameGlobals.currentRound)
                {
                    changeGameState(2);
                }
            }
            
            if (Globals.keyboard.GetSinglePress("Space"))
                {
                    GameGlobals.paused = !GameGlobals.paused;
                }

            ui.Update(this);
        }

        public virtual void AddProjectile(object _info)
        {
             projectiles.Add((Projectile)_info);
        }

        public virtual void AddEnemy(object _info)
        {

            Unit tempUnit = (Unit)_info;
            if (user.GetId() == tempUnit.GetOwnerId())
            {
                user.AddUnit(tempUnit);
            }
            else if (aiPlayer.GetId() == tempUnit.GetOwnerId())
            {
                aiPlayer.AddUnit(tempUnit);
            }
            aiPlayer.AddUnit((Enemy)_info);
        }

        public virtual void AddSpawnPoint(object _info)
        {

            SpawnPoint tempSpawn = (SpawnPoint)_info;
            if (user.GetId() == tempSpawn.GetOwnerId())
            {
                user.AddSpawnPoint(tempSpawn);
            }
            else if (aiPlayer.GetId() == tempSpawn.GetOwnerId())
            {
                aiPlayer.AddSpawnPoint(tempSpawn);
            }
            
        }


        public virtual void CheckScroll(object _info)
        {
            Vector2 tempPosition = (Vector2)_info;

            if (tempPosition.X < -offset.X + (Globals.screenWidth * .4f))
            {
                offset = new Vector2(offset.X + user.GetCharacter().GetSpeed() * 2, offset.Y);
            }

            if (tempPosition.X > -offset.X + (Globals.screenWidth * .6f))
            {
                offset = new Vector2(offset.X - user.GetCharacter().GetSpeed() * 2, offset.Y);
            }

            if (tempPosition.Y < -offset.Y + (Globals.screenHeight * .4f))
            {
                offset = new Vector2(offset.X, offset.Y + user.GetCharacter().GetSpeed() * 2);
            }

            if (tempPosition.Y > -offset.Y + (Globals.screenHeight * .6f))
            {
                offset = new Vector2(offset.X, offset.Y - user.GetCharacter().GetSpeed() * 2);
            }
        }

        public void Draw(Vector2 _offset)
        {
            user.Draw(offset);
            aiPlayer.Draw(offset);

            for (int i=0; i<projectiles.Count; i++)
            {
                projectiles[i].Draw(offset);
            }

            ui.Draw(this);
        }
    }

Here is my Character class and the class of one of my characters

  public class Character : Unit
    {
        public Character(string _path, Vector2 _position, Vector2 _dimensions,int _ownerId) : base(_path, _position, _dimensions,_ownerId)
        {
            projectilesOnScreen = 0;   
        }

        public override void Update(Vector2 _offset)
        {
            bool checkScroll = false;

            if (Globals.keyboard.GetPress("A"))
            {
                SetPosition(new Vector2(GetPosition().X - speed, GetPosition().Y));
                checkScroll = true;
            }
            if (Globals.keyboard.GetPress("D"))
            {
                SetPosition(new Vector2(GetPosition().X + speed, GetPosition().Y));
                checkScroll = true;
            }
            if (Globals.keyboard.GetPress("W"))
            {
                SetPosition(new Vector2(GetPosition().X, GetPosition().Y - speed));  //Y is inverted, this means that 0 is on top .So if the player wants to go up Y has do decrease 
                checkScroll = true;
            }

            if (Globals.keyboard.GetPress("S"))
            {
                SetPosition(new Vector2(GetPosition().X, GetPosition().Y + speed));   //Again, Y is inverted
                checkScroll = true;
            }

            if (checkScroll)
            {
                GameGlobals.CheckScroll(GetPosition());
            }


            SetRotation(Globals.RotateTowards(GetPosition(), new Vector2(Globals.mouseControl.newMousePos.X, Globals.mouseControl.newMousePos.Y) - _offset));
        }

        public override void Draw(Vector2 _offset)
        {
            base.Draw(_offset);
        }
    }

class Mage : Character
    {
        public Mage(Vector2 _position,int _ownerId) : base("2D\\Characters\\mage", _position, new Vector2(60, 60),_ownerId)
        {
            speed = 5.0f;
            healthMax = 130;
            attackArea = 35.0f;
            damage = 50;
            currentHealth = healthMax;
            maxProjectilesOnScreen = 3;
            range = 250f;
        }

        public override void Update( Vector2 _offset)
        {

            base.Update(_offset);

            if (Globals.mouseControl.LeftClick())
            {
                GameGlobals.PassProjectile(new Fireball(new Vector2(GetPosition().X, GetPosition().Y), this, new Vector2(Globals.mouseControl.newMousePos.X, Globals.mouseControl.newMousePos.Y) - _offset));
               
            }

            base.Update(_offset);
        }

    }

The problem is you are not passing the elapsed game time to your classes. Unless I’m missing it in your globals. What you could do is in your Update method when you check to see if you can fire is check to see if the elapsed time is greater than the cool down. Something like.

        private double timeElapsed = 0;

        public override void Update( Vector2 _offset)
        {
            timeElapsed += GameGlobals.gameTime.ElapsedTime.TotalSeconds; // Add a GameTime property to your GameGlobals and set it in the Update method of your Game1 class
            
            base.Update(_offset);

            if (Globals.mouseControl.LeftClick() && timeElapsed > 2) // 2 is the number of seconds between shots
            {
                GameGlobals.PassProjectile(new Fireball(new Vector2(GetPosition().X, GetPosition().Y), this, new Vector2(Globals.mouseControl.newMousePos.X, Globals.mouseControl.newMousePos.Y) - _offset));
                timeElapsed = 0; // reset the timer
            }
        }
    }