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

2016/1/24

Have multiple actors of the same class, but want them to act one at a time

EpicNoMan EpicNoMan

2016/1/24

#
So I have been attempting to create a program that, in essence, is like the game Petanque. The problem I am encountering is that, after creating ten Objects of Class Ball, they all act at the same time. This is not ideal. What is needed is that the first Ball created moves to where the mouse is clicked, and after coming to a stop, the second Ball then moves when to where the mouse is clicked. Right now, what happens is that all ten Balls move towards where the mouse is clicked at the same time. The way the Balls move is that each ball has two variables, moveX and moveY which correspond to the horizontal and vertical velocity respectively. Thus, my plan to deal with the problem is to create an Array of all the Balls. When traversing the array, the condition for moving to the next element would be that the velocity of the Ball in the current element is 0. However, I am not entirely sure how to create all ten Balls and have them be added to an Array, called ballList, and then move as I describe above. Thanks for any help you can provide, and if further clarification (i.e. code extracts) are needed, please let me know.
danpost danpost

2016/1/25

#
One thing at a time. First, create and fill an array of Ball objects. Then, show the code you used (use code tags -- see the link below the reply box "Posting code? read this!").
EpicNoMan EpicNoMan

2016/1/27

#
Okay, here is the code I am using. One thing I did forget to mention is that Ball is actually a subclass of the Circle Class, as there is another subclass, Target, which I want to inherit the same methods as Ball.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final int spacing = 27;
for (int ballNum = 0; ballNum < 10; ballNum++)
{
    int startY = 750; //Defines the starting Y Coordinate of the Balls
    int x = 85 + (int)(ballNum * (Math.sqrt(3) * 1.5 * (double)spacing)); //Defines the spacing between the Balls
    for (int ballcount = 0; ballcount < 1; ballcount++)
    {
        if (ballNum % 2 == 0){ //Adds all the black Balls
            addObject(new Ball(Color.BLACK), x, startY); 
        }
        if (ballNum % 2 == 1){ //Adds all the white Balls
            addObject(new Ball(Color.WHITE), x, startY);       
        }
    }
    List<Ball> ballList = getObjects(Ball.class);
}
danpost danpost

2016/1/27

#
Three things here: (1) line 6 starts a loop that always iterates exactly one time; it does not make any sense to create a loop structure for code that is to execute one time only; in other words, it will work exactly the same if you removed lines 6, 7 and 14; (2) all the math on line 5 seem a bit much; 'Math.sqrt(3) * 1.5 * (double)spacing' evaluates to about '70'; even when multiplied by 'ballNum' using values of 0 through 9, the result will only be different by at most one; in other words:
1
int x = 85 + ballNum * 70;
would be sufficient (not exact -- but close enough); (3) there are two issues with line 15; (a) the line is inside the 'for' loop; meaning you are recreating the list each time you add a ball into the world; it can be moved to after line 16 to have the list created once after all the balls are added into the world; (b) you are declaring the List object inside the method (or constructor) -- meaning that once execution returns from the method (or constructor), you lose any reference to that List object.
EpicNoMan EpicNoMan

2016/1/28

#
Okay, with regards to your points: (1) I took out the loop. I meant to use it for something else, was originally planning on having several rows, but ended up scratching that idea. I believe the loop was just a leftover piece of that I forgot to take out. (2) I realize the math was a bit much. The way it was before, it just worked out very nice spacing that allowed the balls to evenly fill the available space in the world. However, I tried replacing it all with 70, and the difference was barely noticeable. Thus, I also removed the variable 'spacing'. (3) To solve the issue, I created a method called 'createList' and a boolean, 'madeList' which is set as false outside the constructor. Then, an 'if' statement within the constructor, which runs after all Balls have been created, creates a List if 'madeList' is False, then sets 'madeList' to be true, as follows.
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 class GameWorld extends World
{
    boolean madeList = false;
    /**
     * Constructor for objects of class GameWorld.
     *
     */
    public GameWorld()
    {   
        // Create a new world with 800x800 cells with a cell size of 1x1 pixels.
        super(800, 800, 1, false);
 
        addVerticalWalls(400, 200);
        addVerticalWalls(400, 600);
        addHorizontalWalls(400, 200);
        addHorizontalWalls(400, 600);
 
        for (int ballNum = 0; ballNum < 10; ballNum++)
        {
            int startY = 750; //Defines the starting Y Coordinate of the Balls
            int x = 85 + (int)(ballNum * 70); //Defines the spacing between the Balls
            if (ballNum % 2 == 0){ //Adds all the black Balls
                addObject(new Ball(Color.BLACK), x, startY); 
            }
            if (ballNum % 2 == 1){ //Adds all the white Balls
                addObject(new Ball(Color.WHITE), x, startY);       
            }           
        }
        if (madeList = false){
            createList();       
        }
    }
     
    List createList(){
            List<Ball> ballList = getObjects(Ball.class);         
            madeList = true;
            return ballList; 
    }
     
    //...Other methods...
}
Additionally, I am a bit unfamiliar with creating a List this way. Would I just iterate through it as I would any other list, and assume it has as many elements as there are Ball objects?
danpost danpost

