How to paintComponent to display objects in Java? - java

Okay, so I have a GameField class, and a GameObject class and a Panel class.
The GameObject class describes an object, which has an x and y position, width and height, and x and y direction (in which it is currently moving). The GameField class has a few different instances of these objects, some stored by themselves, and some stored in primitive arrays.
The Panel class is supposed to display these objects on the screen. I used JPanel for this.
However, when it comes to actually displaying them on the screen, I'm a bit lost. I need to implement a function called paintComponent(Graphics graphics), which takes in a Graphics object.
To start, I want to display all the objects on the screen, and set their colour. Their size, position, etc. are handled elsewhere. How can I use these attributes to set the actual objects to have a size, position and direction?
I may need to override the paintComponent function to display all the objects in GameField.
If you could help me out with some code, that'd be great.

I'm not quite clear on what you mean by "their size, position, etc. are handled elsewhere". For now, let's assume that you have approximately the following structure (fields and other methods ommitted for clarity) :
class GameObject {
java.awt.Color getColor() { ... }
java.awt.Point getPosition() { ... }
java.awt.Point getDirection() { ... }
java.awt.Dimension getSize { ... }
}
class GameField {
List<GameObject> getGameObjects() { ... }
}
class Panel extends JPanel {
private GameField getGameField() { ... }
#Override
public void paintComponent(Graphics g) {
// this is where the GameObjects must be painted
}
}
The paintComponent method is responsible for the screen representation of the Panel class. If you override it, you have just won that responsibility from it. Luckily, drawing is - if tedious - rather simple. You asked about that Graphics parameter. Very simply put, it is set for you by the magic of Java and gives you a toolbox to use for drawing.
First, you will want to have a clean slate whenever the panel is repainted. You cannot delete anything once it is painted, but you can easily paint the entire panel in a background color of your choice.
g.setColor(Color.white); // everything that is now painted will be white
g.fillRect(0, 0, getWidth(), getHeight()); // fills the entire area with the set color
Now, for each GameObject you have, let's place rectangle in the objects' defined color and size on the screen, with it's center on the object's position.
for (GameObject object : getGameField().getGameObjects()) {
g.setColor(object.getColor());
g.fillRect(object.getPosition().x - (object.getSize().x / 2), object.getPosition().y - object.getSize().y / 2, object.getSize().x, object.getSize().y);
}
The fillRect method requires the first two arguments to be the top-left corner of the rectangle. So to have it centered on the object's position, we subtract half the size from the position for the x and y values respectively. Now you have, for every GameObject, a rectangle of the right diameter in the object's color at the right position.
You should read up on the javadoc on java.awt.Graphics to find out how to draw other stuff, maybe image sprites or lines for the direction or something. It is cumbersome but doable.

Related

Java game 2D overlapping shadows with Swing

