ค่าความเร็วที่ไม่ใช่จำนวนเต็ม - มีวิธีที่สะอาดกว่านี้ไหม


21

บ่อยครั้งที่ฉันต้องการใช้ค่าความเร็วเช่น 2.5 สำหรับการย้ายตัวละครของฉันในเกมที่ใช้พิกเซล การตรวจจับการชนโดยทั่วไปจะทำได้ยากขึ้นหากฉันทำเช่นนั้น ดังนั้นฉันเลยทำอะไรแบบนี้:

moveX(2);
if (ticks % 2 == 0) { // or if (moveTime % 2 == 0)
    moveX(1);
}

ฉันประจบประแจงอยู่ข้างในทุกครั้งที่ฉันต้องเขียนมันมีวิธีที่สะอาดกว่าในการย้ายตัวละครที่มีค่าความเร็วที่ไม่ใช่จำนวนเต็มหรือฉันจะยังคงติดอยู่กับเรื่องนี้ตลอดไปหรือไม่?


11
คุณถือว่าการทำให้หน่วยจำนวนเต็มของคุณเล็กลง (เช่น 1 / 10th ของหน่วยแสดงผล) จากนั้น 2.5 แปลเป็น 25 และคุณยังคงสามารถใช้มันเป็นจำนวนเต็มสำหรับการตรวจสอบทั้งหมดและปฏิบัติต่อทุกเฟรมอย่างสม่ำเสมอ
DMGregory

6
คุณอาจพิจารณาใช้อัลกอริธึมบรรทัด Bresenhamซึ่งสามารถนำมาใช้โดยใช้จำนวนเต็มเท่านั้น
n0rd

1
นี่เป็นเกมคอนโซล 8 บิตที่เล่นกันทั่วไป ดูบทความเช่นนี้เพื่อเป็นตัวอย่างของการเคลื่อนไหวของพิกเซลย่อยด้วยวิธีกำหนดจุดตายตัว: tasvideos.org/GameResources/NES/BattleOfOlympus.html
Lucas

คำตอบ:


13

Bresenham

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

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

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

คุณสามารถปรับใช้สำหรับกรณีของคุณ:

  • "ทิศทาง x" ของคุณ (ในแง่ของอัลกอริทึม Bresenham) คือนาฬิกาของคุณ
  • "ทิศทาง y" ของคุณคือค่าที่คุณต้องการเพิ่มขึ้น (เช่นตำแหน่งของตัวละครของคุณ - ระวังนี่ไม่ใช่ "y" ของเทพดาของคุณหรืออะไรก็ตามบนหน้าจอมากกว่าค่านามธรรม)

"x / y" ที่นี่ไม่ใช่ตำแหน่งบนหน้าจอ แต่เป็นค่าของมิติใดมิติหนึ่งของคุณในเวลา เห็นได้ชัดว่าถ้าผีสางของคุณกำลังวิ่งไปในทิศทางใดก็ได้บนหน้าจอคุณจะมี Bresenhams หลายตัววิ่งแยกกัน 2 สำหรับ 2D และ 3 สำหรับ 3D

ตัวอย่าง

สมมติว่าคุณต้องการที่จะย้ายตัวละครของคุณในการเคลื่อนไหวที่เรียบง่ายจาก 0 ถึง 25 พร้อมหนึ่งในแกนของคุณ เมื่อมันเคลื่อนที่ด้วยความเร็ว 2.5 มันจะไปอยู่ที่เฟรม 10

สิ่งนี้เหมือนกับ "การวาดเส้น" จาก (0,0) ถึง (10,25) คว้า algoritm ของ Bresenham และปล่อยให้มันวิ่ง ถ้าคุณทำถูกต้อง (และเมื่อคุณศึกษามันจะชัดเจนอย่างรวดเร็วว่าคุณทำถูก) แล้วมันจะสร้าง 11 "คะแนน" สำหรับคุณ (0,0), (1,2), (2, 5), (3,7), (4,10) ... (10,25)

คำแนะนำในการปรับตัว

หากคุณ google อัลกอริทึมนั้นและค้นหารหัส (Wikipedia มีสนธิสัญญาที่ค่อนข้างใหญ่) มีบางสิ่งที่คุณต้องระวัง:

  • มันเห็นได้ชัดว่าการทำงานสำหรับทุกชนิดและdx dyคุณสนใจในกรณีใดกรณีหนึ่งโดยเฉพาะ (เช่นคุณจะไม่มีวันdx=0)
  • การดำเนินงานตามปกติจะมีกรณีที่แตกต่างกันหลายแนวทางบนหน้าจอขึ้นอยู่กับว่าdxและdyมีบวกลบและยังว่าabs(dx)>abs(dy)หรือไม่ แน่นอนว่าคุณเลือกสิ่งที่คุณต้องการที่นี่ คุณต้องทำให้แน่ใจเป็นพิเศษว่าทิศทางที่เพิ่มขึ้น1ทุก ๆ เห็บนั้นเป็นทิศทาง "นาฬิกา" ของคุณเสมอ

