Java game: Key Bindings and the EDT - java

I'm working on a game in Java. In the game, the player will be able to control a tank that moves on the screen using Key Bindings. Each arrow-button moves the tank in a different direction.
I'd like to know, if the way I used Key Bindings in the program, is correct and isn't supposed to cause problems with the EDT or with anything else later on.
I will explain in short the structure of the program, and how I used Key Bindings.
There's a Board class, a class that extends JPanel and has
most of the game logic. It also displays the graphics.
There's a KeyBindingsRunnabel class, a class that implements Runnable. This
class has all the KeyBindings code.
Board contains the game-loop. The game loop happens inside a
thread. In the thread, but before the game loop, a new thread is
created and started, that thread runs KeyBindingsRunnabel.
(There's also a Main class that extends JFrame and creates an instance of Board, but that's irrelevant).
I'll demonstrate all of this in short:
Here is the code for the KeyBindingsRunnabel class:
public class KeyBindingsRunnable implements Runnable {
Board b;
Action upAction,leftAction;
InputMap inputMap;
ActionMap actionMap;
public KeyBindingsRunnable (Board b){
this.b = b;
inputMap = b.getInputMap();
actionMap = b.getActionMap();
public void run() {
leftAction = new AbstractAction(){
public void actionPerformed(ActionEvent e){
// Code to move tank left.
}
};
rightAction = new AbstractAction(){
public void actionPerformed(ActionEvent e){
// Code to move tank right.
}
};
inputMap.put(KeyStroke.getKeyStroke("LEFT"),"leftAction");
inputMap.put(KeyStroke.getKeyStroke("RIGHT"),"rightAction");
actionMap.put("leftAction",leftAction);
actionMap.put("rightAction",rightAction);
}
}
The way the Board class uses KeyBindingsRunnable is as follows:
The Board class also implements Runnable. It contains a thread declaration in it's constructor:
Thread gameloop = new Thread(this);
gameloop.start();
In Board's run() method, there are two thing: a game-loop, and before the loop, the creation of a new thread that runs KeyBindingRunnable.
The run() method of Board looks like this:
public void run(){
Thread keyBindingsThread = new Thread(new KeyBindingRunnable(this));
keyBindingsThread.start();
while(gameIsRunning){
// The loop.
// .....
}
}
My question is: Is this the correct way to use Key Bindings without interupting with the EDT, and in general the correct way?
Is there a more effecient/safer/shorter way of using Key Bindings in a 2D game?
Thanks a lot :)

Too many comments for me to read so I don't know if I'm saying anything new or just repeating something.
The way I see it. KeyBindings are just used to indicate the state of keys on the keyboard.
Take a look at KeyboardAnimtion.java from Motion Using the Keyboard. In that example wen the key is pressed the key is added to the Map and when the key is released the key is removed from the map. That is the sole function of the key bindings, to track the keys that are pressed and the associated movement of that key.
The example then uses a Timer to schedule the animation of the object. When the Timer fires the move() method is invoked and the object is moved based on the keys in the Map.
Swing Timer doesn't allow me to use a type of game-loop that has proven to run the game at constant speed without problems.
There is no reason to use the Timer. If your game does its own looping then you should be able to invoke the move() method directly. However, all this does is control that the repaint request is made at a constant time, it does not guarantee that the painting is done immediately, although normally painting requests should be handled right away unless the system is really busy.
I would start by using the Timer an see what happens.

Related

Libgdx Game: Delay Action Based on Score Count