I am currently developing a 2D Java game using Swing as my primary drawing component. Every object has a shadow (BufferedImage) but every shadow overlaps other shadows. Is it possible to only have the shadows not overlap each other? Because I still want the shadows to draw over the player if the object is beneath it, and not if the object is above of the player. Here is a picture for clarity:
I have looked at alpha compositing, I guess I need Source Out? I also thought of having all the shadows (with no transparency) draw on one layer and then draw it with transparency but then it won't draw over the player and other objects like before.
I have a Draw object which is a JPanel and overrides the paintComponent method. Within this method I draw the floor of the current room and then I iterate over the list of objects that belongs to the current room and call each objects' draw method to draw everything.
The object draw method:
public void draw(Graphics g) {
if (visible && checkInScreen()) {
// The required drawing location
int drawLocationX = getX() - globalCameraX;
int drawLocationY = getY() - globalCameraY;
if (shadow) {
g.drawImage(shadowImages.get(imageIndex),
drawLocationX + shadowOffset.x + (getImageWidth()/2),
drawLocationY + shadowOffset.y, null);
}
g.drawImage(images.get(imageIndex), drawLocationX, drawLocationY, null);
//Collisionbox
if (SHOW_COLLISION_BOXES){
g.setColor(Color.WHITE);
g.drawRect(drawLocationX + getCollBoxX(), drawLocationY + getCollBoxY(), getCollBoxW() - getCollBoxX(), getCollBoxH() - getCollBoxY());
}
}
}
My apologies if this question has already been asked but I couldn't find something similar like this.
What I would do to solve this is to have a shadow-layer bitmap. By which I mean:
have your shadow textures saved as a 2D array of boolean values (representing the position of a shadow pixel).
What you can do with this is to then logically or the shadow maps together to create a single layer, which can be layered behind the tree textures to create the shadows.
You may want to change the booleans to floats to represent the colour/intensity of the shadow, then have a larger calculation to merge the shadows together.
The below ShadowMap class is used to store the data for each shadow:
class ShadowMap {
public int xPos, yPos;
public boolean[][] array;
public ShadowMap(int xPos, int yPos, boolean[][] array) {
this.xPos = xPos;
this.yPos = yPos;
this.array = array;
}
}
The ShadowLayer class creates a 2D array for the entire screen, containing if a shadow is present for each pixel:
class ShadowLayer {
public static boolean[][] array = new boolean[SCREEN_WIDTH][SCREEN_HEIGHT];
public static makeNew(ShadowMap[] shadows) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
for (int y = 0; y < SCREEN_HEIGHT; y++) {
array[x][y] = false;
}
}
for (ShadowMap map : shadows) {
for (int i = 0; i < SCREEN_WIDTH; i++) {
for (int j = 0; j < SCREEN_HEIGHT; j++) {
// Logical or such that the pixel at (x, y) has a shadow
// if any shadow map also has a shadow at pixel (x, y)
array[i + map.xPos][j + map.yPos] |= map.array[i][j];
}
}
}
}
}
Using this ShadowLayer class, you just need to darken each pixel on the screen if the ShadowMap has a shadow on the same pixel:
public static Color ajustPixelForShadows(int x, int y, Color pixel) {
return ShadowMap.array[x][y] ? pixel.darken() : pixel;
}
I admit I'm not familiar with Swing so I'm not sure it is possible with that specific interface but the below solution could be used in a variety of 2D graphics engines.
You'll need an off-screen "shadow layer" to draw to that matches the screen dimensions. Initialize the shadow layer to being pure white.
For each object you draw from back to front (y-sorting), do the following, in order, with the shadow layer:
Draw the object's shadow shape in a single solid dark grey color to the shadow layer
Draw the object itself to the shadow layer as a pure white sprite (i.e. all non-transparent pixels in the object's bitmap are white)
Of course, also draw the object itself to the screen.
Then, once all objects have been drawn to both the screen and the shadow layer, draw the shadow layer to the screen using multiply blending. The multiply blend guarantees shadows will darken whatever they are drawn over (unlike alpha blend which, with very light shadows, could potentially actually lighten the colors they are drawn over). It will also make the pure white portions of the layer do nothing, which is what you want.
The above steps mean that after each object draws a shadow, it erases any shadows that would be underneath it in the final scene when it draws itself in white to the shadow layer. Therefore it won't cast a shadow on itself, and objects won't cast shadows over other objects that are technically in front of them.
Objects will still cast shadows onto other objects that are behind them as you wanted, since any parts of the shadow that haven't been erased by an overlapping object will still apply (or if they are erased, will be potentially re-drawn by a later object). And, since you are drawing the shadows as a single non-translucent color to the shadow layer, multiple shadows overlapping won't affect each other either, which was of course the main point.
You could modify this technique depending on what you have available. For example, instead of white you could use a fully transparent shadow layer initially and an "erase" blend mode [(src * 0) + (dst * (1 - srcAlpha))] to draw the objects that erase shadows underneath them. You could then use alpha instead of multiply blend if you prefer for drawing the shadow layer to the screen.

(LibGDX) Changing Color of Actor

So, I have a class that extends Actor, and I'm trying to change the alpha value of it; objectPreview is a type of that class:
#Override
public void display() {
...
// remove previous object preview from stage
objectPreview.remove();
...
// add a translucent preview of where the object will be added
objectPreview.getColor().a = 0.5f;
stage.addActor(objectPreview);
...
stage.draw();
}
And here's my draw method of my custom Actor:
#Override
public void draw(Batch batch, float alpha) {
batch.enableBlending();
batch.draw(texture, pos.x, pos.y);
}
The display method is called every frame, and the objectPreview is an Actor that was added to stage.
However, modifying the alpha value of objectPreview does not work.
Otherwise, this works as intended, placing a preview of the Actor on the screen and clearing / redrawing it every frame.
I have also tried the setColor() method, and that does not work. Even if I change the r, g, b vaulues, nothing happens; the object is still the original Actor's texture.
Why isn't the Color of the actor changing?
When you subclass Actor, it is left up to you to apply its own color in the draw method. I am not sure why they didn't build this into the Actor class, except maybe that there are so many possible ways that color might be used, or because some Actors don't have any visuals associated with them, so applying the color would waste time.
First of all, note that the second argument passed into the draw method is parentAlpha, not alpha as you have labeled it. This is because the parent's alpha should be multiplied by the child's alpha for proper fade effects.
So your updated draw method should look like this:
#Override
public void draw(Batch batch, float parentAlpha) {
batch.enableBlending(); //You can probably remove this line*
Color color = getColor(); //keep reference to avoid multiple method calls
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
batch.draw(texture, pos.x, pos.y);
}
/* * It would only be useful if you have some custom Actors that disable blending.
I don't think any of the built-in actors disable blending. Since many actors will
require blending, it is usually best to leave it on even for fully opaque sprites, in
order to avoid ending up with many draw calls. */
Also note that if you wanted to take advantage of Actor's already existing scaleX and scaleY fields, it would also be up to you to modify the draw method accordingly to use them.

Graphical Components

I have done a program that numerically solves a set of differential equations which describes how an "arbitrary" illness move in an isolated and constant population, it was a programming assignment from a class I took a while ago. What I've done to extend it is to add some graphical components that can pause, reset and "play" the simulation, as well as some components that allows me to change some constants in the equations.
All this was an exercise in programming as I find it to be fun and exciting and want to become better.
However, at the moment I'm stuck, what I want to do now is to make a very simple form of animation of it. I want to visualize the data I get for the number of infected, susceptibles and resistants in a grid as points. I managed to create the grid and have an idea of how to place the dots.
The problem I have is how to draw the dots as the program is working, I can draw one dot in the grid but only as the grid is created, that's it. I need to be able to create a dot at a specific place in the grid, this goes on until the number of dots reaches a finite number, say 30. At that points I want to have the first dot, the one the left, removed, all the dots shifted to the left and place the new dot at the furthest right of the grid, the whole thing is then repeated.
I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason. I've read through my course literature on Java, but despite the extensive sections where he explains most of the different graphical components he does not say that much about those methods, only that you don't call for the paintComponent() method, it is done automatically.
If there is something unclear let me know and I'll try to clarify it.
Thanks in advance.
//
Fox Mulder
I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason.
Basically, say you create a custom component by extending JPanel. When you #Override the paintComponent() method, it get implicitly called for you, so you never have to call it. So what ever you paint inside the method, gets drawn on your surface. For example
public class DrawingPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
}
When you call repaint() you are basically causing the paintComponent method to be call implicitly. So to answer your question, Yes you will need to call it if you want to animate, as you will need to update some kind of variable (like the x and y) in the paintComponent() method, to see any change in the drawing.
You can see more at Performing Custom Painting
Not to handle the actual animation, you'll want to use a javax.swing.Timer. You can see more at How to use Swing Timers. Here's the basic construct
Timer ( int delayInMillis, ActionListener listener )
where delayInMillis is the time to delay between ticks(in this case animations) and the ActionListener listens for "ticks". Each tick, the actionPerformed of the ActionListener is called. There, you can put the code to update any variables you use for animation.
So for example you update the x and y, in the actionPerformed, then call repaint()
public class DrawingPanel extends JPanel {
int x = 50;
int y = 50;
public DrawingPanel() {
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
y += 5;
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
}
Now this was just a simple example. But in your case you want to animate a scatter plot. So what you can do is have a list of Points and in the actionPerformed you can add pull points from that list and push them into another list that is to be drawn. So say you have this
List<Point> originalPoints;
List<Point> pointsToDraw;
...
#Override
protected void paintComponent(Grapchics g) {
super.paintComponent(g);
for (Point point : pointsToDraw) {
g.fillOval(point.x - 5, point.y - 5, 10, 10);
}
}
Basically all the points in pointsToDraw list will be drawn. Initially it will be empty. And in the timer, you can add to the list, until the originalPoints list is exhausted. For example.
List<Point> originalPoints;
List<point> pointsToDraw;
private int currentIndex = 0;
public DrawingPanel(List<Point> originalPoints) {
this.originalPoints = originalPoints;
pointsToDraw = new ArrayList<>();
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
if (currentIndex == originalPoints.size() - 1) {
((Timer)e.getSource()).stop();
} else {
pointsToDraw.add(originalPoints.get(currentIndex));
currentIndex++;
}
repaint();
}
});
timer.start();
}
So basicall you just keep a current index. When the index reaches the size of the original list, you stop the timer. Otherwise you just pop from the originalPoints and push to the pointsToDraw. For every point you add the pointsToDraw, a repaint() is called, and there will be another point for the paintComponent to draw a circle with.
The END
UDPATE
I just reread your question, and I think I have have misunderstood it. If you want all the points drawn, then basically just have one list. And draw all the points initially. with each tick, just remove the first index, advance all the rest up an index, and add a new one to the end. Though this is the implementation of a LinkedList so you may just want to use that

