This site requires JavaScript, please enable it in your browser!
Greenfoot back
JDRSkiing
JDRSkiing wrote ...

2015/3/21

Taking damage and disappearing ...

JDRSkiing JDRSkiing

2015/3/21

#
Hi, I have a few questions relating to issues im having with my current asteroid/spaceship game: 1) I managed to get the asteroid to disappear after being hit by 4 bullets but then when i tried to also make the bullets disappear on impact of the asteroids, the asteroids stopped disappearing .... could anyone tell me why? 2)I have Asteroids being generated randomly but there are far too many and too often ... how might i change this? I'll attach my code for bullets and asteroids below and if anyone could help that would be great! import greenfoot.*; /** * Write a description of class Asteriod here. * @author (your name) * @version (a version number or a date) */ public class Asteriod extends Actor { private int damage = 0; /** * Act - do whatever the Asteriod wants to do. This method is called whenever * the 'Act' or 'Run' button gets pressed in the environment. **/ public void act() { destroyShip(); takeDamage(); if (getWorld() == null) return; //ensure that 'removeAtEdge' will not be executed if the object is not, at this point', in the world// removeAtEdge(); if (getWorld() != null) { moveLeft(); } } public void takeDamage() { if(canSee(Bullet.class)) { damage = damage+1; } if(damage > 3) { getWorld().removeObject(this); return; } } public void removeAtEdge() { if (getX() == 0) { getWorld().removeObject(this); return; } } public void destroyShip() { Spaceship spaceship = (Spaceship) getOneIntersectingObject(Spaceship.class); if (spaceship != null) { getWorld().removeObject(spaceship); Greenfoot.stop(); } } public void moveLeft() { setLocation(getX()-4,getY()); } /** * Return true if we can see an object of class 'clss' right where we are. * False if there is no such object here. */ public boolean canSee(Class clss) { Actor actor = getOneObjectAtOffset(0, 0, clss); return actor != null; } } import greenfoot.*; /** * Write a description of class Bullet here. * * @author (your name) * @version (a version number or a date) */ public class Bullet extends Actor { private Spaceship spaceship; /** * Constructor for a Bullet. **/ public Bullet(Spaceship spaceship) { this.spaceship = spaceship; } /** * Act - do whatever the Bullet wants to do. This method is called whenever * the 'Act' or 'Run' button gets pressed in the environment. */ public void act() { move(5); vanishOnImpact(); if (getWorld() == null) return; vanishAtEdge(); } private void vanishOnImpact() { if(canSee(Asteriod.class)) { getWorld().removeObject(this); } } /** * If at the edge of the world, then disappear. **/ private void vanishAtEdge() { if (atWorldEdge()) { getWorld().removeObject(this); } } /** * Test if we are close to one of the edges of the world. Return true is we are. **/ public boolean atWorldEdge() { if(getX() < 10 || getX() > getWorld().getWidth() - 10) return true; if(getY() < 10 || getY() > getWorld().getHeight() - 10) return true; else return false; } /** * Return true if we can see an object of class 'clss' right where we are. * False if there is no such object here. */ public boolean canSee(Class clss) { Actor actor = getOneObjectAtOffset(0, 0, clss); return actor != null; } }
danpost danpost

2015/3/21

#
The problem is in that you are not dealing with both actions at the same time (or in the proper order). The actions I am referring to are (1) the asteroid taking damage and (2) the bullet being removed from the world. If the bullet acts first and is removed from the world, then the asteroid will not find an intersecting bullet. The easy fix, although not the programmatically correct way in this case, is to use the World instance method 'setActOrder' to force the asteroid to act first. The 'proper' way is to have the asteroid remove the bullet when taking damage remembering to remove the 'vanishOnImpact' method from the bullet class. Lesson learned: as a general rule, never check for the same collision (or action) more than once if possible.
JDRSkiing JDRSkiing

2015/3/21

#
Thank you. I have tried removing the Bullet within the takeDamage() method but it doesn't seem to do anything. Do i need to create a new method for removing the bullet?
danpost danpost

2015/3/21

