ฉันจะมั่นใจได้อย่างไรว่าชิ้นส่วนของรหัสทำงานเพียงครั้งเดียว


18

ฉันมีรหัสบางอย่างที่ฉันต้องการเรียกใช้เพียงครั้งเดียวแม้ว่าสถานการณ์ที่เรียกรหัสนั้นอาจเกิดขึ้นหลายครั้ง

ตัวอย่างเช่นเมื่อผู้ใช้คลิกเมาส์ฉันต้องการคลิกสิ่ง:

void Update() {
    if(mousebuttonpressed) {
        ClickTheThing(); // I only want this to happen on the first click,
                         // then never again.
    }
}

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


7
ฉันพบว่ามันตลกแบบนี้เพราะฉันไม่คิดว่ามันจะตกอยู่ใน "ปัญหาการเขียนโปรแกรมเฉพาะเกม" แต่ฉันไม่เคยชอบกฎนั้นจริงๆ
ClassicThunder

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

คำตอบ:


41

ใช้ธงบูลีน

ในตัวอย่างที่แสดงคุณจะต้องแก้ไขโค้ดให้เป็นดังนี้:

//a boolean flag that lets us "remember" if this thing has happened already
bool thatThingHappened = false;

void Update() {
    if(!thatThingHappened && mousebuttonpressed) {
        //if that thing hasn't happened yet and the mouse is pressed
        thatThingHappened = true;
        ClickTheThing();
    }
}

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


1
คำตอบที่ชัดเจนสำหรับคำถามของคุณ :-) ฉันสนใจที่จะเห็นวิธีการอื่นหากคุณหรือคนอื่น ๆ การใช้บูลีนระดับโลกนี้ทำให้ฉันมีกลิ่นแรงเสมอ
Evorlor

1
@Evorlor ไม่ชัดเจนสำหรับทุกคน :) ฉันสนใจวิธีแก้ปัญหาทางเลือกเช่นกันบางทีเราสามารถเรียนรู้บางสิ่งที่ไม่ชัดเจน!
MichaelHouse

2
@Evorlor เป็นอีกทางเลือกหนึ่งคุณสามารถใช้ผู้ได้รับมอบหมาย (ตัวชี้ฟังก์ชั่น) และเปลี่ยนการกระทำที่ทำ เช่นonStart() { delegate = doSomething; } ... if(mousebuttonpressed) { delegate.Execute(); }และvoid doSomething() { doStuff(); delegate = funcDoNothing; }ในท้ายที่สุดตัวเลือกทั้งหมดจบลงด้วยการมีธงบางประเภทที่คุณตั้งค่า / unset ... และผู้ได้รับมอบหมายในกรณีนี้ไม่มีอะไรอื่น (ยกเว้นถ้าคุณมีมากกว่าสองตัวเลือกว่าจะทำอย่างไร?)
wondra

2
@wondra ใช่นั่นเป็นวิธีที่ เพิ่มมัน เราอาจทำรายการวิธีแก้ปัญหาที่เป็นไปได้สำหรับคำถามนี้
MichaelHouse

1
@JamesSnell มีโอกาสมากขึ้นถ้าเป็นแบบมัลติเธรด แต่ก็ยังเป็นการฝึกฝนที่ดี
MichaelHouse

21

หากค่าสถานะบูลไม่เพียงพอหรือคุณต้องการปรับปรุงความสามารถในการอ่าน * ของโค้ดในvoid Update()วิธีการคุณสามารถพิจารณาใช้ผู้รับมอบสิทธิ์ (ตัวชี้ฟังก์ชั่น):

public class InputController 
{
  //declare delegate type:
  //<accessbility> delegate <return type> <name> ( <parameter1>, <paramteter2>, ...)
  public delegate void ClickAction();

  //declare a variable of that type
  public ClickAction ClickTheThing { get; set; }

  void onStart()
  {
    //assign it the method you wish to execute on first click
    ClickTheThing = doStuff;
  }

  void Update() {
    if(mousebuttonpressed) {
        //you simply call the delegate here, the currently assigned function will be executed
        ClickTheThing();
     }
  }

  private void doStuff()
  {
    //some logic here
    ClickTheThing = doNothing; //and set the delegate to other(empty) funtion
  }

  //or do something else
  private void doNothing(){}
}

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

func1() 
{
  //do logic 1 here
  someDelegate = func2;
}

func2() 
{
  //do logic 2 here
  someDelegate = func3;
}
//etc...