Arranging drawables drawn on canvas

is there a way to arrange drawable drawen on canvas, before redrawing it? I mean setting which drawable will be drawn in the front, and which drawable will be drawn in the back.
They're drawn in the order you draw them. Just try drawing the one which sits in the back first, the front one last.
A way to do it is to have everything you draw define a z-index variable and collect everything you draw at a single point and put them in a list that you then order by the z-index before drawing it.
ex:
Interface IDrawable{
int getZ();
void draw();
}
class drawMe implements IDrawable{
#overrider
draw(){
/// do my drawing
}
#override
int getZ() {return z; }
}
in your drawing or main class
List<IDrawable> myList= new ArrayList();
myList.Add(new DrawMe());
and in your draw method
for(IDrawable draw : myList.OrderBy(z){
draw.draw();
}
thats the concept anyway
You can draw to a temporary canvas for drawing to the main canvas later.
Picture iconFrame = new Picture();
Canvas tempCanvas = iconFrame.beginRecording(width, height);
// width and height refer to the intended destination canvas size
...
// Any configuration before performing the draw should be done here
tempCanvas.drawPath(...);
tempCanvas.drawBitmap(...);
// Any required draw commands for this image should be done to tempCanvas
iconFrame.endRecording();
...
// Any other configuration or draw commands can be done here
iconFrame.draw(canvas);
// Assigns the content of tempCanvas to the top of the main Canvas
In my case, the PorterDuff manipulation of my image within a custom path only worked if it was done first because later draws corrupted it.

How to draw a two dimensional graphic at Java?

I have 2 different lists. Each of them holds x and y value pairs(they have both positive and negative values). How can I draw them on a 2D axis? I want to put points for every value and they will blue for first list and red for second list.
My lists' type:
List<List<Double>>
List<Double> inside of List<...> has 2 variables, first of it for x value and the second one is for y value.
However just I need to how to draw a two dimensional graphic at Java(desktop application) and put points wherever I want, improving code for my variables is less important.
PS:
I want the more and more simple of that kind of graphic:
Something like:
you could use a library like http://www.jfree.org/jfreechart/ (LGPL-License) there are lots of examples around the web, and it's quite easy to use.
here's an example, that seems to match your requirements:
http://www.java2s.com/Code/Java/Chart/JFreeChartMarkerDemo1.htm
Assuming you are using Swing with a panel, you can use the following:
public class JImagePanelExample extends JPanel {
private BufferedImage image;
private Graphics2D drawingBoard;
private int x, y; // Image position in the panel
// Let's assume image is a chart and you need to draw lines
public JImagePanelExample(BufferedImage image, int x, int y) {
super();
this.image = image;
// Retrieving a mean to draw lines
drawingBoard = image.createGraphics();
// Draw what you need to draw (see other methods too)
drawingBoard.drawLine(0, 10, 35, 55);
}
// Called by Swing to draw the image in the panel
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, x, y, null);
}
}
If you don't want to use Swing and you just need to draw in 2D, focus on BufferedImage and Graphics2D only.
There is a Java 2D API: http://java.sun.com/products/java-media/2D/ and many charting libraries easily found with a web search.

Categories