【问题标题】:How do you sort Actors in a libgdx Stage?如何在 libgdx Stage 中对 Actor 进行排序?
【发布时间】:2013-04-14 08:08:33
【问题描述】:

我在对 LibGdx Stage 对象中的 Actor 进行排序时遇到问题。当舞台被渲染时,图像会按照它们添加的顺序被渲染。 Stage 使用一个数组来保存 Actor。我已经尝试设置每个 Actor 的 ZIndex,但它仍然没有排序。然后我尝试像这样创建一个比较器对象:

public class ActorComparator implements Comparator < Actor > {
    @Override
    public int compare(Actor arg0, Actor arg1) {
        if (arg0.getZIndex() < arg1.getZIndex()) {
            return -1;
        } else if (arg0.getZIndex() == arg1.getZIndex()) {
            return 0;
        } else {
            return 1;
        }
    }
}

然后当我想进行实际比较时:

Collections.sort(Stage.getActors(), new ActorComparator());

它给了我以下错误并且不会编译:

The method sort(List<T>, Comparator<? super T>) in the type Collections 
is not applicable for the arguments (Array<Actor>, ActorComparator)

我不知道我做错了什么。谁能给我解释一下?

【问题讨论】:

  • 很高兴我偶然发现了这个问题。 z-index 问题让我绕圈子

标签: java libgdx


【解决方案1】:

不确定这是否是最近添加的,但对我有用的只是在演员数组上调用 sort

gameStage.getActors().sort(new ActorRenderComparator());

那么ActorRenderComparator 可能看起来像这样:

public class ActorRenderComparator implements Comparator<Actor> {
    @Override
    public int compare(Actor a, Actor b) {
        if (a.getY() == b.getY()) {
            return 0;
        }

        if (a.getY() > b.getY()) {
            return -1;
        }

        return 1;
    }
}

