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

2015/4/13

Bullet shooting in the right direction

RagingAsian RagingAsian

2015/4/13

#
In my Tower defense game, whenever my tower begins to shoot it will look at the closest enemy and shoot. After that, the tower will be looking at the closest enemy, but the bullet will move towards a different enemy. Here is the Bullet and Tower class. Tower superclass:
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.ArrayList;
import java.util.List;
/**
 * Write a description of class Towers here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class Towers extends Actor
{
    MouseInfo mouse = Greenfoot.getMouseInfo();
    
    public static int kind;
    
    private boolean placed = false;
    
    public List<Greeps> enemies;
    private Actor closestEnemy;    
    
    int damage;
    int fireRate;
    int range;
    int canShoot;
    int count=0;
 
    int posX = 10;
    int posY= 10;
    public Towers(int dmg, int rng, int fr)
    {        
        this.damage=dmg;
        this.range=rng;
        this.fireRate=fr;      

    }
    
    public void act()
    {        
        enemies = getObjectsInRange(range,Greeps.class);
        checkPlaced();
        turnClosest(enemies);
    }
    
    /**
     *turn to the closest enemy
     *
     * @param enemies A parameter
     */
    public void turnClosest(List<Greeps>enemies)
    {         
           Actor closest = null;
           int distance=1000;
           if(enemies.size()>0)
           {
               for(int i=0;i<enemies.size();i++)
               {
                   Actor creep = enemies.get(i);
                   range=(int)(Math.hypot(creep.getX() - getX(), creep.getY()-getY()));
                   if((range<distance))
                   {
                       closest=creep;
                       distance=range;
                    
                   }
               }
               closestEnemy = closest;
               int targetX=closestEnemy.getX();
               int targetY=closestEnemy.getY();
            
               turnTowards(targetX,targetY);
            
           }
       
    }
    
    /**
     * Check if the tower is able to be placed
     *
     */
    public void checkPlaced()
    {
        
        if (!placed)
        {
          mouse = Greenfoot.getMouseInfo();
          if (mouse != null)
          {
              posY=mouse.getY();
              posX=mouse.getX();
              setLocation (posX, posY);
              if(Greenfoot.isKeyDown("esc")) getWorld().removeObject(this);
             
          }
          
          if (Greenfoot.mouseClicked(this))
          {
             setLocation(posX,posY);
             placed=true;
              
          }            
        }       
    }
}    
Bullets SuperClass:
import greenfoot.*;  
import java.util.List;
import java.util.ArrayList;
/**
 * Write a description of class Bullet here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class Bullets extends Actor
{
    public int bulletType;
    
    private int x=0;
    int dmg;
    int rng;
    int fr;
    public int hp;
    private Actor enemy;    
    public Counter2 moneyCounter;
    private List<Counter2> myList= new ArrayList<Counter2>();
    public Bullets(Actor enemy, Counter2 money)
    {       
        this.enemy=enemy;
        moneyCounter=money;
    }
    
    public void act() 
    {
        move();

        ifAtWorldEdge();
        myList = getWorld().getObjects(Counter2.class);
        moneyCounter = myList.get(0);
    }
    
    public void ifAtWorldEdge()
    {
        if(getX()<0)
        {
            getWorld().removeObject(this);
        }
        if(getX()>600)
        {
            getWorld().removeObject(this);
        }
        if(getY()<0)
        {
            getWorld().removeObject(this);
        }
        if(getY()>425)
        {
            getWorld().removeObject(this);
        }
        
    }
    /**
     * Bullet move towards a greep
     *
     */
    public void move()
    {
        if (enemy.getWorld()!=null)
        {
            int x=enemy.getX();            
            int y=enemy.getY();
            turnTowards(x,y);
            move(2);
            
        } else {
            move (2);
        }
     
    }        

    public void increaseMoney(Counter2 money)
    {
        Actor creeper=getOneIntersectingObject(Greeps.class);
        Greeps greep=(Greeps)creeper;
        moneyCounter=money;
        if ((creeper!=null)&&(hp==0))
        {
            if(greep.type==1)
            {
                moneyCounter.add(10);
            }
            if(greep.type==2)
            {
                moneyCounter.add(15);
            }
            if(greep.type==3)
            {
                moneyCounter.add(20);
            }
        }
    }
}
Super_Hippo Super_Hippo

