2

I am developing a game with different game entities. It works quiet well, but i would like to get rid of some casting operations in my code. For example, when i am checking if a Bullet hits an Enemy, i need to cast both objects to able to reduce the health (property of Enemy) based on the damage (property of Bullet).

I store the entities in a map together with their corresponding classes. It looks like this:

Map<Class<? extends Entity>, List<Entity>> entities;

Here are my methods to put, receive and remove entities from the map:

void add(Entity entity) {
    Class<? extends Entity> type = entity.getClass();
            
    if (!entities.containsKey(type)) {
        entities.put(type, new CopyOnWriteArrayList<>());
    }
    
    entities.get(type).add(entity);
}

List<Entity> getAll(Class<? extends Entity> type) {
    return entities.getOrDefault(type, Collections.emptyList());
}

void remove(Entity entity) {
    getAll(entity.getClass()).remove(entity);
}

Finally here is my code (which runs in a game loop) to check if a Bullet hits an Enemy:

for (Entity bullet : data.getAll(Bullet.class)) {
        for (Entity enemy : data.getAll(Enemy.class)) {
            if (bullet.box.overlaps(enemy.box)) {
                // Bullet hits Enemy
                Bullet bulletHit = (Bullet) bullet;
                Enemy enemyHit = (Enemy) enemy;
                
                enemyHit.health -= bulletHit.damage;
                if (enemyHit.health <= 0) {
                    data.remove(enemyHit);
                }
                data.remove(bulletHit);
                
                break;
            }
        }
    }

Is there any way to avoid these casting operations for the Bullet and the Enemy? One solution i was thinking about, is to get rid of the the map and just use many list of those specific entity types, but this would infalte my code.

DavidL
  • 21
  • 3
  • Do you plan to only store `List`s of non-generic `Object`s? – Turing85 Aug 16 '20 at 16:16
  • Whatever suits my problem. If generics is the answer, i'll use it. I was browsing Stack Overflow after your input and found this (https://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable). What do you think? – DavidL Aug 16 '20 at 16:27
  • I've edited lines 45 and 46 that you mentioned in my answer's comments if you'd like to [*give the lastest experiment a run*](https://www.browxy.com#USER_307884). So does that solve the original problem you set out to solve: „***any way to avoid these casting operations for the Bullet and the Enemy?***“ Or are you dead-set on an implementation that must be done with generics? 99% of the time, the simpler design is most often the most „*correct*“. – deduper Aug 16 '20 at 21:10

2 Answers2

3

…Is there any way to avoid these casting operations for the Bullet and the Enemy?…

TL;DR: Yes. There is. Consider this simple approach.



The long answer

Here's one way to do it (the simplest way, in my opinion)…

...
static void play(Gamer data){ 
     for ( Entity bullet : data.getAll(Bullet.class)) {
        for (Entity enemy : data.getAll(Enemy.class)) {
            if (bullet.getBox( ).overlaps( enemy.getBox( ) ) ) {
                // Bullet hits Enemy
                Damagable bulletHit = bullet;
                Illable enemyHit = enemy;
            
                int health = enemyHit.getHealth( );
                int damage = bulletHit.getDamage( );
                enemyHit.setHealth( health -= damage  );
                if ( health <= 0 ) {
                    data.remove(enemy);
                }
                data.remove(bullet);
            
                break;
            }
        }
    }       
}
...

This way involves the introduction of the two intefaces: Damagable and Illable (of course, you can rename those to anything that expresses your intent more precisely).

Like every design choice, there are trade-offs. But this is a simple way to get rid of those casts.

Click the green Start at the top of the page in that link to run the experiment



The following simplifying refactors emerged from the iterative process that I like to call „Experiment-driven Development“ (EDD)…

...
/*Damagable bulletHit = bullet;
Illable enemyHit = enemy;*/
            
int health = enemy/*Hit*/.getHealth();
int damage = bullet/*Hit*/.getDamage();
enemy/*Hit*/.setHealth(health -= damage);
...

The EDD process includes RPP („Remote Pair Programming“). From the insightful observations that @DavidL made while I „drove“ (see comments) he realized that a side effect of the simpler proposed solution makes his code even simpler than what his original question requested.

The original question being: „How to avoid casting from superclass?“ .

This proposed solution is so flexible that the OP spotted an opportunity to reduce his original code by 2 whole lines plus 9 unnecessary characters (the commented out stuff above).

…But then i would have to implement those interfaces for ALL entities…

In the proposed solution, you would have to implement Entity. You currently do that anyway. You'd have to implement it regardless of which design you go with.

Automatically-generated starter methods for any additional implementations would take no more effort than a single mouse click in a modern IDE. Unless you're writing your game in your OS's equivalent of TextEdit?

In your current code, you have to implement whatever behavior Bullet requires and whatever behavior Enemy calls for. Regardless of which design you opt for, you'd still have to implement those same behaviors.

