Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (481)
Games in Android Showcase (110)
games submitted by our members
Games in WIP (548)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Creating lots of different enemies (Inheritance, polymorphism, etc.)  (Read 2299 times)
0 Members and 1 Guest are viewing this topic.
Offline EatenByAGrue

Junior Member


Medals: 3



« Posted 2013-03-04 02:06:04 »




Hey fellas,

I currently have run into a problem while creating lots of enemies - not that it doesn't work, I'm just not sure what the best approach would be.

I have about 30 different enemy-types (ants, rats, orcs, dragons, etc.) and they all are described by the same fields. Therefore I have created a superclass called enemy:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
public abstract class Enemy
{
   /* General Information */
   private String type;
   private int ac;
   
   /* Health related */
   private int maxHP;
   
   private int attackBonus;
   private int movement;
   
   /* Attack related */
   private String attType;
   private int attNo;
   private int damage;
   private int saveAs;
   private int morale;
   
   private int appearingNo;   // determines how many monsters of this type appear
 
   /* Drops */
   private String treasureType;
   private int exp;
   
   public Enemy(String type, int ac, int maxHP, int attackBonus, int movement, String attType, int attNo, int damage, int saveAs, int morale, int appearingNo, String treasureType, int exp) {
      this.type = type;
      this.ac = ac;
      this.maxHP = maxHP;
      this.attackBonus = attackBonus;
      this.movement = movement;
      this.attType = attType;
      this.attNo = attNo;
      this.damage = damage;
      this.saveAs = saveAs;
      this.morale = morale;
      this.appearingNo = appearingNo;
      this.treasureType = treasureType;
      this.exp = exp;
   }
}


Now when I want to create a giant ant for example I need to call the super.constructor:

1  
2  
3  
4  
5  
6  
public class MobGiantAnt extends Enemy
{
   public MobGiantAnt(String type, int ac, int maxHP, int attackBonus, int movement, String attType, int attNo, int damage, int saveAs, int morale, int appearingNo, String treasureType, int exp) {
      super(type, ac, maxHP, attackBonus, movement, attType, attNo, damage, saveAs, morale, appearingNo, treasureType, exp);
   }
}


Now here is where my question comes to play. All ants share the same stats (same type, same ac, same maxHP, etc.). I obviously don't want to type in all parameters when I create a new ant but rather have new MobGiantAnt() automatically create an ant with the shared base values. Long story short: Is it better to use static fields (as they are shared by all objects created from that class to my understanding).

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
public class MobGiantAnt extends Enemy
{
   final private static String type = "Giant Ant";
   final private static int ac = 17;
   final private maxHP = ...
         .
         .
         .
         .
     
         
   public MobGiantAnt() {
      super(type, ac, maxHP, attackBonus, movement, attType, attNo, damage, saveAs, morale, appearingNo, treasureType, exp);
   }
}


Or to change the fields in the Enemy class to protected and do this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
public class MobGiantAnt extends Enemy
{        
   public MobGiantAnt() {
      this.type = "Giant Ant";
      this.ac = 17;
         .
         .
         .
   }
}


Excuse me if this is a stupid question, but I often get stuck on stuff like that :/

To my understanding protected means that only classes in the same package have access to the fields, but generally use of private fields and getters/setters is suggested. Is it good to have different subclasses for each enemy at all?

It is pitch black. You are likely to be eaten by a grue.
Offline Axeman

Senior Member


Medals: 7



« Reply #1 - Posted 2013-03-04 02:29:33 »

A static variable means that there is only one copy of that variable and it belongs to the class. So you can´t create a lot of objects containing a static "ac" and then expect to behave like you have a unique copy of "ac" for every instance of that class. So basically, if you change ac in MobGiantAnt you will change it for every MobGiantAnt. If it´s static every MobGiantAnt will always have the same ac. This might not be a problem in your game, but it´s a good thing to be aware of... Smiley
Offline ReBirth
« Reply #2 - Posted 2013-03-04 02:40:30 »

Use enum.

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline arnaud_couturier
« Reply #3 - Posted 2013-03-04 03:01:20 »

Object wank IMO.
Your solution looks fine. Just use whatever sounds logical to you. When it doesn't anymore, change to something more logical. Repeat.
The important thing is not how your code is structured, it's how well and quick you can understand it, now and in 3 months.
Offline Rorkien
« Reply #4 - Posted 2013-03-04 03:08:51 »

Well i'd rather have everything set up inside the constructor than passing every value as a parameter megazord.
Yet the first seems easier to read/change. Why not mix both?

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
public class MobGiantAnt extends Enemy
{
   final private static String type = "Giant Ant";
   final private static int ac = 17;
   final private maxHP = ...
         ...  
         
   public MobGiantAnt() {
      super();
      this.type = type;
      this.ac = ac;
         ...
   }
}
Offline actual

JGO Coder


