I've been trying to make a 2D Tile-based game but didnt get very far before having some stuff go wrong. The game is fine except for that it is extremely slow and spaces keep appearing between the tiles. I tried putting all of the tile images into one image to load to make it smoother, but it didn't work. I need help with how to optimize my game for better fps.
Most of Display Class
Player p = new Player();
static Map m = new Map();
Hotbar h = new Hotbar();
static Zombie z = new Zombie();
public static void main(String args[]) {
Thread t1 = new Thread(new Display());
t1.start();
Thread t2 = new Thread(z);
t2.start();
Thread t3 = new Thread(m);
t3.start();
}
#SuppressWarnings("static-access")
public void run() {
while (true) {
try {
oldTime = currentTime;
currentTime = System.currentTimeMillis();
elapsedTime = currentTime - oldTime;
fps = (int) ((1000 / 40) - elapsedTime);
Thread.sleep(fps);
} catch (Exception e) {
e.printStackTrace();
}
if (started) {
p.playerMovement();
p.width = getWidth();
p.height = getHeight();
h.width = getWidth();
h.height = getHeight();
if (p.maptiles != null) {
z.maptiles = new Rectangle[p.maptiles.length];
z.maptiles = p.maptiles;
}
} else {
}
repaint();
}
}
Most of Map Class
BufferedImage backLayer;
BufferedImage backLayer2;
#SuppressWarnings("static-access")
public void createBack() {
backLayer = new BufferedImage(mapwidth * 32, mapheight * 32, BufferedImage.TYPE_INT_ARGB);
Graphics g = backLayer.getGraphics();
Graphics2D gd = (Graphics2D) g;
Color daycolor = new Color(0, 150, 255, 255);
Color nightcolor = new Color(0, 50, 100, Math.abs(time / daylength - 510 / 2));
Color backtilecolor = new Color(10, 10, 20, Math.abs(time / daylength - 510 / 4));
g.setColor(daycolor);
g.fillRect(0, 0, p.width, p.height);
g.setColor(nightcolor);
g.fillRect(0, 0, p.width, p.height);
time++;
if (time > 510 * daylength)
time = 0;
if (time < 100 * daylength || time > 410 * daylength) {
z.SpawnZombie();
} else {
}
g.setColor(backtilecolor);
int i = 0;
for (int x = 0; x < mapwidth; x++) {
for (int y = 0; y < mapheight; y++) {
if (mapdatasky[i] == 0) {
p.mapsky[i] = new Rectangle(x * 32 - p.playerX, y * 32 - p.playerY, 32, 32);
}
if (mapdatasky[i] >= 1) {
g.drawImage(tiles[mapdatasky[i]], x * 32 - p.playerX, y * 32 - p.playerY, 32, 32, null);
mapsky[i] = new Rectangle(x * 32 - p.playerX, y * 32 - p.playerY, 32, 32);
p.mapsky[i] = new Rectangle(x * 32 - p.playerX, y * 32 - p.playerY, 32, 32);
gd.fill(mapsky[i]);
}
i++;
}
}
backLayer2 = backLayer;
}
BufferedImage middleLayer;
BufferedImage middleLayer2;
#SuppressWarnings("static-access")
public void createMiddle() {
middleLayer = new BufferedImage(mapwidth * 32, mapheight * 32, BufferedImage.TYPE_INT_ARGB);
Graphics g = middleLayer.getGraphics();
Color fronttilecolor = new Color(20, 20, 40, Math.abs(time / daylength - 510 / 4));
int i = 0;
for (int x = 0; x < mapwidth; x++) {
for (int y = 0; y < mapheight; y++) {
if (mapdata[i] == -1) {
spawnX = x * 32 - p.width;
spawnY = y * 32 - p.height;
p.spawnX = spawnX;
p.spawnY = spawnY;
}
if (mapdata[i] == 0) {
p.mapsky[i] = new Rectangle(x * 32 - p.playerX, y * 32 - p.playerY, 32, 32);
}
if (mapdata[i] > 0) {
g.drawImage(tiles[mapdata[i]], x * 32 - p.playerX, y * 32 - p.playerY, 32, 32, null);
maptiles[i] = new Rectangle(x * 32 - p.playerX, y * 32 - p.playerY, 32, 32);
p.maptiles[i] = new Rectangle(x * 32 - p.playerX, y * 32 - p.playerY, 32, 32);
}
if (!p.breaking) {
tileid = -1;
}
if (tileid == i) {
g.setColor(new Color(100, 0, 0, 100));
g.fillRect(x * 32 - p.playerX + 16 - timer / (breaktime / 16), y * 32 - p.playerY + 16 - timer / (breaktime / 16), (timer / (breaktime / 16)) * 2, (timer / (breaktime / 16)) * 2);
}
i++;
}
}
g.setColor(fronttilecolor);
g.fillRect(0, 0, p.width, p.height);
middleLayer2 = middleLayer;
}
BufferedImage both;
BufferedImage both2;
public void mergeLayers() {
both = new BufferedImage(mapwidth * 32, mapheight * 32, BufferedImage.TYPE_INT_ARGB);
Graphics g = both.getGraphics();
g.drawImage(backLayer2, 0, 0, mapwidth * 32, mapheight * 32, null);
g.drawImage(middleLayer2, 0, 0, mapwidth * 32, mapheight * 32, null);
both2 = both;
}
public void renderMap(Graphics g) {
g.drawImage(both2, 0, 0, mapwidth * 32, mapheight * 32, null);
if (placetimer > 0)
placetimer--;
}
#Override
public void run() {
while (true) {
try {
oldTime = currentTime;
currentTime = System.currentTimeMillis();
elapsedTime = currentTime - oldTime;
fps = (int) ((1000 / 100) - elapsedTime);
Thread.sleep(fps);
if (!mapset) {
setMap();
mapset = true;
}
createBack();
createMiddle();
mergeLayers();
} catch(Exception e) {
e.printStackTrace();
}
}
}
Here is a picture of the rendering errors between tiles
Game Screenshot:
I agree that a review might be appropriate here.
But general hints: If you added this "merging" (createBack/createMiddle/mergeLayers) for performance reasons: Don't do it! There you are painting ALL tiles (and possibly also some that will not be visible anyhow, that could be clipped away when they are drawn directly with g.drawImage). Painting many small images into a large image, and then painting the large image on the screen, can hardly be faster than directly painting the small images on the screen in the first place....
If you added this "merging" to resolve the "stripes" that are appearing: Don't do it! The stripes come from the coordinates being changed from a different Thread than the one which is painting the images. You can avoid this by changing the way of how you compute the tiles and their coordinates. The code is slightly too ... "complex" to point it out, so I'll use some pseudocode here:
void paintTiles(Graphics g)
{
for (Tile tile : allTiles)
{
g.drawImage(tile, player.x, player.y, null);
}
}
The problem here is that while the painting thread is iterating over all tiles, the other thread may change the player coordinates. For example, some tiles may be painted with player.x=10 and player.y=20, then the other thread changes the player coordinates, and thus the remaining tiles are painted with player.x=15 and player.y=25 - and you'll notice this as a "stripe" appearing between the tiles.
In the best case, this can be resolved rather easily:
void paintTiles(Graphics g)
{
int currentPlayerX = player.x;
int currentPlayerY = player.y;
for (Tile tile : allTiles)
{
g.drawImage(tile, currentPlayerX, currentPlayerY, null);
}
}
This way, the "current" player coordinates will remain the same while iterating over the tiles.
Related
Note that I have found a similar post here, but this question seems to be having this problem consistantly and didn't really offer an explination as to WHY this occurs, only an alternate approach.
I'm creating a Stratego game, and right now I am creating boards where a play can swap around their pieces and then submit the board layout as their army starting locations.
I have a single JButton on each of the frames (one for each player, the second shows up after the first player has submited and left the computer), and the JButton on the first frame only is hidden until you hover it, but only the first time that the program runs after Eclipse is opened.
Can someone give an explination as to why this occurs?
The Main running class
LogicInterpreter logic = new LogicInterpreter();
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
InputFrame inputPlayer1 = new InputFrame(logic, 1, "red", 600, 600);
inputPlayer1.setLocation(dim.width / 2 - inputPlayer1.getSize().width/2,
dim.height / 2 - inputPlayer1.getSize().height / 2);
while(!logic.isSetUp1()){
//Just to make it work
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//Now bring up board 2
InputFrame inputPlayer2 = new InputFrame(logic, 2, "blue", 600, 600);
inputPlayer2.setLocation(dim.width / 2 - inputPlayer2.getSize().width/2,
dim.height / 2 - inputPlayer2.getSize().height / 2);
while(!logic.isSetUp2()){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//Will eventually open the main board
openBoards(logic);
}
This is the relevant setup code for the input frames
public class InputFrame extends JFrame {
private static final long serialVersionUID = 1L;
private LogicInterpreter holder;
private Panel2 jp;
private int height, width;
private Map<Integer, ArrayList<Integer>> lakeCoords = new HashMap<>();
private List<Piece> pieces = new ArrayList<>();
private int playernumber;
private String playerColor;
Piece selectedPiece;
Piece secondSelectedPiece;
boolean hidePieces = false;
JButton submit = new JButton("SUBMIT");
public void addCoords() {
lakeCoords.put(3, new ArrayList<Integer>(Arrays.asList(6, 5)));
lakeCoords.put(4, new ArrayList<Integer>(Arrays.asList(6, 5)));
lakeCoords.put(7, new ArrayList<Integer>(Arrays.asList(6, 5)));
lakeCoords.put(8, new ArrayList<Integer>(Arrays.asList(6, 5)));
}
public void createPieces() {
int y = 1;
if (playernumber == 2) {
y = 6;
}
List<Integer> openValues = new ArrayList<>();
openValues.add(1);
openValues.add(2);
openValues.add(11);
openValues.add(12);
for (int x = 0; x < 2; x++) {
openValues.add(3);
}
for (int x = 0; x < 3; x++) {
openValues.add(4);
}
for (int x = 0; x < 4; x++) {
openValues.add(5);
openValues.add(6);
openValues.add(7);
}
for (int x = 0; x < 5; x++) {
openValues.add(8);
}
for (int x = 0; x < 8; x++) {
openValues.add(9);
}
for (int x = 0; x < 6; x++) {
openValues.add(10);
}
Collections.sort(openValues);
for (int x = 1; x <= 10; x++) {
for (int z = y; z <= 4; z++) {
// 1x1 Marshal
// 2x1 General
// 3x2 Colonel
// 4x3 Major
// 5x4 Captain
// 6x4 Lieutenant
// 7x4 Sergeant
// 8x5 Miner
// 9x8 Scout
// 10x6 Bomb
// 11x1 Flag
// 12x1 Spy
Piece piece = new Piece(new Coords(x, z), openValues.get(0), playerColor);
openValues.remove(0);
pieces.add(piece);
}
}
}
public InputFrame(LogicInterpreter holder, int playerNumber, String playerColor, int height, int width) {
this.height = height;
this.width = width;
playernumber = playerNumber;
this.playerColor = playerColor;
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
addCoords();
this.holder = holder;
createPieces();
jp = new Panel2(height, width);
setResizable(false);
jp.setBackground(new Color(235, 202, 158));
setTitle("Player " + playerNumber + " Arrangement GUI || Click Submit When Ready");
jp.setPreferredSize(new Dimension(600, 600));
jp.setLayout(null);
jp.addMouseListener(new HandleMouse());
getContentPane().add(jp);
pack();
setVisible(true);
if(playernumber == 1)
submit.setBounds(width / 10 * 4, height / 10 * 7, width / 10 * 2, height / 10 * 2);
else
submit.setBounds(width / 10 * 4, height / 10, width / 10 * 2, height / 10 * 2);
submit.setFont(new Font("Arial", Font.BOLD, width * 20 / 600));
submit.setBackground(Color.LIGHT_GRAY);
submit.addActionListener(new CloseListener(this));
jp.add(submit);
}
//More stuff down here about logic and stuff
public class Panel2 extends JPanel {
private static final long serialVersionUID = 1L;
int height = 0;
int width = 0;
public Panel2(int height, int width) {
this.height = height;
this.width = width;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int x = 0; x < width; x += width / 10) {
for (int y = 0; y < height; y += height / 10) {
boolean fill = false;
for (Entry<Integer, ArrayList<Integer>> coords : lakeCoords.entrySet()) {
if ((coords.getKey() - 1 == x / 60 && coords.getValue().get(0) - 1 == y / 60)
|| (coords.getKey() - 1 == x / 60 && coords.getValue().get(1) - 1 == y / 60)) {
fill = true;
break;
}
}
if (fill) {
g.setColor(Color.BLUE);
g.fillRect(x, y, width / 10, height / 10);
g.setColor(Color.BLACK);
g.drawRect(x, y, width / 10, height / 10);
} else {
g.setColor(Color.BLACK);
g.drawRect(x, y, width / 10, height / 10);
}
}
}
if(hidePieces){
for (Piece piece : pieces) {
try {
g.drawImage(ImageIO.read(new File(playerColor + "_pieces/" + (playerColor.equals("blue") ? "Blue" : "Red") + "_Strat_Piece"
+ ".png")), piece.getX() * width / 10 - width / 10,
piece.getY() * height / 10 - height / 10, width / 10, height / 10, null);
} catch(Exception e){}
}
} else {
for (Piece piece : pieces) {
g.drawImage(piece.getImage(), piece.getX() * width / 10 - width / 10,
piece.getY() * height / 10 - height / 10, width / 10, height / 10, null);
}
if (selectedPiece != null) {
g.setColor(Color.BLUE);
g.drawImage(selectedPiece.getImage(), selectedPiece.getX() * width / 10 - width / 10,
selectedPiece.getY() * height / 10 - height / 10, width / 10, height / 10, null);
g.drawRect(selectedPiece.getX() * width / 10 - width / 10,
selectedPiece.getY() * height / 10 - height / 10, width / 10, height / 10);
}
}
}
}
setVisible(true);
....
jp.add(submit); // Note the add() is after the setVisible()
and the JButton on the first frame only is hidden until you hover it, but only the first time that the program runs after Eclipse is opened.
This implies that you are making the frame visible BEFORE adding all the components to the frame.
So the order of the basic logic is:
JPanel panel = new JPanel();
panel.add(...);
frame.add(panel);
frame.pack();
frame.setVisible(true);
Swing components have to be created in EDT. Calling sleep() is EDT will block the UI and is never a good idea. See this for details on EDT: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html
I found some code to display a Mandelbrot set in java, this one has a turqoise colour theme and I was wondering how to change this to another colour, for example a red colour theme. Here is the code:
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class Mandelbrot extends JFrame {
private final int MAX_ITER = 570;
private final double ZOOM = 150;
private BufferedImage I;
private double zx, zy, cX, cY, tmp;
public Mandelbrot() {
super("Mandelbrot Set");
setBounds(100, 100, 800, 600);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
I = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
zx = zy = 0;
cX = (x - 400) / ZOOM;
cY = (y - 300) / ZOOM;
int iter = MAX_ITER;
while (zx * zx + zy * zy < 4 && iter > 0) {
tmp = zx * zx - zy * zy + cX;
zy = 2.0 * zx * zy + cY;
zx = tmp;
iter--;
}
I.setRGB(x, y, iter | (iter << 8));
}
}
}
#Override
public void paint(Graphics g) {
g.drawImage(I, 0, 0, this);
}
public static void main(String[] args) {
new Mandelbrot().setVisible(true);
}
}
The hex code for red is #FF0000 and the rbg decimal code is (255,0,0) if that helps.
Thanks for your time.
The essense of your code (in coloring) is this
I.setRGB(x, y, iter | (iter << 8));
whatever iter is is plugged into the lower bits and also shifted by 8 bits which corersponds to the Green value in the middle.
So I guess you could try
I.setRGB(x, y, iter << 12);
This will plug the iter value in the upper bits (Red Green).
Libgdx Game
For some reason, my SpriteBatch is not able to render both of the 2d int arrays I use to build my dungeon(in class Dungeon1).
I have created a random dungeon building algorithm which inputs into my 2d integer arrays. I'm trying to create a seperate Dungeon class with its own textures that implements the dungeon-building algorithm from the first class, but for some reason the map will not render.
In my main Game class I set my screen:
public class NanoRealms extends Game {
#Override
public void create() {
this.setScreen(new Dungeon1(this));
}
}
Then I use this class to build the dungeon:
public class Dungeon {
public static final int tileSIZE = 64;
private Random rand = new Random();
static int mapSize = 128;
static int[][] bg = new int[mapSize][mapSize];
static int[][] fg = new int[mapSize][mapSize];
private int roomCount = rand.nextInt(20) + 10;
private int minSize = 10;
private int maxSize = 20;
private ArrayList<Room> rooms = new ArrayList<Room>();
private Room[] room = new Room[roomCount];
public Dungeon() {
makeRooms();
squashRooms();
makeCorridors();
fillRoom();
Autotile at = new Autotile(mapSize, bg, fg);
}
...Dungeon Building Algorithm Goes HERE...
}
However, when I try to render the inputs I put into my 2 2d-Arrays, it just wont work.
You can see this in my render method of this class:
public class Dungeon1 implements Screen {
public Texture tiles;
public TextureRegion floor, wall, wallLeft, wallRight, tlCorn, trCorn, blCorn, brCorn, c1, c2, c3, c4;
float wh = Gdx.graphics.getWidth();
float ht = Gdx.graphics.getHeight();
private SpriteBatch batch;
private OrthographicCamera camera;
private int[][] fg = Dungeon.fg;
private int[][] bg = Dungeon.bg;
public Dungeon1(NanoRealms game) {
Dungeon d = new Dungeon();
tiles = new Texture(Gdx.files.internal("Map/tiles.png"));
floor = new TextureRegion(tiles, 2 * 64, 0, Dungeon.tileSIZE, Dungeon.tileSIZE);
wall = new TextureRegion(tiles, 64, 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
wallLeft = new TextureRegion(tiles, 0, 2 * 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
wallRight = new TextureRegion(tiles, 2 * 64, 2 * 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
tlCorn = new TextureRegion(tiles, 0, 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
trCorn = new TextureRegion(tiles, 2 * 64, 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
blCorn = new TextureRegion(tiles, 0, 3 * 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
brCorn = new TextureRegion(tiles, 2 * 64, 3 * 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
c1 = new TextureRegion(tiles, 3 * 64, 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
c2 = new TextureRegion(tiles, 4 * 64, 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
c3 = new TextureRegion(tiles, 3 * 64, 2 * 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
c4 = new TextureRegion(tiles, 4 * 64, 2 * 64, Dungeon.tileSIZE, Dungeon.tileSIZE);
batch = new SpriteBatch();
camera = new OrthographicCamera(30, 30 * (Gdx.graphics.getHeight()/Gdx.graphics.getWidth()));
camera.position.set(3000, 3000, 0);
camera.zoom = 300;
}
public void render(float delta) {
cameraInput();
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT);
////
batch.begin();
batch.setProjectionMatrix(camera.combined);
batch.enableBlending();
for (int x = 0; x < bg.length; x++) {
float w = x * 64;
for (int y = 0; y < bg[x].length; y++) {
float h = y * 64;
if (bg[x][y] == 1) {
batch.draw(floor, w, h);
}
}
}
for (int x2 = 0; x2 < fg.length; x2++) {
float w2 = x2 * 64;
for (int y2 = 0; y2 < fg[x2].length; y2++) {
float h2 = y2 * 64;
if (fg[x2][y2] == 2) {
batch.draw(wall, w2, h2);
} else if (fg[x2][y2] == 3) {
batch.draw(wallLeft, w2, h2);
} else if (fg[x2][y2] == 4) {
batch.draw(wallRight, w2, h2);
} else if (fg[x2][y2] == 5) {
batch.draw(tlCorn, w2, h2);
} else if (fg[x2][y2] == 6) {
batch.draw(trCorn, w2, h2);
} else if (fg[x2][y2] == 7) {
batch.draw(blCorn, w2, h2);
} else if (fg[x2][y2] == 8) {
batch.draw(brCorn, w2, h2);
} else if (fg[x2][y2] == 9) {
batch.draw(c1, w2, h2);
} else if (fg[x2][y2] == 10) {
batch.draw(c2, w2, h2);
} else if (fg[x2][y2] == 11) {
batch.draw(c3, w2, h2);
} else if (fg[x2][y2] == 12) {
batch.draw(c4, w2, h2);
}
}
}
batch.end();
////
}
public void cameraInput() {
if (Gdx.input.isKeyPressed(Keys.Q))
camera.zoom += 0.5;
if (Gdx.input.isKeyPressed(Keys.E))
camera.zoom -= 0.5;
if (Gdx.input.isKeyPressed(Keys.A))
camera.translate(-10, 0, 0);
if (Gdx.input.isKeyPressed(Keys.D))
camera.translate(10, 0, 0);
if (Gdx.input.isKeyPressed(Keys.S))
camera.translate(0, -10, 0);
if (Gdx.input.isKeyPressed(Keys.W))
camera.translate(0, 10, 0);
}
}
When I put the render method and Textures from Dungeon1 into Dungeon, everything works fine. However, I want to create different styled dungeons with different textures without having to rewrite my dungeon algorithm every time.
You didn't say what actually went wrong, so I'm just guessing.
The problem that jumps out at me is that Dungeon1 extends Dungeon, but does not call the super constructor, so your methods makeRooms(), squashRooms(), etc are never called. You do create a Dungeon object and then do nothing with it, so it's thrown away.
So at the top of your Dungeon1 constructor, replace
Dungeon d = new Dungeon();
with
super();
As a side note, it seems odd that you have the static arrays bg and fg in Dungeon, and then your subclass hides them with arrays that have the same name, but then directs those non-static arrays to point at the static arrays from Dungeon. This is convoluted, and a bug waiting to happen. These do not look like something that should be static, so I think you can remove the static key word from both of them. And the subclass does not need to reference them at all. It's a subclass, so it can access them normally without "re-referencing" them.
I'm trying to make some shapes with Java. I created two rectangles with two different colors but I want to create a star shape and I can't find useful source to help me doing this.
Here is my code:
import java.awt.*;
import javax.swing.*;
public class shapes extends JPanel{
#Override
public void paintComponent(Graphics GPHCS){
super.paintComponent(GPHCS);
GPHCS.setColor(Color.BLUE);
GPHCS.fillRect(25,25,100,30);
GPHCS.setColor(Color.GRAY);
GPHCS.fillRect(25,65,100,30);
GPHCS.setColor(new Color(190,81,215));
GPHCS.drawString("This is my text", 25, 120);
}
}
You could try using a polygon and some basic math:
int midX = 500;
int midY = 340;
int radius[] = {118,40,90,40};
int nPoints = 16;
int[] X = new int[nPoints];
int[] Y = new int[nPoints];
for (double current=0.0; current<nPoints; current++)
{
int i = (int) current;
double x = Math.cos(current*((2*Math.PI)/max))*radius[i % 4];
double y = Math.sin(current*((2*Math.PI)/max))*radius[i % 4];
X[i] = (int) x+midX;
Y[i] = (int) y+midY;
}
g.setColor(Color.WHITE);
g.fillPolygon(X, Y, nPoints);
You can also use existing classes e.g. http://java-sl.com/shapes.html for regular polygons and stars.
The Polygon class can be considered as a legacy class that has been there since Java 1.0, but should hardly be used any more in new code. The odd way of specifying the x/y coordinates in separate arrays, and, more importantly, the fact that it only supports int[] arrays limits its application areas. Although it implements the Shape interface, there are more modern implementations of this interface that can be used to represent polygons. In most cases, describing the polygon as a Path2D is easier and more flexible. One can create a Path2D p = new Path2D.Double(); and then do a sequence of moveTo and lineTo calls to geneate the desired shape.
The following program shows how the Path2D class may be used to generate star shapes. The most important method is the createStar method. It is very generic. It receives
the center coordinates for the star
the inner and outer radius of the star
the number of rays that the star should have
the angle where the first ray should be (i.e. the rotation angle of the star)
If desired, a simpler method may be wrapped around this one - as with the createDefaultStar example in the code below.
The program shows different stars, painted as lines and filled with different colors and radial gradient paints, as examples:
The complete program as a MCVE:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DrawStarShape
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new DrawStarShapePanel());
f.setSize(600, 600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class DrawStarShapePanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLACK);
g.draw(createDefaultStar(50, 200, 200));
g.setPaint(Color.RED);
g.fill(createStar(400, 400, 40, 60, 10, 0));
g.setPaint(new RadialGradientPaint(
new Point(400, 200), 60, new float[] { 0, 1 },
new Color[] { Color.RED, Color.YELLOW }));
g.fill(createStar(400, 200, 20, 60, 8, 0));
g.setPaint(new RadialGradientPaint(
new Point(200, 400), 50, new float[] { 0, 0.3f, 1 },
new Color[] { Color.RED, Color.YELLOW, Color.ORANGE }));
g.fill(createStar(200, 400, 40, 50, 20, 0));
}
private static Shape createDefaultStar(double radius, double centerX,
double centerY)
{
return createStar(centerX, centerY, radius, radius * 2.63, 5,
Math.toRadians(-18));
}
private static Shape createStar(double centerX, double centerY,
double innerRadius, double outerRadius, int numRays,
double startAngleRad)
{
Path2D path = new Path2D.Double();
double deltaAngleRad = Math.PI / numRays;
for (int i = 0; i < numRays * 2; i++)
{
double angleRad = startAngleRad + i * deltaAngleRad;
double ca = Math.cos(angleRad);
double sa = Math.sin(angleRad);
double relX = ca;
double relY = sa;
if ((i & 1) == 0)
{
relX *= outerRadius;
relY *= outerRadius;
}
else
{
relX *= innerRadius;
relY *= innerRadius;
}
if (i == 0)
{
path.moveTo(centerX + relX, centerY + relY);
}
else
{
path.lineTo(centerX + relX, centerY + relY);
}
}
path.closePath();
return path;
}
}
I have 2 method.
1)
public static Bitmap drawStar(int W, int H, int color, boolean andRing)
{
Path path = new Path();
Bitmap output = Bitmap.createBitmap(W, H, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
float midW ,min ,fat ,half ,radius;
if(andRing)
{
midW = W / 2;
min = Math.min(W, H);
half = min / 2;
midW = midW - half;
fat = min / 17;
radius = half - fat;
paint.setStrokeWidth(fat);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(midW + half, half, radius, paint);
path.reset();
paint.setStyle(Paint.Style.FILL);
path.moveTo( half * 0.5f, half * 0.84f);
path.lineTo( half * 1.5f, half * 0.84f);
path.lineTo( half * 0.68f, half * 1.45f);
path.lineTo( half * 1.0f, half * 0.5f);
path.lineTo( half * 1.32f, half * 1.45f);
path.lineTo( half * 0.5f, half * 0.84f);
}
else
{
min = Math.min(W, H);
half = min/2;
path.reset();
paint.setStyle(Paint.Style.FILL);
path.moveTo( half * 0.1f , half * 0.65f);
path.lineTo( half * 1.9f , half * 0.65f);
path.lineTo( half * 0.40f , half * 1.65f);
path.lineTo( half , 0 );
path.lineTo( half * 1.60f, half * 1.65f);
path.lineTo( half * 0.1f, half * 0.65f);
}
canvas.drawPath(path, paint);
return output;
}
2)
public static Bitmap drawStar(int W,int H,int spikes,int innerRadius,int outerRadius, int backColor,boolean border, int borderColor)
{
if(W < 10)
W = 10;
if(H < 10)
H = 10;
if(spikes < 5)
spikes = 5;
int smallL = W;
if(H < W)
smallL = H;
if(outerRadius > smallL/2)
outerRadius = smallL/2;
if(innerRadius < 5)
innerRadius = 5;
if(border)
{
outerRadius -=2;
innerRadius -=2;
}
Path path = new Path();
Bitmap output = Bitmap.createBitmap(W, H, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(backColor);
int cx = W/2;
int cy = H/2;
double rot = Math.PI / 2 * 3;
float x,y;
double step = Math.PI / spikes;
path.moveTo(cx, cy - outerRadius);
for (int i = 0; i < spikes; i++)
{
x = (float) (cx + Math.cos(rot) * outerRadius);
y = (float) (cy + Math.sin(rot) * outerRadius);
path.lineTo(x, y);
rot += step;
x = (float) (cx + Math.cos(rot) * innerRadius);
y = (float) (cy + Math.sin(rot) * innerRadius);
path.lineTo(x, y);
rot += step;
}
path.lineTo(cx, cy - outerRadius);
path.close();
canvas.drawPath(path, paint);
if(border)
{
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(borderColor);
canvas.drawPath(path, paint);
}
return output;
}
I wrote this polar clock today and i am almost finished exept i want to align my text inside the line similar to this. Does anyone know how to do this? Ive tried to use FontRenderContext and font metrics but i cant seem to get it to work. Here is the whole source code so you can compile it and see for yourselves.
import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.Calendar;
import java.util.TimeZone;
public class Clock extends Applet implements Runnable {
int[][] colorsInt = {{20,20,20},{100,100,50},{50,100,100},{10,170,50},{79,29,245},{24,69,234},{253,24,103}};
Color[] colors;
int size;
int radius;
boolean anitalias = false;
static final float HPI = (float)(Math.PI / 180f);
public void start() {
enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
new Thread(this).start();
}
public void run() {
setSize(500, 500); // For AppletViewer, remove later.
// Set up the graphics stuff, double-buffering.
BufferedImage screen = new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D)screen.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
WritableRaster wr = screen.getRaster();
Graphics appletGraphics = getGraphics();
// Some variables to use for the fps.
long fpstn = 1000000000 / 600;
int tick = 0, fps = 0, acc = 0;
long lastTime = System.nanoTime();
// Vars
Calendar c;
size = 500;
radius = size / 2;
Arc2D.Float arch;
float scale, radians;
long miliSecond;
int second, minute, hour, month, year, dayOfWeek, dayOfMonth, dayOfYear, daysInMonth, daysInYear;
float[] tvars = new float[6];
float[] vars = new float[6];
String[] names = new String[6];
FontMetrics fm = g.getFontMetrics();
Font font = g.getFont();
FontRenderContext frc = g.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, "Hello world");
int length = gv.getNumGlyphs();
// Init
initColors();
for (int i = 0; i < vars.length; i++)
vars[i] = 0;
// Game loop.
while (true) {
long now = System.nanoTime();
acc += now - lastTime;
tick++;
if (acc >= 1000000000L) {
acc -= 1000000000L;
fps = tick;
tick = 0;
}
// Update
c = Calendar.getInstance();
miliSecond = c.get(Calendar.MILLISECOND);
second = c.get(Calendar.SECOND);
minute = c.get(Calendar.MINUTE);
hour = c.get(Calendar.HOUR_OF_DAY);
dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
dayOfYear = c.get(Calendar.DAY_OF_YEAR);
dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
month = c.get(Calendar.MONTH);
daysInMonth = c.getActualMaximum(Calendar.DAY_OF_MONTH);
daysInYear = c.getActualMaximum(Calendar.DAY_OF_YEAR);
tvars[0] = (second * 1000 + miliSecond) / 60000f * 360f;
tvars[1] = (minute * 60f + second) / 3600f * 360f;
tvars[2] = (hour * 60f + minute) / 1440f * 360f;
tvars[3] = ((dayOfWeek - 2) * 24f + hour) / 168f * 360f;
tvars[4] = ((dayOfMonth - 1) * 24f + hour) / (daysInMonth * 24f) * 360f;
tvars[5] = dayOfYear / (float)daysInYear * 360f;
for (int i = 0; i < vars.length; i++) {
if (tvars[i] - vars[i] > 1) {
vars[i] += (tvars[i] - vars[i]) / 15;
} else if(tvars[i] - vars[i] < -1) {
vars[i] -= (vars[i] - tvars[i]) / 15;
} else {
vars[i] = tvars[i];
}
}
names[0] = second + " Second" + (second > 1 ? "s" : "");
lastTime = now;
// Render
g.setColor(colors[0]);
g.fillRect(0, 0, size, size);
for (int i = 0; i < vars.length; i++) {
scale = i / (float)vars.length * radius * 1.7f;
g.setColor(colors[0]);
g.fillOval((int)(scale / 2), (int)(scale / 2), (int)(size - scale), (int)(size - scale));
g.setColor(colors[i + 1]);
scale += 15;
arch = new Arc2D.Float(scale / 2, scale / 2, size - scale, size - scale, 450 - vars[i], vars[i], Arc2D.PIE);
g.fill(arch);
g.setColor(Color.WHITE);
radians = (vars[i]) * HPI;// vars[i] - 90
scale = ((float)(vars.length - i) / (float)vars.length * (float)radius / 2f * 1.7f) + 15f;
g.translate(radius, radius);
System.out.println(i + ": " + ((1 - scale / radius) * 2));
for (int j = 0; j < names[0].length(); j++) {
char ch = names[0].charAt(j);
radians = ((vars[i] - (names[0].length() - j) * 2) * (1 + (1 - scale / radius) * 2)) * HPI;
g.rotate(radians);
g.drawString(ch + "", 0, -scale);
g.rotate(-radians);
}
g.translate(-radius, -radius);
/*float x = (float)Math.cos(radians) * scale;
float y = (float)Math.sin(radians) * (vars.length - i) / vars.length * radius / 2 * 1.7f;
g.drawRect((int)x + size / 2, (int)y + size / 2, 10, 10);*/
}
scale = vars.length / (float)vars.length * radius * 1.7f;
g.setColor(colors[0]);
g.fillOval((int)(scale / 2), (int)(scale / 2), (int)(size - scale), (int)(size - scale));
g.setColor(Color.WHITE);
g.drawString("FPS " + String.valueOf(fps), 20, 30);
// Draw the entire results on the screen.
appletGraphics.drawImage(screen, 0, 0, null);
do {
Thread.yield();
} while (System.nanoTime() - lastTime < 0);
if (!isActive()) {
return;
}
}
}
public void initColors() {
colors = new Color[colorsInt.length];
for (int i = 0; i < colors.length; i++) {
colors[i] = new Color(colorsInt[i][0], colorsInt[i][1], colorsInt[i][2]);
}
}
}
Here's a simple example of rotating text.
Addendum: You'll want to adjust the the text's radial starting point by stringWidth(name[n]). Your program appears to be rotating individual characters in a effort to follow the arc, while the example appears to be drawing the text in a straight line tangent to the arc. The latter approach may prove simpler. For example, this variation centers the labels across the arc's getStartPoint():
for (int i = 0; i < vars.length; i++) {
...
String s = names[0];
int w = fm.stringWidth(s);
int h = fm.getHeight() + fm.getMaxDescent();
Point2D p = arch.getStartPoint();
int x = (int) p.getX();
int y = (int) p.getY();
radians = (vars[i]) * HPI;
g.rotate(radians, x, y);
g.drawString(s, x - w / 2, y + h);
g.rotate(-radians, x, y);
}
For convenience the code above does rotate() to and fro; for comparison, here's the original example showing repeated concatenations of rotate():
import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
/** #see http://stackoverflow.com/questions/6238037 */
public class RotateText extends JPanel {
private static final Font f = new Font("Serif", Font.BOLD, 32);
private static final String s = "Hello World!";
private static final Color[] colors = {
Color.red, Color.green, Color.blue, Color.cyan
};
private Graphics2D g2d;
private AffineTransform at;
public RotateText() {
setPreferredSize(new Dimension(400, 400));
}
#Override
public void paintComponent(Graphics g) {
g2d = (Graphics2D) g;
g2d.setFont(f);
g2d.setColor(Color.black);
g2d.fillRect(0, 0, getWidth(), getHeight());
at = g2d.getTransform();
int w = this.getWidth();
int h = this.getHeight();
int w2 = g2d.getFontMetrics().stringWidth(s) / 2;
int h2 = 2 * g2d.getFontMetrics().getHeight() / 3;
render(0, w / 2 - w2, h - h2);
render(1, h2, h / 2 - w2);
render(2, w / 2 + w2, h2);
render(3, w - h2, h / 2 + w2);
g2d.setTransform(at);
g2d.setColor(Color.yellow);
g2d.fillRect(w / 3, h / 3, w / 3, h / 3);
}
private void render(int n, int x, int y) {
g2d.setColor(colors[n]);
g2d.setTransform(at);
g2d.rotate(n * Math.PI / 2, x, y);
g2d.drawString(s, x, y);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
//#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new RotateText(), BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
});
}
}
You have to be able to draw text along the curves. There are several ways to do it, but the simplest one is to use Stroke API. You can find an example at http://www.jhlabs.com/java/java2d/strokes/
The other way is using affine transforms. The example is at http://www.java2s.com/Code/Java/2D-Graphics-GUI/Drawtextalongacurve.htm