From 2884e4b8479968531d4839b95e825279892f0be1 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 20 Jun 2018 15:06:53 -0400 Subject: [PATCH] Improvements to playability at low TPS --- .../src/io/anuke/mindustry/core/Renderer.java | 95 ++++++++++++++----- .../anuke/mindustry/core/ThreadHandler.java | 10 +- .../io/anuke/mindustry/entities/Player.java | 18 ++-- .../src/io/anuke/mindustry/entities/Unit.java | 10 ++ .../mindustry/entities/bullet/Bullet.java | 12 ++- .../mindustry/entities/units/BaseUnit.java | 15 +-- 6 files changed, 114 insertions(+), 46 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index 8283ca8291..216b744551 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -6,10 +6,7 @@ import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture.TextureWrap; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.FloatArray; -import com.badlogic.gdx.utils.ObjectIntMap; -import com.badlogic.gdx.utils.Pools; +import com.badlogic.gdx.utils.*; import io.anuke.mindustry.content.fx.Fx; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; @@ -20,8 +17,8 @@ import io.anuke.mindustry.entities.traits.BelowLiquidTrait; import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.graphics.*; -import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.ucore.core.Core; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Graphics; @@ -30,7 +27,11 @@ import io.anuke.ucore.entities.EntityDraw; import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.impl.BaseEntity; import io.anuke.ucore.entities.impl.EffectEntity; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.entities.trait.SolidTrait; import io.anuke.ucore.function.Callable; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.function.Predicate; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Hue; import io.anuke.ucore.graphics.Lines; @@ -38,6 +39,7 @@ import io.anuke.ucore.graphics.Surface; import io.anuke.ucore.modules.RendererModule; import io.anuke.ucore.scene.utils.Cursors; import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Translator; import static io.anuke.mindustry.Vars.*; import static io.anuke.ucore.core.Core.batch; @@ -51,7 +53,9 @@ public class Renderer extends RendererModule{ private FloatArray shieldHits = new FloatArray(); private Array shieldDraws = new Array<>(); private Rectangle rect = new Rectangle(), rect2 = new Rectangle(); - private Vector2 avgPosition = new Vector2(); + private Vector2 avgPosition = new Translator(); + private Vector2 tmpVector1 = new Translator(); + private Vector2 tmpVector2 = new Translator(); private BlockRenderer blocks = new BlockRenderer(); private MinimapRenderer minimap = new MinimapRenderer(); private OverlayRenderer overlays = new OverlayRenderer(); @@ -196,9 +200,9 @@ public class Renderer extends RendererModule{ blocks.drawFloor(); - EntityDraw.draw(groundEffectGroup, e -> e instanceof BelowLiquidTrait); - EntityDraw.draw(puddleGroup); - EntityDraw.draw(groundEffectGroup, e -> !(e instanceof BelowLiquidTrait)); + drawAndInterpolate(groundEffectGroup, e -> e instanceof BelowLiquidTrait); + drawAndInterpolate(puddleGroup); + drawAndInterpolate(groundEffectGroup, e -> !(e instanceof BelowLiquidTrait)); blocks.processBlocks(); blocks.drawBlocks(Layer.block); @@ -213,7 +217,7 @@ public class Renderer extends RendererModule{ Shaders.outline.color.set(Team.none.color); Graphics.beginShaders(Shaders.outline); - EntityDraw.draw(itemGroup); + drawAndInterpolate(itemGroup); Graphics.endShaders(); } @@ -224,12 +228,12 @@ public class Renderer extends RendererModule{ overlays.drawBottom(); - EntityDraw.drawWith(playerGroup, p -> true, Player::drawBuildRequests); + drawAndInterpolate(playerGroup, p -> true, Player::drawBuildRequests); drawAllTeams(true); - EntityDraw.draw(bulletGroup); - EntityDraw.draw(effectGroup); + drawAndInterpolate(bulletGroup); + drawAndInterpolate(effectGroup); overlays.drawTop(); @@ -238,7 +242,7 @@ public class Renderer extends RendererModule{ if(showPaths && debug) drawDebug(); - EntityDraw.drawWith(playerGroup, p -> !p.isLocal && !p.isDead(), Player::drawName); + drawAndInterpolate(playerGroup, p -> !p.isLocal && !p.isDead(), Player::drawName); batch.end(); } @@ -250,25 +254,66 @@ public class Renderer extends RendererModule{ if(group.count(p -> p.isFlying() == flying) + playerGroup.count(p -> p.isFlying() == flying && p.getTeam() == team) == 0 && flying) continue; - EntityDraw.drawWith(unitGroups[team.ordinal()], u -> u.isFlying() == flying, Unit::drawUnder); - EntityDraw.drawWith(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawUnder); + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying, Unit::drawUnder); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawUnder); Shaders.outline.color.set(team.color); Shaders.mix.color.set(Color.WHITE); Graphics.beginShaders(Shaders.outline); Graphics.shader(Shaders.mix, true); - EntityDraw.draw(unitGroups[team.ordinal()], u -> u.isFlying() == flying); - EntityDraw.draw(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team); + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team); Graphics.shader(); blocks.drawTeamBlocks(Layer.turret, team); Graphics.endShaders(); - EntityDraw.drawWith(unitGroups[team.ordinal()], u -> u.isFlying() == flying, Unit::drawOver); - EntityDraw.drawWith(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver); + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying, Unit::drawOver); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver); } } + private void drawAndInterpolate(EntityGroup group){ + drawAndInterpolate(group, t -> true, DrawTrait::draw); + } + + private void drawAndInterpolate(EntityGroup group, Predicate toDraw){ + drawAndInterpolate(group, toDraw, DrawTrait::draw); + } + + private void drawAndInterpolate(EntityGroup group, Predicate toDraw, Consumer drawer){ + EntityDraw.drawWith(group, toDraw, t -> { + float lastx = t.getX(), lasty = t.getY(), lastrot = 0f; + + if(threads.doInterpolate() && threads.isEnabled() && t instanceof SolidTrait){ + SolidTrait s = (SolidTrait)t; + + lastrot = s.getRotation(); + + if(s.lastUpdated() != 0){ + float timeSinceUpdate = TimeUtils.timeSinceMillis(s.lastUpdated()); + float alpha = Math.min(timeSinceUpdate / s.updateSpacing(), 1f); + + tmpVector1.set(s.lastPosition().x, s.lastPosition().y) + .lerp(tmpVector2.set(lastx, lasty), alpha); + s.setRotation(Mathf.slerp(s.lastPosition().z, lastrot, alpha)); + + s.set(tmpVector1.x, tmpVector1.y); + } + } + + drawer.accept(t); + + if(threads.doInterpolate() && threads.isEnabled()) { + t.set(lastx, lasty); + + if (t instanceof SolidTrait) { + ((SolidTrait) t).setRotation(lastrot); + } + } + }); + } + @Override public void resize(int width, int height){ super.resize(width, height); @@ -284,10 +329,12 @@ public class Renderer extends RendererModule{ } public Vector2 averagePosition(){ - avgPosition.setZero(); - for(Player player : players){ - avgPosition.add(player.x, player.y); - } + avgPosition.setZero(); + + drawAndInterpolate(playerGroup, p -> p.isLocal, p -> { + avgPosition.add(p.x, p.y); + }); + avgPosition.scl(1f / players.length); return avgPosition; } diff --git a/core/src/io/anuke/mindustry/core/ThreadHandler.java b/core/src/io/anuke/mindustry/core/ThreadHandler.java index 2c16f19007..0f35d0bc53 100644 --- a/core/src/io/anuke/mindustry/core/ThreadHandler.java +++ b/core/src/io/anuke/mindustry/core/ThreadHandler.java @@ -111,6 +111,10 @@ public class ThreadHandler { return enabled; } + public boolean doInterpolate(){ + return enabled && Math.abs(Gdx.graphics.getFramesPerSecond() - getFPS()) > 15; + } + public boolean isOnThread(){ return impl.isOnThread(); } @@ -118,7 +122,7 @@ public class ThreadHandler { private void runLogic(){ try { while (true) { - long time = TimeUtils.millis(); + long time = TimeUtils.nanoTime(); synchronized (toRun) { for(Runnable r : toRun){ @@ -131,8 +135,8 @@ public class ThreadHandler { logic.update(); logic.doUpdate = false; - long elapsed = TimeUtils.timeSinceMillis(time); - long target = (long) (1000 / 60f); + long elapsed = TimeUtils.nanosToMillis(TimeUtils.timeSinceNanos(time)); + long target = (long) ((1000) / 60f); delta = Math.max(elapsed, target) / 1000f * 60f; diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 4ebe91ef01..52bbf7d47d 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -41,8 +41,6 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; public class Player extends Unit implements BuilderTrait, CarryTrait { - private static final Vector2 movement = new Vector2(); - public static int typeID = -1; public static final int timerShootLeft = 0; @@ -73,6 +71,8 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { private Tile mining; private CarriableTrait carrying; private Trail trail = new Trail(16); + private Vector2 movement = new Vector2(); + private boolean moved; public Player(){ hitbox.setSize(5); @@ -242,13 +242,18 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { @Override public float drawSize() { - return 40; + return isLocal ? Float.MAX_VALUE : 40; } @Override public void draw(){ if((debug && (!showPlayer || !showUI)) || dead) return; + if(!movement.isZero() && moved){ + walktime += Timers.delta() * movement.len()/1.6f * getFloorOn().speedMultiplier; + baseRotation = Mathf.slerpDelta(baseRotation, movement.angle(), 0.13f); + } + boolean snap = snapCamera && isLocal; float px = x, py =y; @@ -491,12 +496,9 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { velocity.add(movement); + float prex = x, prey = y; updateVelocityStatus(mech.drag, debug ? speed : mech.maxSpeed); - - if(!movement.isZero()){ - walktime += Timers.delta() * velocity.len()*(1f/0.5f)/speed * getFloorOn().speedMultiplier; - baseRotation = Mathf.slerpDelta(baseRotation, movement.angle(), 0.13f); - } + moved = distanceTo(prex, prey) > 0.01f; if(!isShooting()){ if(!movement.isZero()) { diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index 1c8e73108e..ef77f766c0 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -51,6 +51,16 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ protected float hitTime; protected float drownTime; + @Override + public float getRotation() { + return rotation; + } + + @Override + public void setRotation(float rotation) { + this.rotation = rotation; + } + @Override public void setCarrier(CarryTrait carrier) { this.carrier = carrier; diff --git a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java index 73dc5d95ee..5a227e4de0 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java +++ b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.entities.bullet; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Pools; +import com.badlogic.gdx.utils.TimeUtils; import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.entities.Unit; @@ -10,6 +11,7 @@ import io.anuke.mindustry.entities.traits.TeamTrait; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.net.In; import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.impl.BulletEntity; import io.anuke.ucore.entities.trait.Entity; @@ -59,7 +61,15 @@ public class Bullet extends BulletEntity implements TeamTrait, SyncT bullet.team = team; bullet.type = type; - bullet.set(x, y); + + //translate bullets backwards, purely for visual reasons + float backDelta = Timers.delta(); + + bullet.lastPosition().set(x-bullet.velocity.x * backDelta, y-bullet.velocity.y * backDelta, bullet.angle()); + bullet.setLastUpdated(TimeUtils.millis()); + bullet.setUpdateSpacing((long)((Timers.delta() / 60f) * 1000)); + bullet.set(x-bullet.velocity.x * backDelta, y-bullet.velocity.y * backDelta); + bullet.add(); } diff --git a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java index 7c42bb56d7..2243e14f6e 100644 --- a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java @@ -1,12 +1,10 @@ package io.anuke.mindustry.entities.units; -import com.badlogic.gdx.math.Vector2; import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.content.fx.ExplosionFx; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.Unit; -import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.entities.bullet.Bullet; import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.game.Team; @@ -20,7 +18,10 @@ import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.EntityGroup; -import io.anuke.ucore.util.*; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Timer; import java.io.DataInput; import java.io.DataOutput; @@ -30,7 +31,6 @@ import static io.anuke.mindustry.Vars.*; public abstract class BaseUnit extends Unit{ private static int timerIndex = 0; - private static Vector2 moveVector = new Translator(); protected static final int timerTarget = timerIndex++; protected static final int timerReload = timerIndex++; @@ -113,7 +113,7 @@ public abstract class BaseUnit extends Unit{ public void targetClosest(){ if(target != null) return; - target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); + //target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); } public UnitState getStartState(){ @@ -236,11 +236,6 @@ public abstract class BaseUnit extends Unit{ CallEntity.onUnitDeath(this); } - @Override - public boolean collidesOthers() { - return !isFlying(); - } - @Override public void added(){ hitbox.setSize(type.hitsize);