如何在libgdx舞台中对演员进行排序?

9

我在LibGdx Stage对象中对演员进行排序遇到了困难。当Stage被渲染时,图像按添加顺序呈现。Stage使用一个数组来保存演员。我已经尝试设置每个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问题让我不断地打转。 - Bobby
8个回答

19

与接受的答案不同,我更喜欢准备几个“层”组,然后按任意顺序向这些组添加。

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);
当然,在bgfg元素内部以及它们之间,addActor()顺序仍然很重要。但在一个fg元素和bg元素之间就不重要了。这种工作流在继承场景中非常有效,其中基类拥有受保护的Group层(例如this.bgthis.fg等),并按顺序将它们添加到舞台中。然后派生类可以向这些图层添加内容,而无需考虑顺序。

1
这是一个很好的答案,但我不认为它适用于OP所需的内容。您的示例非常适用于半静态元素,例如HUD、背景或其他效果层。但是,如果您想动态修改演员的顺序呢?例如,如果演员的Y位置大于门,则演员将在门后面,如果位置低于门,则演员将在门前面。也许我错了,但我认为您的解决方案无法处理这个问题。 - NMC
这个方案在你移动相机之前是可行的。演员坐标以世界单位为基础,因此您需要手动更新演员坐标。例如,您的背景将以与前景相同的速率移动,而您的UI层将在相机移动时被留下。 - EntangledLoops

12

您的代码Stage.getActors()返回一个Actors数组而不是ListCollections.sort()方法只接受列表。 请尝试:

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

有关排序更新(根据问题中的z-index)来自@James Holloway: z-index被舞台覆盖,以成为数组内部顺序。因此,设置Z-Index没有效果,除非您将其设置为高于列表长度的情况,然后它只会将图像放在列表顶部(内部阶段会执行此操作)。这可通过按名称或ID进行排序来解决。


1
这段代码在运行时没有抛出任何错误,但似乎没有产生任何效果。可能是getActors()方法发送了一个副本,如果是这样的话,我需要找到一种方法获取排序后的列表并将其重新设置到舞台中。 - Lokiare
@JamesHolloway,如果可能的话,你能否尝试在Actor类中重写equals()hashcode()方法? - Ezhil V
我发现了一个不同的问题。显然,尽管我设置了ZIndex,但它仍然重置为-1。因此,您的代码可能是正常运行的,但问题似乎出在其他地方。 - Lokiare
好的,我查了一下,显然 z-index 被舞台覆盖,成为 Array<Actor> 的内部顺序。因此设置 Z-Index 没有效果,除非你将其设置为列表长度以上,然后它只会将图像放在列表顶部(内部舞台会这样做)。所以我通过使用我使用的名称进行排序来解决这个问题。如果每个 Actor 都有一个 ID,我也可以通过 ID 来完成。换句话说,你的答案对于排序是正确的,但我的比较器是错误的。如果你能添加这个,那会帮助其他发现这个问题的人。 - Lokiare

2

我让所有演员继承一个名为DepthActor的类,该类包含深度值并实现了Comparable<DepthActor>接口:

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() );
此代码使用了libGDX的Sort实例对舞台中的演员进行排序。

0

可能不适用于所有情况,但我使用LibGdx Actor.userObject存储我的z-索引,然后根据此自动排序。在您的屏幕或游戏类中:

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);

这样做也可以避免在每次渲染调用时对数组进行排序。 希望能有所帮助!


0

我使用了一些浮点数值来对我的玩家进行排序。数值越大,玩家的索引就越高。以下是我的代码片段:

    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--;
    }

0

我的解决方案在快速测试中对我很有效,但仍可能存在一些缺陷。如果有人发现任何问题,请大声喊出来,但这对我来说效果很好,而且相当轻便。

我只在我的卡片上运行它(我正在编写一款纸牌游戏),所以它只会调整舞台上的卡片类,它们潜在地始终位于其他物品如桌子/标签等之上。

我已经在我的类中添加了本地int zIndex,它是比较器中使用的并在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;
    }
}

0
不确定这是否是最近添加的,但对我有效的方法就是简单地在演员数组上调用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;
    }
}

0
最简单的解决方案!我为需要这么长时间才想出来而感到羞愧...
创建一个名为“ZStage”的类,它继承自Stage。重写getActors方法。根据Y索引对演员进行冒泡排序,以获取它们的渲染顺序。 public class ZStage extends Stage {
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;
}

}


如果需要每帧调用它,那么你每帧都在进行冒泡排序。这很耗费资源。为什么不直接使用gdx提供的Timsort呢? - EntangledLoops

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接