หากคุณใช้การทำให้เข้าใจง่ายเหล่านี้ผลลัพธ์จะง่ายมากแน่นอนและกำจัด reals ใด ๆ โดยสิ้นเชิง


1
นี่ควรเป็นคำตอบที่ยอมรับได้ มีเกมที่ตั้งโปรแกรมไว้ใน C64 ในยุค 80 และเศษส่วนบนพีซีในยุค 90 ฉันยังคงประจบประแจงที่การใช้จุดลอยตัวทุกที่ที่ฉันสามารถหลีกเลี่ยงได้ หรือแน่นอนด้วย FPUs ที่แพร่หลายในตัวประมวลผลของวันนี้ข้อโต้แย้งด้านประสิทธิภาพส่วนใหญ่เป็นข้อโต้แย้ง แต่เลขทศนิยมยังคงต้องการทรานซิสเตอร์มากขึ้นใช้พลังงานมากขึ้นและโปรเซสเซอร์จำนวนมากจะปิด FPU ทั้งหมดโดยไม่ใช้งาน ดังนั้นการหลีกเลี่ยงจุดลอยตัวจะทำให้ผู้ใช้มือถือของคุณขอบคุณที่ไม่ดูดแบตเตอรี่หมดอย่างรวดเร็ว
Guntram Blohm สนับสนุนโมนิก้า

@GuntramBlohm คำตอบที่ยอมรับนั้นใช้งานได้ดีอย่างสมบูรณ์เมื่อใช้ Fixed Point ด้วยเช่นกันซึ่งฉันคิดว่าเป็นวิธีที่ดีในการทำเช่นนั้น คุณรู้สึกอย่างไรเกี่ยวกับหมายเลขจุดคงที่?
leetNightshade

เปลี่ยนสิ่งนี้เป็นคำตอบที่ยอมรับหลังจากค้นพบว่านี่เป็นวิธีที่พวกเขาทำใน 8 วันและ 16 บิต
Accumulator

26

มีวิธีที่ยอดเยี่ยมในการทำสิ่งที่คุณต้องการ

นอกจากfloatความเร็วคุณจะต้องมีfloatตัวแปรที่สองซึ่งจะมีและสะสมความแตกต่างระหว่างความเร็วจริงและความเร็วโค้งมน ความแตกต่างนี้จะถูกรวมเข้ากับความเร็วของมันเอง

#include <iostream>
#include <cmath>

int main()
{
    int pos = 10; 
    float vel = 0.3, vel_lag = 0;

    for (int i = 0; i < 20; i++)   
    {
        float real_vel = vel + vel_lag;
        int int_vel = std::lround(real_vel);
        vel_lag = real_vel - int_vel;

        std::cout << pos << ' ';
        pos += int_vel;
    }
}

เอาท์พุท:

10 10 11 11 11 12 12 12 12 13 13 13 13 14 14 14 15 15 15 15 15 16


5
ทำไมคุณถึงชอบโซลูชันนี้มากกว่าการใช้ float (หรือ fixedpoint) สำหรับทั้งความเร็วและตำแหน่งและปัดเศษตำแหน่งเป็นจำนวนเต็มทั้งหมดในท้ายที่สุดเท่านั้น
CodesInChaos

@CodesInChaos ฉันไม่ชอบโซลูชันของฉันมากกว่าโซลูชันนั้น เมื่อฉันเขียนคำตอบนี้ฉันไม่รู้เกี่ยวกับมัน
HolyBlackCat

16

ใช้ค่าลอยตัวสำหรับการเคลื่อนไหวและค่าจำนวนเต็มสำหรับการชนกันและการแสดงผล

นี่คือตัวอย่าง:

class Character {
    float position;
public:
    void move(float delta) {
        this->position += delta;
    }
    int getPosition() const {
        return lround(this->position);
    }
};

เมื่อคุณย้ายคุณใช้move()ซึ่งสะสมตำแหน่งเศษส่วน แต่การชนและการเรนเดอร์สามารถจัดการกับตำแหน่งที่สำคัญโดยใช้getPosition()ฟังก์ชัน


โปรดทราบว่าในกรณีของเกมบนเครือข่ายการใช้ชนิดจุดลอยสำหรับการจำลองโลกอาจมีความยุ่งยาก ดูเช่นgafferongames.com/networking-for-game-programmers/...
liori

@liori หากคุณมีคลาสจุดคงที่ที่ทำหน้าที่เหมือนการแทนที่แบบดรอปดาวน์สำหรับโฟลว์นั่นไม่ได้ช่วยแก้ปัญหาเหล
leetNightshade

@leetNightshade: ขึ้นอยู่กับการใช้งาน
liori

1
ฉันจะบอกว่าปัญหานี้ไม่มีอยู่จริงในทางปฏิบัติฮาร์ดแวร์ที่สามารถใช้งานเกมบนเครือข่ายที่ทันสมัยไม่มี IEEE 754 ลอยตัว ???
Sopel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.