deduper
  • 1,944
  • 9
  • 22
  • But then i would have to implement those interfaces for ALL entities. The Bullet should not be an Entity which is Damagable. – DavidL Aug 16 '20 at 17:37
  • I just saw your Code via browxy. Thank you for your time writing all of this. I did not know you can implement an interface again in a subclass, even though it has already been implemented by the superclass. – DavidL Aug 16 '20 at 17:42
  • „*...The Bullet should not be an Entity which is Damagable...*“ — This expression in your original code says otherwise: „_`enemyHit.health -= bulletHit.damage`_“. That _`.damage`_ property represents the _state_ of a _`Bullet`_. Semantically, it says: „_A `Bullet` can be in a `damaged` state_“. Put that in OO terms, that means a _`Bullet`_ is _`Damagable`_. Does that make any sense? – deduper Aug 16 '20 at 17:51
  • The `damage` property of the Bullet is an Integer. It defines how 'strong' the bullet is, or how much `damage` it can do to other things like the Enemy. Maybe my code is not that readable in that case. – DavidL Aug 16 '20 at 17:57
  • „*...Maybe my code is not that readable in that case...*“ — Not necessarily the case. It's just that's how ***I*** read it. The next guy might look at it exactly the way you intended it in your own mind. You weren't required to, and so you didn't go into the details of the semantics of the properties in your question. But that's the thing. In the absense of those kinds of semantic details, the code itself as you posted it, is open for interpretation. Also, you know your game better 'n me. So what I offered as a solution might give you a better idea of how to proceed with your own design. – deduper Aug 16 '20 at 18:06
  • „*...The `damage` property of the Bullet is an Integer. It defines how 'strong' the bullet is...*“ — You know you can rename _`Damagable`_ to be ***any name*** that relays the appropriate semantics for your game? Right? Same thing for _`Illable`_. ***You*** know ***your*** domain better than anybody. I simply answered your request: „*any way to avoid these casting operations for the Bullet and the Enemy?*“. You can take the ball and run with it however best ***you*** see fit. If you tell me a different, more suitable name, I won't hesitate to change the code in my experiment to whatever. – deduper Aug 16 '20 at 18:19
  • How does _`Fireable`_ strike you? Or what about _`Shootable`_? I'm partial to _`LandsAndGroovesable`_ because I'm a Forensic Science/CSI/Investigation Discovery show fan. But _`Bulletable`_ has a certain ring to it to. ;) Totally your call, my dude. – deduper Aug 16 '20 at 18:38
  • „*...I did not know you can implement an interface again in a subclass...*“ — I just realized what you were referring to. You mean: „_`Enemy extends Entity implements Illable`_ and „_`Bullet extends Entity implements Damagable`_? It's not illegal. But it wasn't intentional. Those were the leftovers of some rapid iterations of refactors I did as a result of my *Experiment Driven Development* (*aka, EDD*). I've [*edited those out now*](https://www.browxy.com#USER_307884) Anything else you want I should change? – deduper Aug 16 '20 at 19:16
  • In your implementaion it's not necessary anymore to change the type of the bullet and enemy. One can just leave the type as is. See line 45 and 46 of your DeduperAnswer class. – DavidL Aug 16 '20 at 19:38
  • „*...line 45 and 46 of your DeduperAnswer class...*“ — Sorry, I've switched context since my last comment. Lost my train of thought there. So, you'd like me to change lines 45 and 46? To what? ***EDIT***: Oh. I see what you mean: _`Entity bulletHit = ...`_ etc. Is that what you mean? So does that solve the original problem you set out to solve? – deduper Aug 16 '20 at 20:56
1

If we can guarantee that we only store non-generic objects within entities, then we can make the accessors type-safe (although we have to cast on one occasions but we will justify this cast). The concept we are going to use is very similar to what ArrayList does, using an Object[] as internal data structure.

Warning: the implementation groups objects by their class-type. Interface-types are completely ignored.

We are going to treat entities as internal data structure, i.e. we cannot allow to leak any references to entites to the outside. With this parameter set we introduce an internal method that gets a List from the map, casted to the correct type:

@SuppressWarnings("unchecked")
private <T extends Entity> List<T> getListCasted(Class<? extends T> type) {
    return (List<T>) entities.getOrDefault(type, Collections.emptyList());
}

Notice that this list is set private, i.e. only for class-internal usage.

Now, we rewrite getAll(...) and remove(...) to use this method:

private <T extends Entity> List<T> getAll(Class<? extends T> type) {
    return new ArrayList<>(getListCasted(type));
}

public void remove(Entity entity) {
    getListCasted(entity.getClass()).remove(entity);
}

Notice that getAll(...) now returns a (mutable) copy of the internal List.

We do not need to modify method add(...).

Ideone Demo

Now we need to justify the unchecked cast we made within getListCasted(...). If we take a look at method add(...), we see that the class-type (key) is the type of the containee of the list. Thus, we can guarantee that under the key of some Class<T extends Entity> a List<T> is stored. Therefore, the cast is justified.

We can even forego the bounding on the Map, using a Map<Class<?>, List<?>>.

Ideone Demo

Turing85
  • 18,217
  • 7
  • 33
  • 58
  • This looks great. It doesnt eliminate the casting problem, however it outsourced it nicely. – DavidL Aug 16 '20 at 18:14
  • Due to the nature of [type erasure](https://docs.oracle.com/javase/tutorial/java/generics/erasure.html), casting cannot be avoided. – Turing85 Aug 16 '20 at 18:15
  • „*...It doesnt eliminate the casting problem, however it outsourced it nicely...*“ So I take that to mean that although you asked for: „*any way to avoid these casting operations for the Bullet and the Enemy?*“, the absence of casting is not what you *really* wanted? So is what your top-most priority *really* is, that the solution *must* involve generics? – deduper Aug 16 '20 at 18:31
  • The best solution for me, would be to eliminate all casting operations without bloating up the code. But maybe that is not possbile in this case. I dont know how i would solve it with generics. – DavidL Aug 16 '20 at 19:48
  • „*...The best solution for me, would be to eliminate all casting operations without bloating up the code...*“ — [*My answer demonstrates that is possible*](https://www.browxy.com#USER_307884). — „*...I dont know how i would solve it with generics...*“ — Nobody loves generics as much as I do. But sometimes generics are overkill. Everybody goes through that phase during which they want to solve everything with generics. But we all learn eventually. You will too: *For a lotta problems, the best solution is plain, simple, good old-fashioned subtype polymorphism in the form of abstract interfaces*. – deduper Aug 16 '20 at 22:18