ฉันจะย้ายกล้องในช่วงพิกเซลเต็มได้อย่างไร


13

โดยทั่วไปฉันต้องการหยุดกล้องไม่ให้เคลื่อนไหวในรูปแบบย่อยเนื่องจากฉันคิดว่าสิ่งนี้นำไปสู่การสไปรต์ที่เปลี่ยนขนาดได้อย่างชัดเจนถ้าเพียงเล็กน้อยเท่านั้น (มีคำศัพท์ที่ดีกว่านี้หรือไม่) โปรดทราบว่านี่เป็นเกมภาพพิกเซลที่ฉันต้องการให้มีกราฟิกพิกเซลที่คมชัด นี่คือ gif ที่แสดงปัญหา:

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้สิ่งที่ฉันลองทำคือ: ย้ายกล้องฉายตำแหน่งปัจจุบัน (ดังนั้นจึงเป็นพิกัดของหน้าจอ) จากนั้นปัดหรือเหวี่ยงเป็น int หลังจากนั้นแปลงให้เป็นพิกัดโลกและใช้เป็นตำแหน่งกล้องใหม่ เท่าที่ฉันรู้สิ่งนี้ควรล็อคกล้องเป็นพิกัดหน้าจอจริงไม่ใช่เศษส่วน

อย่างไรก็ตามด้วยเหตุผลบางอย่างyค่าของตำแหน่งใหม่ก็จะระเบิดออก 334756315000ในเรื่องของการวินาทีมันจะเพิ่มเพื่อสิ่งที่ต้องการ

นี่คือ SSCCE (หรือ MCVE) ตามรหัสใน LibGDX wiki :

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.jpgdungeon_guy.png

ฉันสนใจที่จะเรียนรู้เกี่ยวกับวิธีที่ง่ายกว่าและ / หรือวิธีที่ดีกว่าในการแก้ไขปัญหานี้


"มีคำที่ดีกว่านี้หรือไม่?" aliasing? ในกรณีนั้นการลบรอยหยักหลายตัวอย่าง (MSAA) อาจเป็นทางเลือกอื่นหรือไม่
Yousef Amar

@Paraknight ขออภัยฉันลืมที่จะพูดถึงว่าฉันกำลังทำงานในเกมตัวหนังสือที่ฉันต้องการกราฟิกที่คมชัดดังนั้นการต่อต้านนามแฝงจะไม่ทำงาน (AFAIK) ฉันเพิ่มข้อมูลนั้นลงในคำถาม ขอบคุณแม้ว่า
Joschua

มีเหตุผลในการทำงานที่ความละเอียดสูงกว่ามิฉะนั้นทำงานที่ความละเอียด 1 พิกเซลและยกระดับผลสุดท้าย?
Felsir

@Felsir ใช่ฉันย้ายพิกเซลหน้าจอเพื่อให้เกมรู้สึกราบรื่นที่สุด การย้ายพิกเซลเนื้อทั้งหมดจะดูสั่นไหวมาก
Joschua

คำตอบ:


22

ปัญหาของคุณไม่ได้ขยับกล้องแบบเต็มพิกเซล นั่นคืออัตราส่วนของ texel-to-pixel ของคุณนั้นไม่ใช่จำนวนเต็มเล็กน้อย ฉันจะยืมตัวอย่างบางส่วนจากคำถามนี้คล้ายผมตอบใน StackExchange

นี่คือมาริโอสองชุด - ทั้งคู่เคลื่อนผ่านหน้าจอในอัตราเดียวกัน (ไม่ว่าจะเป็นสไปรต์เคลื่อนที่ไปทางขวาในโลกหรือกล้องเคลื่อนที่ไปทางซ้าย - มันจบลงเทียบเท่ากัน) แต่มีเพียงอันเดียวที่แสดงสิ่งประดิษฐ์ rippling และ ด้านล่างไม่ได้:

มาริโอสองคนสไปรต์