2015/4/14

#
Where is the bullet added to the world?
danpost danpost

2015/4/14

#
In the Towers class, you need to insert (move) line 12 to before line 85 for it to continuously check the current state of the mouse (the value of 'mouse' will never change). Line 14, 'public static int kind;', cannot possibly help in any way in this class (if the field is used for what the name of the field implies for it). 'static' fields hold one value that is shared among the objects of the class; and are not linked in any way to subclasses with a similarly named 'static' field. I feel that you are not fully understanding how inheritance works with subclassing. All non-private non-static fields and methods are accessible, usable, and overridable (methods) by subclasses. For a quick example:
import greenfoot.*;

public class Towers extends Actor
{
    int range;
    int fireTimer;
    int fireRate;
    boolean placed;

    public Towers() {}

    public void act()
    {
        if (!placed)
        {
            checkPlaced();
        }
        else
        {
            checkShoot();
        }
    }

    private void checkPlaced()
    {
        MouseInfo mouse = Greenfoot.getMouseInfo();
        if (mouse != null)
        {
            posY = mouse.getY();
            posX = mouse.getX();
            setLocation (posX, posY);
            if (Greenfoot.isKeyDown("escape"))
            {
                getWorld().removeObject(this);
                return;
            }
        }
        if (Greenfoot.mouseClicked(this))
        {
            setLocation(posX, posY);
            placed = true;
        }
    }

    private void checkShoot()
    {
        if (fireTimer > 0)
        {
            fireTimer--;
        }
        else if (turnClosest())
        {
            Actor bullet = getBullet();
            if (bullet == null) return;
            getWorld().addObject(bullet, getX(), getY());
            bullet.setRotation(getRotation());
            fireTimer = fireRate;
        }
    }

