我需要编写一个程序,就像某个过程的动画模拟一样。没有详细说明,蜗牛(讲师的想法)会在棋盘状的表面上移动,每个矩形(我称之为单元格)都有(x,y)坐标。
我在将此模拟的逻辑与图形分离时遇到问题。例如:
我有一个Snail
课程。它存储蜗牛的坐标并计算其行为。当它确定蜗牛应该从(x,y)移动到(a,b)时,我需要为该移动设置动画,所以我还必须以像素为单位计算蜗牛的位置,我需要在一段时间内重复进行蜗牛到流畅的移动,而不是跳跃。如果我不想在Snail
类中执行此操作,这不会成为问题,因为它与图形严格相关而与逻辑没有任何关系。
我不能只根据它们在棋盘上的坐标制作一个绘制Snail
s的循环,因为它不会反映它的流畅运动,只是当前的位置。
我现在最好的想法是将Snail
扩展为GraphicSnail
,这将另外计算和存储像蜗牛位置的属性(以像素为单位),但这对我来说似乎不够分开。
提前感谢您的帮助。
答案 0 :(得分:2)
您可能想要使用Observer pattern。
使用中间界面进行分离:
public interface SnailObserver {
void update(Snail snail);
}
然后使您的图形相关类实现此接口。我不知道你用来渲染图形的库(如果有的话)。如果您使用的是JavaFX,GraphicSnail
类也可以从ImageView
继承。
public class GraphicSnail implements SnailObserver {
@Overrride
public void update(Snail snail) {
// Use snail.getX() and snail.getY() to obtain
// position of the snail and perform whatever
// graphical updates you wish to make
}
}
最后,这就是Snail
类的样子。请注意附加字段,该字段包含对GraphicSnail
的引用,但通过SnailObserver
接口执行此操作。这就是分离所在。请注意,您还可以存储此类观察者的完整列表。在任何情况下,关键部分是在update()
对象的状态发生变化时在观察者对象上调用Snail
方法,从而使观察者意识到某些事情已发生变化。然后,observer对象检查Snail
对象的当前状态,并相应地修改自己的状态。
public class Snail {
private double x;
private double y;
private SnailObserver observer;
public void move() {
// Move the snail and then notify the observer
// that the snail has changed like so:
observer.update(this);
}
public void registerObserver(SnailObserver observer) {
this.observer = observer;
observer.update(this); // initial sync
}
public double getX() {
return x;
}
public double getY() {
return y;
}
}
最后,在创建Snail
和GraphicSnail
对象后,不要忘记注册观察者:
Snail snail = new Snail();
GraphicSnail graphicSnail = new GraphicSnail();
snail.registerObserver(graphicSnail);
希望这有帮助, 斯泰潘
答案 1 :(得分:1)
与Stephan的答案类似,在模型类中实现observable properties进行模拟。创建单独的外观类,负责可观察模型类的可视化表示。在创建外观时为皮肤指定对模型的引用,并在外观中将侦听器添加到模型的可侦听属性,以便外观可以根据需要对模型状态更改做出反应。
有关此方法的示例,请参阅此Tic-Tac-Toe code中的Square和SquareSkin以及Board和BoardSkin类。这是一段摘录:
class Square {
enum State { EMPTY, NOUGHT, CROSS }
private final SquareSkin skin;
private ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.EMPTY);
public ReadOnlyObjectProperty<State> stateProperty() {
return state.getReadOnlyProperty();
}
public State getState() {
return state.get();
}
private final Game game;
public Square(Game game) {
this.game = game;
skin = new SquareSkin(this);
}
public void pressed() {
if (!game.isGameOver() && state.get() == State.EMPTY) {
state.set(game.getCurrentPlayer());
game.boardUpdated();
game.nextTurn();
}
}
public Node getSkin() {
return skin;
}
}
class SquareSkin extends StackPane {
static final Image noughtImage = new Image(
"http://icons.iconarchive.com/icons/double-j-design/origami-colored-pencil/128/green-cd-icon.png"
);
static final Image crossImage = new Image(
"http://icons.iconarchive.com/icons/double-j-design/origami-colored-pencil/128/blue-cross-icon.png"
);
private final ImageView imageView = new ImageView();
SquareSkin(final Square square) {
getStyleClass().add("square");
imageView.setMouseTransparent(true);
getChildren().setAll(imageView);
setPrefSize(crossImage.getHeight() + 20, crossImage.getHeight() + 20);
setOnMousePressed(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
square.pressed();
}
});
square.stateProperty().addListener(new ChangeListener<Square.State>() {
@Override public void changed(ObservableValue<? extends Square.State> observableValue, Square.State oldState, Square.State state) {
switch (state) {
case EMPTY: imageView.setImage(null); break;
case NOUGHT: imageView.setImage(noughtImage); break;
case CROSS: imageView.setImage(crossImage); break;
}
}
});
}
}
模型类和外观类的使用是如何创建JavaFX的UI控件的。如果您在JavaFX source code base中查看控件类及其外观的实现,您可以看到它是如何工作的。
一般来说,我认为通过扩展Control并使用内置的SkinBase类来创建控件对于大多数应用来说都是过度的。但是,您可以根据自己的需求评估方法。可能你最好在之前链接的Tic-Tac-Toe代码中使用像观察者那样更简单的东西。
此外,根据您希望使用此内容的距离,您可以使用layout definition from code using FXML将CSS stylesheets和样式与代码分开。在大多数情况下,我建议使用样式表。对于您描述的情况,可能不需要FXML分离。