I'm still a bit new at Java and need some help with a game I'm currently working on. I've already implemented the core of the game where balls drop from the top of the screen and the user controls platforms to bounce the balls to the right side of the screen. If the player succeeds, then a point is awarded. I already implemented the code for the bouncing balls, platforms, score, and various states.
The thing I'm stuck on is controlling the number of balls that drop depending on the score. I already have a rough idea of the algorithm. Without going into too much detail it goes something like this:
public class BallContainer{
public ArrayList<Ball> balls;
public BallContainer(ArrayList<Ball> balls){
this.balls = balls;
}
public void drop(int howMany){
//code to activate the gravity of "howMany" random balls with a .5 second delay between them
}
public class MainGame{
public void update(float dt){
//check score and drop a selection of balls with random seconds of delay between each group of balls dropped at a time
}
}
I already have an idea of how many balls and how much of a random delay will occur depending on the score. One thing I'm just stuck on the delaying of the action. I know we can use the java.util.Timer and TimerTask, but I also hear libgdx also has some built in delay methods. Anyway, any help would be appreciated.
Thanks.
You can use libgx Timer class to provide actions to happen after a certain delay.
Here is an example -
Timer.schedule(new Task() {
#Override
public void run() {
//create new balls here or do whatever you want to do.
}
}, DELAY_TIME_IN_SECONDS);
What happens here is you are calling a static method of Timer class called schedule which takes a Task and delaySeconds in float as parameters.
Now as parameter you are creating a new Anonymous inner class object as Task is an abstract class. In the anonymous inner class object, you override the run method and put what you want it to do.
Place the above snippet in the place where you want to create new balls or do some action.
You can just create a new thread, where you will place a flag, then sleep the newly created thread, and after sleep finishes - set your flag to true.
So ur game won't freeze like it will if you gonna sleep the main thread.

Java: change JPanel while saving game

I'm making a small game and I've already implemented a save function in which the game is saved (by writing information to a new XML file). The saving takes a couple of seconds and I want to do the following: while the program is saving the game, I want to change the look of the JPanel, and when it is done saving, I want to go back to another page(show another JPanel).
I have the following code:
confirm.addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e){
String fileNaam = saveGame.getText();
//This method changes the look of the panel
changePanel();
//This method saves the game
model.saveGame(fileNaam);
//This method takes the user back to a previous page
controller.viewTeamPage();
}
});
What happens is that the game is saved and the user is taken back to the teampage, but the panel is never changed. The changePanel() method does work, so that is not the problem but it seems like it is never executed. I was wondering if somebody knows how I can fix this.
EDIT:
private void changePanel () {
panel.removeAll();
panel.repaint();
panel.revalidate();
}
This is the method to change the look of the panel, for now I just remove everything on the panel to keep it simple.
Also, the saving is not done in a separate Thread, is that something I should look at?
EDIT 2: I fixed it by using a thread to save the game and return to the teampage after the saving is done. See the code below.
confirm.addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e){
final String fileNaam = saveGame.getText();
changePanel();
Thread t = new Thread (new Runnable () {
#Override
public void run() {
model.saveGame(fileNaam);
controller.viewTeamPage();
}
});
t.start();
}
});
If you are changing the same panel and not intializing a new panel then the problem i think is that you need to call the panel.revalidate or panel.repaint i think. I made a demo for a Procedural generation project and i had to do this to make my panel change.
Call your save game method from a new thread but don't "join" or "try" to wait for this thread to finish from inside the method actionPerformed();
Make the call to controller.viewTeamPage() after the save game thread is done saving the game. One simple way of doing that would be passing the "controller" object to the constructor of your custom thread so you can make that call after saving the game.
The step 1 is very important in this case because all the calls you are making in the method actionPerformed() are being made in the UI thread, preventing the entire UI from refreshing until the method returns. Even calling repaint() alone, in changePanel(), wont be enough because it just "schedules" a refresh on you panel that will only happen after actionPerformed() returns. If you put the most time consuming call in a separate thread however, the actionPerformed() returns quickly allowing the UI to be refreshed while the game saving thread is doing its job.

keyReleased() method with Key Bindings in Java?

I'm a little bit new to Key Bindings, as I have been using KeyListeners for the majority up until recently when KeyListeners proved to be my biggest obstacle. I'm wondering how you would program a keyReleased-like event with KeyBindings. KeyListeners provided the easy three methods: keyPressed, keyTyped, and keyReleased, but I'm a little bit confused as for how to make this happen with Key Bindings.
Basically, when the user presses UP, I want an object to move upwards. But when the user releases UP, the object should move downwards automatically to simulate basic gravity. Here's a little bit of my code showing the UpAction class.
class UpAction extends AbstractAction
{
public void actionPerformed(ActionEvent tf)
{
north = true;
helitimer.start();
helitimer.start();
helitimer2.start();
repaint();
}
}
The three helitimers are Timer objects that start a series of Timers to increment the y position of the object continuously and smoothly. When the action upAction is invoked, the class UpAction is called and the three timers start in order to move the object.
Is there anyway I could make it so when the user releases UP, the action is no longer invoked and the timers stop?
Thanks a lot!
I'm wondering how you would program a keyReleased-like event with KeyBindings
Exactly the same way you do for a keyPressed event. The difference is in the KeyStroke. Read the KeyStroke API, is shows how to create a KeyStroke for a keyReleased event. There are a couple of different ways depending on how you want to create the KeyStroke.
The KeyStroke methods assume keyPressed, so you will need to add an additional parameter or additional keyword for the keyReleased event. I don't know which method you are using to create the KeyStroke so I can't tell you the exact change. Check the API for the details.
The three helitimers are Timer objects that start a series of Timers to increment the y position of the object continuously and smoothly
You should not need 3 Timer for this. One Timer should work satisfactorily.
Is there anyway I could make it so when the user releases UP, the action is no longer invoked and the timers stop?
If you can "start" a Timer in your keyPress Action, then you can "stop" the Timer in the keyReleasedAction. All you need is a reference to the Timer. Based on the code you posted you already defined the Timer as a class variable so this should not be an issue.
But when the user releases UP, the object should move downwards automatically to simulate basic gravity
Sounds to me like you will need another Timer to do this.
Another option is to always have a Timer running. Then when the UP key is pressed you make the Y increment a negative value. When the UP key is released you make the Y increment a positive value.