#
JDRSkiing wrote...
Thank you. I have tried removing the Bullet within the takeDamage() method but it doesn't seem to do anything. Do i need to create a new method for removing the bullet?
You do not need to. The Actor class has a 'removeTouching' method that is similar to the 'eat' method of the Animal class. In fact you really do not need the 'canSee' method either as the Actor class also has an 'isTouching' method which is similar to it. Your 'takeDamage' method can be written as follows:
1
2
3
4
5
6
7
8
9
public void takeDamage()
{
    if(isTouching(Bullet.class))
    {
        removeTouching(Bullet.class);
        damage = damage+1;
        if(damage > 3) getWorld().removeObject(this);
    }
}
A 'return' statement at the end of a method is a bit redundant as the method will be exited from immediately anyway. If there had happened to be additional code within the method beyond that point, the 'return' statement would had most probably been necessary. Also, I moved the check on the damage count to inside the initial 'if' clause because you only need to check its value when it is increased -- not every act cycle.
JDRSkiing JDRSkiing

2015/3/22

#
That's a great help, thank you
JDRSkiing JDRSkiing

2015/3/22

#
Sorry, just one more thing. I noticed you made a piece of code for someone that created a health counter as shown below: import greenfoot.*; public class HealthBar extends Actor { public static final boolean VERTICAL = true; public static final boolean HORIZONTAL = false; private static final int GAP = 10; private boolean orientation = HORIZONTAL; private GreenfootImage icon; private int initValue; private int value; public HealthBar(String imgFilename, int count, boolean direction) { icon = new GreenfootImage(imgFilename); value = initValue = count; orientation = direction; updateImage(); } private void updateImage() { int across = value; int down = 1; int xGap = GAP; int yGap = 0; if (orientation == VERTICAL) { across = 1; down = value; xGap = 0; yGap = GAP; } int wide = icon.getWidth()*across+xGap*(across+1); int high = icon.getHeight()*down+yGap*(down+1); GreenfootImage base = new GreenfootImage(wide, high); for (int j=0; j<down; j++) for (int i=0; i<across; i++) { int xOffset =xGap+i*(xGap+icon.getWidth()); int yOffset = yGap+j*(yGap+icon.getHeight()); base.drawImage(icon, xOffset, yOffset); } setImage(base); } public void adjustValue(int change) { if (value+change > initValue || value + change < 0) return; value += change; updateImage(); } public int getValue() { return value; } } Would you be able to explain how it is possible to use this in terms of losing a life each time an asteriod hits the spaceship and then ending the game when it reaches zero? Or direct me to a tutorial that might make it clear?
danpost danpost

2015/3/22

#
JDRSkiing wrote...
Would you be able to explain how it is possible to use this in terms of losing a life each time an asteriod hits the spaceship and then ending the game when it reaches zero? Or direct me to a tutorial that might make it clear?
First, let me explain what the class I wrote above does. It will take an image, like a heart or the image of the player, and repeats it for the number of lives remaining for the player. I guess the class is not well named, as it probably should be something like 'LivesBar'. Anyway, let us say, for instance, that you want to display a run of small spaceships so the user can see how many lives (or ships) are remaining. You also want to start, let us say, with four extra ships. Then, you might create a LivesBar object using:
1
2
3
4
5
6
7
8
9
10
11
12
13
/* code in world class */
// instance field (at top of class)
private LivesBar livesbar;
// in world constructor ('public Space()', for example) or in prepare method called by constructor
GreenfootImage shipImage = new SpaceShip().getImage(); // get spaceship image
shipImage.scale(shipImage.getWidth()/3, shipImage.getHeight()/3); // make image smaller
livesbar = new LivesBar(shipImage, 4, LivesBar.VERTICAL); // create lives bar
addObject(livesbar, 30, 30); // add lives bar to world
// getter method; called from actor class using '((Space)getWorld()).getLivesBar()'
public LivesBar getLivesBar()
{
    return livesbar;
}
The spaceship can now use the 'adjustValue' method on the livesbar object to show lose of a life and use the 'getValue' method to compare to zero for game over. For example:
1
2
3
4
5
6
7
8
9
10
11
12
// in spaceship class act method
if (isTouching(Asteroid.class))
{
    LivesBar lb = ((Space)getWorld()).getLivesBar(); // get livesbar object reference
    if (lb.getValue() > 0) {
        lb.adjustValue(-1); // show lose of life
        setLocation(getWorld().getWidth()/2, getWorld().getHeight()/2); // reset ship in center of world
        while (! getObjectsInRange(150, Asteroid.class).isEmpty()) // waits until clear of asteroids
            for (Object obj : getWorld().getObjects(Asteroid.class)) ((Actor)obj).act();
    }
    else { /** game over routine */ }
}
JDRSkiing JDRSkiing