แทนที่จะรบกวนรหัสของคุณด้วยค่าสถานะที่แตกต่างกันหลายสิบค่า
* ด้วยราคาของการบำรุงรักษาที่ต่ำกว่าของรหัสที่เหลือ


ฉันทำโปรไฟล์ด้วยผลลัพธ์ค่อนข้างมากตามที่คาดไว้:

----------------------------------------
|     Method     |  Unity   |    C++   |
| -------------------------------------|
| positive flag  |  21 ms   |   6 ms   |
| negative flag  |  5 ms    |   7 ms   |
| delegate       |  25 ms   |   14 ms  |
----------------------------------------

การทดสอบครั้งแรกดำเนินการบน Unity 5.1.2 โดยวัดSystem.Diagnostics.Stopwatchจากโครงการที่สร้างขึ้น 32 บิต (ไม่ใช่ในตัวออกแบบ!) อีกอันหนึ่งใน Visual Studio 2015 (v140) ที่คอมไพล์ในโหมดการเปิดตัวแบบ 32 บิตพร้อมกับแฟล็ก / Ox การทดสอบทั้งสองทำงานบน Intel i5-4670K CPU @ 3.4GHz พร้อม 10,000,000 ซ้ำสำหรับการใช้งานแต่ละครั้ง รหัส:

//positive flag
if(flag){
    doClick();
    flag = false;
}
//negative flag
if(!flag){ }
else {
    doClick();
    flag = false;
}
//delegate
action();

ข้อสรุป:ในขณะที่คอมไพเลอร์ Unity ทำงานได้ดีเมื่อปรับการเรียกใช้ฟังก์ชันให้ผลลัพธ์ที่เหมือนกันโดยประมาณสำหรับทั้งค่าบวกและค่าสถานะ (21 และ 25 ms ตามลำดับ) การคาดคะเนสาขาหรือการเรียกใช้ฟังก์ชันยังค่อนข้างแพง (หมายเหตุ: ในการทดสอบนี้)
ที่น่าสนใจคือคอมไพเลอร์ Unity ไม่ฉลาดพอที่จะปรับสาขาให้ดีที่สุดเมื่อมีการคาดคะเนความผิดพลาดติดต่อกันถึง 99 ล้านครั้งดังนั้นการทดสอบด้วยตนเองเมื่อทดสอบด้วยตนเองจะทำให้ประสิทธิภาพเพิ่มขึ้นโดยให้ผลลัพธ์ที่ดีที่สุด 5 ms เวอร์ชัน C ++ ไม่แสดงการเพิ่มประสิทธิภาพใด ๆ สำหรับเงื่อนไขการปฏิเสธอย่างไรก็ตามค่าใช้จ่ายโดยรวมของการเรียกใช้ฟังก์ชั่นนั้นต่ำกว่ามาก
สิ่งสำคัญที่สุดคือ:ความแตกต่างนั้นค่อนข้างไม่เกี่ยวข้องเลย สำหรับสถานการณ์โลกแห่งความจริงใด ๆ


4
AFAIK นี้ดีกว่าจากมุมมองประสิทธิภาพเช่นกัน การเรียกใช้ฟังก์ชัน no-op สมมติว่าฟังก์ชันนั้นมีอยู่ในแคชคำสั่งนั้นเร็วกว่าการตรวจสอบสถานะโดยเฉพาะอย่างยิ่งเมื่อการตรวจสอบนั้นซ้อนอยู่ภายใต้เงื่อนไขอื่น ๆ
วิศวกร

2
ฉันคิดว่า Navin ถูกต้องฉันน่าจะมีประสิทธิภาพที่แย่กว่าการตรวจสอบธงบูลีน - ยกเว้นว่า JIT ของคุณจะฉลาดและแทนที่ฟังก์ชันทั้งหมดโดยไม่มีอะไรเลย แต่ถ้าเราไม่ได้โพรไฟล์ผลลัพธ์เราไม่สามารถทราบได้อย่างแน่นอน
wondra

2
อืมคำตอบนี้ทำให้ผมนึกถึงวิวัฒนาการของวิศวกร SW ตลกกันถ้าคุณต้องการ (และแน่นอน) ของการเพิ่มประสิทธิภาพการทำงานนี้ไปได้เลย ถ้าไม่ได้ผมขอแนะนำที่จะให้มันKISSและใช้ธงบูลีนเช่นเสนอในคำตอบอื่น อย่างไรก็ตาม +1 สำหรับตัวเลือกที่แสดงที่นี่!
mucaho