2016/1/28

#
The 'madeList' field is not needed. The constructor of the GameWorld class is only executed once per GameWorld instance created and the list will be filled with all Ball objects within that world instance (and, yes, the number of elements in the list will be equal to the number of Ball objects in that world). If you had a reference to the List object, you can call the 'size' method on it to get how many elements it contains. Now, you are calling 'createList' that is to return a List object. This is fine and good; but, it is not necessary for the code to be in a separate method. You are declaring the List object within the method and returning it to the statement calling the method (the list is created on line 35, returned by line 37 to line 30). But, what happens to that list, now? -- nothing! it is not being used in any way and not being saved anywhere for later use. This means that again, as in (3)(b) above, the List object is lost -- this time immediately at line 30. Remember, you declare a variable by using the format 'Variable-type variable-name'. Where you declare the variable determines its lifespan. If declared inside a set of squiggly brackets within a method or constructor, it belongs to that construct and is lost the moment the execution of the block is completed. If declared inside a method or constructor outside any other constructs, it belongs to that method or constructor and is lost the moment the execution of the method or constructor is completed. If declared within a class, but outside any method or constructor, each instance will be given the field, they belong to the objects created from that class and is lost when no references to that particular object of that class remain. If you declarre the List in the GameWorld class outside any method or constructor, then the list in that world will exist at least as long as that GameWorld instance exists. So, this is more what you want:
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
public class GameWorld extends World
{
    List<Ball> ballList;; // the world will retain the list when declared here
    int ballIndex; // to point at elements in the list
    boolean ballActive; // to indicate ball at 'ballIndex' is currently moving
 
    public GameWorld()
    {   
        super(800, 800, 1, false);
 
        addVerticalWalls(400, 200);
        addVerticalWalls(400, 600);
        addHorizontalWalls(400, 200);
        addHorizontalWalls(400, 600);
  
        for (int ballNum = 0; ballNum < 10; ballNum++)
        {
            Color color = ballNum%2 == 0 ? Color.BLACK : Color.WHITE; // ('if' construct)
            Ball ball = new Ball(color);
            int x = 85+ballNum*70;
            int y = 750;
            addObject(ball, x, y); 
        }
 
        ballList = getObjects(Ball.class);   
    }
           
    //...Other methods...
}
Line 4 declares your iterator. It should be incremented (and returned back to zero when needed) after the current ball reaches its destination. The Ball class will need to store the location of the mouseClick so that it can continue to move toward that location well beyond the act of the click itself. Maybe having the Ball hold the MouseInfo object itself would be more useful (instead of the destination coordinate themselves), in that it can be checked for a null value (set it back to null when the ball reaches its destination). So, the world can place the condition on incrementing the iterator. In review, and to help explain the madness to my method: There are four states that the world can be in: * no ball moving -- waiting on click * click detected -- set ball in motion * ball moving -- waiting for it to reach destination * ball reached destination -- iterate and reset Boolean Really, the world is only interested in two of those states to perform actions at -- the second and fourth instantaneous states (not the time consuming states). But, these two states where the action are performed are controlled by the other two states. That is, the time consuming states provide conditions for the other two states. Therefore, in pseudo-code, you will have something like the following:
1
2
3
4
5
6
7
8
9
if (current ball is not moving and mouse clicked) tell ball to move
if (moving state of current ball does not match the boolean indicator)
{
    change indicator;
    if (indicator indicates current ball is not moving)
    {
        change current ball;
    }
}
EpicNoMan EpicNoMan

2016/2/10

#
Sorry for such a long time before I responded. I was out of the country, and internet access was limited. I managed to fix the problem using the method you suggested. I think what tripped me up was just a conceptual misunderstanding. I kept forgetting that the Act() method would run repeatedly, rather than just once, so encountered several problems related to that. However, I manged to get past that and it all works well now. Thanks so much for your help.
You need to login to post a reply.