2015/3/22

#
Thanks it makes more sense now. However if i try and implement it I'm getting an error when an asteriod touches the ship: java.lang.NullPointerException at Spaceship.act(Spaceship.java:23) It says the error occurs when it tries to getValue of the HealthBar
danpost danpost

2015/3/22

#
The 'public Healthbar' constructor, as written above, which should be 'public LivesBar', takes a String filename argument and I inadvertently had shown code that gives a GreenfootImage value. Replace the ONE above with the TWO below:
1
2
3
4
5
6
7
8
9
10
11
12
public LivesBar(String imgFilename, int count, boolean direction)
{
    this(new GreenfootImage(imgFilename), count, direction);
}
 
public LivesBar(GreenfootImage image, int count, boolean direction)
{
    icon =image;
    value = initValue = count;
    orientation = direction;
    updateImage();
}
danpost danpost

2015/3/22

#
JDRSkiing wrote...
Thanks it makes more sense now. However if i try and implement it I'm getting an error when an asteriod touches the ship: java.lang.NullPointerException at Spaceship.act(Spaceship.java:23) It says the error occurs when it tries to getValue of the HealthBar
This looks like a problem in your world class. Show its code.
JDRSkiing JDRSkiing

2015/3/22

#
I replaced them as you said, and have attached my SpaceWorld code below (i'm using HealthBar over LivesBar): import greenfoot.*; /** * Write a description of class SpaceWorld here. * * @author (your name) * @version (a version number or a date) */ public class SpaceWorld extends World { float speedGen = 0; private MoonCounter moonCounter; private HealthBar healthBar; public MoonCounter getMoonCounter() { return moonCounter; } public HealthBar getHealthBar() { return healthBar; } /** * Constructor for objects of class SpaceWorld. * */ public SpaceWorld() { // Create a new world with 600x400 cells with a cell size of 1x1 pixels. super(900, 600, 1); prepare(); } public void act() { if (Greenfoot.getRandomNumber(100) < 2 + speedGen) { addObject(new Asteriod(), 880, Greenfoot.getRandomNumber(getHeight())); } speedGen += 0.005; } /** * Prepare the world for the start of the program. That is: create the initial * objects and add them to the world. */ private void prepare() { Spaceship spaceship = new Spaceship(); addObject(spaceship, 64, 300); MoonCounter moonCounter = new MoonCounter(); addObject(moonCounter, 850, 50); HealthBar healthBar = new HealthBar("rocket2.png", 3, HealthBar.HORIZONTAL); addObject(healthBar, 60, 20); } }
danpost danpost

2015/3/22

#
Also, using the 'while' loop in the Spaceship act method when hit by an asteroid was not a very good idea on my part. Better would be to add an instance boolean field for immunity -- set it to true when hit an re-centered in the world and set it back to false when clear of asteroids:
1
2
3
4
5
6
7
8
9
10
11
// instance field in spaceship class
private boolean immune;
// at start of act method
if (immune && getObjectsInRange(150, Asteroid.class).isEmpty())
    immune = false;
// collision detection
if ( ! immune && isTouching(Asteroid.class))
{
    // same as before except replace 'while' loop with the following
    immune = true;
}
Super_Hippo Super_Hippo

2015/3/22

#
Change MoonCounter moonCounter = new MoonCounter(); to moonCounter = new MoonCounter(); and HealthBar healthBar = new HealthBar("rocket2.png", 3, HealthBar.HORIZONTAL); to healthBar = new HealthBar("rocket2.png", 3, HealthBar.HORIZONTAL);
danpost danpost

2015/3/22

#
Remove the first 'HealthBar ' from the second to last line of your 'prepare' method. You do not want to create a new variable there -- you want to use the one declared on line 13.
You need to login to post a reply.