    private boolean turnClosest()
    {
        Actor closest = null;
        int distance = range;
        for (Object obj : getObjectsInRange(range, Greeps.class)
        {
            Actor creep = (Actor) obj;
            int dist = (int)(Math.hypot(creep.getX()-getX(), creep.getY()-getY()));
            if (dist < distance)
            {
                closest = creep;
                distance = dist;
            }
        }
        if (closest == null) return false;
        int targetX=closestEnemy.getX();
        int targetY=closestEnemy.getY();
        turnTowards(targetX, targetY);
        return true;
    }

    protected Actor getBullet() { return null; }
}
Notice several things about the class: (1) its constructor is empty; the value of the 'range' and 'fireRate' fields can be set by the constructor of the subclass directly (there is no need to pass those values to the Towers constructor):
public Ice()
{
    range = 65;
    fireRate = 65;
}
(2) The 'getBullet' method may look a bit strange; however, having it in the superclass of the individual tower classes will simplify things. You can now write the 'getBullet' methods (overriding the one in the Towers class) in the individual tower classes to return the appropriate bullet type to the 'checkShoot' method. For example, the Ice class method would be:
protected Actor getBullet()
{
    return new IceBullet();
}
(3) The 'money' or 'moneyCounter' (Counter2) counter field does not need to be passed along from world to tower to bullet like that (and probably should not be). There are other ways to have the bullets acquire that object. Unfortunately, the best way would require you to restructure your arrangement of worlds. You could still use 'Counter2 moneyCounter = (Counter2)getWorld()getObjects(Counter2.class).get(0);' at the beginning of the 'increaseMoney' method instead of bringing it in by way of a parameter. Just make sure you call the method before removing the bullet from the world. (4) notice that I removed the 'damage' field from the Towers class. The tower itself does not give damage -- the bullets do. The field should be defined in the Bullets class and assigned values from the individual subclasses (just like the fields for the Towers class were set by the individual tower subclasses). (5) bullets have very little state and behavior; normally, they move in the direction they were fired, damage obstructions, and exit the world. They are usually not smart, in that they do not continuously turn toward to object they were fired at. That would cause your bullets to looks a bit strange, changing directions in mid-air. I understand that it would take a bit more programming to make them move in the direction they were intended when going at such a slow pace. A few extra fields in the Bullets class could help with that (and changing the way they move a bit). Using the 'addedToWorld' method might come in handy here to get the initial location to be saved. That with a distance counter could be used to aid in moving the bullet more along its line of fire:
// fields
int originX;
int originY;
int distance;
// with overridden method
protected void addedToWorld(World world)
{
    originX = getX();
    originY = getY();
    distance = 20;
    move();
}
// act
public void act()
{
    move();
    // other actions (hitting Greeps object or world edge)
}

private void move()
{
    setLocation(originX, originY);
    distance += 2;
    move(distance);
}
(6) of all the fields you have in the Bullets class (lines 12 through 21 above), I only see a need for the 'dmg' field and maybe the 'rng' field. 'x' is not used anywhere; 'fr' is not relevant to the bullets themselves; 'hp' -- bullets do not have healthpoints; 'enemy' -- bullets are not smart; 'moneyCounter' can be acquired in other ways; and finally 'myList' -- not needed to be saved.
RagingAsian RagingAsian

2015/4/14

#
So would i could just set the fireRate and range variables in the tower subclasses? I don't have to keep initializing them? Ex: Normal subclass:
int range;
int fireRate;
Ice subclass:
int range;
int fireRate;
danpost danpost

2015/4/14

#
RagingAsian wrote...
So would i could just set the fireRate and range variables in the tower subclasses? I don't have to keep initializing them? Ex: Normal subclass:
int range;
int fireRate;
Ice subclass:
int range;
int fireRate;
You would only set the value of those fields in the subclasses. You declare the fields (with lines like those you suggested) ONLY in the Towers class -- not in the subclasses. As long as the fields are not modified with 'static', each object created from any subclass will get a field with that name to hold a value that is specific to that object. If the field is also not modified with 'private', the subclasses can use the field directly (without the use of methods to work with its value -- 'getter' and 'setter' methods, for example). The following pair of classes have been reduced to minimal code for brevity. An example class to be subclassed:
public class Bicycle extends greenfoot.Actor
{
    int numberOfGears;
    int currentGear;
    int speed;

    public Bicycle() {}

    // rest of class (for things in common with ALL Bicycle objects)
}
A sample subclass:
public class MountainBike extends Bicycle
{
    public MountainBike()
    {
        numberOfGears = 12; // field declared in Bicycle class (not in this class)
    }

    // rest of class (for things specific to MountainBike objects)
}
The initial speed would be zero and current gear would start in the lowest gear; so those field do not need reset to other values. The 'numberOfGears' field shows my point. Methods in the Bicycle class that are not 'private' and not 'static' can also be directly called. You can realize that by thinking about the methods of the Actor class. They are not located in your classes that subclass Actor; but, you call them nonetheless. There is also one more construct that can help immensely in certain situations. Think about the 'act' method and how it is called by the greenfoot framework. How does it know which act method to call for which actor? Greenfoot, itself, does not need to. The 'act' method supplied in the Actor class is an empty method. That makes it the default implementation for the method for your actors. When you override it (write an 'act' method into a subclass of Actor), the JVM will look for the last override of the method for the actor. I will use an example from your project here. You could have your Buttons class look like this:
import greenfoot.*;

public abstract class Buttons extends Actor
{
    public void act() 
    {
        clicked();
    }
    
    private void clicked()
    {
        if (Greenfoot.mouseClicked(this))
        {
            perform(); // the call to the overridden method
        }
    }
    
    protected void perform() {} // take note of this line, here (to be overridden)
}
and one of your buttons like this:
import greenfoot.*;

public class Stage1Preview extends Buttons
{
    protected void perform() // the overriding method
    {
        Greenfoot.setWorld(new Stage1());
    }
}
You could then add a different 'perform' method to each subclass of Buttons to give each one a different thing to do.
You need to login to post a reply.