По сути, я хочу, чтобы камера не двигалась в субпикселях, так как я думаю, что это приводит к тому, что спрайты заметно меняют свои размеры, хотя бы немного. (Есть ли лучший термин для этого?) Обратите внимание, что это пиксельная игра, в которой я хочу иметь четкую пиксельную графику. Вот рисунок, который показывает проблему:
Теперь я попытался сделать следующее: переместить камеру, спроецировать текущую позицию (так, чтобы это были координаты экрана), а затем округлить или привести к int. После этого конвертируйте его обратно в мировые координаты и используйте его в качестве новой позиции камеры. Насколько я знаю, это должно привязывать камеру к фактическим координатам экрана, а не к их долям.
Однако по какой-то причине y
ценность новой позиции просто взрывается. В считанные секунды он увеличивается до чего-то вроде 334756315000
.
Вот SSCCE (или MCVE), основанный на коде вики LibGDX :
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
public class PixelMoveCameraTest implements ApplicationListener {
static final int WORLD_WIDTH = 100;
static final int WORLD_HEIGHT = 100;
private OrthographicCamera cam;
private SpriteBatch batch;
private Sprite mapSprite;
private float rotationSpeed;
private Viewport viewport;
private Sprite playerSprite;
private Vector3 newCamPosition;
@Override
public void create() {
rotationSpeed = 0.5f;
playerSprite = new Sprite(new Texture("/path/to/dungeon_guy.png"));
playerSprite.setSize(1f, 1f);
mapSprite = new Sprite(new Texture("/path/to/sc_map.jpg"));
mapSprite.setPosition(0, 0);
mapSprite.setSize(WORLD_WIDTH, WORLD_HEIGHT);
float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight();
// Constructs a new OrthographicCamera, using the given viewport width and height
// Height is multiplied by aspect ratio.
cam = new OrthographicCamera();
cam.position.set(0, 0, 0);
cam.update();
newCamPosition = cam.position.cpy();
viewport = new ExtendViewport(32, 20, cam);
batch = new SpriteBatch();
}
@Override
public void render() {
handleInput();
cam.update();
batch.setProjectionMatrix(cam.combined);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
mapSprite.draw(batch);
playerSprite.draw(batch);
batch.end();
}
private static float MOVEMENT_SPEED = 0.2f;
private void handleInput() {
if (Gdx.input.isKeyPressed(Input.Keys.A)) {
cam.zoom += 0.02;
}
if (Gdx.input.isKeyPressed(Input.Keys.Q)) {
cam.zoom -= 0.02;
}
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
newCamPosition.add(-MOVEMENT_SPEED, 0, 0);
}
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
newCamPosition.add(MOVEMENT_SPEED, 0, 0);
}
if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
newCamPosition.add(0, -MOVEMENT_SPEED, 0);
}
if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
newCamPosition.add(0, MOVEMENT_SPEED, 0);
}
if (Gdx.input.isKeyPressed(Input.Keys.W)) {
cam.rotate(-rotationSpeed, 0, 0, 1);
}
if (Gdx.input.isKeyPressed(Input.Keys.E)) {
cam.rotate(rotationSpeed, 0, 0, 1);
}
cam.zoom = MathUtils.clamp(cam.zoom, 0.1f, 100 / cam.viewportWidth);
float effectiveViewportWidth = cam.viewportWidth * cam.zoom;
float effectiveViewportHeight = cam.viewportHeight * cam.zoom;
cam.position.lerp(newCamPosition, 0.02f);
cam.position.x = MathUtils.clamp(cam.position.x,
effectiveViewportWidth / 2f, 100 - effectiveViewportWidth / 2f);
cam.position.y = MathUtils.clamp(cam.position.y,
effectiveViewportHeight / 2f, 100 - effectiveViewportHeight / 2f);
// if this is false, the "bug" (y increasing a lot) doesn't appear
if (true) {
Vector3 v = viewport.project(cam.position.cpy());
System.out.println(v);
v = viewport.unproject(new Vector3((int) v.x, (int) v.y, v.z));
cam.position.set(v);
}
playerSprite.setPosition(newCamPosition.x, newCamPosition.y);
}
@Override
public void resize(int width, int height) {
viewport.update(width, height);
}
@Override
public void resume() {
}
@Override
public void dispose() {
mapSprite.getTexture().dispose();
batch.dispose();
}
@Override
public void pause() {
}
public static void main(String[] args) {
new Lwjgl3Application(new PixelMoveCameraTest(), new Lwjgl3ApplicationConfiguration());
}
}
и вот иsc_map.jpg
dungeon_guy.png
Мне также было бы интересно узнать о более простых и / или лучших способах решения этой проблемы.
Ответы:
Ваша проблема не в перемещении камеры с шагом в полпикселя. Дело в том, что ваше отношение текселей к пикселям немного нецелое . Я возьму несколько примеров из этого похожего вопроса, на который я ответил на StackExchange .
Вот две копии Марио - обе движутся по экрану с одинаковой скоростью (либо спрайтами, движущимися вправо в мире, либо камерой, движущейся влево - это заканчивается эквивалентно), но только верхняя показывает эти рябь артефакты и нижний не:
Причина в том, что я немного увеличил верхнюю часть Mario в 1 раз в 1,01 раза - эта лишняя доля означает, что он больше не совпадает с пиксельной сеткой экрана.
Небольшой переводческий смещение не является проблемой - «Ближайшая» фильтрация текстур все равно будет привязана к ближайшему текселю, и мы ничего особенного не сделаем.
Но масштаб несоответствие означает, что этот щелчок не всегда в одном направлении. В одном месте пиксель, ищущий ближайший тексель, выберет один немного вправо, в то время как другой пиксель выберет один немного влево - и теперь столбец текселей либо пропущен, либо дублирован между ними, создавая рябь или мерцание, которые перемещается по спрайту по мере продвижения по экрану.
Вот поближе. Я анимировал полупрозрачный грибной спрайт, движущийся плавно, как если бы мы могли рендерить его с неограниченным субпиксельным разрешением. Затем я наложил пиксельную сетку, используя выборку ближайшего соседа. Весь пиксель меняет цвет, чтобы соответствовать части спрайта под точкой выборки (точка в центре).
Масштабирование 1: 1
Несмотря на то, что спрайт плавно перемещается к субпиксельным координатам, его визуализированное изображение по-прежнему корректно фиксируется при каждом перемещении на полный пиксель. Нам не нужно делать ничего особенного, чтобы сделать эту работу.
1: 1,0625 Масштабирование
В этом примере текстовый грибной спрайт 16x16 был масштабирован до 17x17 пикселей экрана, поэтому привязка происходит по-разному от одной части изображения к другой, создавая рябь или волну, которая растягивает и сдавливает его при движении.
Итак, хитрость заключается в том, чтобы настроить размер камеры / поле обзора таким образом, чтобы исходные тексели ваших ресурсов отображались в целое число пикселей экрана. Это не должно быть 1: 1 - любое целое число прекрасно работает:
Масштабирование 1: 3
Вам нужно будет сделать это немного по-разному, в зависимости от вашего целевого разрешения - простое увеличение по размеру окна почти всегда приведет к дробному масштабированию. Некоторое количество отступов по краям экрана может потребоваться для определенных разрешений.
источник
mapSprite
100x100.playerSprite
Размер 1x1, размер окна просмотра 32x20. Кажется, даже изменение всего числа, кратного 16, не решает проблему. Есть идеи?window size
1000x1000 (пикселей),WORLD_WIDTH
xWORLD_HEIGHT
100x100,FillViewport
10x10,playerSprite
1x1. К сожалению, проблема все еще сохраняется.вот небольшой контрольный список:
И только изменить текущее положение камеры, если камера должна двигаться.
Если положение камеры прицеливания отличается от transform.position - установите для transform.position (вашей камеры) значение «target camera position».
это должно решить вашу проблему. Если нет: скажи мне. Однако вы должны подумать о том, чтобы делать эти вещи тоже:
установите «проецирование камеры» на «ортографический» (для вашей ненормально увеличивающейся проблемы y) (теперь вы должны только увеличивать масштаб с «полем зрения»)
установить поворот камеры на 90 ° - с шагом (0 ° или 90 ° или 180 °)
не создавайте мипмапы, если не знаете, следует ли
используйте достаточный размер для ваших спрайтов и не используйте билинейный или трилинейный фильтр
Если 1 единица не является единицей пикселей, возможно, это зависит от разрешения экрана. У вас есть доступ к полю зрения? В зависимости от вашего поля зрения: узнайте, какое разрешение составляет 1 единица.
^ А теперь, если onePixel (ширина) и onePixel (высота) не 1.0000f, просто переместите эти единицы влево и вправо с вашей камерой.
источник
OrthographicCamera
так что, надеюсь, так и будет работать. (Вы используете Unity, верно?) 3-5) Я уже делаю это в своей игре.Я не знаком с libgdx, но у меня были подобные проблемы в других местах. Я думаю, что проблема заключается в том, что вы используете lerp и, возможно, ошибку в значениях с плавающей запятой. Я думаю, что возможным решением вашей проблемы является создание собственного объекта определения местоположения камеры. Используйте этот объект для вашего кода движения и обновите его соответствующим образом, а затем установите положение камеры на ближайшее значение (целое число?) Вашего объекта местоположения.
источник
y
увеличение ненормально) также присутствовала, прежде чем я использовалlerp
. Я фактически добавил это вчера, чтобы люди могли также видеть другую проблему, где размер спрайтов не остается постоянным, когда камера приближается.