【讨论】:

    【解决方案2】:

    我让我的所有演员都扩展了一个 DepthActor 类,该类包含一个深度值并实现了 Comparable&lt;DepthActor&gt;

    import com.badlogic.gdx.scenes.scene2d.Actor;
    
    import java.util.Comparator;
    
    /**
     * @author snd
     * @since May 02, 2017
     */
    public class DepthActor extends Actor implements Comparable<DepthActor>
    {
      public int depth;
    
      @Override
      public int compareTo(DepthActor o)
      {
        return depth < o.depth ? -1 : (depth > o.depth ? 1 : 0);
      }
    }
    

    那么就地排序就很简单了:

    com.badlogic.gdx.utils.Sort.instance().sort( stage.getActors() );

    【讨论】:

      【解决方案3】:

      最简单的解决方案!!我很惭愧,我花了这么长时间才想出这个……

      创建一个扩展 Stage 的“ZStage”类。覆盖 getActors 方法。根据演员的 Y 指数对演员进行冒泡排序,以获得他们的渲染顺序。

      公共类 ZStage 扩展舞台 {

      public ZStage(Viewport vp){
          super(vp);
      }
      
      @Override
      /** Returns the root's child actors.
       * @see Group#getChildren() */
      public Array<Actor> getActors () {
          SnapshotArray<Actor> children = getRoot().getChildren();
      
          boolean sorting = true;
          while(sorting){
              sorting=false;
              for(int x=0; x< children.size-1; x++){
                  if(children.get(x).getY() < children.get(x+1).getY()){
                      children.swap(x,x+1);
                      sorting=true;
                  }
              }
          }
      
          return children;
      }
      

      }

      【讨论】:

      • 如果这需要每帧调用,你就是每帧都在 BubbleSorting。这太贵了。为什么不直接使用 gdx 提供的 Timsort?
      【解决方案4】:

      我的解决方案在快速测试中适用于我,但可能仍然存在一些缺陷。如果人们发现任何东西,请大声喊叫,但这对我来说效果很好,而且很轻。

      我只在我的卡片上运行它(我正在写一个卡片游戏),所以它只调整舞台上的卡片类,它们可能总是高于其他东西,比如表格/标签等。

      我已经在我的类中添加了一个本地 int zIndex,它在 Comparator 中使用,并在 setZIndex 方法的开头设置。

      方法的开头部分是从 Actor 类中的原始部分提升的。

      @Override
      public void setZIndex(int index) {
          this.zIndex = index;
          if (index < 0) throw new IllegalArgumentException("ZIndex cannot be < 0.");
          Group parent = this.getParent();
          if (parent == null) return;
          Array<Actor> children = parent.getChildren();
          if (children.size == 1) return;
          ArrayList<Card> allCards = new ArrayList<Card>();
          for(Actor child : children) {
              if(child instanceof Card) {
                  allCards.add((Card)child);
              }
          }
          Collections.sort(allCards, new ZIndexComparator());
          for (Card card : allCards) {
              children.removeValue(card, false);
              children.add(card);
          }
      }
      
      class ZIndexComparator implements Comparator<Card> {
          public int compare(Card card1, Card card2) {
              return card1.zIndex - card2.zIndex;
          }
      }
      

      【讨论】:

        【解决方案5】:

        我使用了一些浮点值来对我的玩家进行排序。数值越大,玩家的指数越高。这是我的 sn-p:-

            Map<Actor, Float> map = new HashMap<Actor, Float>();
            map.put(playerActor, playerArea);
            for (int i = 0; i < totalEnemies; i++) {
                if (i != playerImageNo) {
                    map.put(enemyActor[i], enemyArea[i]);
                }
            }
            int noOfPlayers = map.size();
        
            // Sort Map based on values(size)
            Set<Entry<Actor, Float>> set = map.entrySet();
            List<Entry<Actor, Float>> list = new ArrayList<Entry<Actor, Float>>(set);
            Collections.sort(list, new Comparator<Map.Entry<Actor, Float>>() {
                @Override
                public int compare(Map.Entry<Actor, Float> o1,
                        Map.Entry<Actor, Float> o2) {
                    return (o2.getValue()).compareTo(o1.getValue());
                }
            });
        
            for (Map.Entry<Actor, Float> entry : list) {
                entry.getKey().setZIndex(noOfPlayers);          
                System.out.println("Player: " + entry.getKey() + ", index: " + noOfPlayers);
                noOfPlayers--;
            }
        

        【讨论】:

          【解决方案6】:

          可能不适合所有情况,但我使用 LibGdx Actor.userObject 来存储我的 z-index,然后根据它进行自动排序。在您的 Screen 或 Game 类中:

          class ActorZOrderComparator implements Comparator<Actor> {
              @Override
              public int compare(Actor o1, Actor o2) {
                  if (o1 != null && o1.getUserObject() != null && o1.getUserObject() instanceof Integer
                          && o2 != null && o2.getUserObject() != null && o2.getUserObject() instanceof Integer) {
                      int z1 = (Integer) o1.getUserObject();
                      int z2 = (Integer) o2.getUserObject();
                      return z1 - z2;
                  } else if (o1 != null && o2 != null) {
                      return o1.getZIndex() - o2.getZIndex();
                  } else if (o1 != null) {
                      return -1;
                  } else if (o2 != null) {
                      return 1;
                  } else {
                      return 0;
                  }
              }
          }
          
          protected ActorZOrderComparator zOrderComparator = new ActorZOrderComparator();
          private boolean invalidZOrder;
          
          protected void addActor(Actor actor) {
              stage.addActor(actor);
              if (actor.getUserObject() instanceof Integer) {
                  invalidZOrder = true;
              }
          }
          
          @Override
          public void render(float delta) {
              if (invalidZOrder) {
                  Arrays.sort(stage.getRoot().getChildren().begin(), zOrderComparator);
                  stage.getRoot().getChildren().end();
                  invalidZOrder = false;
              }
          }
          

          然后您使用新的 addActor 方法(不是 stage.addActor)添加演员:

          Image leftBar = new Image(skin, "leftBar");
              leftBar.setPosition(0, GameSettings.VIEWPORT_HEIGHT * 0.5f, Align.left);
              leftBar.setUserObject(1); // <-- this is the z-index, set it before calling addActor(..)
              addActor(leftBar);
          

          这也将避免在每次渲染调用时对数组进行排序。 希望对您有所帮助!

          【讨论】:

            【解决方案7】:

            与公认的答案相比,我更喜欢准备几个“层”组,然后以我喜欢的任何顺序添加到这些组中。

            Group bg = new Group();
            Group fg = new Group();
            // the order is important in the following two lines
            stage.addActor(bg); 
            stage.addActor(fg); 
            
            bg.addActor(whatever);
            fg.addActor(whatever);
            bg.addActor(whatever);
            fg.addActor(whatever);
            

            (当然addActor() 的顺序在bg 的元素之间和fg 的元素之间仍然很重要,但在fg 的元素和bg 的元素之间不重要。)

            此工作流在继承场景中运行良好,其中基类拥有受保护的组层(this.bgthis.fg、...)并将它们按顺序添加到阶段。然后派生类可以在不考虑顺序的情况下向这些层添加内容。

            【讨论】:

            • 这是一个很好的答案,但我认为它不适用于 OP 的需求。您的示例非常适合 HUD、背景或其他效果层等半静态元素。但是如果你想动态地修改演员的顺序怎么办?例如,如果 Y 位置大于门,则演员将在门后,如果位置低于门,则演员将在门前。也许我错了,但我认为您的解决方案无法解决这个问题。
            • 在您移动相机之前,这将非常有效。 Actor 坐标采用世界单位,因此您需要手动更新 Actor 坐标。例如,您的背景将以与您的前景相同的速率移动,而您的 UI 层将随着相机的移动而被抛在后面。
            【解决方案8】:

            看起来您的代码 Stage.getActors() 返回 ArrayActors 而不是 ListCollections.sort() 方法只接受列表。 试试:

            Collections.sort(Arrays.asList(Stage.getActors().toArray()), new ActorComparator());
            

            @James Holloway 的排序更新(按问题中的 z-index): z-index 被阶段覆盖为数组的内部顺序。因此,设置 Z-Index 没有任何效果,除非您将其设置为高于列表的长度,然后它只是将图像放在列表的顶部(内部 Stage 会这样做)。这可以通过按名称或 ID 排序来解决。

            【讨论】:

            • 这即使在运行时也不会抛出任何错误,但似乎没有效果。它可能的 getActors() 正在发送一个副本,在这种情况下,我需要以某种方式获取排序列表并将其重置为阶段。
            • @JamesHolloway 你可以尝试在Actor 类中覆盖equals()hashcode(),如果可能的话?
            • 我发现了一个不同的问题。显然,即使我将 ZIndex 设置为以某种方式重置为 -1。所以你的代码可能工作正常,但问题出在其他地方。
            • 好的,所以我环顾四周,显然 z-index 被舞台覆盖,无论 Array 的内部顺序是什么。因此,设置 Z-Index 没有任何效果,除非您将其设置为高于列表的长度,然后它只是将图像放在列表的顶部(内部 Stage 会这样做)。所以我通过按我使用的名称排序来解决它。如果每个演员都有一个 ID,我也可以这样做。换句话说,您的答案对于排序是正确的,但我的比较器是错误的。如果您可以添加它,它将帮助其他找到此问题的人。
            • @JamesHolloway 很高兴知道这一点。感谢更新。请查看我编辑的答案。
            猜你喜欢
            • 2015-10-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-07-25
            • 2015-04-11
            • 1970-01-01
            • 1970-01-01
            • 2017-02-21
            相关资源
            最近更新 更多