I am trying to make a program that work like this:
In Window class every time I click on the button, the method panel2 of Panel is called: first it is drawing a first circle, then a second one (after the time defined in the timer). Then, I click again on the button, and it is drawing a fist circle, then a second one then a third one. etc.
The problem is that it when I click to obtain 3 circles appearing one after the other, the two first circles drawn at the previous step (before I pressed a second time the button) stay on the screen and only the third circle is drawn when i press the button (instead of having : first circle drawn, second circle drawn, third circle drawn). I hope I am clear.
Here is a simple code:
Window
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Window extends JFrame implements ActionListener{
int h = 2;
Panel b = new Panel();
JPanel container = new JPanel();
JButton btn = new JButton("Start");
JButton bouton = new JButton();
Panel boutonPane = new Panel();
public Window(){
this.setTitle("Animation");
this.setSize(300, 300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
JPanel top = new JPanel();
btn.addActionListener(this);
top.add(btn);
container.add(top);
this.setContentPane(container);
this.setVisible(true);
}
public void window2(){
this.setTitle("ADHD");
this.setSize(1000,700);
this.setLocationRelativeTo(null);
if (h < 11){
boutonPane.panel2(h);
bouton.addActionListener(this);
boutonPane.add(bouton);
this.add(boutonPane);
this.setContentPane(boutonPane);
updateWindow2();
}
this.setVisible(true);
}
public void updateWindow2(){
boutonPane.panel2(h);
this.revalidate();
this.repaint();
}
public void actionPerformed(ActionEvent e){
if ((JButton) e.getSource() == btn){
System.out.println("pressed0");
window2();
}
if ((JButton) e.getSource() == bouton){
h++;
System.out.println("pressed" + h);
updateWindow2();
}
}
public static void main(String[] args){
Window w = new Window();
}
}
Panel
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Panel extends JPanel implements ActionListener{
int m;
int u=0;
int lgi, lrgi;
int [] ta;
Timer timer1 = new Timer(300, this);
Panel(){
}
public void panel2(int n){
m=n;
ta = new int [n];
for(int it=0; it<m;it++){
ta[it]=100*it;
}
timer1.start();
}
public void paintComponent(Graphics gr){
super.paintComponent(gr);
gr.setColor(Color.red);
for(int i=0;i<m;i++){
gr.fillOval(ta[i],ta[i], 150, 150);
}
}
#Override
public void actionPerformed(ActionEvent arg0) {
if(u<m){
u++;
revalidate();
repaint();
}
}
}
Your code needs use two int values to decide how many circles to draw and when:
The first int should be the count of current circles to draw, say called, currentCirclesToDraw.
The second int will be the number of circles to draw total.
If you use a List<Ellipse2D> like I suggest, then this number will be the size of the list. So if the List is called ellipseList, then the 2nd number will be ellipseList.size().
The first variable will be incremented in the timer up to the size of the list, but no larger, and will be used by paintComponent method to decide how many circles to draw.
Key point here: the first number, the currentCirclesToDraw, must be re-set to 0 when the button is pressed. This way your paintComponent method will start out drawing 0 circles, then 1, then 2, ...
For example, the paintComponent method could look like so:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(CIRCLE_COLOR);
for (int i = 0; i < currentCirclesToDraw && i < ellipseList.size(); i++) {
g2.fill(ellipseList.get(i));
}
}
I use the second term in the for loop conditional statement, i < currentCirclesToDraw && i < ellipseList.size() as an additional fail-safe to be sure that we don't try to draw more circles then we have in our list.
My Timer's ActionListener would increment the currentCirclesToDraw variable and call repaint. It would stop the Timer once currentCirclesToDraw reaches the size of the ellipseList:
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (currentCirclesToDraw < ellipseList.size()) {
currentCirclesToDraw++;
repaint();
} else {
// stop the Timer
((Timer)e.getSource()).stop();
}
}
}
And my button's actionPerformed method would reset currentCirclesToDraw to 0, would add a new Ellipse2D to my ellipseList (if we've not yet reached the MAX_CIRCLE_INDEX), would call repaint() to clear the JPanel, and would construct and start the Timer:
public void actionPerformed(java.awt.event.ActionEvent arg0) {
currentCirclesToDraw = 0; // this is key -- reset the index used to control how many circles to draw
if (ellipseList.size() < MAX_CIRCLE_INDEX) {
double x = (ellipseList.size()) * CIRCLE_WIDTH / Math.pow(2, 0.5);
double y = x;
double w = CIRCLE_WIDTH;
double h = CIRCLE_WIDTH;
ellipseList.add(new Ellipse2D.Double(x, y, w, h));
}
repaint(); // clear image
new Timer(TIMER_DELAY, new TimerListener()).start();
};
Edit 3/30/14
Note it all can be put together like this:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
/**
* http://stackoverflow.com/a/22714405/522444
* http://stackoverflow.com/questions/22712655/repaint-in-panel-method-not-updated
* #author Pete
*
*/
#SuppressWarnings("serial")
public class TimerCircles extends JPanel {
private static final int PREF_W = 1000;
private static final int PREF_H = 700;
private static final Color CIRCLE_COLOR = Color.RED;
public static final int MAX_CIRCLE_INDEX = 11;
public static final int TIMER_DELAY = 300;
public static final int CIRCLE_WIDTH = 100;
private final List<Ellipse2D> ellipseList = new ArrayList<>();
private int currentCirclesToDraw = 0;
public TimerCircles() {
add(new JButton(new ButtonAction("New Circle", KeyEvent.VK_C)));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(CIRCLE_COLOR);
for (int i = 0; i < currentCirclesToDraw && i < ellipseList.size(); i++) {
g2.fill(ellipseList.get(i));
}
}
private class ButtonAction extends AbstractAction {
public ButtonAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
public void actionPerformed(java.awt.event.ActionEvent arg0) {
currentCirclesToDraw = 0; // this is key -- reset the index used to control how many circles to draw
if (ellipseList.size() < MAX_CIRCLE_INDEX) {
double x = (ellipseList.size()) * CIRCLE_WIDTH / Math.pow(2, 0.5);
double y = x;
double w = CIRCLE_WIDTH;
double h = CIRCLE_WIDTH;
ellipseList.add(new Ellipse2D.Double(x, y, w, h));
}
repaint(); // clear image
new Timer(TIMER_DELAY, new TimerListener()).start();
};
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (currentCirclesToDraw < ellipseList.size()) {
currentCirclesToDraw++;
repaint();
} else {
// stop the Timer
((Timer)e.getSource()).stop();
}
}
}
private static void createAndShowGui() {
TimerCircles mainPanel = new TimerCircles();
JFrame frame = new JFrame("TimerCircles");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Related
I want to plot many sinusoids with different frequencies on a panel, but because painting is destructive, I cannot achieve this. I can only see the last sinusoid plotted.
In the code, the generateSinus() method is called only three times, but it might be called many times and I don't want to call g.fillOval() method for each sinusoid in the paintComponent() method.
Is there a way to achieve non-destructive painting and see all sinusioids with different frequencies?
Please see the code below:
package testdrawsinus;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TestDrawSinus extends JPanel
{
private static double[] x;
private static double[] y;
private static boolean buttonClicked = false;
private static JPanel panel = new TestDrawSinus();
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.GREEN);
if (buttonClicked)
{
for (int i=0; i<x.length; i++)
{
g.fillOval((int)x[i] + panel.getWidth()/2, -1*((int)y[i]) + panel.getHeight()/2, 10, 10);
}
buttonClicked = false;
}
}
private static void generateSinus(int freq)
{
x = new double[200];
y = new double[200];
for (int i=0; i<=199; i++)
{
x[i]= (double)i;
y[i] = 100*Math.sin(2*Math.PI*freq*i/200);
}
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
panel.setLayout(null);
panel.setBounds(20,20, 700,400);
panel.setBackground(Color.BLACK);
frame.add(panel);
JButton button1 = new JButton();
button1.setText("plot");
button1.setBounds(300, 500, 150, 50);
frame.add(button1);
button1.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
buttonClicked = true;
generateSinus(1);
panel.repaint();
generateSinus(2);
panel.repaint();
generateSinus(3);
panel.repaint();
}
});
}
}
Thanks for your help.
There are a number of ways you might do this, one way would be to seperate each series into its own model, then have the panel paint each model, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
private Color[] masterColors = new Color[]{
Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK,
Color.RED, Color.WHITE, Color.YELLOW
};
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
TestDrawSinus sinusPane = new TestDrawSinus();
JFrame frame = new JFrame();
frame.add(sinusPane);
JButton button1 = new JButton();
button1.setText("plot");
frame.add(button1, BorderLayout.SOUTH);
button1.addActionListener(new ActionListener() {
private List<Color> colors = new ArrayList<>();
private int freq = 0;
#Override
public void actionPerformed(ActionEvent e) {
sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
}
protected Color nextColor() {
if (colors.isEmpty()) {
colors.addAll(Arrays.asList(masterColors));
Collections.shuffle(colors);
}
return colors.remove(0);
}
});
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private Random rnd = new Random();
private SinusPlot generateSinus(int freq, Color color) {
double[] x = new double[200];
double[] y = new double[200];
for (int i = 0; i < 200; i++) {
x[i] = (double) i;
y[i] = 100 * Math.sin(2d * Math.PI * freq * i / 200d);
}
return new SinusPlot(x, y, color);
}
public class SinusPlot {
private double[] x;
private double[] y;
private Color color;
public SinusPlot(double[] x, double[] y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getSize() {
return x.length;
}
public double getXAt(int index) {
return x[index];
}
public double getYAt(int index) {
return y[index];
}
public Color getColor() {
return color;
}
}
public class TestDrawSinus extends JPanel {
private List<SinusPlot> plots = new ArrayList<>(8);
public void addSinusPlot(SinusPlot plot) {
plots.add(plot);
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (SinusPlot plot : plots) {
System.out.println(plot.getColor());
g2d.setColor(plot.getColor());
for (int i = 0; i < plot.getSize(); i++) {
Ellipse2D dot = new Ellipse2D.Double(((getWidth() - plot.getSize()) / 2) + plot.getXAt(i), plot.getYAt(i) + getHeight() / 2, 10, 10);
g2d.fill(dot);
}
}
g2d.dispose();
}
}
}
null layouts aren't going to help you, take the time to learn understand how the layout management system works, it will save you lot of time and effort. See Laying Out Components Within a Container for more details.
static is not your friend (especially in this context), make the effort to understand how to live without it (and when to use it)
Howcome this code below wont work? I want to add new Ovals to the ArrayList every 200 ms and display them and run them one by one. It works fine when Im running one particle s.runner(); but it doesnt seem to run all my particles.
MAIN:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.Timer;
public class ExempelGraphics extends JFrame implements ActionListener {
Timer t;
private int inc = 0;
ArrayList<Surface> particle = new ArrayList<>();
Surface s;
public ExempelGraphics() {
t = new Timer(10, this);
t.start();
s = new Surface(10, 10);
initUI();
}
private void initUI() {
add(s);
setSize(350, 250);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
// s.runner();
// add
if (inc++ % 20 == 0) {
particle.add(new Surface(10, 10));
}
// display
for (int i = 0; i < particle.size(); i++) {
Surface p = particle.get(i);
p.runner();
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ExempelGraphics ex = new ExempelGraphics();
ex.setVisible(true);
}
});
}
}
GRAPHICS:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class Surface extends JPanel {
private int locX = 0;
private int locY = 0;
public Surface(int locX, int locY) {
this.locX = locX;
this.locY = locY;
}
public void runner() {
locX = locX + 1;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
g2d.fillOval(locX, locY, 10, 10);
}
}
I think that you're program structure is broken. You should have only one JPanel here that does the drawing, that has its paintComponent overridden, and your Surface class should be a logical class and not a component class -- in other words, don't have it extend JPanel, and give it a public void draw(Graphics g) method where you draw the oval. Then have the drawing JPanel hold an ArrayList of these surfaces, and in the main JPanel's paintComponent method, iterate through the surfaces, calling each one's draw method.
Also your Timer's delay is not realistic and is too small. 15 would be much more realistic.
Also, don't call repaint() from within surface, since that will generate too many repaint calls unnecessarily. Instead call it from within the Timer's ActionListener after calling the runner methods on all the Surface objects.
Also note that every time you add a component to a JFrame's contentPane in a default fashion, you cover up the previously added components. If you go by my recommendations above, this isn't an issue since you'd only be adding that single JPanel to it.
For example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class ExampleGraphics2 extends JPanel {
private static final int PREF_W = 650;
private static final int PREF_H = 500;
private static final int TIMER_DELAY = 20;
private List<Surface> surfaces = new ArrayList<>();
public ExampleGraphics2() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Surface surface : surfaces) {
surface.draw(g);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
private int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
index++;
index %= 20;
if (index == 0) {
surfaces.add(new Surface(10, 10));
}
for (Surface surface : surfaces) {
surface.runner();
}
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("Example Graphics 2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ExampleGraphics2());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
package foo1;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class Surface {
private int locX = 0;
private int locY = 0;
public Surface(int locX, int locY) {
this.locX = locX;
this.locY = locY;
}
public void runner() {
locX = locX + 1;
}
public void draw(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
g2d.fillOval(locX, locY, 10, 10);
}
}
Although there are questions similar, I think mine is slightly different because of how I have my code set up. I have a JFrame within my main method. However, I only have JPanel in my constructor. I tried to make some of my variables static so that I could access them in the main method and say, for instance, if the x-coordinate of this graphic plus its width is greater than frame.getWidth().. but that won't work for some reason. I don't want to bombard anyone with code so I will just try to put the main information and if you need more, I'll update it.
package finalProj;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Ellipse2D;
public class nonaMaingamePractice extends JPanel implements ActionListener, KeyListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private static Ellipse2D ellipse;
static Toolkit tools = Toolkit.getDefaultToolkit();
static int screenWidth = (int)(Math.round(tools.getScreenSize().getWidth()));
static int screenHeight = (int)(Math.round(tools.getScreenSize().getHeight()));
private static Rectangle paddleRect;
JLabel text = new JLabel("cool");
Timer timeMove = new Timer(1, this);
Timer timeBall = new Timer(10, new timeBall());
private static double x = screenWidth/2, y = (screenHeight*0.8), xx = 0, yy = 0, score = 0, Ox = screenWidth/2, Oy = screenHeight/2, Oyy = 0, width = 100, height = 30;
public nonaMaingamePractice(){
setLayout(new BorderLayout());
timeBall.start();
timeMove.start();
addKeyListener(this);
setFocusable(true);
JPanel panelNorth = makePanel();
panelNorth.setBackground(Color.CYAN);
add(panelNorth, BorderLayout.NORTH);
JLabel scoreLabel = new JLabel("Score: " + score);
panelNorth.add(scoreLabel);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.BLUE);
paddleRect = new Rectangle((int)x, (int)y, (int)width, (int)height);
ellipse = new Ellipse2D.Double(Ox, Oy+Oyy, 50, 50);
Graphics2D graphics = (Graphics2D)g;
graphics.fill(paddleRect);
graphics.fill(ellipse);
}
#Override
public void actionPerformed(ActionEvent e) {
x = x + xx;
y = y + yy;
if(x<0){
x=0;
xx=0;
}
repaint();
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if(c==KeyEvent.VK_RIGHT){
xx=1;
}else if(c==KeyEvent.VK_LEFT){
xx=-1;
}
}
#Override
public void keyReleased(KeyEvent e) {
xx=0;
}
protected JPanel makePanel() {
#SuppressWarnings("serial")
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
};
pane.setBackground(Color.CYAN);
return pane;
}
protected class timeBall implements ActionListener{
Timer timeWhateva = new Timer(100, this);
#Override
public void actionPerformed(ActionEvent e) {
try{
System.out.println(paddleRect.getX());
if(ellipse.intersects(paddleRect)){
timeWhateva.start();
Oy+=-1;
System.out.println(ellipse.getX() + " " + ellipse.getY());
}else if(!ellipse.intersects(paddleRect)){
Oyy+=1;
}
}catch(RuntimeException NullPointerException){
System.out.println(NullPointerException.getMessage());
}
repaint();
}
}
public static void main(String[] args){
nonaMaingamePractice main = new nonaMaingamePractice();
JFrame frame = new JFrame();
frame.add(main);
frame.setVisible(true);
frame.setTitle("Project 4 game");
frame.setSize(screenWidth, screenHeight);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Okay, so there seems to a few things that are wrong.
First, don't rely on static for cross object communication, this is a really bad idea which will come back to bite you hard. Instead, pass information to the classes which need it.
Second, I'd focus on having a single Timer (or "main-loop") which is responsible for updating the current state of the game and scheduling repaints. This is the basic concept of Model-View-Controller paradigm
The first thing I'm going to do is take your code apart completely and rebuild it...
To start with, I want some kind of interface which provides information about the current state of the game and which I can pass instances of to other parts of the game in order for them to make decisions and update the state of the game...
public interface GameView {
public boolean isKeyRightPressed();
public boolean isKeyLeftPressed();
public Dimension getSize();
public void updateState();
}
This provides information about the state of the right and left keys, the size of the view and provides some basic functionality to request that the view update it's current state
Next, we need some way to model the state of the game...
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
public interface GameModel {
public Rectangle getPaddle();
public Ellipse2D getBall();
public void ballWasMissed();
}
So, this basically maintains information about the paddle and ball and provides a means by which the "main game loop" can provide notification about the state of the game back to the model
Next, we need to the actual "main game loop" or controller. This is responsible for updating the state of the model and updating the view...
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
public class MainLoop implements ActionListener {
private GameView gameView;
private GameModel gameModel;
private int ballYDelta = 1;
public MainLoop(GameView gameView, GameModel gameModel) {
this.gameView = gameView;
this.gameModel = gameModel;
}
#Override
public void actionPerformed(ActionEvent e) {
Rectangle paddle = gameModel.getPaddle();
Ellipse2D ball = gameModel.getBall();
// Update the paddle position...
if (gameView.isKeyLeftPressed()) {
paddle.x--;
} else if (gameView.isKeyRightPressed()) {
paddle.x++;
}
// Correct for overflow...
if (paddle.x < 0) {
paddle.x = 0;
} else if (paddle.x + paddle.width > gameView.getSize().width) {
paddle.x = gameView.getSize().width - paddle.width;
}
// Update the ball position...
Rectangle bounds = ball.getBounds();
bounds.y += ballYDelta;
if (bounds.y < 0) {
bounds.y = 0;
ballYDelta *= -1;
} else if (bounds.y > gameView.getSize().height) {
// Ball is out of bounds...
// Notify the gameView so it knows what to do when the ball goes
// out of the game view's viewable, ie update the score...
// Reset ball position to just out side the top of the view...
gameModel.ballWasMissed();
bounds.y = -bounds.height;
} else if (paddle.intersects(bounds)) {
// Put the ball to the top of the paddle
bounds.y = paddle.y - bounds.height;
// Bounce
ballYDelta *= -1;
}
ball.setFrame(bounds);
// Update the view
gameView.updateState();
}
}
This is basically where we are making decisions about the current position of the objects and updating their positions. Here we check for "out-of-bounds" positions and update their states appropriately (for example, the ball can "bounce" and change directions)
The delta values are quite small, so you might want to play around with those
And finally, we need something that pulls it all together...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class NonaMaingamePractice extends JPanel implements KeyListener, GameView {
/**
*
*/
private static final long serialVersionUID = 1L;
JLabel text = new JLabel("cool");
private Timer timeBall;
private GameModel model;
private boolean init = false;
private boolean rightIsPressed;
private boolean leftIsPressed;
public NonaMaingamePractice() {
setLayout(new BorderLayout());
addKeyListener(this);
setFocusable(true);
JPanel panelNorth = makePanel();
panelNorth.setBackground(Color.CYAN);
add(panelNorth, BorderLayout.NORTH);
JLabel scoreLabel = new JLabel("Score: " + 0);
panelNorth.add(scoreLabel);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
if (getWidth() > 0 && getHeight() > 0 && !init) {
init = true;
model = new DefaultGameModel(getSize());
timeBall = new Timer(40, new MainLoop(NonaMaingamePractice.this, model));
timeBall.start();
} else if (model != null) {
model.getPaddle().y = (getHeight() - model.getPaddle().height) - 10;
}
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
Graphics2D graphics = (Graphics2D) g;
if (model != null) {
graphics.fill(model.getPaddle());
graphics.fill(model.getBall());
}
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_RIGHT) {
rightIsPressed = true;
} else if (c == KeyEvent.VK_LEFT) {
leftIsPressed = true;
}
}
#Override
public void keyReleased(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_RIGHT) {
rightIsPressed = false;
} else if (c == KeyEvent.VK_LEFT) {
leftIsPressed = false;
}
}
protected JPanel makePanel() {
#SuppressWarnings("serial")
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
};
pane.setBackground(Color.CYAN);
return pane;
}
#Override
public boolean isKeyRightPressed() {
return rightIsPressed;
}
#Override
public boolean isKeyLeftPressed() {
return leftIsPressed;
}
#Override
public void updateState() {
// Maybe update the score??
repaint();
}
public static void main(String[] args) {
NonaMaingamePractice main = new NonaMaingamePractice();
JFrame frame = new JFrame();
frame.add(main);
frame.setVisible(true);
frame.setTitle("Project 4 game");
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
I've just moved over from Pygame so Java 2D in an applet is a little new to me, especially when it comes to repainting the screen. In pygame you can simply do display.fill([1,1,1]) but how do I do this in an applet in Java? I understand the use of repaint() but that doesn't clear the screen - any moving object is not 'removed' from the screen so you just get a long line of painted circles.
Here's my code that I've been testing with:
package circles;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Random;
public class circles extends Applet implements Runnable {
private static final long serialVersionUID = -6945236773451552299L;
static Random r = new Random();
String msg = "Click to play!";
static int w = 800, h = 800;
int[] txtPos = { (w/2)-50,(h/2)-50 };
int[] radiusRange = { 5,25 };
int[] circles;
static int[] posRange;
int x = 0, y = 0;
int radius = 0;
int cursorRadius = 10;
boolean game = false;
public static int[] pos() {
int side = r.nextInt(5-1)+1;
switch(side) {
case 1:
posRange = new int[]{ 1,r.nextInt(w),r.nextInt((h+40)-h)+h,r.nextInt(270-90)+90 };
break;
case 2:
posRange = new int[]{ 2,r.nextInt((w+40)-w)+w,r.nextInt(h),r.nextInt(270-90)+90 };
break;
case 3:
posRange = new int[]{ 3,r.nextInt(w),r.nextInt(40)-40,r.nextInt(180) };
break;
case 4:
posRange = new int[]{ 4,r.nextInt(40)-40,r.nextInt(h),r.nextInt(180) };
break;
}
System.out.println(side);
return posRange;
}
public void start() {
setSize(500,500);
setBackground(Color.BLACK);
new Thread(this).start();
}
public void run() {
}
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics e) {
Graphics2D g = (Graphics2D) e;
if(System.currentTimeMillis()%113==0) {
x+=1;
y+=1;
}
g.setColor(Color.BLUE);
g.fillOval(x,y,20,20);
repaint();
}
}
You need to call super.paint(g); in your paint method, as to not leave paint artifacts.
Never call repaint() from inside the paint method
Don't explicitly call paint, as you do in update(), when you mean to call reapaint()
just update the x and y values from inside the update() method, then call repaint()
You don't need to take a Graphics argument in update()
You need to call update() somewhere repeatedly in a loop, as it updates the x and y and reapint()s
If your class is going to be a Runnable, then you should put some code in the run() method. That's probably where you should have your loop
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class circles extends Applet implements Runnable {
int x = 0, y = 0;
public void start() {
setSize(500, 500);
setBackground(Color.BLACK);
new Thread(this).start();
}
public void run() {
while (true) {
try {
update();
Thread.sleep(50);
} catch (InterruptedException ex) {
}
}
}
public void update() {
x += 5;
y += 6;
repaint();
}
public void paint(Graphics e) {
super.paint(e);
Graphics2D g = (Graphics2D) e;
g.setColor(Color.BLUE);
g.fillOval(x, y, 20, 20);
}
}
Side Notes
Why use Applets in the first place. If you must, why use AWT Applet and not Swing JApplet? Time for an upgrade.
Here's how I'd redo the whole thing in Swing, using a Swing Timer instead of a loop and Thread.sleep, as you should be doing.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Circle extends JPanel{
private static final int D_W = 500;
private static final int D_H = 500;
int x = 0;
int y = 0;
public Circle() {
setBackground(Color.BLACK);
Timer timer = new Timer(50, new ActionListener(){
public void actionPerformed(ActionEvent e) {
x += 5;
y += 5;
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillOval(x, y, 20, 20);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new Circle());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
See How to use Swing Timers
See Create GUIs with Swing
Here's more advanced example for you to look at and ponder.
UPDATE
"Problem is, that's a JPANEL application. I specifically want to make an applet easily usable on a web page. "
You can still use it. Just use the JPanel. Take out the main method, and instead of Applet, use a JApplet and just add the JPanel to your applet. Easy as that.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JApplet;
import javax.swing.JPanel;
import javax.swing.Timer;
public class CircleApplet extends JApplet {
#Override
public void init() {
add(new Circle());
}
public class Circle extends JPanel {
private static final int D_W = 500;
private static final int D_H = 500;
int x = 0;
int y = 0;
public Circle() {
setBackground(Color.BLACK);
Timer timer = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent e) {
x += 5;
y += 5;
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillOval(x, y, 20, 20);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
}
}
I created a program that draws 4 shapes with MousePressed Listiner. The program should draws shapes with random colors. My program draws shapes correctrly but didn't draw them with random color. Can you give me some idea how to program that.
Thanks
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JToolBar;
import javax.swing.border.EmptyBorder;
public class Pole extends JFrame {
public static int i;
public static void main(String[] args) {
JFrame frame= new JFrame("Shape Stamper!");
JPanel container;
JButton circle = new JButton("Circle");
JButton square = new JButton("Square");
JButton rectangle = new JButton("Rectangle");
JButton oval = new JButton("Oval");
container = new JPanel(new GridLayout(1, 4));
container.add(circle);
container.add(square);
container.add(rectangle);
container.add(oval);
circle.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
i = 1;
}
});
square.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
i = 2;
}
});
rectangle.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
i = 3;
}
});
oval.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
i = 4;
}
});
MyComponent shape = new MyComponent();
frame.setSize(500, 500);
frame.add(shape, BorderLayout.CENTER);
frame.add(container, BorderLayout.SOUTH);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JComponent;
/**
*
* #author Taras
*/
public class MyComponent extends JComponent {
int i;
Color randColor;
public MyComponent()
{
this.i = i;
addMouseListener(new MouseHandler());
}
private ArrayList<Rectangle2D> arrOfRect=new ArrayList<>();
private ArrayList<Ellipse2D> arrOfEllipse=new ArrayList<>();
// private ArrayList<Color> randColor = new ArrayList<>();
public void paintComponent(Graphics g)
{
Graphics2D g2=(Graphics2D) g;
g.setColor(new Color(randRed(), randGreen(), randBlue()));
for(Rectangle2D r: arrOfRect){
//g2.setColor(Color.GREEN);
g2.draw(r);
}
for(Ellipse2D e: arrOfEllipse){
g2.draw(e);}
repaint();
}
public void add(Point2D p)
{
double x=p.getX();
double y=p.getY();
if (Pole.i == 1){
Ellipse2D ellipse = new Ellipse2D.Double(x, y, 100,100);
//randColor = new Color(randRed(), randGreen(), randBlue());
arrOfEllipse.add(ellipse);
}
if (Pole.i == 2){
Rectangle2D rectangls=new Rectangle2D.Double(x, y, 100, 100);
arrOfRect.add(rectangls);
}
if (Pole.i == 3){
Rectangle2D rectangls=new Rectangle2D.Double(x, y, 150, 100);
arrOfRect.add(rectangls);
}
if (Pole.i == 4){
Ellipse2D ellipse = new Ellipse2D.Double(x, y, 100,50);
arrOfEllipse.add(ellipse);
}
}
private class MouseHandler extends MouseAdapter {
public void mousePressed(MouseEvent event)
{
add(event.getPoint());
//Color rColor = new Color(randRed(), randGreen(), randBlue());
//randColor.add(rColor);
randColor = new Color(randRed(), randGreen(), randBlue());
}
}
private int randRed() {
int red;
Random randomNumber = new Random();
red = randomNumber.nextInt(255);
return red;
}
private int randGreen() {
int green;
Random randomNumber = new Random();
green = randomNumber.nextInt(255);
return green;
}
private int randBlue() {
int blue;
Random randomNumber = new Random();
blue = randomNumber.nextInt(255);
return blue;
}
}
Your problem in next you set Color only once for all components here:
g.setColor(new Color(randRed(), randGreen(), randBlue()));
but if you want to change color for each shape you need to call setColor() each time.
You can use one instance of Random change your paintComponent() method like next and your shapes will have different colors :
Random rand = new Random();
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (Rectangle2D r : arrOfRect) {
g.setColor(new Color(rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
g2.draw(r);
}
for (Ellipse2D e : arrOfEllipse) {
g.setColor(new Color(rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
g2.draw(e);
}
}
Also it's wrong to call repaint() inside paintComponent() method, remove that line and repaint your panel after adding new shape, for example:
private class MouseHandler extends MouseAdapter {
public void mousePressed(MouseEvent event) {
add(event.getPoint());
repaint();
}
}
If the objective is to paint every shape with a random color then you'd have to update graphics color for every shape that is drawn. Ie:
for (Rectangle2D r : arrOfRect) {
g.setColor(getRandomColor());
g2.draw(r);
}
There is no need to allocate new Random in every call, you can make it a private member. For example:
private Random randomNumber = new Random();
private Color getRandomColor() {
return new Color(randomNumber.nextFloat(),
randomNumber.nextFloat(), randomNumber.nextFloat());
}
If you want to assign a random color to each shape in add() method and keep it then you'd have to maintain a map of colors, or some other structure that serves the goal.
Other minor but important notes:
Don't forget to call super.paintComponent() inside your implementation of paintComponent.
Do not call repaint() in paintComponent(), as it eventually triggers paintComponent. So you're creating unnecessary repaints.
Do call repaint() inside add() to repaint the component with a newly created shape.
See Performing Custom Painting for more details and examples.