If our world's going to have a bunch of creatures then we should start there. Add a list for them to the World class and initialize it in the World constructor.
private List<creature> creatures;
We need a way to get the creature at a specific location.
public Creature creature(int x, int y){
for (Creature c : creatures){
if (c.x == x && c.y == y)
return c;
}
return null;
}
And we'll update the addAtEmptyLocation method to make sure we don't add a creature where one already is. This will also be the place to add new creatures to our list.
public void addAtEmptyLocation(Creature creature){
int x;
int y;
do {
x = (int)(Math.random() * width);
y = (int)(Math.random() * height);
}
while (!tile(x,y).isGround() || creature(x,y) != null);
creature.x = x;
creature.y = y;
creatures.add(creature);
}
If we want multiple creatures then we have to change how we display the world. Currently we just display the player. Get rid of that line and let's change the displayTiles method to show any creatures in the section of the world we're interested in.
private void displayTiles(AsciiPanel terminal, int left, int top) {
for (int x = 0; x < screenWidth; x++){
for (int y = 0; y < screenHeight; y++){
int wx = x + left;
int wy = y + top;
Creature creature = world.creature(wx, wy);
if (creature != null)
terminal.write(creature.glyph(), creature.x - left, creature.y - top, creature.color());
else
terminal.write(world.glyph(wx, wy), x, y, world.color(wx, wy));
}
}
}
This is actually a very inefficient way to do this. It would be far better to draw all the tiles and then, for each creature, draw it if it is in the viewable region of left to left+screenWidth and top to top+screenHeight. That way we loop through
screenWidth * screenHeight tiles + the number of creatures
. The way I wrote we loop through screenWidth * screenHeight * the number of creatures
. That's much worse. I don't know why I didn't realize this when I first wrote this since I've always drawn the creatures after the tiles. Consider this an example of one way to not do it. You should be able to run it. It will look the same as before but now we can throw in some more creatures and they'll show up too. Our fungus first needs it's own ai:
package rltut;
public class FungusAi extends CreatureAi {
public FungusAi(Creature creature) {
super(creature);
}
}
And add it to our CreatureFactory so we can make them.
public Creature newFungus(){
Creature fungus = new Creature(world, 'f', AsciiPanel.green);
world.addAtEmptyLocation(fungus);
new FungusAi(fungus);
return fungus;
}
Now modify the PlayScreen to populate the world with fungus.
public PlayScreen(){
screenWidth = 80;
screenHeight = 21;
createWorld();
CreatureFactory creatureFactory = new CreatureFactory(world);
createCreatures(creatureFactory);
}
private void createCreatures(CreatureFactory creatureFactory){
player = creatureFactory.newPlayer();
for (int i = 0; i < 8; i++){
creatureFactory.newFungus();
}
}
If you run it now you should see some green f's standing about.
We've got some monsters but to way to slay them. Let's work on that now; starting with the Creature class.
public void moveBy(int mx, int my){
Creature other = world.creature(x+mx, y+my);
if (other == null)
ai.onEnter(x+mx, y+my, world.tile(x+mx, y+my));
else
attack(other);
}
public void attack(Creature other){
world.remove(other);
}
For now an attack will be an instant kill - just tell the world to get rid of the creature. Later on we can add hitpoints and whatnot.
Here's how the world class can remove a killed creature:
public void remove(Creature other) {
creatures.remove(other);
}
There you go. Stationary monsters waiting to be slain.
And now time to throw in a little something to make things interesting. What if the fungi were able to reproduce and spread? I really like games where each creature has something special instead of all being the same things with slightly different stats so this could be interesting. We first need to let each creature know when it's time to update itself and whatever else it wants to do for it's turn. First and add a method to the World class that lets each creature know it's time to take a turn.
public void update(){
for (Creature creature : creatures){
creature.update();
}
}
Your IDE is probably letting you know that the Creature class doesn't have an update method. Let's add one that delegates to the ai.
public void update(){
ai.onUpdate();
}
Now your IDE is probably letting you know that the CreatureAi class doesn't have an onUpdate method so go ahead and add an empty one. We want the fungi to spread to a nearby open space every once in a while as part of it's behavior during updating. Here's what I came up with:
package rltut;
public class FungusAi extends CreatureAi {
private CreatureFactory factory;
private int spreadcount;
public FungusAi(Creature creature, CreatureFactory factory) {
super(creature);
this.factory = factory;
}
public void onUpdate(){
if (spreadcount < 5 && Math.random() < 0.02)
spread();
}
private void spread(){
int x = creature.x + (int)(Math.random() * 11) - 5;
int y = creature.y + (int)(Math.random() * 11) - 5;
if (!creature.canEnter(x, y))
return;
Creature child = factory.newFungus();
child.x = x;
child.y = y;
spreadcount++;
}
}
You can play around with how far it spreads, how often it spreads, and how many times it can spread. Don't forget to modify the newFungus method to pass itself into the FungusAi constructor. The last thing you need to do is tell the world to let everyone take a turn by calling world.update() in the PlayScreen after handling user input. The user's input makes the player move and each creature can move after that. Since we're relying on Java's event handling it would be very cumbersome to make our code pause and wait for user input during the player's onUpdate like with many other roguelikes. Just one tiny detail left. If you run this then you will probably see some exceptions happen because we're adding new creatures to our list while looping through the same list — which you can't do. There are different ways of dealing with this but the easiest I know of is to just make a copy of the list and loop through that instead.
public void update(){
List<creature> toUpdate = new ArrayList<creature>(creatures);
for (Creature creature : toUpdate){
creature.update();
}
}
download the code
So now you've got some underground caves with a hero and some fungi. You can attack and they can spread. We're a bit closer to being an actual game. Next up, actual combat.
No comments:
Post a Comment