Medals: 23



« Reply #5 - Posted 2013-03-04 04:00:54 »

Another option which might work for you is to keep all of your stats in a text file and then read it in to an object whose responsibility it is to just store the stats. For instance you could have a file like so...

[ANT]
ac = 17
maxHp = 36
...
...
[DRAGON]
ac = 255
...
...


You could then have a simple object (Call it MonsterStats) that has fields for each of the parameters you have (type, ac, maxHp, etc). At start up you read in the file and create a hashmap of monster type to MonsterStats. Then in your Enemy class you read in the MonsterStats object as a whole rather than all of the individual values.
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
public Enemy(MonsterStats stats) {

   maxHp = stats.maxHp;
  ....
}

// sub class constructor

public GiantAnt() {
   super(MonsterStatLibrary.get("ANT"));
}


With this approach, if you want to add or remove a stat, you don't have to go through every subclass and change every call to super, you just have to change Enemy and MonsterStats. Personally, I also prefer to have the data defined in an external file which makes it easier to see how various monsters compare and makes tweaking values easier.



EDIT
Here is another option that might give you some ideas that is somewhat along the lines you are looking for.


  
Offline 65K
« Reply #6 - Posted 2013-03-04 06:57:29 »

If you do not need to implement different behaviour, you do not need sub classes (now). Add individual classes later if required.

Instead of supplying 13 different parameters (unreadable code) for each object creation, rather invent some kind of enemy factory/builder which allows to get new objects by a, for example, simple createEnemy(enemyType) call. Then you don't have to hide and spread parameters which will be adjusted a lot of times during development. Or add special create methods like

createBossEnemy()
createEnemy(enemyType, boolean juggleHitpoints)
createEnemey(enemyType, Map properties)

Whatever.

For constructors, only force to submit those parameters which must be specified to get a full working object, assign default values to others.

Offline tyeeeee1
« Reply #7 - Posted 2013-03-04 19:05:37 »

You could also just set up a small database with pre-created enemies stored within and then load them and all of their stats when needed.
Offline JESTERRRRRR

Senior Member


Medals: 7
Exp: 1 year



« Reply #8 - Posted 2013-03-04 19:09:56 »

if things like maxHP are the same per ant your static one looked fine imo, i'd just roll with it and get on with the game
Offline LaughingSeraphim

Junior Newbie





« Reply #9 - Posted 2013-03-04 22:02:41 »

I am new to java , but in other languages I think the suggestion given by 65k would be most correct IMO.
If you pass an enemy type, and level to the constructor, you can set all of that stuff up pretty easily.

In fact, I did a similar thing in a c class some years back. The code to setup the monsters stats need not even be in the enemy super class, and in fact it may be better to remove it for sanity sake. Use whatever means you want to pass the monster type to the constructor. An enum works. Then inside of the constructor, you can have the values be something like

this.ac = rpgKit.getACstat(MonsterType, Level);
this.hp = rpgKit.getHPstat(MonsterType,Level);

then in the rpgKit, getACstat can use a straightforward case selection(switch) statement to determine which stat to return on that call.

switch MonsterType
{
case 0:
     //--rat AC--//
    ac = level \ 2;
    break;
case 1:
    //--dasterFunk Zombie--//
   ac =  (level/3) + 5;
}

boss switch can be done in couple ways
Boss type monster, then use the level to determien stuff, spawnCritter(Boss,1);//--rat boss--//
regular monster type, with a level of -1,-2 etc to represent the Boss level spawnCritter(RAT,-20); lvl 2 rat boss

or, use a subclass for the bosses, which makes it possible to determine other behaviors like healing and summons, without having those things isntantiated with enemies that never use them.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline ReBirth
« Reply #10 - Posted 2013-03-05 02:02:25 »

You can also use something like json or xml so you can easily adjust stats later.

Offline wreed12345

JGO Knight


Medals: 24
Projects: 2
Exp: 2 years


http://linebylinecoding.blogspot.com/


« Reply #11 - Posted 2013-03-05 02:18:49 »

What benefits do you get from setting up enemy statistics through something like xml or a plain text file compared to simply coding the stats into the game? Just easier modifying?

Offline arnaud_couturier
« Reply #12 - Posted 2013-03-05 02:38:08 »

None while developing, but it "looks" like it's the proper way.

Once the game is released, it can be useful for players and modders though, or if non-coders need to tweak those values.
Offline ReBirth
« Reply #13 - Posted 2013-03-05 02:38:52 »

Yes, depends on how hard you will modify (adjust) it or if you're on team of dev. However you'll ofc hardcore it on release.

Offline actual

JGO Coder


Medals: 23



« Reply #14 - Posted 2013-03-05 03:34:47 »