Start a game from within an action listener

I have a Blackjack game that I've made in Java and I want to signal the start of the game by clicking a button. All my action listeners work just fine and all that, but the problem lies in that I can't figure out how to start the game without it running completely within the actionPerformed method. Obviously, a function continuously running within the actionPerformed method will effectively disable the rest of my GUI. Here's a code snippet....
go.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// START GAME SOMEHOW but must run outside of action listener
}
});
Obviously, a function continuously running within the actionPerformed method will effectively disable the rest of my GUI.
This is a valid observation and shows that you have understand the fundamental rule when working with Swing.
Your game is most likely event driven (correct me if I'm wrong) so the action performed by the button should just set the program in a new state, waiting for further events. This is nothing that should be time consuming, and is typically done directly by the EDT.
Of course, if you want to do a fancy start-new-game animation, that needs to be performed in a separate thread, in which case you simply start the animation thread (I would recommend using a SwingWorker though) from within the actionPerformed method, and then return.
In code, I imagine it would look something like this:
go.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Remove the menu components
someJPanel.removeAll();
// Show the game table
someJPanel.add(new GamePanel());
someJPanel.revalidate();
someJPanel.repaint();
// done. Wait for further user actions.
}
});
You game should probably start in its own thread and manage that itself (hard to say), but to get you going you could start your game in a new "external" thread, something like this in your actionPerformed:
public void actionPerformed(ActionEvent e) {
Thread thread = new Thread("Game thread") {
public void run() {
startGame(); //or however you start your game
}
};
thread.start();
}
I believe that you want to extend javax.swing.SwingWorker.
The non-ui start-up functionality would run in doInBackground and the done method would be called when it finishes to update the ui.
There's even an example in the javadoc Class Description to update a progressbar with the status of what's happening in start-up.

Javax Swing Timer Help

I am having some problems concerning starting javax.swing.Timer after a mouse click. I want to start the timer to perform some animation after the user clicks on a button but it is not working.
Here are the code snippets:
public class ShowMe extends JPanel{
private javax.swing.Timer timer;
public ShowMe(){
timer = new javax.swing.Timer(20, new MoveListener());
}
// getters and setters here
private class MoveListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// some code here to perform the animation
}
}
}
This is the class which contains a button so that when the user clicks on the button the timer starts to begin the animation
public class Test{
// button declarations go here and registering listeners also here
public void actionPerformed(ActionEvent e) {
if(e.getSource() == this.btnConnect){
ShowMe vis = new ShowMe();
vis.getTimer().start();
}
}
}
I want to start the timer to begin the animation but it is not working.
Need help how to make a timer start after button click.
Thanks.
You must call the start() method of the timer to start it.
public ShowMe(){
timer = new javax.swing.Timer(20, new MoveListener());
timer.start();
}
EDIT:
I have not seen that start() is being called in the Test class...
Next step would be to add some logging/printing to the MouseListener class to check if it is being called or not
private class MoveListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("MouseListener activated"); // TODO delete this line
// some code here to perform the animation
}
}
If it's running (I can't find any reason why not in the posted code), the problem is as Ash wrote above:
You created a new instance assigned to vis and started its Timer, but you have not added that instance to any visible container.
(maybe you added another instance of ShowMe earlier in the code...)
Some things to try:
Check that your panel is visible, e.g. make the background color red.
Check that the animation is being updated. For example, if you are animating by drawing different frames in a paint() method, then you will need to call repaint() in your timer, after updating the variables controlling animation. Alternatively, if animation is done by changing layout properties (e.g. to move a component around) then a call to validate() will be needed.
Using swing timer can get you started, but it's really the bare underpinnings. There are also libraries avaialbe that will allow you to go further with less effort:
animated transitions
Trident animation library
I know this question is a bit old, but I don't think you got an answer.
I believe the problem is that the ShowMe class and its Timer is being garbage collected, and hence fails to do what you think it should.
You are creating a new local ShowMe variable that goes out of scope as soon as the actionPerformed method completes. The Timer and its ActionListener are local to the ShowMe class instance, so when the actionPerformed method completes, they are also available for GC.
I'm not sure what the ShowMe class is doing. It appears to be a JPanel, so I assume it is something you want to display. It sounds like in your Test class (or real class), it might be better to have a ShowMe data member that you can just call start one when the button is clicked, instead of creating a new one every time.
Your usage of the Timer class seems to be correct. Maybe the problem lies in the MoveListener.
Did you remember to use the paintImmediately() method to repaint your animation?
If you use just repaint() you won't see a smooth animation, since repeated calls to repaint() are reduced to one call.

Categories

Resources