Hi I am developing a game that a fighter moves right and left and shoots. For the shooting part, I tried to use a for loop to slow the speed down and user can see the bullet. But it wasn't enough. I used sleep too but not a good answer. Now I have no idea what to do.
Here is my paintComponent calss:
package game;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class PaintComponent extends JPanel implements KeyListener {
int dx = 200-15;
int dy = 450;
int my = 450;
ArrayList<Bullet> bullets = new ArrayList<>();
public Rectangle2D rec =new Rectangle2D.Double(dx , dy, 30, 10);
Rectangle2D recB = new Rectangle2D.Double(dx+13 , my, 6, 6);
// public Polygon pol = new Polygon
private BufferedImage imageBg, imageFi, imageBu;
public PaintComponent() {
this.addKeyListener(this);
this.setFocusable(true);
this.setBackground(Color.white);
try {
imageBg = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\bg.jpg"));
imageBu = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\bul.png"));
imageFi = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\fi.png"));
} catch (IOException ex) {
System.out.println("No background image is available!");
}
}
public void shoot(){
if(bullets != null){
for(int i=0; i<bullets.size(); i++){
for(int j=0; j<200; j++){
bullets.get(i).setdy(my-j);
}
System.out.println(bullets.get(i).getdy());
}
}
}
public void moveRec(KeyEvent evt){
switch(evt.getKeyCode()){
case KeyEvent.VK_LEFT:
dx -= 5;
rec.setRect(dx, dy, 30, 10);
recB.setRect(dx+13, dy, 6, 6);
repaint();
break;
case KeyEvent.VK_RIGHT:
dx += 5;
rec.setRect(dx, dy, 30, 10);
recB.setRect(dx+13, dy, 6, 6);
repaint();
break;
case KeyEvent.VK_SPACE:
Bullet b = new Bullet(dx, dy);
bullets.add(b);
shoot();
break;
}
}
#Override
public void paintComponent(Graphics grphcs)
{super.paintComponent(grphcs);
Graphics2D gr = (Graphics2D) grphcs;
int X = (int) rec.getCenterX();
int Y = (int) rec.getCenterY();
gr.drawImage(imageBg, 0, 0, null);
gr.drawImage(imageFi, X-50, Y-75, null);
gr.setColor(Color.GRAY);
if(bullets != null){
for(int i=0;i<bullets.size();i++){
gr.drawImage(imageBu, null, bullets.get(i).getdx(), bullets.get(i).getdy());
repaint();
}
}
gr.draw(recB);
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e)
{moveRec(e);}
#Override
public void keyReleased(KeyEvent e)
{}
}
and this is my bullet calss:
package game;
public class Bullet {
private int x, y, newy;
public Bullet(int x, int y){
this.x = x;
this.y = y;
}
public int getdy(){
return y;
}
public void setdy(int newy){
y = newy;
}
public int getdx(){
return x;
}
}
I think you are going wrong with slowing down the loop. The last thing you want to do is slow down the game loop or sleep the game loop. This will affect all you objects with in the game.
There are multiple way to go about this:
Smaller increments per tick
One of the most obvious things you could do is make the increment of the bullet smaller. Lets take a look at your shoot(); method:
public void shoot(){
if(bullets != null){
for(int i=0; i<bullets.size(); i++){
for(int j=0; j<200; j++){
bullets.get(i).setdy(my-j);
}
System.out.println(bullets.get(i).getdy());
}
}
}
As far as i understand you are iterating 200x over all the bullets, each tick the bullet's y axis gets changed, using the formula "my - j" or "450 - the tick index"
In order to slow down the bullet you would need to divide the j with a certain number to get the desired speed of the bullet. For instance: "my - (j / 2)" would impact the speed of the bullet. Try to play around with these variables to get the desired speed.
Adding a speed modifier
What a lot of games to is a speed modifier or a base speed for each projectile. This could be of use to you, the only thing i noticed that you are kinda trying to simulate loss of velocity. To achieve this result you would need another variable. Let call that "time to live" for right now.
So if we modify the bullet class it would look like this. Noticed we also have a new function called Move();. This will calculate the next move based upon the variables.
public class Bullet {
private int x, y, newy;
private speed, ttl; //ttl = time to live
public Bullet(int x, int y, int speed){
this.x = x;
this.y = y;
this.speed= speed;
this.ttl = 250;
}
public int Move()
{
//Do some calculation to perform loss of velocity within a reasonable range. Because these number might be overkill
this.speed -= (ttl / 100);
y += this.speed;
ttl--;
}
public int getdy(){
return y;
}
public void setdy(int newy){
y = newy;
}
public int getdx(){
return x;
}
}
What the code now does is it calculates the speed based upon the time to live variable the longer the bullet live the less velocity it will have. Adjusting the speed variable makes you able to control the bullet better. And to say so myself it looks a lot more neater in the shoot method:
public void shoot(){
if(bullets != null){
for(int i=0; i<bullets.size(); i++){
bullets.get(i).Move();
}
}
}
Of course there is more to it, like checking if the speed and time to live dont go out of bounds and stuff, but i think your smart enough to figure that out ;)
Running it off a timer
As ControlAltDel said you can implement a timer, im not an expert on java so im not going in depth on this. But it surely it is a possibility. Its nothing more then implement the current shoot method inside the tick function of the timer. Of course removing the for i<200 loop. Since its not very effecient.
Anyways
If i did get something wrong or misunderstood (or even grammer mistaked :) ) the problem, im sorry. If there are any question i loved to hear from you ;).
And please not that this is untested code and im only here to explain things you could try to get it working a intended!
Sincerly,
Syntasu.
UPDATE:
Some explaining on how to update the bullet's.
In order to update the bullets we need to make it run off a loop. Since in this case the main loop is were also where the drawing is happening, the "paintComponent" method. There is already a loop withing the paint component to draw the bullet, only thing we have to do is to add our logic regarding the .Move(); method.
The paint component will look as following ( + i also fixed the tabbing ):
#Override
public void paintComponent(Graphics grphcs)
{
super.paintComponent(grphcs);
Graphics2D gr = (Graphics2D) grphcs;
int X = (int) rec.getCenterX();
int Y = (int) rec.getCenterY();
gr.drawImage(imageBg, 0, 0, null);
gr.drawImage(imageFi, X-50, Y-75, null);
gr.setColor(Color.GRAY);
if(bullets != null)
{
for(int i=0;i<bullets.size();i++)
{
//Here is were we loop over the bullet list, lets add the move method
bullets.get(i).Move();
gr.drawImage(imageBu, null, bullets.get(i).getdx(), bullets.get(i).getdy());
repaint();
}
}
gr.draw(recB);
}
The thing is added is "bullets.get(i).Move();". This will now run every frame. This will work in theory (inb4 im not testing these codes). Going by the assumption you use multiple instance's of the bullet class, each class should encapsulate their own speed and time to live variable.
Implementing this will make the shoot method obsolete. What you can do is move the code inside the paintComponent that is related to shooting and move that to the shoot function.
Regarding the time to live variable, i would like to add one more piece to the code. This will take care of garbage collection of bullets. Since now they live indefinitly:
for(int i=0;i<bullets.size();i++)
{
Bullet b = bullets.get(i);
if(b.ttl >= 1)
{
bullets.get(i).Move();
gr.drawImage(imageBu, null, b.getdx(), b.getdy());
}
else
{
//Remove the bullet from the list
//In C# its bullets.Remove(b);
}
repaint();
}
Hopefully this resolves the issue of the bullet not moving. And potential performance issue due the bullets not being destroyed. In before, it there are any questions i love to hear them! ;)
Finally I did it with adding a timer in my bullet class and repaint() in my paintcomponent method!
package game;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class Bullet {
private int x, y;
private int speed, ttl;
public final Timer timer1;
public Bullet(int x, int y, int speed){
this.x = x;
this.y = y;
this.speed= speed;
this.ttl = 250;
ActionListener actListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Move();
}
};
this.timer1 = new Timer(50, actListener);
timer1.start();
}
public int getdy(){
return y;
}
public void setdy(int newy){
y = newy;
}
public int getdx(){
return x;
}
public int Move(){
//Do some calculation to perform loss of velocity within a reasonable range. Because these number might be overkill
this.speed -= (ttl / 100);
y += this.speed;
ttl--;
return y;
}
}
In your game loop make it update the bullet every so many ticks. You most likely never want to use sleep to slow down something in a game as it will need a new thread or it will sleep the whole game.
If you do not know what a game loop is, a game loop is basically a loop which continually takes input, updates the game (such as a bullet), renders everything, then pauses the entire program (you can use sleep) for the amount of time which the loop has left over from what is expected. You also want to update the game according to how many ticks or milliseconds have passed since the last update. This will prevent the game from running faster or slower on different computers.
I haven't read this all the way but THIS might help.
Also, not sure if this is correct, I think using Canvas instead of a JPanel will be better for making a game. It is what I used for my first java game.
Edit:
If you want to use JPanel and no game loop you can make the shoot method create a new thread and use the sleep to slow it down. Also the way you have the shoot method setup can cause problems if multiple bullets are shot because it will loop through each bullet in two separate loops and if you have a separate thread the bullets array can be modified while it is looping in one thread therefor causing an error.
Try this:
public void shoot(Bullet bullet){
new Thread(new Runnable(){
for(int j=0; j<200; j++){
bullet.setdy(my-j);
try{
Thread.sleep(time);//set the time
catch(Exception e){
e.printStackTrace();
}
}
System.out.println(bullet.getdy());
getBullets().remove(bullet);
}).start();
}
Create a method called getBullets(). Make sure it has the synchronized modifier.
protected synchronized ArrayList<Bullet> getBullets(){
return bullets;
}
This will make sure it can only be modified by one thread at a time.
Finally where the player presses space, change bullets.add(b); to getBullets().add(b);
Related
I'm making a game similar to agar.io where a blob goes around and eats dots. I'm making it on my phone and you control the blob with your finger to collect the dots. I noticed that when I collect a single dot some random other dots dissappearing as well. I debugged some and found out that unless you collected the dots in the order that they were added to the array, any dot with a lower array order would be destroyed. Example: if you collected the dot added to the array 7th then dots 0-6 would dissappear, bit if you collected 1 then 2 and so on then no other dots would dissappear randomly. I created another simpler example to explore this problem. Now it's a simple screen with 5 circles. You can pick up and drag and drop any circle. I noticed the same problem where you drag a circle and other circles randomly dissappear even though there is no code to make them dissappear. My code is as follows:
// Drag n' Drop //
Objects[] box;
int objCount = 5;
void setup() {
box = new Objects[objCount];
for (int i = 0; i < objCount; i++){
box[i] = new Objects(random(displayWidth),random(displayHeight),200);
}
}
void draw() {
background(170);
for (Objects boxes : box) {
boxes.display();
boxes.dragging();
}
}
class Objects {
float x, y;
float s;
Objects(float tempX, float tempY, int tempS) {
x = tempX;
y = tempY;
s = tempS;
}
void display() {
ellipse(x, y, s, s);
}
void dragging() {
if (dist(x, y, mouseX, mouseY) < 500) {
x = mouseX;
y = mouseY;
s = 300;
}
}
}
I believe my problem may lie in the loop I use to call the display function of the box object, but I cannot find out any other way to make it work. Any help us very much appreciated. Thank you for your time. PS Im using processing to run this code.
Kelton
Firstly, I'd like to thank you for I have never played with Processing before and you inspired me to download it!
There are quite a few things wrong that I'd like to point out and maybe steer you in the right direction. The main issue lies within your dragging() method you are not actually removing the objects you are just moving them to your mouse position, giving you the illusion they are being removed!
Anyway, as you said you were creating the game Agar.io, I would assume that you yourself should have your own Blob. For the sake of my Java brain I have switched what you called Objects to Blobs.
First off, the setup.
import java.util.*;
public static final int BLOB_COUNT = 10;
List<Blob> blobs = new ArrayList<Blob>();
// this is our blob, the one that displays in the middle of the screen
Blob myBlob = new Blob(mouseX, mouseY, 50);
void setup() {
size(1000, 500);
for (int i = 0; i < BLOB_COUNT; i++){
blobs.add(new Blob(random(displayWidth/2),random(displayHeight/2),50));
}
}
Notice how I am using ArrayLists rather than an array, this will make it easier for you to add and remove from the List.
Next, the draw() so this happens each frame.
void draw() {
background(170);
// refreshes the players blob wherever the cursor is!
myBlob.setX(mouseX);
myBlob.setY(mouseY);
myBlob.display();
// display the other blobs on the screen
for (Blob boxes : blobs) {
boxes.display();
boxes.dragging();
}
}
Notice, we want to update our blob to the current position of the mouse!
Lastly, the Blob class!
class Blob {
float x, y;
float size;
Blob(float tempX, float tempY, int size) {
this.x = tempX;
this.y = tempY;
this.size = size;
}
void display() {
ellipse(x, y, size, size);
}
void dragging() {
if (dist(x, y, mouseX, mouseY) < myBlob.getSize()/2) {
myBlob.setBlobSize(25);
this.x = random(displayWidth/2);
this.y = random(displayHeight/2);
}
}
void setX(float x){
this.x = x;
}
void setY(float y) {
this.y = y;
}
void setBlobSize(float size) {
this.size += size;
}
float getSize() {
return this.size;
}
}
So now, we check in the dragging() method whether the blob is close to our blob, and if it is we want to consume that blob (which increases our mass) and then we want that blob to re-spawn to another location, well that's how most Agar.io games work, but of course this is entirely up to you. There is also much more accurate ways to calculate the area of the blob and determine whether two blobs are within touching distance, but I'll leave the maths to you.
Over the past week or so I've been working on a game, where you're a white square that tries to dodge red squares which fall from the top of the screen. You can only move left and right, there are only 3 available positions to move to (left, middle, right), and the red square begins to fall faster as the game progresses. When I move the player (white square) everything repaints just fine, but when the red square's position updates it doesn't repaint at all. Say the red square moves once every second; if I wait one second, the repaint doesn't do anything. However, if I wait one second (or any amount of time) and then move the player, both squares jump to their new positions at once. After looking through the code for an hour, I still can't find any issues with it, and it doesn't help that I'm very new to coding, which probably means it's a really obvious mistake.
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class boxface extends JComponent implements KeyListener {
private static int x=0, y=0;
static int counter = 0;
static Thread t = new Thread();
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()== KeyEvent.VK_RIGHT)
moveRight();
else if(e.getKeyCode()== KeyEvent.VK_LEFT)
moveLeft(); }
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
static Rectangle player = new Rectangle(x, 400, 50, 50);
Rectangle bg = new Rectangle(0, 0, 700, 750);
static int x2 = ((int)((Math.random()*3)))*50;
static Rectangle enemy = new Rectangle(x2, y, 50, 50);
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
g2.fill(bg);
g2.setColor(Color.WHITE);
g2.fill(player);
g2.setColor(Color.RED);
g2.fill(enemy); }
public void moveLeft() {
if(x > 0) {
x -= 50;
player.setLocation(x, 400);
repaint();}}
public void moveRight() {
if(x < 100) {
x += 50;
player.setLocation(x, 400);
repaint();} }
public void enemyDown() {
if(enemy.getBounds().y == 400) {
x2=((int)((Math.random()*3)))*50;
y=0;
enemy.setLocation(x2, y);
counter++;
repaint(); }
else {
y = y + 50;
enemy.setLocation(x2, y);
repaint();}}
public boxface(){
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false); }
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setBounds(400, 200, 156, 479);
f.setMinimumSize(new Dimension(156, 0));
f.setResizable(false);
f.getContentPane().add(new boxface());
f.setVisible(true); }
});
boxface exec = new boxface();
int t = 20;
for(long i = 0; i < 21; i++) {
if(i == t) {
exec.enemyDown();
i = 0; }
if(counter == 10) t = 15;
if(counter == 25) t = 10;
if(counter == 50) t = 7;
if(counter == 100) t = 5;
if(counter == 150) t = 3;
if(counter == 225) t = 1;
if(enemy.intersects(player))
break;
System.out.println(i); }
}
}
Swing uses a "passive rendering" algorithm. This means that the UI will be updated at irregular intervals based on decisions been made by the RepaintManager
See Painting in AWT and Swing for more details
What you need is some way to schedule regular updates. Before I discuss that, some basic game theory...
Game/Main Loop...
Most games have a concept of a "main loop". The "main loop" does a number of jobs, it:
Updates the state of the game. This includes:
Updating the position of the player based on the current state of the input (keyboard/mouse/joystick)
Updating the position of other objects/obstacles within the world
Collision detection
Other states which need to be updated before they can be rendered
Rendering the current state
While you could use a Thread to accomplish this, Swing is NOT thread safe, meaning, you should never update the state of the UI (or any state that the UI relies on) outside the context of the Event Dispatching Thread. Swing is also single threaded, so you can't simply use Thread.sleep or some other loop within the context of the Event Dispatching Thread either.
The simplest solution would be to use a Swing Timer. This is pseudo loop, which is called repeatedly after a specified delay. Each time the timer is triggered, you'd perform the "main loop" operations and call repaint, thus, schedule a (mostly) regular update to the UI.
See How to use Swing Timers for more details
Other considerations...
Because the state of the game is actually updated within the "main loop", you can no longer respond to key events directly. Instead, you need to set a series of flags indicating the current state of your input triggers (ie isAPressed at its simplest form).
In most cases, this would invoke a Set and predefined series of inputs. I like to use a enum as it clearly defines the accepted inputs (up/down/left/right etc...). When pressed, you add the appropriate input to the Set and on release, you remove it. It deals with any repeated states and removes the oddity of the delay between the first press and the repeated key events.
Talking about key events. KeyListener has known issues, while there are "hacks" to "work around" it, they are just that, "hacks".
The recommend method for monitoring for (limited) key input is to use the key bindings API
I'm working on a Mario game and am in need of assistance and suggestions on how to go about creating hit detection for a tilemap.
Currently, the player has the ability to walk/jump through the blocks.
I added in a fixed detection to the ground for now which I am hoping to replace with regular hit detection.
I understand that there are four sides to each block and the player. Only some blocks need hit detection and some things you might need to know is that the player stays at 300px(middle of screen) 98% of the time.
The only thing that moves is the map
The map is rendered from a .txt file and is rendered like so:
for(int y=0;y<map.length;y++) {
for(int x=0;x<map[y].length;x++) {
int index = map[y][x];
int yOffset = 0;
if(index>(tileSheet.getWidth() / Engine.TILE_WIDTH) -1) {
yOffset++;
index = index - (tileSheet.getWidth() / Engine.TILE_WIDTH);
}
g.drawImage(tileSheet,
((x * Engine.TILE_WIDTH)*scale)+position,
((y * Engine.TILE_HEIGHT)*scale),
(((x * Engine.TILE_WIDTH) + Engine.TILE_WIDTH )*scale)+position,
(((y * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT)*scale),
index * Engine.TILE_WIDTH,
yOffset * Engine.TILE_HEIGHT,
(index * Engine.TILE_WIDTH) + Engine.TILE_WIDTH,
(yOffset * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT,
null
);
}
}
//This code is actually longer(included file later on)
Colour hit detection is too slow and inconsistent for multi coloured tiles
Since the map is moving I suppose I need to move the hit detection boxes with it. As for selecting the boxes that it should detect might be difficult. Maybe it would be a better idea to make the code NOT hit detect certain tiles.
My attempts have ended in obfuscation of code. Can anyone suggest the easiest way to implement the hit detection? (keep in mind I have jumping).
The important codes are listed below:
Board.java(The panel where everything is drawn)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.*; //Imported to allow use of Image
import java.awt.event.*; //Imported to allow use of ActionListener
import javax.swing.*; //Import swing
public class Board extends JPanel implements ActionListener { //Class Board
private TileLayer l; //Instance of TileLayer class
private Menu m; //Instance of menu class
private Player p; //Instance of player class
Timer time; //A timer
public static enum STATE {MENU,GAME}; //The game states
public static STATE State = STATE.MENU; //Set the first state to menu
//END
//GLOBAL
//DECLARATIONS
public Board() {
l = TileLayer.FromFile("D:/ICS3U1/EvilMario/map.txt"); //Tile map data from .txt file
this.addMouseListener(new MouseInput()); //Listen for mouse input
this.addKeyListener(new AL()); //Listen for key input
p = new Player(); //Start running Player class
m = new Menu(); //Start running Menu class
setFocusable(true); //Allows movement
time = new Timer(20,this); //Timer set to update "this" class every 20 milliseconds(Approximately 50fps)
time.start(); //Actually start the timer
}
public void actionPerformed(ActionEvent e) {
p.move(); //Call the move method from the player class
repaint(); //Repaint
}
public void paintComponent(Graphics g) { //Graphics method
super.paintComponent(g); //Super hero?
Graphics2D g2d = (Graphics2D) g; //Cast 2D graphics
if(State==STATE.GAME) {
if(p.distanceTraveled<300)l.DrawLayer(g,0);else l.DrawLayer(g, -(p.distanceTraveled-300)); //Draw the tile map
g2d.drawImage(p.getImage(), p.getX(), p.getY(), 48, 48, null); //Draw the player
if(p.distanceTraveled==3488) System.out.println("You have won the game!"); //Draw the end game screen
} else {
m.render(g); //Render the menu
}
}
private class AL extends KeyAdapter { //Action Listener extends key adapter
public void keyPressed(KeyEvent e) { //On key press
p.keyPressed(e); //Send whatever key was pressed TO the keyPressed method in the player class
}
public void keyReleased(KeyEvent e) { //On key release
p.keyReleased(e); //Send whatever key was released TO the keyReleased method in the player class
}
}
}
Player.java(player logic)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.Image;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
public class Player {
int x, dx, y, distanceTraveled; //x coordinate,change in x coordinate,y coordinate,1st rep bg,2nd rep bg,dist traveled
Image player; //The player variable
ImageIcon walk_L_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_L_anim.gif");
ImageIcon walk_L_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_L_idle.png");
ImageIcon walk_R_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_R_anim.gif");
ImageIcon walk_R_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_R_idle.png");
ImageIcon jump_L_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_L_anim.gif");
ImageIcon jump_L_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_L_idle.png");
ImageIcon jump_R_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_R_anim.gif");
ImageIcon jump_R_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_R_idle.png");
boolean holdingLeft = false;
boolean holdingRight = false;
static boolean jumping = false;
static boolean falling = false;
static int jumpingTime = 350;
public Player() {
player = walk_R_idle.getImage(); //Give the player the image
x = 75; //The original x position of the player
y = 277; //The original y position of the player
distanceTraveled = 75; //Original distance traveled
}
public void move() {
if(x>=0 && x<=300) { //If the player is within the moving area
x = x+dx; //The x position is updated to become itself+the amount you moved
}
if(x<0) //If the player has reached he very left side of the screen(0px)
x=0; //Move him up a pixel so he can move again
if(x>300) //If the player has reached the center of the screen(300px)
x=300; //Move him down a pixel so he can move again
distanceTraveled=distanceTraveled+dx; //Calculate distanceTraveled
if(distanceTraveled<0) //Make sure distanceTraveled isn't a negative
distanceTraveled=0; //Make sure distanceTraveled isn't a negative
if(distanceTraveled>=300) //Keep player at center position once past 300 mario meters
x=300; //Keep player at center position once past 300 mario meters
if(holdingLeft && !holdingRight) {
if(distanceTraveled<300)dx=-5; else dx=-4;
if(jumping && !falling) {
player = jump_L_anim.getImage();
y-=8;
} else {
player = walk_L_anim.getImage();
if(y<277)
y+=8;
}
} else if(holdingRight && !holdingLeft) {
if(distanceTraveled<300)dx=5; else dx=4;
if(jumping && !falling) {
player = jump_R_anim.getImage();
y-=8;
} else {
player = walk_R_anim.getImage();
if(y<277)
y+=8;
}
} else if(!holdingRight && !holdingLeft) {
dx = 0;
if(jumping && !falling) {
player = jump_R_anim.getImage();
y-=8;
} else {
if(y<277)
y+=8;
}
}
if(y==277) {
falling = false;
}
System.out.println("LEFT: "+holdingLeft+" JUMP: "+jumping+" RIGHT: "+holdingRight+" FALLING: "+falling+" Y: "+y);
}
public int getX() { return x; } //This method will return the x. Is used by other classes
public int getY() { return y; } //This method will return the y. Is used by other classes
public Image getImage() { return player; } //This method will return the player. Is used by other classes
public void keyPressed(KeyEvent e) { //Called from the board class, the argument is whatever key was pressed
int key = e.getKeyCode(); //The key originally sent from the board class
if(key == KeyEvent.VK_LEFT && !holdingLeft)
holdingLeft = true;
if(key == KeyEvent.VK_RIGHT && !holdingRight)
holdingRight = true;
if(key == KeyEvent.VK_UP && !jumping && !falling)
new Thread(new JumpThread(this)).start();
}
public void keyReleased(KeyEvent e) { //Called from the board class, the argument is whatever key was released
int key = e.getKeyCode(); //The key originally sent from the board class
if(key == KeyEvent.VK_LEFT) { //If the left or right key was released
dx = 0; //Stop moving
holdingLeft = false;
player = walk_L_idle.getImage();
}
if(key == KeyEvent.VK_RIGHT) {
dx = 0;
holdingRight = false;
player = walk_R_idle.getImage();
}
}
}
TileLayer.java (Rendering of the tile layer)(Probably most important part relating to the question)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.Graphics; //
public class TileLayer {
private int[][] map; //2D array
private BufferedImage tileSheet; //The tile sheet
public TileLayer(int[][] existingMap) { //
map = new int[existingMap.length][existingMap[0].length]; //map initialized
for(int y=0;y<map.length;y++) { //Loop through all boxes
for(int x=0;x<map[y].length;y++) { //Loop through all boxes
map[y][x] = existingMap[y][x]; //Update the map
}
}
tileSheet = LoadTileSheet("D:/ICS3U1/EvilMario/images/tilemap.gif"); //Load the tilesheet
}
public TileLayer(int width, int height) {
map = new int[height][width];
}
public static TileLayer FromFile(String fileName) {
TileLayer layer = null;
ArrayList<ArrayList<Integer>> tempLayout = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
String currentLine;
while((currentLine = br.readLine()) !=null) {
if(currentLine.isEmpty())
continue;
ArrayList<Integer> row = new ArrayList<>();
String[] values = currentLine.trim().split(" ");
for(String string: values) {
if(!string.isEmpty()) {
int id = Integer.parseInt(string);
row.add(id);
}
}
tempLayout.add(row);
}
} catch(IOException e) {
System.out.println("ERROR");
}
int width = tempLayout.get(0).size();
int height = tempLayout.size();
layer = new TileLayer(width,height);
for(int y=0;y<height;y++) {
for(int x=0;x<width;x++) {
layer.map[y][x] = tempLayout.get(y).get(x);
}
}
layer.tileSheet = layer.LoadTileSheet("D:/ICS3U1/EvilMario/images/tilemap.gif");
return layer;
}
public BufferedImage LoadTileSheet(String fileName) {
BufferedImage img = null;
try {
img = ImageIO.read(new File(fileName));
} catch(Exception e) {
System.out.println("Could not load image");
}
return img;
}
int scale = 2;
public void DrawLayer(Graphics g, int position) {
for(int y=0;y<map.length;y++) {
for(int x=0;x<map[y].length;x++) {
int index = map[y][x];
int yOffset = 0;
if(index>(tileSheet.getWidth() / Engine.TILE_WIDTH) -1) {
yOffset++;
index = index - (tileSheet.getWidth() / Engine.TILE_WIDTH);
}
g.drawImage(tileSheet,
((x * Engine.TILE_WIDTH)*scale)+position,
((y * Engine.TILE_HEIGHT)*scale),
(((x * Engine.TILE_WIDTH) + Engine.TILE_WIDTH )*scale)+position,
(((y * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT)*scale),
index * Engine.TILE_WIDTH,
yOffset * Engine.TILE_HEIGHT,
(index * Engine.TILE_WIDTH) + Engine.TILE_WIDTH,
(yOffset * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT,
null
);
}
}
}
}
Engine.java (Not as important)(Simple variables for tile sizes)
package EvilMario;
public class Engine {
public static final int TILE_WIDTH = 16;
public static final int TILE_HEIGHT = 16;
}
If you need other pieces of code, just ask for them. I am not asking you to give me a specific answer to the question but simply a method that would work with my following code.
A specific answer would be nice though :)
I also believe the answer to this question will be useful to others because this method was explained in a popular java 2d game tutorial video(They never showed hit detection).
Methods I tried:
Creating a new java file called HitDetectionLayer with the exact code in TileLayer.java that stored positions in arrays. It failed :(
Ok, I'm not entirely sure what you are doing, if you throw up some images it would be more clear.
At any rate, 'hit detection' aka collision detection is a very complex topic, but it depends on what you want to do. If you want everything to be boxes or circles, then it is quite easy. If however you want things to rotate or you want collision for complex shapes it becomes extreme difficult.
Most games use circles or spheres for collision. You put the majority of your graphics (it may not fit perfectly either leaving part of your images in or out of the circle but that's life). Now lets say you have your mario sprite and one of those turtles. Well, you have circles around them both and once the circles touch you trigger your event.
The math for this is very easy because circles are by definition a perimeter around a constant length. Look at this:
You probably already know this, and it may seem obvious, but if you think about it this is what a circle really is: a consistent length in every fathomable direction. The directions are measured in degrees and from there you move on to trigonometry but you don't need that. What you need is coordinance aka vectors. So look at this:
All you need to determine circle collision is the distance between the circles. No matter what angle the circles collide from it does not matter because the distances from the circle's centre are consistent all the way around. Even if the circles are different sizes, it doesn't matter, just account for the radii difference.
Too compute all of this, you would write a method like this:
public boolean testDistanceBetween( float radius1, float radius2,
float x1, float x2, float y1, float y2 ){
double distanceBetween = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
if(distanceBetween < (radius1+radius2) ){
return true;
}
return false;
}
The moral of the story is that circles are just good that way. If you want to do rectangle collision you take the bottom-left and top right point and you test if other rectangles are in between those points. This should be pretty straight forward, each point is a vector, each rectangle has 4 points. If any of the 4 points of one rectangle are between points on the other rectangle, there is collision.
You can use this system to handle ground and walls also. For example, if ground is at Y=300, then if your sprite's y coordinance are == 300, you suspend your gravity.
The main thing I wanted to explain is that if you intend to have rotating rectangles or polygons and you want to detect collision on them... good luck. It can be done yes, but you should understand you are implementing complex physics, especially when/if you implement gravity.
So my answer is cautionary: there is NO easy way to detect collision of rotating rectangles or polygons. Circles and static rectangles are the limits. If you really want to do rotating rectangles/polygons get a physics engine. Box2d is pretty good and has a Java version Jbox2d.
I am making an RPG with a tilemap. To generate the tilemap i loop through a 2 dimensional array but that means that when I repaint I have to do that each time. If I repaint too much the screen flickers how could I stop this.
package sexyCyborgFromAnOtherDimension;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class Game extends JPanel
{
KeyLis listener;
int mapX = 20;
int mapY = 20;
boolean up = false;
boolean down = false;
boolean right = false;
boolean left = false;
String[][] map;
public Game()
{
super();
try
{
map = load("/maps/map1.txt");
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
listener = new KeyLis();
this.setFocusable(true);
this.requestFocus();
this.addKeyListener(listener);
Timer timer = new Timer();
TimerTask task = new TimerTask()
{
#Override
public void run()
{
if(up)
{
mapY++;
repaint();
}
if(down)
{
mapY--;
repaint();
}
if(right)
{
mapX--;
repaint();
}
if(left)
{
mapX++;
repaint();
}
}
};
timer.scheduleAtFixedRate(task, 0, 10);
}
public void paint(Graphics g)
{
super.paintComponent(g);
setDoubleBuffered(true);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (int x = 0; x < map.length; x++)
{
for (int y = 0; y < map[x].length; y++)
{
switch(map[x][y])
{
case "0":
g.setColor(Color.GREEN);
break;
case "1":
g.setColor(Color.GRAY);
break;
}
g.fillRect(y*20+mapX, x*20+mapY, 20, 20);
}
}
g.setColor(Color.BLACK);
g.fillRect(400, 400, 20, 20);
}
String[][] load(String file) throws IOException
{
BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file)));
int lines = 1;
int length = br.readLine().split(" ").length;
while (br.readLine() != null) lines++;
br.close();
BufferedReader br1 = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file)));
String[][] map = new String[lines][length];
for (int i = 0; i < lines; i++)
{
String line = br1.readLine();
String[] parts = line.split(" ");
for (int y = 0; y < length; y++)
{
map[i][y] = parts[y];
}
}
br1.close();
return map;
}
private class KeyLis extends KeyAdapter
{
#Override
public void keyPressed(KeyEvent e)
{
switch (e.getKeyCode())
{
case KeyEvent.VK_UP:
up = true;
break;
case KeyEvent.VK_DOWN:
down = true;
break;
case KeyEvent.VK_LEFT:
left = true;
break;
case KeyEvent.VK_RIGHT:
right = true;
break;
}
}
#Override
public void keyReleased(KeyEvent e)
{
switch (e.getKeyCode())
{
case KeyEvent.VK_UP:
up = false;
break;
case KeyEvent.VK_DOWN:
down = false;
break;
case KeyEvent.VK_LEFT:
left = false;
break;
case KeyEvent.VK_RIGHT:
right = false;
break;
}
}
}
}
Thank you for your help
Edit
Using javax.swing.Timer removes all flickering even with a 10 ms delay.
A number of small things jump out at me.
Firstly. You might be better using javax.swing.Timer instead of java.util.Timer, this will at least allow the events to flow a little better.
Secondly, 10 milliseconds is to short a time period, seriously, you don't need 100fps, 60fps is about 17 milliseconds, I normally use 40 milliseconds for 25fps. This might give the EDT some breathing room to actually respond to the repaint requests.
Thirdly, you should be using paintComponent instead of paint. It's low enough in the call chain to guaranteed to be double buffered
Fourthly, you should avoid calling any method that might reschedule a repaint (like setDoubleBuffered for example, to this in the constructor if you must, but, Swing components are double buffered by default)
Fifthly, where possible, paint all "static" or slow changing content to a backing buffer and paint that instead. This will increase the speed at which paint can work as it doesn't get stuck in a lot of small loops.
You may want to take a look at Painting in AWT and Swing for more details about the paint process
Some additional examples...
Swing animation running extremely slow - was able to go from 500 elements up to 4500 elements moving on the screen at one time - talks about resource management as well
Java Bouncing Ball
How to make line animation smoother?
the images are not loading
And because kleo scares me (and it's also a good idea), you should also take a look at How to use Key Bindings, which will solve your focus issues with the KeyListener
You have set double buffering on the panel, but the things you are drawing on it yourself are not being double buffered. Calling setDoubleBuffered(true) only applies buffering for child components of the container.
You need manually buffer stuff you draw yourself.
I would suggest moving away from a JPanel and use a Canvas instead. Have a look at how a Canvas is used with a buffer strategy in this class, where it can handle hundreds of things being drawn on screen with perfect smoothness and no flickering (the game runs on a 10ms sleep loop too).
Not really sure if it solves your problem, but you can consider to use a 1dimensional array.
when int width; is the width of your screen/tile/whatever you loop and int height is the height you coud do the following:
instead of, what you do:
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
// dosomething with your array
myarray[x][y];
}
}
you could go through this way
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
// dosomething with your array
myarray[x + y * width];
}
}
I've been writing this little painting program thing, but whenever I release the mouse and move to another point on the screen, the line is drawn over there. I tried clearing the points when the mouse has been released, but that deletes everything on screen.
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
public class PaintingCanvas extends Canvas implements MouseMotionListener, MouseListener {
private ArrayList<Point> points = new ArrayList<Point>();
public PaintingCanvas(int width, int height) {
setBounds(0, 0, width, height);
addMouseMotionListener(this);
addMouseListener(this);
}
public void paint(Graphics g) {
for (int i = 0; i < points.size() - 2; i++) {
Point p1 = points.get(i);
Point p2 = points.get(i + 1);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
}
#Override
public void mouseDragged(MouseEvent e) {
points.add(e.getPoint());
repaint();
}
}
I suggest:
Call the super.paint(g); method first thing in your paint method.
When the mouse is released, paint the image represented by the points ArrayList to a BufferedImage and then clear the points ArrayList, and then call repaint().
Draw the BufferedImage in the paint method. before drawing your lines (but check that it's not null first). You do this with the Graphics#drawImage(Image image, int x, int y, ...) method.
Better, re-write this to work in Swing by painting in a JPanel's paintComponent method.
Of course this is happening - once you start drawing again, the new points are added after the old ones. Once you paint after that, they are included. You'll need to seperate the different paths from each other.
Have you looked at the Path classes? If you are simply drawing discrete lines to a screen, the GeneralPath class might be a simple solution.
The Drawing Arbitrary Shapes tutorial explains how to use these.
Basically, every time the user pressed the mouse (on a mousePressed event), you would call the path's moveTo(x, y) method. For every segment (replacing what you currently do in the mouseDragged() method), you would call the path's lineTo(x, y) method.
No matter what, you -definitely- need to handle mousePressed or mouseReleased events, or both, as you are looking for some way to indicate the start of a new line/path, rather than using the old one.
private void jPanel1MouseDragged(java.awt.event.MouseEvent evt) {
points.add(evt.getPoint());
for (int i = 0; i < points.size() - 2; i++)
{
Point p1 = points.get(i);
Point p2 = points.get(i + 1);
jPanel1.getGraphics().drawLine(p1.x, p1.y, p2.x, p2.y);
}
}
private void jPanel1MousePressed(java.awt.event.MouseEvent evt) {
points.clear();
points.add(evt.getPoint());
}
I would make a 2d arraylist in which the first array is points added to until you lift up your finger then a new array is added for the second line etc...
Insert a garbage point -1,-1 in points inside mouseReleased method and check for it inside paint method and skip that point. Updated code:
#Override
public void mouseReleased(MouseEvent e) {
points.add(new Point(-1, -1));
}
#Override
public void paint(Graphics g) {
for (int i = 0; i < points.size() - 1; i++) {
Point p = points.get(i+1);
int x2 = p.x;
int y2 = p.y;
if ( x2 == -1 || y2 == -1) {
++i;
//continue // Considered bad practice, can play havoc with your system. (source http://xkcd.com/292/ ).
}
else {
p = points.get(i);
int x1 = p.x;
int y1 = p.y;
g.drawLine(x1, y1, x2, y2);
}
}
}
OP, how it feels to see this question after 3 years? :D