About / Background
This is indirectly a follow up from: General Game Loop 3.0, if you prefer to test against it, feel free to. That code is very outdated now though. A test suite is given at the end.
For the actual class to direct feedback against: MyText.java
, a short description of it follows beneath, as well as a test suite for it.
The idea is to build a wrapper around java.awt.Graphics.drawString(String str, int x, int y);
, making it more ease and more effective to use in game development.
Remarks
There is in an Interface
that is being implemented by MyText
that holds everything in common for rendering. I avoided it here as feedback there is not really needed at the current state.
These fields are loaded from a configuration file that I also choose to omit from this feedback session, as it just adds another layer of complexity to it.
private String delimeter = "...";
private Font font = new Font("Verdana", 0, 12);
private Color color = Color.BLACK;
I've chosen to omit all @Javadoc as of now as well. The idea is that code should be clear enough even without.
Currently it is missing one feature, that is "rainbow" colored font, will later on add support for something like [red]some red text[/red] back to origina- [blue]aha and blue again[/blue]
.
Feedback target
This class is fairly massive, might be the case of splitting it into three classes? One that handles lines, one with fixed width and one with fixed height and width? However, the later class is not getting much cleaner than this one. Right now it is very easy to switch between them during development.
private Image stringToText(String string)
is a mess, are there easier/more clean roads to take to achieve what it is supposed to do?Usability - Is this something (with @Javadoc) that you could happily work with when developing games, if not, what is missing?
MyText
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MyText
{
private String string;
private Image image;
private final int x;
private final int y;
private int w;
private int h;
private final Font font;
private final Color color;
private String delimeter = "...";
private MyText (TextBuilder textBuilder) {
string = textBuilder.string;
font = textBuilder.font;
color = textBuilder.color;
x = textBuilder.x;
y = textBuilder.y;
w = textBuilder.w;
h = textBuilder.h;
delimeter = textBuilder.delimeter;
image = stringToText (textBuilder.string);
}
public int getX () {
return x;
}
public int getY () {
return y;
}
public int getW () {
return w;
}
public int getH () {
return h;
}
public void render(Graphics g) {
g.drawImage(image, x, y, null);
}
public void invalidate (String s) {
if (!string.equals(s)) {
image = stringToText (s);
}
}
private Image stringToText(String string) {
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
List<String> lines = new ArrayList<>(Arrays.asList(string.split("\\r?\\n")));
// break longest lines based on width
if (w > 0 && w < Integer.MAX_VALUE) {
for (int i = 0; i < lines.size(); i++) {
if (fm.stringWidth(lines.get(i)) > w) {
List<String> words = new ArrayList<>(Arrays.asList(lines.get(i).split(" ")));
String new_line = words.get(0);
// word is too long
if (fm.stringWidth(new_line) > w) {
return null;
}
int j = 1;
for (; j < words.size(); j++) {
if (fm.stringWidth(" " + new_line + words.get(j)) < w) {
new_line += " " + words.get(j);
} else {
lines.set(i, new_line);
break;
}
}
String leftOver = "";
for (; j < words.size(); j++) {
leftOver += words.get(j) + " ";
}
leftOver = leftOver.substring(0, leftOver.length()-1);
lines.add(i+1, leftOver);
}
}
}
// update width to the longest string
w = 0;
for (int i = 0; i < lines.size(); i++) {
w = Math.max(w, fm.stringWidth(lines.get(i)));
}
// add delimiter if string passed height, find how many lines fit
if (h < fm.getHeight() * lines.size()) {
int index = lines.size();
while (index-1 > h / fm.getHeight()) {
index--;
}
String new_line = lines.get(index);
while (fm.stringWidth(delimeter) + fm.stringWidth(new_line) > w) {
// delimiter too long to fit at all
if (fm.stringWidth(delimeter) > w) {
break;
}
new_line = new_line.substring(0, new_line.lastIndexOf(' '));
}
new_line += delimeter;
lines.remove(index);
lines.add(index, new_line);
for (int i = lines.size()-1; i > index ; i--) {
lines.remove(lines.size()-1);
}
}
// update height to the total lines
h = fm.getHeight() * lines.size();
// clean
g.dispose();
// render new image
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
g = img.createGraphics();
g.setFont(font);
fm = g.getFontMetrics();
g.setColor(color);
for (int i = 0; i < lines.size(); i++) {
g.drawString(lines.get(i), 0, fm.getHeight()*(i+1)-4);
}
g.dispose();
return img;
}
public static class TextBuilder
{
private final String string;
private final int x;
private final int y;
private int w = Integer.MAX_VALUE;
private int h = Integer.MAX_VALUE;
private String delimeter = "...";
private Font font = new Font("Verdana", 0, 12);
private Color color = Color.BLACK;
public TextBuilder (String string, int x, int y) {
this.string = string;
this.x = x;
this.y = y;
}
public TextBuilder setMaxWidth (int w) {
this.w = w;
return this;
}
public TextBuilder setMaxBounds (int w, int h) {
this.w = w;
this.h = h;
return this;
}
public TextBuilder setDelimeter (String delimeter) {
this.delimeter = delimeter;
return this;
}
public TextBuilder fontSize (String name, int style, int size) {
this.font = new Font (name, style, size);
return this;
}
public TextBuilder color (Color color) {
this.color = color;
return this;
}
public MyText build () {
return new MyText (this);
}
}
}
Explanation
public void render(Graphics g);
Invoked from the render loop, per the test case.
public void invalidate (String s)
Let's say you have a dynamic variable, health
, when you take damage you want to update the String
contained in the MyText
, it is done via this method. It keeps all other values.
Test Case
This is just here so you can try the code without having to write a test application yourself.
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import test.MyText.TextBuilder;
public class MyMain extends Canvas implements Runnable
{
MyText aLine = new TextBuilder ("Hello", 100, 100).build();
MyText aList = new TextBuilder ("Hello, Hello, Hello, Hello, Hello, Hello, Hello, Hello", 200, 100).setMaxWidth(50).build();
MyText aBox = new TextBuilder ("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's. Wtf is this all about.", 300, 100).setMaxBounds(100, 100).build();
public static void main (String[] args) {
new MyMain ();
}
public MyMain () {
Frame frame = new Frame ();
frame.add (this);
frame.setPreferredSize(new Dimension(500,300));
frame.pack ();
frame.setLocationRelativeTo (null);
frame.setVisible (true);
// off we go
Thread thread = new Thread(this);
thread.start ();
}
@Override
public void run() {
while (true) {
while (true) {
BufferStrategy bufferstrategy = getBufferStrategy ();
if (bufferstrategy == null) {
createBufferStrategy(3);
break;
}
Graphics g = bufferstrategy.getDrawGraphics();
g.clearRect(0, 0, 500, 300);
/* Test Cases Begin */
aLine.render(g);
aList.render(g);
aBox.render(g);
/* Test Cases End */
g.dispose();
bufferstrategy.show();
}
}
}
}