เหตุผลก็คือฉันปรับขนาด Mario ด้านบนเล็กน้อยโดย 1 เท่าของ 1.01x ซึ่งเป็นเศษส่วนเล็ก ๆ ที่เพิ่มขึ้นนั่นหมายความว่าเขาจะไม่เข้ากับกริดพิกเซลของหน้าจออีกต่อไป

การเยื้องศูนย์ขนาดเล็กที่แปลไม่ได้เป็นปัญหา - การกรองพื้นผิว "ใกล้ที่สุด" จะรีบไปที่เท็กเซลที่ใกล้เคียงที่สุดโดยที่เราไม่ต้องทำอะไรเป็นพิเศษ

แต่สเกลไม่ตรงกันหมายความว่าการจัดระยะนี้ไม่ได้อยู่ในทิศทางเดียวกันเสมอไป ในจุดหนึ่งพิกเซลที่ค้นหาเท็กเซลที่ใกล้ที่สุดจะเลือกหนึ่งทางด้านขวาเล็กน้อยในขณะที่อีกพิกเซลหนึ่งเลือกทางซ้ายเล็กน้อย - และตอนนี้คอลัมน์ของเท็กซัสถูกละไว้หรือทำซ้ำในระหว่างการสร้างระลอกคลื่นหรือระยับที่ เคลื่อนที่ผ่านสไปรท์ขณะเดินทางผ่านหน้าจอ

นี่คือลักษณะที่ใกล้ชิด ฉันทำภาพเคลื่อนไหวสไปรท์เห็ดโปร่งแสงเคลื่อนไหวอย่างราบรื่นราวกับว่าเราสามารถแสดงผลด้วยความละเอียด sub-pixel ไม่ จำกัด จากนั้นฉันก็วางตารางพิกเซลโดยใช้การสุ่มตัวอย่างเพื่อนบ้านที่ใกล้ที่สุด พิกเซลทั้งหมดเปลี่ยนสีเพื่อให้ตรงกับส่วนของสไปรท์ใต้จุดสุ่มตัวอย่าง (จุดที่อยู่ตรงกลาง)

1: 1 การปรับสเกล

สไปรต์ที่ปรับขนาดอย่างถูกต้องเคลื่อนไหวโดยไม่มีระลอก

แม้ว่าสไปรต์เคลื่อนที่อย่างราบรื่นไปยังพิกัด sub-pixel แต่ภาพที่เรนเดอร์ยังคงถ่ายได้อย่างถูกต้องในแต่ละครั้งที่เดินทางเต็มพิกเซล เราไม่จำเป็นต้องทำอะไรเป็นพิเศษเพื่อให้งานนี้สำเร็จ

1: 1.0625 การปรับสเกล

16x16 sprite แสดงผล 17x17 บนหน้าจอแสดงสิ่งประดิษฐ์

ในตัวอย่างนี้สไปรต์เห็ดขนาด 16x16 texel ได้ปรับขนาดหน้าจอได้ถึง 17x17 พิกเซลและการจัดระยะจึงเกิดความแตกต่างจากส่วนหนึ่งของภาพไปยังอีกส่วนหนึ่งสร้างระลอกคลื่นหรือคลื่นที่ทอดยาว

ดังนั้นเคล็ดลับคือการปรับขนาด / มุมมองกล้องของคุณเพื่อให้แหล่งที่มาของเนื้อหาของคุณประมวลผลแผนที่เป็นจำนวนพิกเซลหน้าจอจำนวนเต็ม ไม่จำเป็นต้องเป็น 1: 1 - จำนวนทั้งหมดจะใช้ได้ดี:

1: 3 การปรับสเกล

ซูมเข้าแบบสามสเกล

คุณจะต้องทำสิ่งนี้แตกต่างกันเล็กน้อยขึ้นอยู่กับความละเอียดเป้าหมายของคุณเพียงปรับขนาดให้พอดีกับหน้าต่างจะส่งผลให้เกิดการแตกหัก อาจต้องใช้การขยายบางบริเวณขอบของหน้าจอสำหรับความละเอียดที่แน่นอน


