Calling repaint() from a non-graphical class?
Author |
Message |
Insectoid
|
Posted: Sat Mar 17, 2012 6:56 pm Post subject: Calling repaint() from a non-graphical class? |
|
|
So, I'm working on a game, and so far everything works, except I can't get it to repaint when things move.
If I resize the window with the mouse, it works fine. But that's not really acceptable.
I've created an Entity class that has an animate() method that takes Graphics as a parameter and draws itself to that Graphics object.
I have Character subclass of Entity that contains methods for manipulating in-game characters (moving around, etc).
I have a Player class that has a Character field which takes keyboard input and invokes appropriate functions of Character.
I've got my main class that creates a frame and adds a canvas to it. My paint() method iterates over an ArrayList of Entities and invokes the animate() method of each.
Everything's working fine, like I said, except it doesn't repaint() so nothing moves unless you 'cheat' by resizing the window to get the jvm to invoke paint(). Any idea how I can fix this?
Main class:
Java: |
/*
*Final Project main class
*It's a game or something.
*/
package finalproject;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
/**
*
* @author jordan
*/
public class FinalProject extends Canvas{
public static final int SCREEN_WIDTH = 640;
public static final int SCREEN_HEIGHT = 480;
private static ArrayList<Entity> entities;
static Character c1 = new Character();
static Player p1 = new Player (c1 );
public FinalProject (){
setSize (SCREEN_WIDTH, SCREEN_HEIGHT );
setBackground (Color. white);
c1 = new Character();
p1 = new Player (c1 );
setVisible (true);
this. addKeyListener(p1 );
}
public static void main (String[] args ) {
FinalProject a = new FinalProject ();
Frame aFrame = new Frame();
aFrame. setSize(640, 480);
aFrame. add(a );
aFrame. setVisible(true);
aFrame. addKeyListener(p1 );
entities = new ArrayList<Entity> ();
entities. add(new Platform (10, 10, 50, 50));
entities. add(new Platform (100, 100, 10, 10));
entities. add (c1 );
}
public void paint (Graphics g ){
g. clearRect(0, 0, 640, 480);
for (int i = 0; i < entities. size(); i++ ){
entities. get(i ). animate(g );
}
}
}
|
Entity class:
Java: |
/*
* Entity class
* Contains functions for anything drawn to the screen. Everything drawn on the screen must extend entity.
*/
package finalproject;
import java.awt.*;
/**
* Object Oriented Programming - Assignment # - Question #
* By Jordan Dykstra - 0494591
* @description
*/
public class Entity {
protected double x, y;
protected boolean show;
// protected GeoShape shape; //a geometric shape roughly the dimensions of the entity, used for collision etc.
public Entity(){
show = true;
}
public double getX (){
return x;
}
public double getY (){
return y;
}
public void show (boolean show ){
this. show = show;
}
public void animate (Graphics g ){} //Override in subclasses.
}
|
Character class:
Java: |
/*
* Character class. Used for all characters.
*/
package finalproject;
import java.awt.*;
public class Character extends Entity{
private int score;
private int jumpHeight;
private double speed;
private double velY;
private double velX;
private double gravity = 1;
Image image;
public Character(){
super ();
velY = 0;
velX = 0;
score = 0;
speed = 1;
super. x = 0;
super. y = 400;
}
public void animate (Graphics g ){
x += velX;
y += velY;
if (airborne ()) velY -= gravity;
//x+=1;
if (show ) g. drawRect((int)x, (int)y, 10, 30);
}
public void setImage (Image image ){
this. image = image;
}
public Image getImage (){
return this. image;
}
public void moveRight (boolean a ){
System. out. println (velX );
if (a ) velX += speed;
else if (velX > 0) velX -= speed;
}
public void moveLeft (boolean a ){
System. out. println (velX );
if (a )velX -= speed;
else if (velX < 0)velX += speed;
}
public void jump (){
velY += 10;
}
public boolean airborne (){
return false;
}
}
|
Player class:
Java: |
/*Player class.
*Enables keyboard input for a character.
*/
package finalproject;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Player implements KeyListener{
Character c;
public Player (Character c ){
this. c = c;
}
public void keyPressed (KeyEvent e ){
//System.out.println ("Bleh");
switch (e. getKeyCode()){
case KeyEvent. VK_LEFT : c. moveLeft(true);break;
case KeyEvent. VK_RIGHT : c. moveRight(true);break;
}
}
public void keyReleased (KeyEvent e ){
//switch (e.getKeyCode()){
//}
}
public void keyTyped (KeyEvent e ){}
}
|
|
|
|
|
|
|
Sponsor Sponsor
|
|
|
DemonWasp
|
Posted: Sat Mar 17, 2012 8:04 pm Post subject: RE:Calling repaint() from a non-graphical class? |
|
|
Based on glancing at this: http://java.sun.com/products/jfc/tsc/articles/painting/ , it looks like you should just add a finalProjectInstance.repaint() call to the end of Player.keyPressed(). Then, when you press a key, it forces a re-draw.
The other alternative is to have a timer event that calls repaint() on a regular basis (every 20ms, for example). |
|
|
|
|
|
Insectoid
|
Posted: Sat Mar 17, 2012 10:13 pm Post subject: RE:Calling repaint() from a non-graphical class? |
|
|
How am I supposed to access FinalProject.repaint() from Character? I don't want it in Player 'cause Player isn't supposed to do any painting. I have instances of Character inside FinalProject, so Characters should not be able to access FinalProject functions.
The repaint timer could work, but I don't really wanna do that 'cause it could cause a lot of flickering (I think). I don't have time to try it out right now, but I might give it a shot tomorrow. |
|
|
|
|
|
DemonWasp
|
Posted: Sat Mar 17, 2012 11:52 pm Post subject: RE:Calling repaint() from a non-graphical class? |
|
|
The repaint timer does make more sense in general, yes. You could also make an inner class inside FinalProject that implements KeyListener, add it, and have it do the repainting.
If you're concerned about flickering, Google that: http://www.codeproject.com/Articles/2136/Double-buffer-in-standard-Java-AWT |
|
|
|
|
|
Insectoid
|
Posted: Sun Mar 18, 2012 11:14 am Post subject: RE:Calling repaint() from a non-graphical class? |
|
|
Excellent, it's working.
Now, right now the controls in the Player class are hard-coded with the keyboard constants. I'd like to switch to variables so that I can use the same Player class for multiple players with different controls (the reason I created it in the first place). However, switch() cases can only be constants (apparently), so Java doesn't like it if I do something like this:
switch (e.getKeyCode) {
case keyUp : c.jump();break;
}
I could fix this with an ugly set of if conditions, but I don't really want to. The ideal solution is to have an array of keys assigned to a function (jump, setLeft, setRight, attack, etc) and just iterate over that, but then I'd need some form of function pointer, which Java doesn't have (also, each function may have a different set of parameters). |
|
|
|
|
|
DemonWasp
|
Posted: Sun Mar 18, 2012 11:47 am Post subject: RE:Calling repaint() from a non-graphical class? |
|
|
There's a really excellent reason that switch labels must use compile-time constants, and it has to do with how switch statements get compiled into bytecode (and then machine code). Generally, they get reduced to a "jump table", so switch statements can be resolved by one array-retrieve and one jump operation. That doesn't work at all if you can change the switch statement at runtime.
You could get around this if your players control schemes were declared as final, as the following example shows. I really don't recommend this approach.
Java: |
int foo = 3;
final int bar = 3; // if you remove 'final', it won't compile...
switch ( foo ) {
case bar:
System.out.println ( "bar!" );
break;
default:
System.out.println ( "default" );
break;
}
|
Java doesn't have function pointers in the strictest sense, no. But, you can use any object that implements a standard interface or extends a standard class in roughly the same way. It's a little bit more cumbersome, but introduces explicit type safety.
What I would recommend is something like this:
1. There is an Action interface. It has one method, run(), which takes any context provided when an action is run (such as the KeyEvent instance). If implementations need more details (such as the Player instance they operate on), then you should provide that in the constructor.
2. You have several classes that implement Action. For example, you would have the Jump, Move, and Attack actions. Each one handles its own little thing. Don't be afraid to make move (left) and move (right) into the same class (MoveAction).
3. Player has a "binding" Map from integer key-codes to Action instances: Map<Integer,Action> bindings = new HashMap<Integer,Action>(); . You add instances of Action based on keycodes. So:
Player 1:
Java: |
Action moveAction = new MoveAction ( this ); // pass this Player to MoveAction so it knows who to move
Action jumpAction = new JumpAction ( this );
bindings.put ( KeyEvent.VK_LEFT, moveAction );
bindings.put ( KeyEvent.VK_RIGHT, moveAction );
bindings.put ( KeyEvent.VK_SPACE, jumpAction );
...
|
Player 2:
Java: |
Action moveAction = new MoveAction ( this ); // pass this Player to MoveAction so it knows who to move
Action jumpAction = new JumpAction ( this );
bindings.put ( KeyEvent.VK_A, moveAction );
bindings.put ( KeyEvent.VK_D, moveAction );
bindings.put ( KeyEvent.VK_SPACE, jumpAction );
...
|
4. Now, each player has (or is, as you have above...) one KeyListener that automagically knows how to dispatch key events to the correct Action instance:
Java: |
public void keyPressed (KeyEvent e){
Action action = bindings.get ( e.getKeyCode() );
if ( action != null ) { // May not have an action defined for that key; check...
action.run ( e );
}
}
|
With this strategy, it should be pretty easy to do the following neat things:
A. Have multiple key maps, possibly stored in files. You just have to store the value(s) that are bound to each action. For example, move is bound to VK_A and VK_B for Player 2.
B. Add new actions easily.
C. Have different actions between Player instances, or even modify them on the fly. You can add new actions at run-time just by putting a new entry in the 'bindings' map. |
|
|
|
|
|
|
|