1
หลีกเลี่ยงเงื่อนไขที่เป็นไปได้ ฉันกำลังพูดถึงสิ่งที่เกิดขึ้นในระดับเครื่องไม่ว่าจะเป็นรหัสท้องถิ่นของคุณเองคอมไพเลอร์ JIT หรือล่าม L1 cache hit นั้นใกล้เคียงกับ register hit ซึ่งอาจจะช้ากว่าเล็กน้อยคือ 1 หรือ 2 รอบในขณะที่การคาดคะเนผิดพลาดของสาขาซึ่งเกิดขึ้นน้อยกว่า แต่ก็ยังโดยเฉลี่ยมากเกินไป ดูคำตอบของ Jalf: stackoverflow.com/questions/289405/…และสิ่งนี้: stackoverflow.com/questions/11227809/…
วิศวกร

1
โปรดจำไว้ว่ายิ่งไปกว่านั้นเงื่อนไขเหล่านี้ในคำตอบของ Byte56 จะต้องมีการเรียกทุกการอัปเดตตลอดอายุการใช้งานของโปรแกรมของคุณและอาจซ้อนกันหรือมีซ้อนกันอยู่ในเงื่อนไข ตัวชี้ฟังก์ชั่น / ผู้ได้รับมอบหมายเป็นวิธีที่จะไป
วิศวกร

3

เพื่อความสมบูรณ์

(ไม่แนะนำให้คุณทำเช่นนี้เพราะจริงๆแล้วมันค่อนข้างตรงไปตรงมาที่จะเขียนอะไรบางอย่างif(!already_called)แต่มันจะ "ถูกต้อง" ที่จะทำ)

ไม่น่าแปลกใจที่มาตรฐาน C ++ 11 ได้แสดงถึงปัญหาที่ค่อนข้างน่ารำคาญในการเรียกใช้ฟังก์ชั่นหนึ่งครั้งและทำให้มันชัดแจ้งเป็นพิเศษ:

#include <mutex>

void Update() {
if(mousebuttonpressed) {
    static std::once_flag f;
    std::call_once(f, ClickTheThing);
}

}

เป็นที่ยอมรับ, การแก้ปัญหามาตรฐานคือค่อนข้างเหนือกว่าเรื่องเล็ก ๆ น้อย ๆ ต่อหน้าเธรดเนื่องจากมันยังรับประกันว่าจะมีการโทรหนึ่งครั้งเสมอ

อย่างไรก็ตามโดยปกติคุณจะไม่ได้วนเหตุการณ์แบบมัลติเธรดและกดปุ่มของเมาส์หลายตัวในเวลาเดียวกันดังนั้นความปลอดภัยของเธรดนั้นค่อนข้างไม่จำเป็นสำหรับกรณีของคุณ

ในแง่การปฏิบัติหมายความว่านอกเหนือจากการเริ่มต้นเธรดที่ปลอดภัยของบูลีนท้องถิ่น (ซึ่ง C ++ 11 รับประกันว่าจะปลอดภัยต่อเธรด) รุ่นมาตรฐานต้องดำเนินการ test_set อะตอมก่อนการโทรหรือไม่โทร ฟังก์ชั่น.

ยังถ้าคุณชอบที่ชัดเจนมีวิธีแก้ปัญหา

แก้ไข:
ฮะ ตอนนี้ฉันนั่งอยู่ที่นี่โดยดูรหัสนั้นสักครู่ฉันเกือบจะอยากแนะนำ
จริงๆแล้วมันไม่ได้โง่อย่างที่เห็นในตอนแรกมันชัดเจนและชัดเจนในการสื่อสารความตั้งใจของคุณ ... เนื้อหาที่มีโครงสร้างดีกว่าและอ่านง่ายกว่าโซลูชันอื่น ๆ ที่เกี่ยวข้องifหรือ


2

คำแนะนำบางอย่างจะแตกต่างกันไปขึ้นอยู่กับสถาปัตยกรรม

สร้าง clickThisThing () เป็นตัวชี้ฟังก์ชั่น / ตัวแปร / DLL / ดังนั้น / etc ... และตรวจสอบให้แน่ใจว่ามันจะเริ่มต้นไปยังฟังก์ชั่นที่คุณต้องการเมื่อวัตถุ / องค์ประกอบ / โมดูล / ฯลฯ ... ถูกยกตัวอย่าง