1
ก่อนอื่นขอบคุณมาก! นี่คือการสร้างภาพที่ยอดเยี่ยม มี upvote สำหรับคนเดียว น่าเศร้าที่ฉันไม่พบสิ่งที่ไม่ใช่จำนวนเต็มในรหัสที่ให้ ขนาดของmapSpriteคือ 100x100 ขนาดplayerSpriteถูกตั้งค่าเป็น 1x1 และวิวพอร์ตถูกกำหนดเป็นขนาด 32x20 แม้แต่การเปลี่ยนทุกอย่างเป็นทวีคูณ 16 ก็ยังไม่สามารถแก้ปัญหาได้ ความคิดใด ๆ
Joschua

2
เป็นไปได้ทั้งหมดที่จะมีจำนวนเต็มทุกที่และยังคงได้อัตราส่วนที่ไม่ใช่จำนวนเต็ม นำตัวอย่างของสไปรต์ 16 texel ที่แสดงบนหน้าจอ 17 พิกเซล ค่าทั้งสองเป็นจำนวนเต็ม แต่อัตราส่วนไม่เป็นเช่นนั้น ฉันไม่รู้เกี่ยวกับ libgdx มากนัก แต่ใน Unity ฉันจะดูว่าฉันแสดงจำนวนข้อความต่อหน่วยโลก (เช่นความละเอียดของสไปรท์หารด้วยขนาดของสไปรต์โลก) จำนวนหน่วยที่ฉันแสดงในโลกของฉัน หน้าต่าง (ใช้ขนาดของกล้อง) และจำนวนพิกเซลหน้าจอในหน้าต่างของฉัน การผูกมัดค่าทั้งสามนี้เราสามารถคำนวณอัตราส่วน texel-to-pixel สำหรับขนาดหน้าต่างการแสดงผลที่กำหนด
DMGregory

"และวิวพอร์ตถูกตั้งค่าเป็นขนาด 32x20" - ยกเว้นว่า "ขนาดลูกค้า" ของหน้าต่างของคุณ (พื้นที่ที่สามารถแสดงผลได้) เป็นจำนวนเต็มหลายตัวซึ่งฉันเดาว่ามันไม่ใช่ 1 หน่วยในวิวพอร์ตของคุณจะเป็นจำนวนเต็ม จำนวนพิกเซล
MaulingMonkey

ขอบคุณ ฉันลองอีกครั้ง window sizeคือ 1,000x1000 (พิกเซล) WORLD_WIDTHx WORLD_HEIGHTคือ 100x100 FillViewportคือ 10x10 playerSpriteคือ 1x1 น่าเศร้าที่ปัญหายังคงมีอยู่
Joschua

รายละเอียดเหล่านี้จะเป็นอะไรที่ค่อนข้างมากในการพยายามเรียงลำดับในเธรดความคิดเห็น คุณต้องการเริ่มห้องแชทเพื่อการพัฒนาเกมหรือส่งข้อความถึงฉันโดยตรงบน Twitter @D_M_Gregory?
DMGregory

1

นี่คือรายการตรวจสอบขนาดเล็ก:

  1. แยก "ตำแหน่งกล้องปัจจุบัน" ด้วย "เล็งตำแหน่งกล้อง" (เช่นที่กล่าวถึง ndenarodev) ถ้าเช่น "ตำแหน่งกล้องปัจจุบัน" คือ 1.1 - ทำให้ "ตำแหน่งกล้องเล็ง" 1.0

และเปลี่ยนตำแหน่งกล้องปัจจุบันเท่านั้นหากกล้องควรเคลื่อนที่
หากตำแหน่งกล้องเล็งเป็นอย่างอื่นที่นอกเหนือจาก transform.position - ตั้ง transform.position (ของกล้องของคุณ) เป็น "เล็งตำแหน่งกล้อง"

aim_camera_position = Mathf.Round(current_camera_position)

if (transform.position != aim_camera_position)
{
  transform.position = aim_camera_position;
}

