我最近发现LibGDX中的默认可渲染排序功能并不完全符合我的需要。 (见; Draw order changes strangely as camera moves?)
当它们应该在后面呈现时,基本上是在前面呈现的一些对象。
幸运的是,所讨论的可更换物总是有一种保证关系。物体彼此连接,因此当一个物体移动另一个物体时。一个对象可以被视为字面上“固定”到另一个对象,所以总是在前面。
这让我觉得如果我为每个对象指定了一个“z-index”(int)和“groupname”(String),我可以手动接管绘制顺序,对于具有相同组名的东西,确保它们按照z-index指定的顺序位于列表中彼此相邻的位置。 (从低到高)
//For example an array of renderables like
0."testgroup2",11
1."testgroup",20
2."testgroup2",10
3.(no zindex attribute)
4."testgroup",50
//Should sort to become
0."testgroup",20
1."testgroup",50
2.(no zindex attribute)
3."testgroup2",10
4."testgroup2",11
// assuming the object2 in testgroup2 are closer to the camera, the one without a index second closest, and the rest furthest<br>
//(It is assumed that things within the same group wont be drastically different distances)
我在libgdx中实现了一个排序系统来执行此操作,如下所示;
/**
* The goal of this sorter is to sort the renderables the same way LibGDX would do normally (in DefaultRenderableSorter)<br>
* except if they have a ZIndex Attribute.<br>
* A Zindex attribute provides a groupname string and a number.<br>
* Renderables with the attribute are placed next to others of the same group, with the order within the group determined by the number<br>
*
* For example an array of renderables like;<br><br>
* 0."testgroup",20<br>
* 1."testgroup2",10<br>
* 2.(no zindex attribute)<br>
* 3."testgroup",50<br>
* <br>Should become;<br><br>
* 0."testgroup",20<br>
* 1."testgroup",50<br>
* 2.(no zindex attribute)<br>
* 3."testgroup2",10<br>
* <br>
* assuming the object in testgroup2 is closer to the camera, the one without a index second closest, and the rest furthest<br>
* (It is assumed that things within the same group wont be drastically different distances)<br>
*
* @param camera - the camera in use to determine normal sort order when we cant place in a existing group
* @param resultList - an array of renderables to change the order of
*/
private void customSorter(Camera camera, Array<Renderable> resultList) {
//make a copy of the list to sort. (This is probably a bad start)
Array <Renderable> renderables = new Array <Renderable> (resultList);
//we work by clearing and rebuilding the Renderables array (probably not a good method)
resultList.clear();
//loop over the copy we made
for (Renderable o1 : renderables) {
//depending of if the Renderable as a ZIndexAttribute or not, we sort it differently
//if it has one we do the following....
if (o1.material.has(ZIndexAttribute.ID)){
//get the index and index group name of it.
int o1Index = ((ZIndexAttribute)o1.material.get(ZIndexAttribute.ID)).zIndex;
String o1GroupName = ((ZIndexAttribute)o1.material.get(ZIndexAttribute.ID)).group;
//setup some variables
boolean placementFound = false; //Determines if a placement was found for this renderable (this happens if it comes across another with the same groupname)
int defaultPosition = -1; //if it doesn't find another renderable with the same groupname, this will be its position in the list. Consider this the "natural" position based on distance from camera
//start looping over all objects so far in the results (urg, told you this was probably not a good method)
for (int i = 0; i < resultList.size; i++) {
//first get the renderable and its ZIndexAttribute (null if none found)
Renderable o2 = resultList.get(i);
ZIndexAttribute o2szindex = ((ZIndexAttribute)o2.material.get(ZIndexAttribute.ID));
if (o2szindex!=null){
//if the renderable we are comparing too has a zindex, then we get its information
int o2index = o2szindex.zIndex;
String o2groupname = o2szindex.group;
//if its in the same group as o1, then we start the processing of placing them nexto eachother
if (o2groupname.equals(o1GroupName)){
//we either place it in front or behind based on zindex
if (o1Index<o2index){
//if lower z-index then behind it
resultList.insert(i, o1);
placementFound = true;
break;
}
if (o1Index>o2index){
//if higher z-index then it should go in front UNLESS there is another of this group already there too
//in which case we just continue (which will cause this to fire again on the next renderable in the inner loop)
if (resultList.size>(i+1)){
Renderable o3 = resultList.get(i+1);
ZIndexAttribute o3szindex = ((ZIndexAttribute)o3.material.get(ZIndexAttribute.ID));
if (o3szindex!=null){
String o3groupname = o3szindex.group;
if (o3groupname!=null && o3groupname.equals(o1GroupName)){
//the next element is also a renderable with the same groupname, so we loop and test that one instead
continue;
}
}
}
// Gdx.app.log("zindex", "__..placeing at:"+(i+1));
//else we place after the current one
resultList.insert(i+1, o1);
placementFound = true;
break;
}
}
}
//if no matching groupname found we need to work out a default placement.
int placement = normalcompare(o1, o2); //normal compare is the compare function in DefaultRenderableSorter.
if (placement>0){
//after then we skip
//(we are waiting till we are either under something or at the end
} else {
//if placement is before, then we remember this position as the default (but keep looking as there still might be matching groupname, which should take priority)
defaultPosition = i;
//break; //break out the loop
}
}
//if we have checked all the renderables positioned in the results list, and none were found with matching groupname
//then we use the defaultposition to insert it
if (!placementFound){
//Gdx.app.log("zindex", "__no placement found using default which is:"+defaultPosition);
if (defaultPosition>-1){
resultList.insert(defaultPosition, o1);
} else {
resultList.add(o1);
}
}
continue;
}
//...(breath out)...
//ok NOW we do placement for things that have no got a ZIndexSpecified
boolean placementFound = false;
//again, loop over all the elements in results
for (int i = 0; i < resultList.size; i++) {
Renderable o2 = resultList.get(i);
//if not we compare by default to place before/after
int placement = normalcompare(o1, o2);
if (placement>0){
//after then we skip
//(we are waiting till we are either under something or at the end)
continue;
} else {
//before
resultList.insert(i, o1);
placementFound = true;
break; //break out the loop
}
}
//if no placement found we go at the end by default
if (!placementFound){
resultList.add(o1);
};
} //go back to check the next element in the incomeing list of renderables (that is, the copy we made at the start)
//done
}
//Copy of the default sorters compare function
//;
private Camera camera;
private final Vector3 tmpV1 = new Vector3();
private final Vector3 tmpV2 = new Vector3();
public int normalcompare (final Renderable o1, final Renderable o2) {
final boolean b1 = o1.material.has(BlendingAttribute.Type) && ((BlendingAttribute)o1.material.get(BlendingAttribute.Type)).blended;
final boolean b2 = o2.material.has(BlendingAttribute.Type) && ((BlendingAttribute)o2.material.get(BlendingAttribute.Type)).blended;
if (b1 != b2) return b1 ? 1 : -1;
// FIXME implement better sorting algorithm
// final boolean same = o1.shader == o2.shader && o1.mesh == o2.mesh && (o1.lights == null) == (o2.lights == null) &&
// o1.material.equals(o2.material);
o1.worldTransform.getTranslation(tmpV1);
o2.worldTransform.getTranslation(tmpV2);
final float dst = (int)(1000f * camera.position.dst2(tmpV1)) - (int)(1000f * camera.position.dst2(tmpV2));
final int result = dst < 0 ? -1 : (dst > 0 ? 1 : 0);
return b1 ? -result : result;
}
据我所知,我的customSorter函数产生了我想要的顺序 - 现在看来它们是按照正确的顺序绘制的。
然而,这似乎也是一个hackjob,我相信我的排序算法非常低效。
我想咨询如何;
a)改进我自己的算法,特别是在进行跨平台LibGDX开发时要记住的任何怪癖(即数组类型,关于android / web等的内存管理)
b)替代更有效的解决方案,具有与正常绘制顺序排序类似的“z索引覆盖”。
注释; 。分组是必要的。这是因为虽然事物在一组中相对于彼此牢固地被卡住,但是组本身也可以在彼此前面/后面移动。 (但不是之间)。这使得绘制顺序的“全局”覆盖变得棘手,而不是每个组的本地覆盖。
。如果它有帮助,我可以以任何方式添加/更改zindexattribute对象。
。我想某种方式“预先存储”数组中的每组对象可以帮助事情,但不是100%确定如何。
答案 0 :(得分:1)
首先,如果不需要,请不要复制列表。带有renderables的列表可能非常庞大,因为它也可能包含资源。复制将非常缓慢。如果你需要一些本地的东西并且需要性能,那么试着把它作为最终的,因为它可以提高性能。
因此,一个简单的方法是Java的默认排序。您需要为您的类实现一个Comperator,例如带有z索引的Class可能如下所示:
public class MyRenderable {
private float z_index;
public MyRenderable(float i)
{
z_index = i;
}
public float getZ_index() {
return z_index;
}
public void setZ_index(float z_index) {
this.z_index = z_index;
}
}
如果你想要更快的排序,因为你的列表在运行时不会改变那么多,你可以实现一个插入排序,因为如果列表是预先排序的那样,它可以做得更快。如果它没有预先排序,它确实需要更长的时间,但一般来说它应该只是第一个排序调用,在你的情况下它很多无序。
private void sortList(ArrayList<MyRenderable> array) {
// double starttime = System.nanoTime();
for (int i = 1; i < array.size(); i++) {
final MyRenderable temp = array.get(i);
int j = i - 1;
while (j >= 0 && array.get(j).getZ_index() < temp.getZ_index()) {
array.set(j + 1, array.get(j));
j--;
}
array.set(j + 1, temp);
}
// System.out.println("Time taken: " + (System.nanoTime() - starttime));
}
要使用此方法,只需使用数组
调用它即可sortList(renderbales);
在您的情况下,您需要处理没有Z索引的那些。也许你可以给他们一个0,因为他们会在正确的位置排序(我猜)。否则,您可以使用z情况下的给定方法和非z情况下的常规方法。
在评论中的对话之后。我不认为将所有内容都推入一个列表是个好主意。它很难排序,而且速度很慢。更好的方法是列出一组。由于您想拥有组,因此编程组。不要使用字符串名称,使用ID或类型(更容易排序,它并不重要)。所以一个简单的小组就是这样:
public class Group{
//think about privates and getters or methods to add things which also checks some conditions and so on
public int groupType;
public ArrayList<MyRenderable> renderables;
}
现在你所有的小组都进入了一个列表。 (这包含所有的渲染文件)
ArrayList<Group> allRenderables = new ArrayList<>();
最后但并非最不重要的是对组进行排序并对可渲染项进行排序。因为我不认为您的组ID /名称会在运行时更改,所以将它们排序一次,甚至使用SortedSet而不是ArrayList。但基本上整个排序看起来像这样:
for(Group g: allRenderables)
sortRenderables(g.renderables); //now every group is sorted
//now sort by group names
sortGroup(allRenderables);
使用如下所示的以下insertionsorts
public static void sortRenderables(ArrayList<MyRenderable> array) {
for (int i = 1; i < array.size(); i++) {
final MyRenderable temp = array.get(i);
int j = i - 1;
while (j >= 0 && array.get(j).getZ_index() < temp.getZ_index()) {
array.set(j + 1, array.get(j));
j--;
}
array.set(j + 1, temp);
}
}
public static void sortGroup(ArrayList<Group> array) {
for (int i = 1; i < array.size(); i++) {
final Group temp = array.get(i);
int j = i - 1;
while (j >= 0 && array.get(j).groupType < temp.groupType) {
array.set(j + 1, array.get(j));
j--;
}
array.set(j + 1, temp);
}
}