ในตัวจัดการเมื่อใดก็ตามที่มีการกดปุ่มเมาส์จากนั้นเรียกตัวชี้ฟังก์ชัน clickThisThing () จากนั้นแทนที่ตัวชี้ด้วยตัวชี้ฟังก์ชัน NOP (ไม่มีการดำเนินการ) ในทันที

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

หรือคุณสามารถใช้การตั้งค่าสถานะและตรรกะเพื่อข้ามสาย

หรือคุณสามารถตัดการเชื่อมต่อฟังก์ชั่น Update () หลังจากการโทรดังนั้นโลกภายนอกจะลืมเรื่องนี้และไม่เคยโทรออกอีกเลย

หรือคุณมี clickThisThing () ใช้แนวคิดเหล่านี้เพื่อตอบสนองงานที่มีประโยชน์เพียงครั้งเดียว วิธีนี้คุณสามารถใช้พื้นฐาน clickThisThingOnce () วัตถุ / องค์ประกอบ / โมดูลและยกตัวอย่างได้ทุกที่ที่คุณต้องการพฤติกรรมนี้และไม่มีผู้อัพเดตใด ๆ ของคุณที่ต้องการตรรกะพิเศษทั่วทุกที่


2

ใช้พอยน์เตอร์ของฟังก์ชันหรือผู้รับมอบสิทธิ์

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

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

ตัวชี้ฟังก์ชั่นมีอยู่ด้วยเหตุผลนี้ - ใช้พวกมัน!


1
เรากำลังคิดตามแนวเดียวกัน วิธีที่มีประสิทธิภาพมากที่สุดคือตัดการเชื่อมต่อสิ่งอัปเดต () จากใครก็ตามที่เรียกมันอย่างไม่ลดละเมื่อทำงานเสร็จซึ่งเป็นเรื่องธรรมดามากในสัญญาณสไตล์ Qt + การตั้งค่าสล็อต OP ไม่ได้ระบุสภาพแวดล้อมรันไทม์ดังนั้นเราจึงเป็นอุปสรรคเมื่อมันมาถึงการเพิ่มประสิทธิภาพที่เฉพาะเจาะจง
Patrick Hughes

1

ในบางภาษาสคริปต์เช่น javascript หรือ lua สามารถทำการทดสอบการอ้างอิงฟังก์ชันได้อย่างง่ายดาย ในLua ( love2d ):

function ClickTheThing()
      .......
end
....

local ctt = ClickTheThing  --I make a local reference to function

.....

function update(dt)
..
    if ctt then
      ctt()
      ctt=nil
    end
..
end

0

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


6
สิ่งนี้จะทำลายสถาปัตยกรรมบางอย่างที่เขียนป้องกันหน่วยความจำของโปรแกรมหรือเรียกใช้จาก ROM นอกจากนี้ยังอาจตั้งค่าการเตือนแบบสุ่มโดยขึ้นอยู่กับสิ่งที่ติดตั้ง
Patrick Hughes

0

ในไพ ธ อนถ้าคุณวางแผนที่จะกระตุกให้กับผู้อื่นในโครงการ:

code = compile('main code'+'del code', '<string>', 'exec')
exec code

สคริปต์นี้ทำลายรหัส:

from time import time as t

code = compile('del code', '<string>', 'exec')

print 'see code object:'
print code

while True:
    try:
        user = compile(raw_input('play with it (variable name is code) (type done to be done:\t'), '<user_input>', 'exec')
        exec user
    except BaseException as e:
        print e
        break

exec code

print 'no more code object:'
try:
    print code
except BaseException as e:
    print e

while True:
    try:
        user = compile(raw_input('try to play with it again (type done to be done:\t'), '<user_input>', 'exec')
        exec user
    except BaseException as e:
        print e
        break

0

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

ลองสรุปเหตุผลที่เราเรียกใช้ตรรกะในคลาสเมื่อ:

class Once
{
    private Action body;
    public bool HasBeenCalled { get; private set; }
    public Once(Action body)
    {
        this.body = body;
    }
    public void Call()
    {
        if (!HasBeenCalled)
        {
            body();
        }
        HasBeenCalled = true;
    }
    public void Reset() { HasBeenCalled = false; }
}

ใช้มันเหมือนตัวอย่างที่ให้มามีลักษณะดังนี้:

