【问题标题】:Separating logic from graphics in animated simulation在动画模拟中将逻辑与图形分离
【发布时间】:2018-06-16 00:58:46
【问题描述】:

我需要编写一个程序,该程序的行为类似于某个过程的动画模拟。没有详细说明,会有蜗牛(讲师的想法)在棋盘状的表面上移动,其中每个矩形(我称之为单元格)都有 (x, y) 坐标。

我在将此模拟的逻辑与图形分开时遇到问题。例如:

我有一个Snail 课程。它存储蜗牛的坐标并计算其行为。当它确定蜗牛应该从(x,y)移动到(a,b)时,我需要为该移动设置动画,因此我还必须以像素为单位计算蜗牛的位置,并且我需要在一段时间内重复执行此操作以使蜗牛能够流畅地移动,而不是跳跃。如果不是因为我不想在 Snail 类中这样做,这不会是一个问题,因为它是严格的图形相关的,与逻辑没有任何关系。

我不能只根据Snails 在棋盘上的坐标绘制一个循环,因为它不反映它的流畅运动,只是当前位置。

我目前最好的想法是将Snail 扩展为GraphicSnail,这将额外计算和存储诸如蜗牛位置(以像素为单位)之类的属性,但这对我来说似乎不够独立。

提前感谢您的帮助。

【问题讨论】:

    标签: java animation graphics simulation


    【解决方案1】:

    您可能想使用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 接口执行此操作。这就是分离的所在。请注意,您还可以存储此类观察者的完整列表。在任何情况下,关键部分是在Snail 对象的状态发生变化时调用观察者对象上的update() 方法,从而使观察者意识到某些事情发生了变化。观察者对象然后检查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;
        }
    
    } 
    

    最后,不要忘记在创建 SnailGraphicSnail 对象后注册观察者:

    Snail snail = new Snail();
    GraphicSnail graphicSnail = new GraphicSnail();
    snail.registerObserver(graphicSnail);
    

    希望这会有所帮助, 步步高

    【讨论】:

      【解决方案2】:

      与 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 类来创建控件对于大多数应用程序来说都是多余的。但是,您可以根据您的需要评估该方法。您可能最好在之前链接的井字游戏代码中使用更简单的方法,例如观察者皮肤方法。

      此外,根据您想要达到的程度,您可以使用CSS stylesheetslayout definition from code using FXML 和样式与代码分开。我建议在大多数情况下使用样式表。对于您描述的情况,可能不需要 FXML 分离。

      【讨论】:

        猜你喜欢
        • 2013-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-16
        • 1970-01-01
        • 2022-11-23
        • 1970-01-01
        • 2023-03-28
        相关资源
        最近更新 更多