สิ่งนี้จะช่วยแก้ปัญหาของคุณได้ ถ้าไม่: บอกฉัน อย่างไรก็ตามคุณควรลองทำสิ่งเหล่านั้นด้วย:

  1. ตั้งค่า "การฉายภาพกล้อง" เป็น "ortographic" (สำหรับปัญหา y ที่เพิ่มขึ้นอย่างผิดปกติ) (จากนี้ไปคุณควรซูมด้วย "Field of View")

  2. ตั้งค่าการหมุนกล้องใน 90 ° - ขั้นตอน (0 °หรือ 90 °หรือ 180 °)

  3. อย่าสร้าง mipmaps หากคุณไม่รู้ว่าควรทำอย่างไร

  4. ใช้ขนาดที่เพียงพอสำหรับสไปรต์ของคุณและอย่าใช้ตัวกรอง bilinear หรือ trilinear

  5. 5

หาก 1 หน่วยไม่ใช่หน่วยพิกเซลอาจขึ้นอยู่กับความละเอียดหน้าจอ คุณมีสิทธิ์เข้าถึงมุมมองหรือไม่? ขึ้นอยู่กับสาขาที่คุณดู: ค้นหาว่าความละเอียด 1 หน่วยเป็นเท่าใด

float tan2Fov = tan(radians( Field of view / 2)); 
float xScrWidth = _CamNear*tan2Fov*_CamAspect * 2; 
float xScrHeight = _CamNear*tan2Fov * 2; 
float onePixel(width) = xWidth / screenResolutionX 
float onePixel(height) = yHeight / screenResolutionY 

^ และตอนนี้ถ้าหนึ่งพิกเซล (กว้าง) และหนึ่งพิกเซล (สูง) ไม่ใช่ 1.0000f เพียงแค่ย้ายหน่วยเหล่านั้นไปทางซ้ายและขวาด้วยกล้องของคุณ


เฮ้! ขอบคุณสำหรับคำตอบ. 1) ฉันเก็บตำแหน่งกล้องจริงแล้วและกล้องที่โค้งมนแยกต่างหาก 2) ฉันใช้ LibGDX OrthographicCameraเพื่อหวังว่าจะได้ผลเช่นนั้น (คุณกำลังใช้ Unity ใช่มั้ย) 3-5) ฉันทำสิ่งเหล่านั้นในเกมแล้ว
Joschua

โอเค - จากนั้นคุณต้องทราบว่าหน่วย 1 พิกเซลเป็นเท่าใด มันอาจจะขึ้นอยู่กับความละเอียดหน้าจอ ฉันแก้ไข "5. " ในคำตอบของฉันโพสต์ ลองนี้
OC_RaizW

0

ฉันไม่คุ้นเคยกับ libgdx แต่ประสบปัญหาคล้ายกันที่อื่น ฉันคิดว่าปัญหาอยู่ที่ข้อเท็จจริงที่ว่าคุณกำลังใช้ lerp และอาจเกิดข้อผิดพลาดในค่าทศนิยม ฉันคิดว่าทางออกที่เป็นไปได้สำหรับปัญหาของคุณคือการสร้างวัตถุตำแหน่งกล้องของคุณเอง ใช้วัตถุนี้สำหรับรหัสการเคลื่อนไหวของคุณและอัปเดตตามนั้นจากนั้นตั้งค่าตำแหน่งกล้องเป็นค่าที่ต้องการ (จำนวนเต็ม?) ที่ใกล้ที่สุดของวัตถุตำแหน่งของคุณ


ก่อนอื่นขอขอบคุณสำหรับความเข้าใจของคุณ มันอาจมีบางอย่างเกี่ยวกับข้อผิดพลาดของ floating point อย่างไรก็ตามปัญหาที่เกิดขึ้น (จากyการเพิ่มขึ้นอย่างผิดปกติ) lerpยังคงอยู่ก่อนที่ผมใช้ ฉันเพิ่มเมื่อวานนี้จริง ๆเพื่อให้ผู้คนได้เห็นปัญหาอื่นที่ขนาดของสไปรต์ไม่คงที่เมื่อกล้องใกล้เข้ามา
Joschua
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.