Once clickTheThingOnce = new Once(ClickTheThing);

void Update() {
    if(mousebuttonpressed) {
        clickTheThingOnce.Call(); // ClickTheThing is only called once
                                  // or until .Reset() is called
    }
}

0

คุณสามารถพิจารณาการใช้ตัวแปรคงที่

void doOnce()
{
   static bool executed = false;

   if(executed == false)
   {
      ...Your code here
      executed = true;
   }
}

คุณสามารถทำสิ่งนี้ในClickTheThing()ฟังก์ชั่นของตัวเองหรือสร้างฟังก์ชั่นอื่นและโทรClickTheThing()ในร่างกายถ้า

มันคล้ายกับแนวคิดของการใช้การตั้งค่าสถานะตามที่แนะนำในการแก้ปัญหาอื่น ๆ แต่การตั้งค่าสถานะได้รับการป้องกันจากรหัสอื่นและไม่สามารถเปลี่ยนแปลงที่อื่นเนื่องจากการท้องถิ่นกับฟังก์ชัน


1
... แต่สิ่งนี้จะป้องกันไม่ให้คุณเรียกใช้รหัสเมื่อ 'ต่อวัตถุเช่น' (ถ้านั่นคือสิ่งที่ผู้ใช้ต้องการ .. ;))
Vaillancourt

0

ฉันเจอปัญหานี้ด้วย Xbox Controller Input แม้ว่าจะไม่เหมือนกัน แต่มันก็ค่อนข้างคล้ายกัน คุณสามารถเปลี่ยนรหัสในตัวอย่างของฉันเพื่อให้เหมาะกับความต้องการของคุณ

แก้ไข: สถานการณ์ของคุณจะใช้สิ่งนี้ ->

https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagrawmouse

และคุณสามารถเรียนรู้วิธีสร้างคลาสอินพุตดิบผ่าน ->

https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input

แต่ .. ตอนนี้เข้าสู่อัลกอริธึมที่ยอดเยี่ยมที่สุด ... ไม่ได้จริงๆ แต่เฮ้ .. มันเจ๋งมาก :)

* ดังนั้น ... เราสามารถเก็บสถานะของปุ่มทุกปุ่มซึ่งถูกกดวางจำหน่ายและกดลง !!! นอกจากนี้เรายังสามารถตรวจสอบเวลาการถือครองได้ แต่ต้องใช้คำสั่ง if เพียงคำสั่งเดียวและสามารถตรวจสอบจำนวนปุ่มใด ๆ ได้ แต่มีกฎบางอย่างดูด้านล่างสำหรับข้อมูลนี้

เห็นได้ชัดว่าถ้าเราต้องการตรวจสอบว่ามีอะไรกดปล่อย ฯลฯ คุณจะทำ "ถ้า (นี่) {}" แต่นี่คือการแสดงว่าเราสามารถรับสถานะกดแล้วปิดมันในเฟรมถัดไปเพื่อให้คุณ " ismousepressed "จะเป็นเท็จในครั้งต่อไปที่คุณตรวจสอบ

รหัสแบบสมบูรณ์ที่นี่: https://github.com/JeremyDX/DX_B/blob/master/DX_B/XGameInput.cpp

มันทำงานอย่างไร..

ดังนั้นฉันไม่แน่ใจว่าค่าที่คุณได้รับเมื่อคุณแสดงให้เห็นว่ามีการกดปุ่มหรือไม่ แต่โดยทั่วไปเมื่อฉันโหลดใน XInput ฉันได้รับค่า 16 บิตระหว่าง 0 ถึง 65535 ซึ่งมีสถานะเป็นไปได้ 15 บิตสำหรับ "กด"

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

ดังนั้นสิ่งที่ฉันทำคือต่อไปนี้

ก่อนอื่นเราจะสร้างตัวแปร "CURRENT" ทุกครั้งที่เราตรวจสอบข้อมูลนี้เราตั้งค่า "หมุนเวียน" เป็นตัวแปร "ก่อนหน้า" แล้วเก็บข้อมูลใหม่เป็น "ปัจจุบัน" ตามที่เห็นที่นี่ ->

uint64_t LAST = CURRENT;
CURRENT = gamepad.wButtons;

ด้วยข้อมูลนี้ที่นี่มันเป็นที่ที่น่าตื่นเต้น !!

ตอนนี้เราสามารถทราบได้ว่าปุ่มใดที่ถูกกดลง!

