Spread chunk updates over several frames
- At most 1 chunk is updated (previously known as 'buildModel') every frame - 3x3x3 chunks closest to the player are updated in real-time without the aforementioned limit
This commit is contained in:
parent
5d570a810b
commit
c9087e7215
@ -19,7 +19,7 @@ public class Client {
|
||||
private final ServerCommsChannel comms;
|
||||
|
||||
public Client(WorldData world, ServerCommsChannel comms) {
|
||||
this.world = new WorldRender(world);
|
||||
this.world = new WorldRender(world, this);
|
||||
this.comms = comms;
|
||||
|
||||
comms.addListener(new DefaultClientCommsListener(this));
|
||||
|
@ -59,7 +59,6 @@ implements GenericChunk<
|
||||
private final WorldRender world;
|
||||
private final ChunkData data;
|
||||
|
||||
private boolean needsUpdate;
|
||||
private Model model = null;
|
||||
|
||||
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists =
|
||||
@ -108,16 +107,12 @@ implements GenericChunk<
|
||||
}
|
||||
|
||||
public synchronized void markForUpdate() {
|
||||
this.needsUpdate = true;
|
||||
}
|
||||
|
||||
public synchronized boolean needsUpdate() {
|
||||
return needsUpdate;
|
||||
getWorld().markChunkForUpdate(getPosition());
|
||||
}
|
||||
|
||||
public synchronized void render(ShapeRenderHelper renderer) {
|
||||
if (model == null || needsUpdate()) {
|
||||
buildModel();
|
||||
if (model == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.pushTransform().translate(
|
||||
@ -131,7 +126,7 @@ implements GenericChunk<
|
||||
renderer.popTransform();
|
||||
}
|
||||
|
||||
private void buildModel() {
|
||||
public synchronized void update() {
|
||||
Collection<ChunkRenderOptimizer> optimizers =
|
||||
ChunkRenderOptimizers.getAllSuppliers().stream()
|
||||
.map(ChunkRenderOptimizerSupplier::createOptimizer)
|
||||
@ -159,7 +154,6 @@ implements GenericChunk<
|
||||
.forEach(builder::addPart);
|
||||
|
||||
model = new StaticModel(builder);
|
||||
needsUpdate = false;
|
||||
}
|
||||
|
||||
private void buildBlock(
|
||||
|
@ -20,10 +20,12 @@ package ru.windcorp.progressia.client.world;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import ru.windcorp.progressia.client.Client;
|
||||
import ru.windcorp.progressia.client.graphics.backend.FaceCulling;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||
import ru.windcorp.progressia.client.world.block.BlockRender;
|
||||
@ -31,11 +33,14 @@ import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
|
||||
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRender;
|
||||
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
|
||||
import ru.windcorp.progressia.common.util.VectorUtil;
|
||||
import ru.windcorp.progressia.common.world.ChunkData;
|
||||
import ru.windcorp.progressia.common.world.ChunkDataListeners;
|
||||
import ru.windcorp.progressia.common.world.WorldData;
|
||||
import ru.windcorp.progressia.common.world.WorldDataListener;
|
||||
import ru.windcorp.progressia.common.world.entity.EntityData;
|
||||
import ru.windcorp.progressia.common.world.generic.ChunkSet;
|
||||
import ru.windcorp.progressia.common.world.generic.ChunkSets;
|
||||
import ru.windcorp.progressia.common.world.generic.GenericWorld;
|
||||
|
||||
public class WorldRender
|
||||
@ -46,35 +51,52 @@ implements GenericWorld<
|
||||
ChunkRender,
|
||||
EntityRenderable
|
||||
> {
|
||||
|
||||
|
||||
private final WorldData data;
|
||||
private final Client client;
|
||||
|
||||
private final Map<ChunkData, ChunkRender> chunks =
|
||||
Collections.synchronizedMap(new HashMap<>());
|
||||
private final Map<EntityData, EntityRenderable> entityModels =
|
||||
Collections.synchronizedMap(new WeakHashMap<>());
|
||||
|
||||
public WorldRender(WorldData data) {
|
||||
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
|
||||
|
||||
public WorldRender(WorldData data, Client client) {
|
||||
this.data = data;
|
||||
this.client = client;
|
||||
|
||||
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
|
||||
data.addListener(new WorldDataListener() {
|
||||
@Override
|
||||
public void onChunkLoaded(WorldData world, ChunkData chunk) {
|
||||
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
|
||||
addChunk(chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
|
||||
chunks.remove(chunk);
|
||||
removeChunk(chunk);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void addChunk(ChunkData chunk) {
|
||||
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
|
||||
markChunkForUpdate(chunk.getPosition());
|
||||
}
|
||||
|
||||
protected void removeChunk(ChunkData chunk) {
|
||||
chunks.remove(chunk);
|
||||
}
|
||||
|
||||
public WorldData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public ChunkRender getChunk(ChunkData chunkData) {
|
||||
return chunks.get(chunkData);
|
||||
}
|
||||
@ -95,10 +117,67 @@ implements GenericWorld<
|
||||
}
|
||||
|
||||
public void render(ShapeRenderHelper renderer) {
|
||||
updateChunks();
|
||||
|
||||
getChunks().forEach(chunk -> chunk.render(renderer));
|
||||
renderEntities(renderer);
|
||||
}
|
||||
|
||||
private void updateChunks() {
|
||||
synchronized (chunksToUpdate) {
|
||||
if (chunksToUpdate.isEmpty()) return;
|
||||
|
||||
int updates = updateChunksNearLocalPlayer();
|
||||
int maximumUpdates = getMaximumChunkUpdatesPerFrame();
|
||||
|
||||
updateRandomChunks(maximumUpdates - updates);
|
||||
}
|
||||
}
|
||||
|
||||
private int updateChunksNearLocalPlayer() {
|
||||
EntityData entity = getClient().getLocalPlayer().getEntity();
|
||||
if (entity == null) return 0;
|
||||
|
||||
int[] updates = new int[] { 0 };
|
||||
|
||||
VectorUtil.iterateCuboidAround(entity.getChunkCoords(null), 3, chunkPos -> {
|
||||
if (chunksToUpdate.contains(chunkPos)) {
|
||||
getChunk(chunkPos).update();
|
||||
chunksToUpdate.remove(chunkPos);
|
||||
|
||||
updates[0]++;
|
||||
}
|
||||
});
|
||||
|
||||
return updates[0];
|
||||
}
|
||||
|
||||
private void updateRandomChunks(int allowedUpdates) {
|
||||
if (allowedUpdates <= 0) return;
|
||||
|
||||
for (Iterator<Vec3i> it = chunksToUpdate.iterator(); it.hasNext();) {
|
||||
Vec3i chunkPos = it.next();
|
||||
ChunkRender chunk = getChunk(chunkPos);
|
||||
|
||||
if (chunk != null) {
|
||||
chunk.update();
|
||||
allowedUpdates--;
|
||||
}
|
||||
|
||||
it.remove();
|
||||
|
||||
if (allowedUpdates <= 0) return;
|
||||
}
|
||||
}
|
||||
|
||||
private int getMaximumChunkUpdatesPerFrame() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int getPendingChunkUpdates() {
|
||||
return chunksToUpdate.size();
|
||||
}
|
||||
|
||||
private void renderEntities(ShapeRenderHelper renderer) {
|
||||
FaceCulling.push(false);
|
||||
|
||||
@ -122,5 +201,11 @@ implements GenericWorld<
|
||||
return EntityRenderRegistry.getInstance().get(entity.getId())
|
||||
.createRenderable(entity);
|
||||
}
|
||||
|
||||
public void markChunkForUpdate(Vec3i chunkPos) {
|
||||
if (getData().getChunk(chunkPos) != null) {
|
||||
chunksToUpdate.add(chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public class VectorUtil {
|
||||
X, Y, Z, W;
|
||||
}
|
||||
|
||||
public static void forEachVectorInCuboid(
|
||||
public static void iterateCuboid(
|
||||
int x0, int y0, int z0,
|
||||
int x1, int y1, int z1,
|
||||
Consumer<? super Vec3i> action
|
||||
@ -38,6 +38,57 @@ public class VectorUtil {
|
||||
Vectors.release(cursor);
|
||||
}
|
||||
|
||||
public static void iterateCuboid(
|
||||
Vec3i vMin, Vec3i vMax,
|
||||
Consumer<? super Vec3i> action
|
||||
) {
|
||||
iterateCuboid(vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, action);
|
||||
}
|
||||
|
||||
public static void iterateCuboidAround(
|
||||
int cx, int cy, int cz,
|
||||
int dx, int dy, int dz,
|
||||
Consumer<? super Vec3i> action
|
||||
) {
|
||||
if (dx < 0) throw new IllegalArgumentException("dx " + dx + " is negative");
|
||||
if (dy < 0) throw new IllegalArgumentException("dy " + dx + " is negative");
|
||||
if (dz < 0) throw new IllegalArgumentException("dz " + dx + " is negative");
|
||||
|
||||
if (dx % 2 == 0) throw new IllegalArgumentException("dx " + dx + " is even, only odd accepted");
|
||||
if (dy % 2 == 0) throw new IllegalArgumentException("dy " + dy + " is even, only odd accepted");
|
||||
if (dz % 2 == 0) throw new IllegalArgumentException("dz " + dz + " is even, only odd accepted");
|
||||
|
||||
dx /= 2;
|
||||
dy /= 2;
|
||||
dz /= 2;
|
||||
|
||||
iterateCuboid(cx - dx, cy - dy, cz - dz, cx + dx + 1, cy + dy + 1, cz + dz + 1, action);
|
||||
}
|
||||
|
||||
public static void iterateCuboidAround(
|
||||
Vec3i center,
|
||||
Vec3i diameters,
|
||||
Consumer<? super Vec3i> action
|
||||
) {
|
||||
iterateCuboidAround(center.x, center.y, center.z, diameters.x, diameters.y, diameters.z, action);
|
||||
}
|
||||
|
||||
public static void iterateCuboidAround(
|
||||
int cx, int cy, int cz,
|
||||
int diameter,
|
||||
Consumer<? super Vec3i> action
|
||||
) {
|
||||
iterateCuboidAround(cx, cy, cz, diameter, diameter, diameter, action);
|
||||
}
|
||||
|
||||
public static void iterateCuboidAround(
|
||||
Vec3i center,
|
||||
int diameter,
|
||||
Consumer<? super Vec3i> action
|
||||
) {
|
||||
iterateCuboidAround(center.x, center.y, center.z, diameter, action);
|
||||
}
|
||||
|
||||
public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) {
|
||||
Vec4 vec4 = Vectors.grab4();
|
||||
vec4.set(in, 1f);
|
||||
|
@ -183,7 +183,7 @@ implements GenericChunk<
|
||||
}
|
||||
|
||||
public void forEachBlock(Consumer<Vec3i> action) {
|
||||
VectorUtil.forEachVectorInCuboid(
|
||||
VectorUtil.iterateCuboid(
|
||||
0, 0, 0,
|
||||
BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK,
|
||||
action
|
||||
|
@ -2,9 +2,11 @@ package ru.windcorp.progressia.common.world.generic;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import glm.vec._3.i.Vec3i;
|
||||
import gnu.trove.impl.sync.TSynchronizedLongSet;
|
||||
import gnu.trove.set.hash.TLongHashSet;
|
||||
|
||||
public class ChunkSets {
|
||||
@ -14,13 +16,19 @@ public class ChunkSets {
|
||||
}
|
||||
|
||||
public static ChunkSet newSyncHashSet(Object mutex) {
|
||||
return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet(), mutex));
|
||||
return new SynchronizedChunkSet(new LongBasedChunkSet(new TLongHashSet()), mutex);
|
||||
}
|
||||
|
||||
public static ChunkSet newSyncHashSet() {
|
||||
return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet()));
|
||||
return newSyncHashSet(null);
|
||||
}
|
||||
|
||||
public static ChunkSet empty() {
|
||||
return EMPTY_SET;
|
||||
}
|
||||
|
||||
private ChunkSets() {}
|
||||
|
||||
private final static ChunkSet EMPTY_SET = new ChunkSet() {
|
||||
|
||||
@Override
|
||||
@ -89,10 +97,174 @@ public class ChunkSets {
|
||||
|
||||
};
|
||||
|
||||
public static ChunkSet empty() {
|
||||
return EMPTY_SET;
|
||||
private static class SynchronizedChunkSet implements ChunkSet {
|
||||
|
||||
private final ChunkSet parent;
|
||||
private final Object mutex;
|
||||
|
||||
public SynchronizedChunkSet(ChunkSet parent, Object mutex) {
|
||||
Objects.requireNonNull(parent, "parent");
|
||||
this.parent = parent;
|
||||
|
||||
this.mutex = mutex == null ? this : mutex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Vec3i> iterator() {
|
||||
return parent.iterator(); // Must be synchronized manually by user!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super Vec3i> action) {
|
||||
synchronized (mutex) { parent.forEach(action); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
synchronized (mutex) { return parent.size(); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
synchronized (mutex) { return parent.isEmpty(); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Vec3i pos) {
|
||||
synchronized (mutex) { return parent.contains(pos); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Vec3i pos) {
|
||||
synchronized (mutex) { return parent.add(pos); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Vec3i pos) {
|
||||
synchronized (mutex) { return parent.remove(pos); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(int x, int y, int z) {
|
||||
synchronized (mutex) { return parent.contains(x, y, z); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(int x, int y, int z) {
|
||||
synchronized (mutex) { return parent.add(x, y, z); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(int x, int y, int z) {
|
||||
synchronized (mutex) { return parent.remove(x, y, z); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(GenericChunk<?, ?, ?, ?> chunk) {
|
||||
synchronized (mutex) { return parent.contains(chunk); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(GenericChunk<?, ?, ?, ?> chunk) {
|
||||
synchronized (mutex) { return parent.add(chunk); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(GenericChunk<?, ?, ?, ?> chunk) {
|
||||
synchronized (mutex) { return parent.remove(chunk); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C extends GenericChunk<C, ?, ?, ?>> void forEachIn(GenericWorld<?, ?, ?, C, ?> world,
|
||||
Consumer<? super C> action) {
|
||||
synchronized (mutex) { parent.forEachIn(world, action); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(ChunkSet other) {
|
||||
synchronized (mutex) { return parent.containsAll(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAny(ChunkSet other) {
|
||||
synchronized (mutex) { return parent.containsAny(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(ChunkSet other) {
|
||||
synchronized (mutex) { parent.addAll(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(ChunkSet other) {
|
||||
synchronized (mutex) { parent.removeAll(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void retainAll(ChunkSet other) {
|
||||
synchronized (mutex) { parent.retainAll(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
synchronized (mutex) { parent.clear(); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Iterable<? extends Vec3i> other) {
|
||||
synchronized (mutex) { return parent.containsAll(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAny(Iterable<? extends Vec3i> other) {
|
||||
synchronized (mutex) { return parent.containsAny(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(Iterable<? extends Vec3i> other) {
|
||||
synchronized (mutex) { parent.addAll(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Iterable<? extends Vec3i> other) {
|
||||
synchronized (mutex) { parent.removeAll(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void retainAll(Iterable<? extends Vec3i> other) {
|
||||
synchronized (mutex) { parent.retainAll(other); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeIf(Predicate<? super Vec3i> condition) {
|
||||
synchronized (mutex) { parent.removeIf(condition); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void retainIf(Predicate<? super Vec3i> condition) {
|
||||
synchronized (mutex) { parent.retainIf(condition); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
|
||||
synchronized (mutex) { return parent.containsAllChunks(chunks); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAnyChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
|
||||
synchronized (mutex) { return parent.containsAnyChunks(chunks); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
|
||||
synchronized (mutex) { parent.addAllChunks(chunks); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) {
|
||||
synchronized (mutex) { parent.removeAllChunks(chunks); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ChunkSets() {}
|
||||
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public interface GenericChunk<
|
||||
}
|
||||
|
||||
default void forEachBiC(Consumer<? super Vec3i> action) {
|
||||
VectorUtil.forEachVectorInCuboid(
|
||||
VectorUtil.iterateCuboid(
|
||||
0, 0, 0,
|
||||
BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK,
|
||||
action
|
||||
@ -63,7 +63,7 @@ public interface GenericChunk<
|
||||
}
|
||||
|
||||
default void forEachBiW(Consumer<? super Vec3i> action) {
|
||||
VectorUtil.forEachVectorInCuboid(
|
||||
VectorUtil.iterateCuboid(
|
||||
Coordinates.getInWorld(getX(), 0),
|
||||
Coordinates.getInWorld(getY(), 0),
|
||||
Coordinates.getInWorld(getZ(), 0),
|
||||
|
@ -112,49 +112,6 @@ public class TickAndUpdateUtil {
|
||||
tickEntity(EntityLogicRegistry.getInstance().get(data.getId()), data, TickContextMutable.start().withServer(server).build());
|
||||
}
|
||||
|
||||
// public static BlockTickContext getBlockTickContext(
|
||||
// Server server,
|
||||
// Vec3i blockInWorld
|
||||
// ) {
|
||||
// MutableBlockTickContext result = new MutableBlockTickContext();
|
||||
// result.init(server, blockInWorld);
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// public static TileTickContext getTileTickContext(
|
||||
// Server server,
|
||||
// Vec3i blockInWorld,
|
||||
// BlockFace face,
|
||||
// int layer
|
||||
// ) {
|
||||
// MutableTileTickContext result = new MutableTileTickContext();
|
||||
// result.init(server, blockInWorld, face, layer);
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// public static TileTickContext getTileTickContext(
|
||||
// Server server,
|
||||
// TileDataStack stack,
|
||||
// int index
|
||||
// ) {
|
||||
// MutableTileTickContext result = new MutableTileTickContext();
|
||||
// result.init(server, stack, index);
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// public static TileTickContext getTileTickContext(
|
||||
// Server server,
|
||||
// TileReference ref
|
||||
// ) {
|
||||
// MutableTileTickContext result = new MutableTileTickContext();
|
||||
// result.init(server, ref);
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// public static TickContext getTickContext(Server server) {
|
||||
// return getBlockTickContext(server, null);
|
||||
// }
|
||||
|
||||
private TickAndUpdateUtil() {}
|
||||
|
||||
}
|
||||
|
@ -73,6 +73,12 @@ public class LayerTestGUI extends GUILayer {
|
||||
128
|
||||
));
|
||||
|
||||
panel.addChild(new DynamicLabel(
|
||||
"ChunkUpdatesDisplay", new Font().withColor(0x37A3E6).deriveShadow(),
|
||||
() -> "Pending updates: " + Integer.toString(ClientState.getInstance().getWorld().getPendingChunkUpdates()),
|
||||
128
|
||||
));
|
||||
|
||||
panel.getChildren().forEach(c -> {
|
||||
if (c instanceof Label) {
|
||||
labels.add((Label) c);
|
||||
|
@ -29,8 +29,8 @@ public class TestPlayerControls {
|
||||
|
||||
private TestPlayerControls() {}
|
||||
|
||||
private static final double MODE_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS;
|
||||
private static final double MIN_JUMP_DELAY = 200 * Units.MILLISECONDS;
|
||||
private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS;
|
||||
private static final double MIN_JUMP_DELAY = 400 * Units.MILLISECONDS;
|
||||
|
||||
// Horizontal and vertical max control speed when flying
|
||||
private static final float FLYING_SPEED = 6.0f * Units.METERS_PER_SECOND;
|
||||
|
Reference in New Issue
Block a user