I made a program to display interference patterns for light waves. I did this by using the paint method on a JPanel to draw 2 sources and then drawing concentric circles around them. This would be double slit interference, so I allowed one of the sources to move around to experiment with the slit width.
The problem is that when I run this, my computer says it is using 80% of my CPU! There's really not much to it. Circle in the middle, circles around it, and it moves. My code follows.
main class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class main {
static final int UP = -1;
static final int DOWN = 1;
static final int RIGHT = 1;
static final int LEFT = -1;
static final int NOMOVEMENT = 0;
static int verticalMovement = NOMOVEMENT;
static int horizontalMovement = NOMOVEMENT;
public static void main(String[] args) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Point s1 = new Point((int)(screenSize.getWidth()/3), 50);
Point s2 = new Point((int)(2*screenSize.getWidth()/3), 50);
JFrame frame = new JFrame("Physics Frame");
frame.setPreferredSize(screenSize);
PhysicsPane pane = new PhysicsPane(screenSize, 15, s1, s2);
pane.setPreferredSize(screenSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
Timer time = new Timer(1000 / 20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (verticalMovement){
case UP:
s2.changeY(UP);
break;
case DOWN:
s2.changeY(DOWN);
break;
default:
break;
}
switch (horizontalMovement){
case RIGHT:
s2.changeX(RIGHT);
break;
case LEFT:
s2.changeX(LEFT);
break;
default:
break;
}
pane.repaint();
}
});
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==37){
horizontalMovement = LEFT;
} else if (e.getKeyCode()==38){
verticalMovement = UP;
} else if (e.getKeyCode()==39){
horizontalMovement = RIGHT;
} else if (e.getKeyCode()==40){
verticalMovement = DOWN;
}
if(e.getKeyChar()=='a'){
pane.setWaveLength(2);
}
if(e.getKeyChar()=='s'){
pane.setWaveLength(-2);
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()){
case 37:
horizontalMovement = NOMOVEMENT;
break;
case 38:
verticalMovement = NOMOVEMENT;
break;
case 39:
horizontalMovement = NOMOVEMENT;
break;
case 40:
verticalMovement = NOMOVEMENT;
break;
}
}
});
frame.setVisible(true);
time.start();
}
}
Panel class. If there's an inefficiency with the drawing method, it would be here.
import javax.swing.*;
import java.awt.*;
public class PhysicsPane extends JPanel {
private Dimension size;
private Point[] pointArray = new Point[2];
private double waveLength;
public PhysicsPane(Dimension size, double wavelength, Point source1, Point source2) {
this.size = size;
pointArray[0] = source1;
pointArray[1] = source2;
setPreferredSize(size);
this.waveLength = wavelength;
}
#Override
public void paintComponent(Graphics g){
for (int i = 0; i < 2; i++) {
g.setColor(Color.black);
double x = this.pointArray[i].getX();
double y = this.pointArray[i].getY();
g.fillOval((int)x, (int)y, 2, 2);
for (int j = (int)waveLength; j < 1500; j+=waveLength) {
g.setColor(Color.red);
g.drawOval((int)x-(j/2+1), (int)y-(j/2+1), 2 + j, 2 + j);
}
}
}
public void setWaveLength(double increment){
this.waveLength+=increment;
}
}
Point Class: Really just puts x and y coordinates into one construct. Not important particularly
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public void changeX(double dX){
this.x+=dX;
}
public void changeY(double dY){
this.y+=dY;
}
}
So what is wrong with my program? Why is such a simple animation consuming so much of my processor?
UPDATED CODE SECTION:
#Override
public void paintComponent(Graphics g){
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = bi.getGraphics();
for (int i = 0; i < 2; i++) {
graphics.setColor(Color.black);
double x = this.pointArray[i].getX();
double y = this.pointArray[i].getY();
graphics.fillOval((int)x, (int)y, 2, 2);
for (int j = (int)waveLength; j < 1500; j+=waveLength) {
graphics.setColor(Color.red);
graphics.drawOval((int)x-(j/2+1), (int)y-(j/2+1), 2 + j, 2 + j);
}
}
g.drawImage(bi, 0, 0, new ImageObserver() {
#Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
return false;
}
});
}
The improvement that I recommend is removal of unnecessary tasks being performed, notably how your code updates the pane being drawn on, even when there aren't changes.
The following update reduced CPU usage from 12% to 0% (static frame):
Timer time = new Timer(1000 / 20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
boolean refresh = false;
switch (verticalMovement) {
case UP:
s2.changeY(UP);
refresh = true;
break;
case DOWN:
s2.changeY(DOWN);
refresh = true;
break;
default:
break;
}
switch (horizontalMovement) {
case RIGHT:
s2.changeX(RIGHT);
refresh = true;
break;
case LEFT:
s2.changeX(LEFT);
refresh = true;
break;
default:
break;
}
if (refresh == true) {
pane.repaint();
}
}
});
Related
I have created a game similar to snake in which the user is first prompted with a jpanel asking which difficulty they want, and whatever JButton they pick influences the size of the map as well as the delay between the snake movements. I have the map size working just fine, but the delay variable never seems to change. I suspect it has something to do with the way the timer is being casted, but I have no idea how to fix it. I am also wondering how when the program is first ran it seems some of the variables don't update, but the second time it is ran all of them are updated. Here is my class with the original variables and collision detection:
import java.util.Random;
import javax.swing.JLabel;
public class GameEngine extends JPanel implements ActionListener{
//creates the size of the panel as well as creating the resolution for all objects, including the players and food.
static final int sWidth = 600;
static final int sHeight = 600;
public static int size = 24;
static int objectSize = (sHeight*sWidth) / size;
public static int delay = 100;
final int playerX[] = new int[objectSize];
final int playerY[] = new int[objectSize];
int bodySize = 4;
int score = 0;
int appleX;
int appleY;
char direction = 'D';
boolean started = false;
Random random;
Timer timer;
boolean easy;
JLabel score1;
public static String difficulty;
GameEngine(){
random = new Random();
this.setPreferredSize(new Dimension(sWidth,sHeight));
this.setBackground(Color.black);
this.setFocusable(true);
this.addKeyListener(new UserMovement());
gameStart();
}
public void gameStart() {
newApple();
started = true;
timer = new Timer(delay,this);
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void drawHead(Graphics g) {
g.setColor(new Color(100,252,0));
g.fillRect(playerX[0], playerY[0], size, size);
}
public void draw(Graphics g) {
if(started) {
//draws the apples
g.setColor(Color.red);
g.fillOval(appleX, appleY, size, size);
for (int i = 0; i < bodySize; i++) {
if(i == 0) {
drawHead(g);
}
else {
g.setColor(new Color(60,180,0));
g.fillRect(playerX[i], playerY[i], size, size);
}
}
g.setColor(Color.white);
g.setFont(new Font("Bold", Font.BOLD, 20));
FontMetrics metrics = getFontMetrics(g.getFont());
g.drawString("Score: " + score,(sWidth - metrics.stringWidth("Score: " + score))/2,g.getFont().getSize());
}
}
public void newApple(){
appleX = random.nextInt((int)(sWidth/size))*size;
appleY = random.nextInt((int)(sHeight/size))*size;
}
//moves the player by using and modifying their coordinates
public void move() {
for (int i = bodySize; i > 0; i--) {
playerX[i] = playerX[i-1];
playerY[i] = playerY[i-1];
}
switch(direction) {
case 'W':
playerY[0] = playerY[0] - size;
break;
case 'S':
playerY[0] = playerY[0] + size;
break;
case 'A':
playerX[0] = playerX[0] - size;
break;
case 'D':
playerX[0] = playerX[0] + size;
break;
}
}
public void checkFood() {
if(playerX[0] == appleX && playerY[0] == appleY)
{
bodySize++;
score++;
newApple();
}
}
public void checkCol() {
//checks for head collision with the body
for(int i = bodySize; i > 0; i--) {
if((playerX[0] == playerX[i]) && (playerY[0] == playerY[i])) {
started = false;
}
}
//checks if head touches any of the walls of the program
if(playerX[0] < 0) {
started = false;
}
if(playerX[0] > sWidth) {
started = false;
}
if(playerY[0] < 0) {
started = false;
}
if(playerY[0] > sHeight) {
started = false;
}
if(started != true) {
timer.stop();
}
}
public void actionPerformed(ActionEvent e){
if(started == true) {
move();
checkFood();
checkCol();
}
repaint();
}
public class UserMovement extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if(direction != 'D') {
direction = 'A';
}
break;
case KeyEvent.VK_RIGHT:
if(direction != 'A') {
direction = 'D';
}
break;
case KeyEvent.VK_UP:
if(direction != 'S') {
direction = 'W';
}
break;
case KeyEvent.VK_DOWN:
if(direction != 'W') {
direction = 'S';
}
break;
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
}
}
and here is the code calling and changing the delay and size variables:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class StartMenu extends JPanel {
StartMenu()
{
JButton easy = new JButton();
JButton hard = new JButton();
this.setPreferredSize(new Dimension(350,240));
this.setLayout(null);
this.setBackground(Color.black);
this.setFocusable(true);
easy.setBounds(75,40,200,40);
hard.setBounds(75,120,200,40);
this.add(easy);
this.add(hard);
easy.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
setVisible(false);
new SnakeStart();
GameEngine.size = 48;
GameEngine.delay = 140;
}
});
hard.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
setVisible(false);
new SnakeStart();
GameEngine.size = 24;
GameEngine.delay = 70;
}
});
}
}
I assume your GameEngine instance is created before the StartMenu action listeners are executed. If that assumption is correct, that means that GameEngine.timer with the default value of delay is created in the GameEngine constructor and is not updated after the delay is changed.
You need to make sure that you explicitly update your timer with the new delay value after StartMenu actions are called.
thank you for helping me.
I'm making random panel generator.
first, user chooses what shape to draw and my code randomly draw 1~10 shapes on JFrame.
Next, user can choose Kth panel and change its color.
But, here's the problem: Once it added to JFrame, it won't change color.
for example, if I change 1st shape color to red and show it, its color change to red. But I change 1st shape color to blue, it won't change.
Before show it, its color changes well if I don't show it on JFrame.
I discovered that I can solve this by making a new application each time before showing it, but My purpose is to use only one JFrame.
Here's my code.
import java.awt.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import java.util.Scanner;
public class Shapes
{
private class TransparentPanel extends JPanel
{
{
setOpaque(false);
}
}
public static void main(String[] args)
{
enginePart();
}
static void enginePart()
{
JFrame application= new JFrame();
btnConstructor makeBtn = new btnConstructor();
makeShapes panel = new makeShapes(0,0,0,0,0,0,0);
randomNumMemorize memorizeIt = new randomNumMemorize();
Random getRandomNum = new Random();
int[] thirdChoice = new int[2], KthShapeCntField = new int[10000];
int memorizeItArr[][][] = new int[10000][25][10];
//3차원 배열을 선언하여 저장. 1번째 칸에는 도형의 코드, 2번째 칸에는 k번째 도형의 각각 갯수, 3번째 칸에는 k번째 도형의 정보를 담습니다.
int makeItCnt, thirdChoiceOfK, thirdChoiceOfColor;
int applicationWidth=500, applicationHeight=500, choiceCnt=0;
application.setSize(applicationWidth, applicationHeight);
application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
while(true)
{
int first_choice = makeBtn.optionBtn();
//입력하기, 색깔바꾸기, 종료하기 중 하나 선택
if(first_choice==0)
{//입력하기 선택
int secondChoice = makeBtn.drawSelected();//사용자 도형 설정
makeItCnt = getRandomNum.nextInt(9)+1;//생성할 도형 개수 지정
KthShapeCntField[choiceCnt] = makeItCnt;
for(int i=0;i<makeItCnt;i++)
{
memorizeItArr[choiceCnt][i] = memorizeIt.randomNumMemorize(secondChoice, 0);
}
choiceCnt+=1;
//k번째 도형 == choiceCnt
continue;
}
else if(first_choice==1)
{//k번째 도형 색깔 바꾸기 선택
System.out.println("==============REDRAW!!================");
thirdChoice = makeBtn.changeColorSelected();//k 선택부
thirdChoiceOfK = thirdChoice[0] - 1;//콤퓨타는 0부터 세니까 여기서 1 빼줍니다.
thirdChoiceOfColor = thirdChoice[1];
int temp= thirdChoiceOfK, i=0;
if(thirdChoiceOfK == -1 || thirdChoiceOfColor==-1) continue;
while(temp-KthShapeCntField[i]>=0)
{
temp-=KthShapeCntField[i++];
}
memorizeItArr[i][temp][5] = thirdChoiceOfColor;
continue;
}
else
{//추가동작 선택
/*JFrame application= new JFrame();
application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
application.setSize(500,500);*/
int fourthChoice = makeBtn.additionalActionSelected();
if(fourthChoice==0)
{//도형 숨기기
hide(panel, application, applicationWidth, applicationHeight);
continue;
}
else if(fourthChoice==1)
{//도형 보이기
int temp=0;
application.revalidate();
application.repaint();
for(int i=0;i<choiceCnt;i++)
{
for(int j=0; j<KthShapeCntField[i]; j++)
{
panel = new makeShapes(choiceCnt, memorizeItArr[i][j][0], memorizeItArr[i][j][1], memorizeItArr[i][j][2], memorizeItArr[i][j][3], memorizeItArr[i][j][4], memorizeItArr[i][j][5]);
panel.validate();
panel.repaint();
application.add(panel);
panel.setOpaque(false);//투명화
System.out.printf("%dth Shape color is: %d\n",temp++, memorizeItArr[i][j][5]);
application.setVisible(true);
}
}
}
else
{//종료
System.exit(0);
}
}
}
}
static void hide(makeShapes panel, JFrame application, int width, int height)
{
panel = new makeShapes(-1,100,0,0,width,height,100);
application.add(panel);
application.setVisible(true);
}
}
class makeShapes extends JPanel
{
private int choice, x, y, size1, size2;
private int color=0, cnt;// 0:red, 1:green, 2:blue, 3:yellow, 4:purple 5:black
public makeShapes(int cnt, int choice, int x, int y, int size1, int size2, int usrColor)
{
this.choice = choice;
this.x = x;
this.y = y;
this.size1 = size1;
this.size2 = size2;
this.color = usrColor;
this.cnt = cnt;
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
String s_cnt = Integer.toString(this.cnt);
switch(color)
{
case 100:
g.setColor(Color.WHITE);
break;
case 1:
g.setColor(Color.red);
break;
case 2:
g.setColor(Color.green);
break;
case 3:
g.setColor(Color.blue);
break;
case 4:
g.setColor(Color.yellow);
break;
case 5: case 0:
g.setColor(Color.BLACK);
break;
}
switch (choice)
{
case 100:
g.fillRect(0,0,500,500);
break;
case 0:
g.drawOval(x, y, size1, size2);
break;
case 1:
g.drawRect(x, y, size1, size2);
break;
case 2:
g.drawLine(x,y, x+size1, x+size2);
break;
}
}
}
class randomNumMemorize
{
Random getRandom = new Random();
private int choice, x, y, size1, size2, cnt;
private int color=1;
public int[] randomNumMemorize(int usrchoice, int usrcolor)
{
this.choice = usrchoice;
this.x = getRandom.nextInt(400);
this.y = getRandom.nextInt(400);
this.size1 = getRandom.nextInt(100)+1;
this.size2 = getRandom.nextInt(100)+1;
this.color = usrcolor;
int[] retArr = {choice, x, y, size1, size2, color};
return retArr;
}
}
class btnConstructor
{
public int optionBtn()
{
String btn_msg[] = {"Draw Shapes", "Change Color", "Additional Action"};
int num = JOptionPane.showOptionDialog(null,"Choose what you want.","User choice",JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.QUESTION_MESSAGE,null,btn_msg,"Draw Shapes");
return num;
}
public int drawSelected()
{
String btn_msg[] = {"Circle", "Rectangle", "Line"};
int num = JOptionPane.showOptionDialog(null,"Choose your shape","Shape choice",JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE,null,btn_msg,"Circle");
return num;
}
public int[] changeColorSelected()
{
int choiceOfK, choiceOfColor;
String numOfK = JOptionPane.showInputDialog("Enter number that you want to change color");
String numOfColor = JOptionPane.showInputDialog("Which Color do you prefer?\nRed:1 Green:2 Blue:3 Yellow:4 Black:5");
if(numOfK==null || numOfColor==null) choiceOfK = choiceOfColor=-1;
else {
choiceOfK = Integer.parseInt(numOfK);
choiceOfColor = Integer.parseInt(numOfColor);
}
int[] return_it = {choiceOfK, choiceOfColor};
return return_it;
}
public int additionalActionSelected()
{
String btn_msg[] = {"Hide", "Show", "Exit"};
int num = JOptionPane.showOptionDialog(null,"Choose your action","User choice",JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE,null,btn_msg,"Restore");
return num;
}
}
I've tried this:
panel.revalidate();
panel.repaint();
application.revalidate();
application.repaint();
So here is the code after taking the tips into account.
The map is kept being repainted, the keylistener has changed but there still seems to be a problem.
The problem again is, the little square in the upper left corner will not move, which is the desired outcome.
package schoolgamev2;
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Spel {
public static void main(String[] args) {
Speelveld map = new Speelveld();
map.Speelveld();
map.GenerateMap();
}
}
class Speelveld extends JPanel {
final int rijen = 16;
final int kolommen = 16;
Speler speler = new Speler();
Loopgebied loopgebied = new Loopgebied();
Blokken blok = new Blokken();
int[][] coordinaten = new int[rijen][kolommen];
public void Speelveld() {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(this);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
setFocusable(true);
addKeyListener(new KeyListener(this));
}
//genereer map
public void GenerateMap() {
Random random = new Random();
int x;
int y;
for (y = 0; y < rijen; y++) { //scan langs y
for (x = 0; x < kolommen; x++) { //scan langs x
//selecteert type blok voor coordinaten x y
coordinaten[x][y] = random.nextInt(4);
//debugprint
}
//debugprint
}
coordinaten[0][0] = 4; //speler begint altijd links boven
coordinaten[15][15] = 5; //finish is altijd rechts onder
}
public int[][] getCoordinaten() {
return coordinaten;
}
public Speler getSpeler2() {
return speler;
}
public int getSpelerX() {
return speler.getX();
}
public int getSpelerY() {
return speler.getY();
}
public void setSpelerX(int x) {
speler.setX(x);
}
public void setSpelerY(int y) {
speler.setY(y);
}
//#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x;
int y;
for (y = 0; y < rijen; y++) { //scan langs y
System.out.println("");
for (x = 0; x < kolommen; x++) { //scan langs x
blok.setX(x);
blok.setY(y);
blok.setType(coordinaten[x][y]);
System.out.print(coordinaten[x][y] + " ");
switch (blok.getType()) {
case 0:
loopgebied.teken(g);
break;
case 4:
speler.teken(g);
/*case 5:
eindveld.teken(g);
break;*/
default:
break;
}
}
}
}
}
class Speler extends Blokken {
Blokken blok = new Blokken();
public void teken(Graphics g) {
g.drawRect(blok.getX(), blok.getY(), 10, 10);
}
}
class Loopgebied extends Blokken {
Blokken blok = new Blokken();
public void teken(Graphics g) {
g.drawRect(blok.getX() * size, blok.getY() * size, size, size);
}
}
class Blokken {
private static int x;
private static int y;
private static int type;
public int size = 16;
//setters voor x y en type
public void setX(int xIn) {
x = xIn;
}
public void setY(int yIn) {
y = yIn;
}
public void setType(int typeIn) {
type = typeIn;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getType() {
return type;
}
}
class KeyListener extends KeyAdapter {
private static final int SCALE = 3;
private Speelveld speelveld;
public KeyListener(Speelveld speelveld) {
this.speelveld = speelveld;
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
int deltaX = 0;
int deltaY = 0;
if (key == KeyEvent.VK_LEFT) {
deltaX = -1 * SCALE;
deltaY = 0;
} else if (key == KeyEvent.VK_UP) {
deltaX = 0;
deltaY = -1 * SCALE;
} else if (key == KeyEvent.VK_RIGHT) {
deltaX = 1 * SCALE;
deltaY = 0;
} else if (key == KeyEvent.VK_DOWN) {
deltaX = 0;
deltaY = 1 * SCALE;
} else {
return;
}
int x = speelveld.getSpelerX() + deltaX;
int y = speelveld.getSpelerY() + deltaY;
speelveld.setSpelerX(x);
speelveld.setSpelerY(y);
speelveld.repaint();
}
}
As mentioned in comments, you got your player, the Speler class, that extends JPanel and is wired to use a KeyListener, but note that it is not being used as a JPanel, and so the KeyListener is non-functioning, because they only work when they have been added to a visible component that has focus.
Suggestions:
Again as per comments, make Speler a non-GUI logical class. Meaning don't have it extend JPanel or any other Swing component, and certainly don't add a KeyListener to it.
Instead give it code for the player's behavior and for having the player draw itself, and that's it. What I'm suggesting is that you try to separate part of your "Model" here the player from the "View" here the Swing GUI.
Have one JPanel and only one that does drawing. Have it override paintComponent (don't forget to call the super's paintComponent within it), and have it draw each logical component by calling the logical component's draw method (public void verf(Graphics2D g2)? or public void teken(Graphics2D g2)?) within its paintComponent method.
As a general rule, you also don't want your GUI component classes, the JPanel here, directly implementing listener interfaces, such as the KeyListener, but will want to keep them separate.
Either make this drawing JPanel focusable, give it focus and add the KeyListener, a separate class, to it.
Or better, use Key Bindings as per the tutorial: Key Bindings
For example the simple [MCVE] below doesn't use a Swing Timer or change velocities, but rather it uses a KeyListener to change position of the Speler2 object using only one drawing JPanel and a KeyListener. If I were making this more robust and larger, I would use the Swing Timer, would use Key Bindings, and would change the velocities using the key bindings.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Spel2 {
private static final int VELD_WIDTH = 500;
private static void createAndShowGui() {
Speelveld2 speelveld2 = new Speelveld2(VELD_WIDTH, VELD_WIDTH);
JFrame frame = new JFrame("Spel2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(speelveld2);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
speelveld2.requestFocusInWindow();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MyKeyListener extends KeyAdapter {
private static final int SCALE = 3;
private Speelveld2 speelveld2;
public MyKeyListener(Speelveld2 speelveld2) {
this.speelveld2 = speelveld2;
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
int deltaX = 0;
int deltaY = 0;
if (key == KeyEvent.VK_LEFT) {
deltaX = -1 * SCALE;
deltaY = 0;
} else if (key == KeyEvent.VK_UP) {
deltaX = 0;
deltaY = -1 * SCALE;
} else if (key == KeyEvent.VK_RIGHT) {
deltaX = 1 * SCALE;
deltaY = 0;
} else if (key == KeyEvent.VK_DOWN) {
deltaX = 0;
deltaY = 1 * SCALE;
} else {
return;
}
int x = speelveld2.getSpelerX() + deltaX;
int y = speelveld2.getSpelerY() + deltaY;
speelveld2.setSpelerX(x);
speelveld2.setSpelerY(y);
speelveld2.repaint();
}
}
#SuppressWarnings("serial")
class Speelveld2 extends JPanel {
private int prefW;
private int prefH;
private Speler2 speler2 = new Speler2();
public Speelveld2(int prefW, int prefH) {
this.prefW = prefW;
this.prefH = prefH;
setFocusable(true);
addKeyListener(new MyKeyListener(this));
}
public Speler2 getSpeler2() {
return speler2;
}
public int getSpelerX() {
return speler2.getX();
}
public int getSpelerY() {
return speler2.getY();
}
public void setSpelerX(int x) {
speler2.setX(x);
}
public void setSpelerY(int y) {
speler2.setY(y);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
speler2.teken(g2);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(prefW, prefH);
}
}
class Speler2 extends Blokken2 {
private static final int RECT_W = 10;
public Speler2() {
super(BlokkenType.SPELER);
}
#Override
public void teken(Graphics2D g2) {
int x = getX();
int y = getY();
g2.drawRect(x, y, RECT_W, RECT_W);
}
}
class Blokken2 {
private int x;
private int y;
private int velx = 0;
private int vely = 0;
private BlokkenType type;
private BufferedImage img;
public Blokken2(BlokkenType type) {
this.type = type;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getVelx() {
return velx;
}
public void setVelx(int velx) {
this.velx = velx;
}
public int getVely() {
return vely;
}
public void setVely(int vely) {
this.vely = vely;
}
public BlokkenType getType() {
return type;
}
public void setType(BlokkenType type) {
this.type = type;
}
public void setImg(BufferedImage img) {
this.img = img;
}
public BufferedImage getImg() {
return img;
}
public void teken(Graphics2D g2) {
if (img != null) {
g2.drawImage(img, x, y, null);
}
}
}
enum BlokkenType {
LOOPGEBIED, BARRICADE, MUUR, SLEUTEN, SPELER, EINDVELD
}
Edit: your latest code has an error here:
class Speler extends Blokken {
Blokken blok = new Blokken();
public void teken(Graphics g) {
g.drawRect(blok.getX(), blok.getY(), 10, 10);
}
}
Regarding your edit:
Now when you create a Speler instance, you create TWO Blokken instances, one which is the Speler instance since it extends Blokken and the other which is contained by the Speler instance since it also has a Blokken field.
You update the x/y state of the first one, but you draw with the second one, and that's why no motion is being displayed. The solution is obvious: use one or the other but not both. Either have Speler extend Blokken or have it contain a Blokken instance but don't do both.
I have a program that draws a square that I can drive around on a JPanel. I had this problem where whenever I would drive it, it would leave artifacts from where it had been on the screen. Kind of would look like JPanel Drawing Glitch when I would drive it around. Then I found a solution online that at the beginning of my paint(Graphics g) method, I should place a "super.paintComponent(g)".
That worked perfectly. But at the same time I kind of enjoyed have the opportunity to mess around with drawing with this car, so I added a piece of code that allowed me to choose whether or not I would be super.paintComponent(g)'ing. But when I transferred this code to another computer, it no longer had the original bug, not allowing me to paint if I wanted to. My question is: How can I replicate this bug, drawing all my previous positions behind me as I drive?
Here is my code.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
/**
* Created by chris on 1/5/16.
*/
public class Game extends JPanel{
int[] size = new int[2];
private Car car;
private JFrame frame;
private final double speedScalar = .5;
private final double angleScalar = 5;
boolean isTurningLeft = false;
boolean isTurningRight = false;
boolean isSpeedingUp = false;
boolean isSlowingDown = false;
private static Toolkit tk = Toolkit.getDefaultToolkit();
private ArrayList<Point> rocks = new ArrayList<>();
public static final int width = tk.getScreenSize().width;
public static final int height = tk.getScreenSize().height;
public boolean isDrawing = false;
public Color[] colorChoices = {Color.black,Color.blue,Color.red,Color.orange,Color.pink} ;
public Color chosenColor = Color.black;
public Game(){
frame = new JFrame();
frame.setPreferredSize(new Dimension(width,height));
car = new Car(300,300);
setPreferredSize(new Dimension(width,height));
frame.add(this);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
System.out.println(e.getKeyChar());
switch(e.getKeyCode()){
case 37:
isTurningLeft = true;
isTurningRight = false;
break;
case 38:
isSpeedingUp = true;
isSlowingDown = false;
break;
case 39:
isTurningLeft = false;
isTurningRight = true;
break;
case 40:
isSpeedingUp = false;
isSlowingDown = true;
break;
}
}
#Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()){
case 37:
isTurningLeft = true;
isTurningRight = false;
break;
case 40:
isSpeedingUp = true;
isSlowingDown = false;
break;
case 39:
isTurningLeft = false;
isTurningRight = true;
break;
case 38:
isSpeedingUp = false;
isSlowingDown = true;
break;
case 32:
isDrawing = !isDrawing;
}
if(e.getKeyChar()=='c'){
chosenColor = colorChoices[(int)(Math.random()*5)];
} else if (e.getKeyChar()==' '){
isDrawing=!isDrawing;
}
}
#Override
public void keyReleased(KeyEvent e) {
switch(e.getKeyCode()){
case 37:
isTurningLeft = false;
break;
case 40:
isSpeedingUp = false;
break;
case 39:
isTurningRight = false;
break;
case 38:
isSlowingDown = false;
break;
}
}
});
Timer time = new Timer(1000/45, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(isTurningRight){
car.setAngle(car.getAngle()+Math.toRadians(angleScalar));
} else if (isTurningLeft){
car.setAngle(car.getAngle()-Math.toRadians(angleScalar));
}
if(isSpeedingUp){
car.setSpeed(car.getSpeed()-speedScalar);
} else if (isSlowingDown){
car.setSpeed(car.getSpeed()+speedScalar);
}
if(car.getSpeed()<0){
car.setSpeed(0);
}
car.setX(car.getX() + (Math.cos(car.getAngle())*car.getSpeed()));
car.setY(car.getY() + (Math.sin(car.getAngle())*car.getSpeed()));
if(car.getX()<0){
car.setX(0);
car.setSpeed(0);
}
if(car.getX()>width){
car.setX(width);
car.setSpeed(0);
}
if(car.getY()<0){
car.setY(0);
car.setSpeed(0);
}
if(car.getY()>height){
car.setY(height);
car.setSpeed(0);
}
repaint();
}
});
double end = Math.random()*15;
frame.setVisible(true);
time.start();
}
public void paint(Graphics g){
if(!isDrawing) {
super.paintComponent(g);
}
/**
* Painting the car
*/
g.setColor(chosenColor);
double sin = Math.sin(car.getAngle());
double cos = Math.cos(car.getAngle());
int[] xPoints = {(int)car.getX(),(int)(car.getX()+cos*car.width),(int)(car.getX()-sin*car.length+cos*car.width),
(int)(car.getX()-sin*car.length)};
int[] yPoints = {(int)car.getY(),(int)(car.getY()+car.width*sin),(int)(car.getY()+cos*car.length+sin*car.width),
(int)(car.getY()+car.length*cos)};
Polygon car = new Polygon(xPoints,yPoints,4);
int[] recX = {400,450,450,400};
int[] recY = {400,400,450,450};
Rectangle2D test = new Rectangle(400,400,100,100);
((Graphics2D)g).fill(car);
((Graphics2D)g).fill(test);
if(car.intersects(test)){
chosenColor = Color.red;
}
tk.sync();
}
/**
* Created by chris on 1/5/16.
*/
public class Main {
public static void main(String[] args) {
Game g = new Game();
}
}
/**
* Created by chris on 1/5/16.
* Car class for my driving program.
*/
public class Car {
private double speed;
private double angle;
/**
* Coordinates on plane
*/
private double x;
private double y;
//For some reason this ends up being the size of the front
public int length = 10;
//and this ends up being the size of the sides.
public int width = 25;
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public double getAngle() {
return angle;
}
public void setAngle(double angle) {
this.angle = angle;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public Car(double x, double y) {
this.x = x;
this.y = y;
speed = 0;
angle = 0;
}
}
I'm making a tiled map and I came across this problem:
When i'm moving my character it's going off the map and then falls (due to gravity)
How do I make this map infinite?
And also, how do I store which blocks are destroyed and which not? So that i can repaint the screen with the same map and when you walk back to the starting point the brocken blocks are still there.
Just Tell me if I need to provide Code.
I'll give you my world.java
package game.test.src;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.ImageIcon;
public class World {
public Rectangle[] blocks;
public boolean[] isSolid;
public Image[] blockImg;
public final int arrayNum = 500;
//Block Images
public Image BLOCK_GRASS, BLOCK_DIRT, BLOCK_STONE, BLOCK_SKY;
private int x, y, xDirection, yDirection;;
//map navigation
static final int PAN_UP = 0, PAN_DOWN = 1, PAN_LEFT= 2, PAN_RIGHT = 3;
public World(){
BLOCK_GRASS = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_grass.png").getImage();
BLOCK_DIRT = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_dirt.png").getImage();
BLOCK_STONE = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_stone.png").getImage();
BLOCK_SKY = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_sky.png").getImage();
blocks = new Rectangle[500];
blockImg = new Image[500];
isSolid = new boolean[arrayNum];
loadArrays();
}
private void loadArrays(){
for(int i = 0; i < arrayNum; i++){
if(x >= 500){
x = 0;
y += 20;
}
if(i >= 0 && i < 100){
blockImg[i] = BLOCK_SKY;
isSolid[i] = false;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 100 && i < 125){
blockImg[i] = BLOCK_GRASS;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 125 && i < 225){
blockImg[i] = BLOCK_DIRT;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 225 && i < 500){
blockImg[i] = BLOCK_STONE;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
x += 20;
}
}
public void draw(Graphics g){
for(int i = 0; i < arrayNum; i++){
g.drawImage(blockImg[i], blocks[i].x, blocks[i].y, null);
}
}
public void moveMap(){
for(Rectangle r : blocks){
r.x += xDirection;
r.y += yDirection;
}
}
public void stopMoveMap(){
setXDirection(0);
setYDirection(0);
}
private void setXDirection(int dir){
xDirection = dir;
}
private void setYDirection(int dir){
yDirection = dir;
}
public void navigateMap(int nav){
switch(nav){
default:
System.out.println("default case entered... Doing nothing.");
break;
case PAN_UP:
setYDirection(-1);
break;
case PAN_DOWN:
setYDirection(1);
break;
case PAN_LEFT:
setXDirection(-1);
break;
case PAN_RIGHT:
setXDirection(1);
break;
}
}
}
here is my Player.java
package game.test.src;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
public class Player {
static final int MOVE_UP = 0, MOVE_DOWN = 1, MOVE_LEFT= 2, MOVE_RIGHT = 3;
private World world;
private Rectangle playerRect;
private Image playerImg;
//Block Variables
private int hoverX, hoverY;
private boolean hovering = false;
protected static int xDirection;
protected static int yDirection;
private Weapon weapon;
public Player(World world){
this.world = world;
playerImg = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/Character.png").getImage();
playerRect = new Rectangle(50, 0, 10, 36);
weapon = new Weapon(weapon.PICKAXE);
}
private static void setXDirection(int d){
xDirection = d;
}
private static void setYDirection(int d){
yDirection = d;
}
public void update()
{
move();
checkForCollision();
}
private void checkForCollision() {
}
private void move()
{
playerRect.x += xDirection;
playerRect.y += yDirection;
gravity();
}
private void gravity()
{
for(int i=0;i<world.arrayNum; i++)
{
if(!world.isSolid[i])
{
setYDirection(1);
}
else if(world.isSolid[i] && playerRect.intersects(world.blocks[i]))
{
setYDirection(0);
}
}
}
//MotionEvents
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseClicked(MouseEvent e)
{
}
public void mouseMoved(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
int px = playerRect.x;
int py = playerRect.y;
for(int i = 0; i < world.arrayNum; i++)
{
if(weapon.isEquipped(Weapon.PICKAXE) &&
x > world.blocks[i].x && x < world.blocks[i].x + world.blocks[i].width &&
y > world.blocks[i].x && y < world.blocks[i].y + world.blocks[i].height && world.isSolid[i] &&
(world.blocks[i].x + (world.blocks[i].width / 2) ) <= (px + playerRect.width/2) + weapon.WEAPON_RADIUS &&
(world.blocks[i].x + (world.blocks[i].width / 2) ) >= (px + playerRect.width/2) - weapon.WEAPON_RADIUS &&
(world.blocks[i].y + (world.blocks[i].height / 2) ) <= (py + playerRect.height/2) + weapon.WEAPON_RADIUS &&
(world.blocks[i].y + (world.blocks[i].height / 2) ) >= (py + playerRect.height/2) - weapon.WEAPON_RADIUS)
{
hovering = true;
hoverX = world.blocks[i].x;
hoverY = world.blocks[i].y;
break;
}
else
hovering = false;
}
}
public void mouseDragged(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
//Drawing Methods
public void draw(Graphics g)
{
g.drawImage(playerImg, playerRect.x, playerRect.y, null);
if(hovering)
drawBlockOutline(g);
}
private void drawBlockOutline(Graphics g)
{
g.setColor(Color.black);
g.drawRect(hoverX, hoverY, world.blocks[0].width,world.blocks[0].height);
}
private class Weapon
{
public static final int UNARMED = 0;
public static final int PICKAXE = 1;
public static final int GUN = 2;
public int CURRENT_WEAPON;
public int WEAPON_RADIUS;
public Weapon(int w)
{
switch(w)
{
default:
System.out.println("No weapon selected");
break;
case UNARMED:
CURRENT_WEAPON = UNARMED;
WEAPON_RADIUS = 100;
break;
case PICKAXE:
CURRENT_WEAPON = PICKAXE;
WEAPON_RADIUS = 100;
break;
case GUN:
CURRENT_WEAPON = GUN;
WEAPON_RADIUS = 100;
break;
}
}
public void selectWeapon(int w)
{
switch(w)
{
default:
System.out.println("No weapon selected");
break;
case UNARMED:
CURRENT_WEAPON = UNARMED;
WEAPON_RADIUS = 100;
break;
case PICKAXE:
CURRENT_WEAPON = PICKAXE;
WEAPON_RADIUS = 100;
break;
case GUN:
CURRENT_WEAPON = GUN;
WEAPON_RADIUS = 100;
break;
}
}
public boolean isEquipped(int w)
{
if(w == CURRENT_WEAPON)
{
return true;
}
else
return false;
}
}
public void moveMap(){
for(Rectangle r : world.blocks){
r.x += xDirection;
r.y += yDirection;
}
}
public static void stopMoveMap(){
setXDirection(0);
setYDirection(0);
}
private static void setXDirection1(int dir){
xDirection = dir;
}
private static void setYDirection1(int dir){
yDirection = dir;
}
public static void navigatePlayer(int nav){
switch(nav){
default:
System.out.println("default case entered... Doing nothing.");
break;
case MOVE_UP:
setYDirection1(-1);
break;
case MOVE_DOWN:
setYDirection1(1);
break;
case MOVE_LEFT:
setXDirection1(-1);
break;
case MOVE_RIGHT:
setXDirection1(1);
break;
}
}
}
Thanks for the help!
At a basic level you need a 3 dimensional array to store every single block. The problem is, that won't get you an "Infinite" world, it will get you one limited to memory.
Notch solved it by using "Chunks"--which are 3D arrays of a fixed size that can be swapped to disk when necessary.
You should also learn about how bits can be used to pack storage, for anything large you will need it--For your example, each block can be held in 3 bits, 2 for the blocks and one more for "broken". If you used this instead of a byte array you would use less than 1/2 the storage, which means you could maybe go twice as far in your world before needing to read another chunk from disk.
If you want an easier introduction to writing this kind of app, look into writing a minecraft mod using Bukkit--much of the detail work is handled for you and you can actually pick up a lot of knowledge about how stuff is done before trying to write a Minecraft clone from scratch.
So what you need is essentially a two-dimensional data structure which can be extended indefinitely (or until memory runs out) into both dimensions.
There are myriads of ways to solve this problem.
One way would be a two dimensional double-linked list (double-linked net?) where each map tile has a reference to the four adjacent tiles. That means you keep track of the tile in the center of the viewport and render the scene by iterating into all four directions until you leave the screen. When you hit an un-initialized tile, it's time to generate it.