BUTTONS_HOLD = LAST & CURRENT;

สิ่งนี้จะเป็นพื้นมันเปรียบเทียบสองค่าและกดปุ่มใด ๆ ที่แสดงในทั้งสองจะอยู่ 1 และทุกอย่างอื่นตั้งเป็น 0

Ie (1 | 2 | 4) & (2 | 4 | 8) จะให้ผลตอบแทน (2 | 4)

ตอนนี้เรามีปุ่มใดที่ "HELD" ลง เราสามารถรับส่วนที่เหลือ

กดง่ายมากเราใช้สถานะ "CURRENT" และลบปุ่มค้างไว้

BUTTONS_PRESSED = CURRENT ^ BUTTONS_HOLD;

การเปิดตัวเป็นสิ่งเดียวที่เราเปรียบเทียบกับรัฐสุดท้ายของเราแทน

BUTTONS_RELEASED = LAST ^ BUTTONS_HOLD;

ดูที่สถานการณ์ที่กด ถ้าสมมุติว่าเรามี 2 | 4 | กด 8 ครั้ง เราพบว่า 2 | 4 ที่จัดขึ้น เมื่อเราลบ Held Bits ออกเราจะเหลือเพียง 8 เท่านั้นนี่คือบิตที่ถูกกดใหม่สำหรับรอบนี้

สามารถนำไปใช้สำหรับปล่อยได้เช่นเดียวกัน ในสถานการณ์นี้ "LAST" ถูกตั้งค่าเป็น 1 | 2 | 4. ดังนั้นเมื่อเราลบ 2 | 4 บิต เราเหลือ 1 ข้อดังนั้นปุ่ม 1 จึงถูกนำออกใช้ตั้งแต่เฟรมสุดท้าย

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

ฉันยังต้องการจัดเก็บเอกสารข้อมูลดังนั้นแม้ว่าสถานการณ์ของฉันจะไม่สมบูรณ์ ... สิ่งที่เราทำคือตั้งค่าโฮลด์บิตที่เราต้องการตรวจสอบ

ดังนั้นทุกครั้งที่เราตั้งค่าข้อมูล Press / Release / Hold เราตรวจสอบว่าข้อมูลการพักยังคงเท่ากับการตรวจสอบบิตปัจจุบัน หากเราไม่รีเซ็ตมันก็ถึงเวลาปัจจุบัน ในกรณีของฉันฉันตั้งให้เฟรมดัชนีดังนั้นฉันรู้ว่ามันถูกระงับไว้กี่เฟรม

ข้อเสียของวิธีการนี้คือฉันไม่สามารถรับเวลาการพักรายบุคคลได้ แต่คุณสามารถตรวจสอบหลายบิตพร้อมกันได้ นั่นคือถ้าฉันตั้งบิตค้างไว้ที่ 1 | 16 ถ้าทั้ง 1 หรือ 16 ไม่ถูกระงับสิ่งนี้จะล้มเหลว ดังนั้นจึงต้องการให้ปุ่มเหล่านั้นทั้งหมดถูกกดค้างไว้เพื่อดำเนินการฟ้องร้องต่อไป

จากนั้นถ้าคุณดูในรหัสคุณจะเห็นการเรียกใช้ฟังก์ชันที่เรียบร้อยทั้งหมด

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


ดังนั้นโดยทั่วไปคุณใช้ bitfield แทนบูลตามที่ระบุไว้ในคำตอบอื่น ๆ ?
Vaillancourt

ใช่แล้วฮ่า ๆ ๆ ..
Jeremy Trifilo

มันสามารถใช้สำหรับความต้องการของเขาแม้ว่าด้วยโครงสร้าง msdn ด้านล่าง "usButtonFlags" มีค่าเดียวของปุ่มทุกรัฐ docs.microsoft.com/en-us/windows/desktop/api/winuser/…
Jeremy Trifilo

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

ใช่ฉันแค่ให้สถานการณ์ส่วนบุคคลที่เกี่ยวข้องและสิ่งที่ฉันทำเพื่อให้บรรลุ ฉันจะทำความสะอาดในภายหลัง แต่แน่นอนและบางทีฉันจะเพิ่มแป้นพิมพ์และเมาส์และให้วิธีการที่
Jeremy Trifilo

-3

โง่วิธีราคาถูก แต่คุณสามารถใช้สำหรับวง

for (int i = 0; i < 1; i++)
{
    //code here
}

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