用于 2d 游戏引擎的 Graphics2D 包装器
Graphics2D wrapper for 2d game engine
我正在尝试编写一个 2d 游戏引擎,并且我正在尝试实现一个视口系统,以便当我在某个视口中绘图时,游戏坐标将被 t运行sformed 为屏幕坐标而无需手动执行 t运行sformation.
我想要做的是创建一个添加 setViewport
方法的 Graphics2D
包装器。
我认为有 2 个选项:
创建一个 class,它有一个 Graphics2D
的实例,并且具有与 Graphics2D
和 setViewport
相同的所有方法,只需调用Graphics2D
实例上的相应方法。
Subclass Graphics2D
并添加一个 setViewport
方法,然后从 Graphics2D
转换为这个新的 class
我尝试了#2,因为#1 看起来非常不切实际,但 运行 变成了 ClassCastException
。我无法将 Graphics
或 Graphics2D
转换为这个新的 class。当我在转换前打印图形对象时(Graphics
或 Graphics2D
),两者都显示为 sun.java2d.SunGraphics2D
.
我是不是在尝试子class 和投射时做错了什么?如果没有,我该如何解决这个问题?
我为自己编写的游戏创建了一个坐标系。请随意使用这些 classes 作为示例。
这是坐标系class。
package com.ggl.game.utilities;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
/**
* <p>
* This class creates a Cartesian and Polar coordinate system to overlay a Swing
* drawing area (usually a <code>JPanel</code>). The user of the class sets a
* limit as to how far the X axis or the Y axis extends in the negative and
* positive direction from (0, 0). Point (0, 0) will be placed in the center of
* the drawing area.
* </p>
* <p>
* Since the drawing area is usually not square, the limit applies to the
* shorter dimension. The actual limits can be retrieved from this class, and
* will change if the user of the application changes the drawing area size by
* maximizing or normalizing the application <code>JFrame</code> window.
* </p>
* <p>
* Using a Cartesian or Polar coordinate system frees the user of this class
* from having to worry about Swing pixel coordinates.
* </p>
*
* @author Gilbert G. Le Blanc
* @version 1.0 - 23 February 2015
*
* @see com.ggl.game.utilities.Polar2D
* @see java.awt.geom.Point2D
* @see javax.swing.JFrame
* @see javax.swing.JPanel
*/
public class CoordinateSystem {
private double conversionFactor;
private double xLimit;
private double yLimit;
private int height;
private int width;
/**
* <p>
* This creates a Cartesian and Polar coordinate system over a Swing drawing
* area (usually a <code>JPanel</code>). If the drawing area is square, the
* X axis extends from -limit to limit and the Y axis extends from -limit to
* limit.
* </p>
* <p>
* If the drawing area is not square, then the smaller dimension, either X
* or Y, extends from -limit to limit. The larger dimension extends beyond
* the limit in both directions.
* </p>
* <p>
* Since most displays are not square, the X and Y axis will usually have
* different limits.
* </p>
*
* @param limit
* - The limit of the X and Y axis in a Cartesian coordinate
* system.
* @param width
* - The width of the drawing area in pixels.
* @param height
* - The height of the drawing area in pixels.
*/
public CoordinateSystem(double limit, int width, int height) {
this.width = width;
this.height = height;
if (width > height) {
this.xLimit = limit * width / height;
this.yLimit = limit;
this.conversionFactor = (limit + limit) / (double) height;
} else if (width < height) {
this.xLimit = limit;
this.yLimit = limit * height / width;
this.conversionFactor = (limit + limit) / (double) width;
} else {
this.xLimit = limit;
this.yLimit = limit;
this.conversionFactor = (limit + limit) / (double) width;
}
}
/**
* This method changes the drawing area dimension, along with the X and Y
* axis limits.
*
* @param dimension
* - A <code>Dimension</code> with the new drawing area
* dimension.
*/
public void setDrawingSize(Dimension dimension) {
setDrawingSize(dimension.width, dimension.height);
}
/**
* This method changes the drawing area width and height, along with the X
* and Y axis limits.
*
* @param width
* - The width of the drawing area in pixels.
* @param height
* - The height of the drawing area in pixels.
*/
public void setDrawingSize(int width, int height) {
xLimit = xLimit / this.width * width;
yLimit = yLimit / this.height * height;
this.width = width;
this.height = height;
}
/**
* This method returns the Cartesian coordinate limit for the X axis.
*
* @return The Cartesian coordinate limit for the X axis.
*/
public double getxLimit() {
return xLimit;
}
/**
* This method returns the Cartesian coordinate limit for the Y axis.
*
* @return The Cartesian coordinate limit for the Y axis.
*/
public double getyLimit() {
return yLimit;
}
/**
* This method converts a Polar coordinate distance and theta angle in
* radians to a pixel location on a drawing area.
*
* @param distance
* - A Polar coordinate distance
* @param theta
* - A Polar coordinate theta angle in radians
* @return A pixel location on a drawing area.
*/
public Point convertPolarToPixels(double distance, double theta) {
return convertToPixels(new Polar2D.Double(distance, theta));
}
/**
* This method converts a Cartesian coordinate x and y to a pixel location
* on a drawing area.
*
* @param x
* - A Cartesian coordinate x.
* @param y
* - A Cartesian coordinate y.
* @return A pixel location on a drawing area.
*/
public Point convertPointToPixels(double x, double y) {
return convertToPixels(new Point2D.Double(x, y));
}
/**
* This method converts a Polar coordinate to a pixel location on a drawing
* area.
*
* @param polar
* - A Polar coordinate.
* @return A pixel location on a drawing area.
*/
public Point convertToPixels(Polar2D polar) {
double x = polar.getDistance() * Math.cos(polar.getTheta());
double y = polar.getDistance() * Math.sin(polar.getTheta());
return convertToPixels(new Point2D.Double(x, y));
}
/**
* This method converts a Cartesian coordinate to a pixel location on a
* drawing area.
*
* @param cartesian
* - A Cartesian coordinate.
* @return A pixel location on a drawing area.
*/
public Point convertToPixels(Point2D cartesian) {
int x = (int) Math
.round((cartesian.getX() + xLimit) / conversionFactor);
int y = (int) Math.round((-cartesian.getY() + yLimit)
/ conversionFactor);
return new Point(x, y);
}
/**
* This method converts a pixel location on a drawing area to a Cartesian
* coordinate.
*
* @param x
* - The x pixel location.
* @param y
* - The y pixel location.
* @return A Cartesian coordinate.
*/
public Point2D convertToCartesian(int x, int y) {
return convertToCartesian(new Point(x, y));
}
/**
* This method converts a pixel location on a drawing area to a Cartesian
* coordinate.
*
* @param point
* - The pixel location.
* @return A Cartesian coordinate.
*/
public Point2D convertToCartesian(Point point) {
double x = (double) point.x * conversionFactor - xLimit;
double y = (double) -point.y * conversionFactor + yLimit;
return new Point2D.Double(x, y);
}
/**
* This method converts a pixel location on a drawing area to a Polar
* coordinate.
*
* @param x
* - The x pixel location.
* @param y
* - The y pixel location.
* @return A Polar coordinate.
*/
public Polar2D convertToPolar(int x, int y) {
return convertToPolar(new Point(x, y));
}
/**
* This method converts a pixel location on a drawing area to a Polar
* coordinate.
*
* @param point
* - The pixel location.
* @return A Polar coordinate.
*/
public Polar2D convertToPolar(Point point) {
double x = (double) point.x * conversionFactor - xLimit;
double y = (double) -point.y * conversionFactor + yLimit;
double distance = Math.sqrt(x * x + y * y);
double theta = Math.atan2(y, x);
return new Polar2D.Double(distance, theta);
}
}
这是 Polar2D class。它是 Point2D class.
的克隆
package com.ggl.game.utilities;
/**
* The <code>Polar2D</code> class defines a point representing a location in
* distance, theta angle coordinate space.
* <p>
* This class is only the abstract superclass for all objects that store a 2D
* coordinate. The actual storage representation of the coordinates is left to
* the subclass.
*
* @version 1.0 - 23 February 2015
* @author Jim Graham (author of Point2D), Gilbert Le Blanc
*/
public abstract class Polar2D implements Cloneable {
/**
* The <code>Float</code> class defines a point specified in float
* precision.
*/
public static class Float extends Polar2D {
/**
* The distance of this <code>Polar2D</code>.
*
* @since 1.7
*/
public float distance;
/**
* The theta angle of this <code>Polar2D</code>.
*
* @since 1.7
*/
public float theta;
/**
* Constructs and initializes a <code>Polar2D</code> with coordinates
* (0, 0).
*
* @since 1.7
*/
public Float() {
}
/**
* Constructs and initializes a <code>Polar2D</code> with the specified
* coordinates.
*
* @param distance
* The distance to which to set the newly constructed
* <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set the newly
* constructed <code>Polar2D</code>
* @since 1.7
*/
public Float(float distance, float theta) {
this.distance = distance;
this.theta = theta;
}
/**
* Returns the distance of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return the distance of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getDistance() {
return (double) distance;
}
/**
* Returns the theta angle in radians of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return the theta angle in radians of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getTheta() {
return (double) theta;
}
/**
* Returns the theta angle in degrees of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return the theta angle in degrees of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getThetaInDegrees() {
double degrees = 180D / Math.PI * theta;
return (degrees < 0D) ? degrees + 360D : degrees;
}
/**
* Sets the location of this <code>Polar2D</code> to the specified
* <code>double</code> distance and theta angle in radians.
*
* @param distance
* The distance to which to set this <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set this
* <code>Polar2D</code>
* @since 1.7
*/
@Override
public void setLocation(double distance, double theta) {
this.distance = (float) distance;
this.theta = (float) theta;
}
/**
* Sets the location of this <code>Polar2D</code> to the specified
* <code>float</code> coordinates.
*
* @param distance
* The distance to which to set this <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set this
* <code>Polar2D</code>
* @since 1.7
*/
public void setLocation(float distance, float theta) {
this.distance = distance;
this.theta = theta;
}
/**
* Returns a <code>String</code> that represents the value of this
* <code>Polar2D</code>.
*
* @return A <code>String</code> representation of this
* <code>Polar2D</code>.
* @since 1.7
*/
@Override
public String toString() {
return "Polar2D.Float[" + distance + ", " + theta + "]";
}
}
/**
* The <code>Double</code> class defines a point specified in
* <code>double</code> precision.
*/
public static class Double extends Polar2D {
/**
* The distance of this <code>Polar2D</code>.
*
* @since 1.7
*/
public double distance;
/**
* The theta angle in radians of this <code>Polar2D</code>.
*
* @since 1.7
*/
public double theta;
/**
* Constructs and initializes a <code>Polar2D</code> with (0, 0)
* distance and theta angle in radians.
*
* @since 1.7
*/
public Double() {
}
/**
* Constructs and initializes a <code>Polar2D</code> with the specified
* coordinates.
*
* @param distance
* The distance to which to set the newly constructed
* <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set the newly
* constructed <code>Polar2D</code>
* @since 1.7
*/
public Double(double distance, double theta) {
this.distance = distance;
this.theta = theta;
}
/**
* Returns the distance of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The distance of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getDistance() {
return distance;
}
/**
* Returns the theta angle in radians of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The theta angle in radians of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getTheta() {
return theta;
}
/**
* Returns the theta angle in degrees of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The theta angle in degrees of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getThetaInDegrees() {
double degrees = 180D / Math.PI * theta;
return (degrees < 0D) ? degrees + 360D : degrees;
}
/**
* Sets the location of this <code>Polar2D</code> to the specified
* <code>double</code> coordinates.
*
* @param distance
* The distance to which to set this <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set this
* <code>Polar2D</code>
* @since 1.7
*/
@Override
public void setLocation(double distance, double theta) {
this.distance = distance;
this.theta = theta;
}
/**
* Returns a <code>String</code> that represents the value of this
* <code>Polar2D</code>.
*
* @return A <code>String</code> representation of this
* <code>Polar2D</code>.
* @since 1.7
*/
@Override
public String toString() {
return "Polar2D.Double[" + distance + ", " + theta + "]";
}
}
/**
* This is an abstract class that cannot be instantiated directly.
* Type-specific implementation subclasses are available for instantiation
* and provide a number of formats for storing the information necessary to
* satisfy the various accessor methods below.
*
* @see java.awt.geom.Polar2D.Float
* @see java.awt.geom.Polar2D.Double
* @see java.awt.Point
*/
protected Polar2D() {
}
/**
* Returns the distance of this <code>Polar2D</code> in <code>double</code>
* precision.
*
* @return The distance of this <code>Polar2D</code>.
* @since 1.7
*/
public abstract double getDistance();
/**
* Returns the theta angle in radians of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The theta angle in radians of this <code>Polar2D</code>.
* @since 1.7
*/
public abstract double getTheta();
/**
* Returns the theta angle in degrees of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The theta angle in degrees of this <code>Polar2D</code>.
* @since 1.7
*/
public abstract double getThetaInDegrees();
/**
* Sets the location of this <code>Polar2D</code> to the specified
* <code>double</code> coordinates.
*
* @param distance
* The distance of this <code>Polar2D</code>
* @param theta
* The theta angle in radians of this <code>Polar2D</code>
* @since 1.7
*/
public abstract void setLocation(double distance, double theta);
/**
* Sets the location of this <code>Polar2D</code> to the same coordinates as
* the specified <code>Polar2D</code> object.
*
* @param p
* the specified <code>Polar2D</code> the which to set this
* <code>Polar2D</code>
* @since 1.7
*/
public void setLocation(Polar2D p) {
setLocation(p.getDistance(), p.getTheta());
}
/**
* Returns the square of the distance between two points.
*
* @param distance1
* The distance of the first point
* @Parm theta1 The theta angle in radians of the first point
* @param distance2
* The distance of the second point
* @param theta2
* The theta angle in radians of the second point
* @return The square of the distance between the two specified points.
*/
public static double distanceSq(double distance1, double theta1,
double distance2, double theta2) {
double x1 = distance1 * Math.cos(theta1);
double y1 = distance1 * Math.sin(theta1);
double x2 = distance2 * Math.cos(theta2);
double y2 = distance2 * Math.sin(theta2);
return (x1 * x2 + y1 * y2);
}
/**
* Returns the distance between two points.
*
* @param distance1
* The distance of the first point
* @param theta1
* The theta angle in radians of the first point
* @param distance2
* The distance of the second point
* @param theta2
* The theta angle in radians of the second point
* @return The distance between the two specified points.
*/
public static double distance(double distance1, double theta1,
double distance2, double theta2) {
double x1 = distance1 * Math.cos(theta1);
double y1 = distance1 * Math.sin(theta1);
double x2 = distance2 * Math.cos(theta2);
double y2 = distance2 * Math.sin(theta2);
return Math.sqrt(x1 * x2 + y1 * y2);
}
/**
* Returns the square of the distance from this <code>Polar2D</code> to a
* specified point.
*
* @param distance
* The distance of the specified point
* @param theta
* The theta angle in radians of the specified point
* @return The square of the distance between this <code>Polar2D</code> and
* the specified point.
*/
public double distanceSq(double distance, double theta) {
double x1 = distance * Math.cos(theta);
double y1 = distance * Math.sin(theta);
double x2 = getDistance() * Math.cos(getTheta());
double y2 = getDistance() * Math.sin(getTheta());
return (x1 * x2 + y1 * y2);
}
/**
* Returns the square of the distance from this <code>Polar2D</code> to a
* specified <code>Polar2D</code>.
*
* @param pt
* The specified <code>Polar2D</code>
* @return The square of the distance between this <code>Polar2D</code> to a
* specified <code>Polar2D</code>.
*/
public double distanceSq(Polar2D pt) {
double x1 = pt.getDistance() * Math.cos(pt.getTheta());
double y1 = pt.getDistance() * Math.sin(pt.getTheta());
double x2 = getDistance() * Math.cos(getTheta());
double y2 = getDistance() * Math.sin(getTheta());
return (x1 * x2 + y1 * y2);
}
/**
* Returns the distance from this <code>Polar2D</code> to a specified point.
*
* @param distance
* The distance of the specified point
* @param theta
* The theta angle in radians of the specified point
* @return The distance between this <code>Polar2D</code> and a specified
* point.
*/
public double distance(double distance, double theta) {
double x1 = distance * Math.cos(theta);
double y1 = distance * Math.sin(theta);
double x2 = getDistance() * Math.cos(getTheta());
double y2 = getDistance() * Math.sin(getTheta());
return Math.sqrt(x1 * x2 + y1 * y2);
}
/**
* Returns the distance from this <code>Polar2D</code> to a specified
* <code>Polar2D</code>.
*
* @param pt
* the specified <code>Polar2D</code>
* @return The distance between this <code>Polar2D</code> and the specified
* <code>Polar2D</code>.
*/
public double distance(Polar2D pt) {
double x1 = pt.getDistance() * Math.cos(pt.getTheta());
double y1 = pt.getDistance() * Math.sin(pt.getTheta());
double x2 = getDistance() * Math.cos(getTheta());
double y2 = getDistance() * Math.sin(getTheta());
return Math.sqrt(x1 * x2 + y1 * y2);
}
/**
* Creates a new object of the same class and with the same contents as this
* object.
*
* @return a clone of this instance.
* @exception OutOfMemoryError
* if there is not enough memory.
* @see java.lang.Cloneable
* @since 1.7
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
/**
* Returns the hash code for this <code>Polar2D</code>.
*
* @return a hash code for this <code>Polar2D</code>.
*/
@Override
public int hashCode() {
long bits = java.lang.Double.doubleToLongBits(getDistance());
bits ^= java.lang.Double.doubleToLongBits(getTheta()) * 31;
return (((int) bits) ^ ((int) (bits >> 32)));
}
/**
* Determines whether or not two points are equal. Two instances of
* <code>Polar2D</code> are equal if the values of their <code>x</code> and
* <code>y</code> member fields, representing their position in the
* coordinate space, are the same.
*
* @param obj
* an object to be compared with this <code>Polar2D</code>
* @return <code>true</code> if the object to be compared is an instance of
* <code>Polar2D</code> and has the same values; <code>false</code>
* otherwise.
* @since 1.7
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Polar2D) {
Polar2D p2d = (Polar2D) obj;
return (getDistance() == p2d.getDistance())
&& (getTheta() == p2d.getTheta());
}
return super.equals(obj);
}
}
一个 OO 设计原则是 "favor composition over inheritance",这是使用 包装器 class 或 Decorator 设计模式(我认为课程幻灯片中的包装器是什么意思)。所以你所做的实际上是优雅解决方案,原因有很多:
它可以防止 Graphics2D
中未来的实现更改,如果您使用继承并且不幸地向 Graphics2D
添加了一个与您的新方法具有相同签名的新方法并且不同的 return 类型,您的 class 将不再编译。如果您使用相同的签名和 return 类型,您将覆盖 Graphics2D
中的新方法,这可能(并且已经)导致几天令人沮丧的调试。
这种方式的继承违反了封装使得软件在长期运行.
[=62中变得脆弱和容易出错=]
使用组合,您可以保护您的 class 免受将来 class 的更改,您的 组合 ,您的 class 将 转发 对它的 private
Graphics2D
实例的所有方法调用并分别处理变换坐标。
它还允许将来轻松扩展,使用继承会将您绑定到 Graphics2D
的当前实现,可能会限制您的 class.
的性能
Java API 中有一个这样的例子:Properties
class extends HashTable
,这说明了继承的不当使用,因为 [= =17=] 不是 一个 HashTable
,它不应该以相同的方式使用。在这种情况下,调用 Properties p.getProperty(key)
可能会给出与 p.get(key)
不同的结果,因为后一种情况不考虑默认值。
Decorator设计模式:
public class Wrapper {
private WrappedClass w;
public Wrapper(WrappedClass w) {
this.w = w;
}
// Forward calls to WrappedClass methods to the private instance.
public ReturnType example(Argument a) { return w.example(a); }
// Add your methods here:
}
虽然这看起来是一种乏味的方法,但由于上述原因,在漫长的 运行 中是值得的。 Java API 中接口的存在,例如上面 HashSet
的 Set
接口,使得编写这样的 classes 更容易,虽然我不知道如果 Graphics2D
.
存在这样的接口
来源:有效 Java 第 2 版 - Joshua Bloch
一种选择是使用代理。它使您可以包装 Graphics2D 的所有功能,而无需对每个方法进行编码。
GraphicsView 接口声明了附加功能:
import java.awt.Rectangle;
public interface GraphicsView {
void setViewport(Rectangle r);
Rectangle getViewport();
boolean isViewportActive();
void setViewportActive(boolean active);
}
Graphics2DWrapperFactory 是 Graphics2D + GraphicsView 的代理:
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Graphics2DWrapperFactory {
public static Graphics2D wrapGraphics2D(final Graphics2D g) {
GraphicsView gv = new GraphicsView() {
// Implement new functionality here...
private boolean active;
Rectangle r;
@Override
public Rectangle getViewport() {
System.err.println("getViewport called");
return r;
}
@Override
public void setViewport(Rectangle r) {
this.r = r;
System.err.println("setViewport called");
}
@Override
public boolean isViewportActive() {
System.err.println("isViewportActive called");
return active;
}
@Override
public void setViewportActive(boolean active) {
this.active = active;
System.err.println("setViewportActive called");
}
};
InvocationHandler invocationHandler = new GraphicsWrapperInvocationHandler(g, gv);
Class<?> interfaces[] = { Graphics2D.class, GraphicsView.class};
return (Graphics2D) Proxy.newProxyInstance(g.getClass().getClassLoader(), interfaces, invocationHandler);
}
private static class GraphicsWrapperInvocationHandler implements InvocationHandler {
private final GraphicsView gv;
private final Graphics2D g2d;
public GraphicsWrapperInvocationHandler(Graphics2D g, GraphicsView gv) {
this.g2d = g;
this.gv = gv;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
switch(methodName) {
// If the is calling one of the GraphicsView methods, delegate to gv
case "setViewport":
case "getViewport":
case "isViewportActive":
case "setViewportActive":
return method.invoke(gv, args);
// Otherwise, it's a Graphics2D methods. Delegate to g2d
default:
return method.invoke(g2d, args);
}
}
}
}
我正在尝试编写一个 2d 游戏引擎,并且我正在尝试实现一个视口系统,以便当我在某个视口中绘图时,游戏坐标将被 t运行sformed 为屏幕坐标而无需手动执行 t运行sformation.
我想要做的是创建一个添加 setViewport
方法的 Graphics2D
包装器。
我认为有 2 个选项:
创建一个 class,它有一个
Graphics2D
的实例,并且具有与Graphics2D
和setViewport
相同的所有方法,只需调用Graphics2D
实例上的相应方法。Subclass
Graphics2D
并添加一个setViewport
方法,然后从Graphics2D
转换为这个新的 class
我尝试了#2,因为#1 看起来非常不切实际,但 运行 变成了 ClassCastException
。我无法将 Graphics
或 Graphics2D
转换为这个新的 class。当我在转换前打印图形对象时(Graphics
或 Graphics2D
),两者都显示为 sun.java2d.SunGraphics2D
.
我是不是在尝试子class 和投射时做错了什么?如果没有,我该如何解决这个问题?
我为自己编写的游戏创建了一个坐标系。请随意使用这些 classes 作为示例。
这是坐标系class。
package com.ggl.game.utilities;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
/**
* <p>
* This class creates a Cartesian and Polar coordinate system to overlay a Swing
* drawing area (usually a <code>JPanel</code>). The user of the class sets a
* limit as to how far the X axis or the Y axis extends in the negative and
* positive direction from (0, 0). Point (0, 0) will be placed in the center of
* the drawing area.
* </p>
* <p>
* Since the drawing area is usually not square, the limit applies to the
* shorter dimension. The actual limits can be retrieved from this class, and
* will change if the user of the application changes the drawing area size by
* maximizing or normalizing the application <code>JFrame</code> window.
* </p>
* <p>
* Using a Cartesian or Polar coordinate system frees the user of this class
* from having to worry about Swing pixel coordinates.
* </p>
*
* @author Gilbert G. Le Blanc
* @version 1.0 - 23 February 2015
*
* @see com.ggl.game.utilities.Polar2D
* @see java.awt.geom.Point2D
* @see javax.swing.JFrame
* @see javax.swing.JPanel
*/
public class CoordinateSystem {
private double conversionFactor;
private double xLimit;
private double yLimit;
private int height;
private int width;
/**
* <p>
* This creates a Cartesian and Polar coordinate system over a Swing drawing
* area (usually a <code>JPanel</code>). If the drawing area is square, the
* X axis extends from -limit to limit and the Y axis extends from -limit to
* limit.
* </p>
* <p>
* If the drawing area is not square, then the smaller dimension, either X
* or Y, extends from -limit to limit. The larger dimension extends beyond
* the limit in both directions.
* </p>
* <p>
* Since most displays are not square, the X and Y axis will usually have
* different limits.
* </p>
*
* @param limit
* - The limit of the X and Y axis in a Cartesian coordinate
* system.
* @param width
* - The width of the drawing area in pixels.
* @param height
* - The height of the drawing area in pixels.
*/
public CoordinateSystem(double limit, int width, int height) {
this.width = width;
this.height = height;
if (width > height) {
this.xLimit = limit * width / height;
this.yLimit = limit;
this.conversionFactor = (limit + limit) / (double) height;
} else if (width < height) {
this.xLimit = limit;
this.yLimit = limit * height / width;
this.conversionFactor = (limit + limit) / (double) width;
} else {
this.xLimit = limit;
this.yLimit = limit;
this.conversionFactor = (limit + limit) / (double) width;
}
}
/**
* This method changes the drawing area dimension, along with the X and Y
* axis limits.
*
* @param dimension
* - A <code>Dimension</code> with the new drawing area
* dimension.
*/
public void setDrawingSize(Dimension dimension) {
setDrawingSize(dimension.width, dimension.height);
}
/**
* This method changes the drawing area width and height, along with the X
* and Y axis limits.
*
* @param width
* - The width of the drawing area in pixels.
* @param height
* - The height of the drawing area in pixels.
*/
public void setDrawingSize(int width, int height) {
xLimit = xLimit / this.width * width;
yLimit = yLimit / this.height * height;
this.width = width;
this.height = height;
}
/**
* This method returns the Cartesian coordinate limit for the X axis.
*
* @return The Cartesian coordinate limit for the X axis.
*/
public double getxLimit() {
return xLimit;
}
/**
* This method returns the Cartesian coordinate limit for the Y axis.
*
* @return The Cartesian coordinate limit for the Y axis.
*/
public double getyLimit() {
return yLimit;
}
/**
* This method converts a Polar coordinate distance and theta angle in
* radians to a pixel location on a drawing area.
*
* @param distance
* - A Polar coordinate distance
* @param theta
* - A Polar coordinate theta angle in radians
* @return A pixel location on a drawing area.
*/
public Point convertPolarToPixels(double distance, double theta) {
return convertToPixels(new Polar2D.Double(distance, theta));
}
/**
* This method converts a Cartesian coordinate x and y to a pixel location
* on a drawing area.
*
* @param x
* - A Cartesian coordinate x.
* @param y
* - A Cartesian coordinate y.
* @return A pixel location on a drawing area.
*/
public Point convertPointToPixels(double x, double y) {
return convertToPixels(new Point2D.Double(x, y));
}
/**
* This method converts a Polar coordinate to a pixel location on a drawing
* area.
*
* @param polar
* - A Polar coordinate.
* @return A pixel location on a drawing area.
*/
public Point convertToPixels(Polar2D polar) {
double x = polar.getDistance() * Math.cos(polar.getTheta());
double y = polar.getDistance() * Math.sin(polar.getTheta());
return convertToPixels(new Point2D.Double(x, y));
}
/**
* This method converts a Cartesian coordinate to a pixel location on a
* drawing area.
*
* @param cartesian
* - A Cartesian coordinate.
* @return A pixel location on a drawing area.
*/
public Point convertToPixels(Point2D cartesian) {
int x = (int) Math
.round((cartesian.getX() + xLimit) / conversionFactor);
int y = (int) Math.round((-cartesian.getY() + yLimit)
/ conversionFactor);
return new Point(x, y);
}
/**
* This method converts a pixel location on a drawing area to a Cartesian
* coordinate.
*
* @param x
* - The x pixel location.
* @param y
* - The y pixel location.
* @return A Cartesian coordinate.
*/
public Point2D convertToCartesian(int x, int y) {
return convertToCartesian(new Point(x, y));
}
/**
* This method converts a pixel location on a drawing area to a Cartesian
* coordinate.
*
* @param point
* - The pixel location.
* @return A Cartesian coordinate.
*/
public Point2D convertToCartesian(Point point) {
double x = (double) point.x * conversionFactor - xLimit;
double y = (double) -point.y * conversionFactor + yLimit;
return new Point2D.Double(x, y);
}
/**
* This method converts a pixel location on a drawing area to a Polar
* coordinate.
*
* @param x
* - The x pixel location.
* @param y
* - The y pixel location.
* @return A Polar coordinate.
*/
public Polar2D convertToPolar(int x, int y) {
return convertToPolar(new Point(x, y));
}
/**
* This method converts a pixel location on a drawing area to a Polar
* coordinate.
*
* @param point
* - The pixel location.
* @return A Polar coordinate.
*/
public Polar2D convertToPolar(Point point) {
double x = (double) point.x * conversionFactor - xLimit;
double y = (double) -point.y * conversionFactor + yLimit;
double distance = Math.sqrt(x * x + y * y);
double theta = Math.atan2(y, x);
return new Polar2D.Double(distance, theta);
}
}
这是 Polar2D class。它是 Point2D class.
的克隆package com.ggl.game.utilities;
/**
* The <code>Polar2D</code> class defines a point representing a location in
* distance, theta angle coordinate space.
* <p>
* This class is only the abstract superclass for all objects that store a 2D
* coordinate. The actual storage representation of the coordinates is left to
* the subclass.
*
* @version 1.0 - 23 February 2015
* @author Jim Graham (author of Point2D), Gilbert Le Blanc
*/
public abstract class Polar2D implements Cloneable {
/**
* The <code>Float</code> class defines a point specified in float
* precision.
*/
public static class Float extends Polar2D {
/**
* The distance of this <code>Polar2D</code>.
*
* @since 1.7
*/
public float distance;
/**
* The theta angle of this <code>Polar2D</code>.
*
* @since 1.7
*/
public float theta;
/**
* Constructs and initializes a <code>Polar2D</code> with coordinates
* (0, 0).
*
* @since 1.7
*/
public Float() {
}
/**
* Constructs and initializes a <code>Polar2D</code> with the specified
* coordinates.
*
* @param distance
* The distance to which to set the newly constructed
* <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set the newly
* constructed <code>Polar2D</code>
* @since 1.7
*/
public Float(float distance, float theta) {
this.distance = distance;
this.theta = theta;
}
/**
* Returns the distance of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return the distance of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getDistance() {
return (double) distance;
}
/**
* Returns the theta angle in radians of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return the theta angle in radians of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getTheta() {
return (double) theta;
}
/**
* Returns the theta angle in degrees of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return the theta angle in degrees of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getThetaInDegrees() {
double degrees = 180D / Math.PI * theta;
return (degrees < 0D) ? degrees + 360D : degrees;
}
/**
* Sets the location of this <code>Polar2D</code> to the specified
* <code>double</code> distance and theta angle in radians.
*
* @param distance
* The distance to which to set this <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set this
* <code>Polar2D</code>
* @since 1.7
*/
@Override
public void setLocation(double distance, double theta) {
this.distance = (float) distance;
this.theta = (float) theta;
}
/**
* Sets the location of this <code>Polar2D</code> to the specified
* <code>float</code> coordinates.
*
* @param distance
* The distance to which to set this <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set this
* <code>Polar2D</code>
* @since 1.7
*/
public void setLocation(float distance, float theta) {
this.distance = distance;
this.theta = theta;
}
/**
* Returns a <code>String</code> that represents the value of this
* <code>Polar2D</code>.
*
* @return A <code>String</code> representation of this
* <code>Polar2D</code>.
* @since 1.7
*/
@Override
public String toString() {
return "Polar2D.Float[" + distance + ", " + theta + "]";
}
}
/**
* The <code>Double</code> class defines a point specified in
* <code>double</code> precision.
*/
public static class Double extends Polar2D {
/**
* The distance of this <code>Polar2D</code>.
*
* @since 1.7
*/
public double distance;
/**
* The theta angle in radians of this <code>Polar2D</code>.
*
* @since 1.7
*/
public double theta;
/**
* Constructs and initializes a <code>Polar2D</code> with (0, 0)
* distance and theta angle in radians.
*
* @since 1.7
*/
public Double() {
}
/**
* Constructs and initializes a <code>Polar2D</code> with the specified
* coordinates.
*
* @param distance
* The distance to which to set the newly constructed
* <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set the newly
* constructed <code>Polar2D</code>
* @since 1.7
*/
public Double(double distance, double theta) {
this.distance = distance;
this.theta = theta;
}
/**
* Returns the distance of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The distance of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getDistance() {
return distance;
}
/**
* Returns the theta angle in radians of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The theta angle in radians of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getTheta() {
return theta;
}
/**
* Returns the theta angle in degrees of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The theta angle in degrees of this <code>Polar2D</code>.
* @since 1.7
*/
@Override
public double getThetaInDegrees() {
double degrees = 180D / Math.PI * theta;
return (degrees < 0D) ? degrees + 360D : degrees;
}
/**
* Sets the location of this <code>Polar2D</code> to the specified
* <code>double</code> coordinates.
*
* @param distance
* The distance to which to set this <code>Polar2D</code>
* @param theta
* The theta angle in radians to which to set this
* <code>Polar2D</code>
* @since 1.7
*/
@Override
public void setLocation(double distance, double theta) {
this.distance = distance;
this.theta = theta;
}
/**
* Returns a <code>String</code> that represents the value of this
* <code>Polar2D</code>.
*
* @return A <code>String</code> representation of this
* <code>Polar2D</code>.
* @since 1.7
*/
@Override
public String toString() {
return "Polar2D.Double[" + distance + ", " + theta + "]";
}
}
/**
* This is an abstract class that cannot be instantiated directly.
* Type-specific implementation subclasses are available for instantiation
* and provide a number of formats for storing the information necessary to
* satisfy the various accessor methods below.
*
* @see java.awt.geom.Polar2D.Float
* @see java.awt.geom.Polar2D.Double
* @see java.awt.Point
*/
protected Polar2D() {
}
/**
* Returns the distance of this <code>Polar2D</code> in <code>double</code>
* precision.
*
* @return The distance of this <code>Polar2D</code>.
* @since 1.7
*/
public abstract double getDistance();
/**
* Returns the theta angle in radians of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The theta angle in radians of this <code>Polar2D</code>.
* @since 1.7
*/
public abstract double getTheta();
/**
* Returns the theta angle in degrees of this <code>Polar2D</code> in
* <code>double</code> precision.
*
* @return The theta angle in degrees of this <code>Polar2D</code>.
* @since 1.7
*/
public abstract double getThetaInDegrees();
/**
* Sets the location of this <code>Polar2D</code> to the specified
* <code>double</code> coordinates.
*
* @param distance
* The distance of this <code>Polar2D</code>
* @param theta
* The theta angle in radians of this <code>Polar2D</code>
* @since 1.7
*/
public abstract void setLocation(double distance, double theta);
/**
* Sets the location of this <code>Polar2D</code> to the same coordinates as
* the specified <code>Polar2D</code> object.
*
* @param p
* the specified <code>Polar2D</code> the which to set this
* <code>Polar2D</code>
* @since 1.7
*/
public void setLocation(Polar2D p) {
setLocation(p.getDistance(), p.getTheta());
}
/**
* Returns the square of the distance between two points.
*
* @param distance1
* The distance of the first point
* @Parm theta1 The theta angle in radians of the first point
* @param distance2
* The distance of the second point
* @param theta2
* The theta angle in radians of the second point
* @return The square of the distance between the two specified points.
*/
public static double distanceSq(double distance1, double theta1,
double distance2, double theta2) {
double x1 = distance1 * Math.cos(theta1);
double y1 = distance1 * Math.sin(theta1);
double x2 = distance2 * Math.cos(theta2);
double y2 = distance2 * Math.sin(theta2);
return (x1 * x2 + y1 * y2);
}
/**
* Returns the distance between two points.
*
* @param distance1
* The distance of the first point
* @param theta1
* The theta angle in radians of the first point
* @param distance2
* The distance of the second point
* @param theta2
* The theta angle in radians of the second point
* @return The distance between the two specified points.
*/
public static double distance(double distance1, double theta1,
double distance2, double theta2) {
double x1 = distance1 * Math.cos(theta1);
double y1 = distance1 * Math.sin(theta1);
double x2 = distance2 * Math.cos(theta2);
double y2 = distance2 * Math.sin(theta2);
return Math.sqrt(x1 * x2 + y1 * y2);
}
/**
* Returns the square of the distance from this <code>Polar2D</code> to a
* specified point.
*
* @param distance
* The distance of the specified point
* @param theta
* The theta angle in radians of the specified point
* @return The square of the distance between this <code>Polar2D</code> and
* the specified point.
*/
public double distanceSq(double distance, double theta) {
double x1 = distance * Math.cos(theta);
double y1 = distance * Math.sin(theta);
double x2 = getDistance() * Math.cos(getTheta());
double y2 = getDistance() * Math.sin(getTheta());
return (x1 * x2 + y1 * y2);
}
/**
* Returns the square of the distance from this <code>Polar2D</code> to a
* specified <code>Polar2D</code>.
*
* @param pt
* The specified <code>Polar2D</code>
* @return The square of the distance between this <code>Polar2D</code> to a
* specified <code>Polar2D</code>.
*/
public double distanceSq(Polar2D pt) {
double x1 = pt.getDistance() * Math.cos(pt.getTheta());
double y1 = pt.getDistance() * Math.sin(pt.getTheta());
double x2 = getDistance() * Math.cos(getTheta());
double y2 = getDistance() * Math.sin(getTheta());
return (x1 * x2 + y1 * y2);
}
/**
* Returns the distance from this <code>Polar2D</code> to a specified point.
*
* @param distance
* The distance of the specified point
* @param theta
* The theta angle in radians of the specified point
* @return The distance between this <code>Polar2D</code> and a specified
* point.
*/
public double distance(double distance, double theta) {
double x1 = distance * Math.cos(theta);
double y1 = distance * Math.sin(theta);
double x2 = getDistance() * Math.cos(getTheta());
double y2 = getDistance() * Math.sin(getTheta());
return Math.sqrt(x1 * x2 + y1 * y2);
}
/**
* Returns the distance from this <code>Polar2D</code> to a specified
* <code>Polar2D</code>.
*
* @param pt
* the specified <code>Polar2D</code>
* @return The distance between this <code>Polar2D</code> and the specified
* <code>Polar2D</code>.
*/
public double distance(Polar2D pt) {
double x1 = pt.getDistance() * Math.cos(pt.getTheta());
double y1 = pt.getDistance() * Math.sin(pt.getTheta());
double x2 = getDistance() * Math.cos(getTheta());
double y2 = getDistance() * Math.sin(getTheta());
return Math.sqrt(x1 * x2 + y1 * y2);
}
/**
* Creates a new object of the same class and with the same contents as this
* object.
*
* @return a clone of this instance.
* @exception OutOfMemoryError
* if there is not enough memory.
* @see java.lang.Cloneable
* @since 1.7
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
/**
* Returns the hash code for this <code>Polar2D</code>.
*
* @return a hash code for this <code>Polar2D</code>.
*/
@Override
public int hashCode() {
long bits = java.lang.Double.doubleToLongBits(getDistance());
bits ^= java.lang.Double.doubleToLongBits(getTheta()) * 31;
return (((int) bits) ^ ((int) (bits >> 32)));
}
/**
* Determines whether or not two points are equal. Two instances of
* <code>Polar2D</code> are equal if the values of their <code>x</code> and
* <code>y</code> member fields, representing their position in the
* coordinate space, are the same.
*
* @param obj
* an object to be compared with this <code>Polar2D</code>
* @return <code>true</code> if the object to be compared is an instance of
* <code>Polar2D</code> and has the same values; <code>false</code>
* otherwise.
* @since 1.7
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Polar2D) {
Polar2D p2d = (Polar2D) obj;
return (getDistance() == p2d.getDistance())
&& (getTheta() == p2d.getTheta());
}
return super.equals(obj);
}
}
一个 OO 设计原则是 "favor composition over inheritance",这是使用 包装器 class 或 Decorator 设计模式(我认为课程幻灯片中的包装器是什么意思)。所以你所做的实际上是优雅解决方案,原因有很多:
它可以防止
Graphics2D
中未来的实现更改,如果您使用继承并且不幸地向Graphics2D
添加了一个与您的新方法具有相同签名的新方法并且不同的 return 类型,您的 class 将不再编译。如果您使用相同的签名和 return 类型,您将覆盖Graphics2D
中的新方法,这可能(并且已经)导致几天令人沮丧的调试。这种方式的继承违反了封装使得软件在长期运行.
[=62中变得脆弱和容易出错=]使用组合,您可以保护您的 class 免受将来 class 的更改,您的 组合 ,您的 class 将 转发 对它的
private
Graphics2D
实例的所有方法调用并分别处理变换坐标。它还允许将来轻松扩展,使用继承会将您绑定到
的性能Graphics2D
的当前实现,可能会限制您的 class.
Java API 中有一个这样的例子:Properties
class extends HashTable
,这说明了继承的不当使用,因为 [= =17=] 不是 一个 HashTable
,它不应该以相同的方式使用。在这种情况下,调用 Properties p.getProperty(key)
可能会给出与 p.get(key)
不同的结果,因为后一种情况不考虑默认值。
Decorator设计模式:
public class Wrapper {
private WrappedClass w;
public Wrapper(WrappedClass w) {
this.w = w;
}
// Forward calls to WrappedClass methods to the private instance.
public ReturnType example(Argument a) { return w.example(a); }
// Add your methods here:
}
虽然这看起来是一种乏味的方法,但由于上述原因,在漫长的 运行 中是值得的。 Java API 中接口的存在,例如上面 HashSet
的 Set
接口,使得编写这样的 classes 更容易,虽然我不知道如果 Graphics2D
.
来源:有效 Java 第 2 版 - Joshua Bloch
一种选择是使用代理。它使您可以包装 Graphics2D 的所有功能,而无需对每个方法进行编码。
GraphicsView 接口声明了附加功能:
import java.awt.Rectangle;
public interface GraphicsView {
void setViewport(Rectangle r);
Rectangle getViewport();
boolean isViewportActive();
void setViewportActive(boolean active);
}
Graphics2DWrapperFactory 是 Graphics2D + GraphicsView 的代理:
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Graphics2DWrapperFactory {
public static Graphics2D wrapGraphics2D(final Graphics2D g) {
GraphicsView gv = new GraphicsView() {
// Implement new functionality here...
private boolean active;
Rectangle r;
@Override
public Rectangle getViewport() {
System.err.println("getViewport called");
return r;
}
@Override
public void setViewport(Rectangle r) {
this.r = r;
System.err.println("setViewport called");
}
@Override
public boolean isViewportActive() {
System.err.println("isViewportActive called");
return active;
}
@Override
public void setViewportActive(boolean active) {
this.active = active;
System.err.println("setViewportActive called");
}
};
InvocationHandler invocationHandler = new GraphicsWrapperInvocationHandler(g, gv);
Class<?> interfaces[] = { Graphics2D.class, GraphicsView.class};
return (Graphics2D) Proxy.newProxyInstance(g.getClass().getClassLoader(), interfaces, invocationHandler);
}
private static class GraphicsWrapperInvocationHandler implements InvocationHandler {
private final GraphicsView gv;
private final Graphics2D g2d;
public GraphicsWrapperInvocationHandler(Graphics2D g, GraphicsView gv) {
this.g2d = g;
this.gv = gv;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
switch(methodName) {
// If the is calling one of the GraphicsView methods, delegate to gv
case "setViewport":
case "getViewport":
case "isViewportActive":
case "setViewportActive":
return method.invoke(gv, args);
// Otherwise, it's a Graphics2D methods. Delegate to g2d
default:
return method.invoke(g2d, args);
}
}
}
}