Some of it comes down to personal preference (I prefer to use this sort of set up when I can). I find having a list of options/parameters in a config file is easier for me to manage. You can also do some nice things like in this example, imagine having a CSVfile with each stat as a column and each row is a Monster. You could then easily see how various monsters compare. You do have to write some code to read and parse the file format but tends to be a pretty simple thing to do.
Offline EatenByAGrue

Junior Member


Medals: 3



« Reply #15 - Posted 2013-03-31 13:53:07 »

Thanks for all the replies they definitely helped!
I now use Properties to read the attributes from external text files.
This is what my system looks like now:


1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
public abstract class Entity
{
   final private FileHandler fh = FileHandler.getInstance();
   
   /* General Information */
   private String name;
   private String race;
   
   private int cb;
   private int cn;
   private int cr;
   private int in;
   private int mg;
   private int nt;
   private int st;
   private int wp;
   
   private int dv;
   private int mv;
   private int sz;
   private int lf;
   private int hp;
   
   /* Abstract methods */
   @Override
   public abstract String toString();
   
   /* Constructors */
   protected Entity() {}
   
   /* Concrete Methods */
   protected void initializeAbilities(String path) {
      race = fh.loadProperty("race", path);
      cb = Integer.parseInt(fh.loadProperty("cb", path));
      cn = Integer.parseInt(fh.loadProperty("cn", path));
      cr = Integer.parseInt(fh.loadProperty("cr", path));
      in = Integer.parseInt(fh.loadProperty("in", path));
      mg = Integer.parseInt(fh.loadProperty("mg", path));
      nt = Integer.parseInt(fh.loadProperty("nt", path));
      st = Integer.parseInt(fh.loadProperty("st", path));
      wp = Integer.parseInt(fh.loadProperty("wp", path));
      dv = Integer.parseInt(fh.loadProperty("dv", path));
      mv = Integer.parseInt(fh.loadProperty("mv", path));
      sz = Integer.parseInt(fh.loadProperty("sz", path));
      lf = Integer.parseInt(fh.loadProperty("lf", path));
   }
   
   /* Getters && Setters */
   public String getName() {
      return name;
   }
   .
   .
   .
   .
   .

}


1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
public abstract class Enemy extends Entity
{
   final private FileHandler fh = FileHandler.getInstance();
   final private Random rnd = Randomizer.getInstance();
   
   /* Attributes */
   private int sp;      // Speed
 
   public Enemy(String namePath, String propPath) {
      setName(loadRandomName(namePath));
      initializeAbilities(propPath);
   }
   
   private String loadRandomName(String path) {
      ArrayList<String> names = fh.readCompleteFile(path);   // Stores all names from the file
     String name = null;         // The name to be returned
     int index;               // The index number of the item to pick
     
      /* Provide a random index in range of the array list and select the
       * according item in the list */

      while (name == null) {
         index = rnd.nextInt(names.size());
         name = names.get(index).trim();
      }
     
      return name;
   }
   
   @Override
   protected void initializeAbilities(String path) {
      super.initializeAbilities(path);
      sp = Integer.parseInt(fh.loadProperty("sp", path));
   }
   
   @Override
   public String toString() {
// Ignore this - it's just for testing
     System.out.println("Name: " + getName());
      System.out.println("Race: " + getRace());
      System.out.println("CB: " + getCb());
      System.out.println("CN: " + getCn());
      System.out.println("CR: " + getCr());
      System.out.println("IN: " + getIn());
      System.out.println("MG: " + getMg());
      System.out.println("NT: " + getNt());
      System.out.println("ST: " + getSt());
      System.out.println("WP: " + getWp());
      System.out.println("DV: " + getDv());
      System.out.println("MV: " + getMv());
      System.out.println("SZ: " + getSz());
      System.out.println("LF: " + getLf());
      System.out.println("SP: " + getSp());
      return null;
   }
   
   /* Getters && Setters */
   public int getSp() {
      return sp;
   }
}


1  
2  
3  
4  
5  
6  
7  
8  
9  
public class Zombie extends Enemy
{
   final private static String NAMEPATH = "files/enemies/names/zombie.name";
   final private static String PROPPATH = "files/enemies/races/zombie.prop";
   
   public Zombie() {
      super(NAMEPATH, PROPPATH);
   }
}


It is pitch black. You are likely to be eaten by a grue.
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

atombrot (26 views)
2014-08-19 09:29:53

Tekkerue (24 views)
2014-08-16 06:45:27

Tekkerue (23 views)
2014-08-16 06:22:17

Tekkerue (14 views)
2014-08-16 06:20:21

Tekkerue (22 views)
2014-08-16 06:12:11

Rayexar (60 views)
2014-08-11 02:49:23

BurntPizza (39 views)
2014-08-09 21:09:32

BurntPizza (30 views)
2014-08-08 02:01:56

Norakomi (37 views)
2014-08-06 19:49:38

BurntPizza (67 views)
2014-08-03 02:57:17
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!