ข้ามไปที่เนื้อหา

เปิดใช้งาน callback อินพุตบนหน้าจอล็อก

เมื่อ PC โฮสต์เปลี่ยนเป็นหน้าจอล็อกของ Windows อินพุตคีย์บอร์ดและเมาส์ในระดับระบบปฏิบัติการ (OS) จะถูกบล็อกโดยนโยบายความปลอดภัยของ OS ในสภาพแวดล้อมระยะไกล ยังสามารถสตรีมหน้าจอและเสียงได้ แต่จะไม่สามารถควบคุมเกมจริงได้

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

Remote Play Plugin มีฟีเจอร์ เล่นโดยไม่ปลดล็อกหน้าจอล็อก ในการตั้งค่า Remote Play ของ Hive Console เมื่อเปิดใช้งานฟีเจอร์นี้ อินพุตทั้งหมด เช่น คีย์บอร์ด เมาส์ ทัช จอยสติ๊ก วีล และซูม จะถูกส่งตรงไปยัง callback ของเกม

พฤติกรรมหลัก

  • การส่งผ่าน callback: อินพุตทั้งหมดจะถูกส่งไปยังฟังก์ชัน callback ที่ลงทะเบียนด้วย RegisterCallback ในรูปแบบ eventType: "Control" โดยทำงานเหมือนกันไม่ว่าสถานะจะล็อกหรือไม่ และยังส่งผ่าน callback ต่อไปแม้ปลดล็อกหน้าจอแล้ว
  • ข้ามเส้นทางอินพุตของ OS: จะไม่ใช้เส้นทาง SendInput ของ OS อีกต่อไป จึงรับอินพุตได้อย่างสม่ำเสมอไม่ว่า Windows จะล็อกอยู่หรือไม่
  • ฉีดเข้าเกมโดยตรง: เกมจะนำข้อมูลอินพุตที่ได้รับผ่าน callback ส่งเข้า internal input system โดยตรง และประมวลผลการควบคุมเหมือนกันทั้งในสถานะล็อกและสถานะปกติ

สิ่งที่ต้องเตรียม

เตรียมรายการต่อไปนี้ก่อนใช้ฟีเจอร์นี้

Warning

หากเปิดใช้งานฟีเจอร์ เล่นโดยไม่ปลดล็อกหน้าจอล็อก ใน Hive Console โดยไม่ได้ลงทะเบียน callback ผ่าน RegisterCallback อินพุตทั้งหมดจะสูญหายและทำให้ควบคุมระยะไกลได้ยาก ต้องลงทะเบียน callback ก่อนเสมอ

โปรโตคอลเหตุการณ์ Control

หลังจากเปิดใช้งานฟีเจอร์ เล่นโดยไม่ปลดล็อกหน้าจอล็อก อินพุตที่ส่งผ่าน callback จะใช้รูปแบบ JSON เดียวกับ เหตุการณ์แชท และค่า eventType จะถูกกำหนดเป็น "Control" เสมอ ประเภทอินพุตจริงจะแยกด้วย controlType ภายใน

โครงสร้าง envelope ทั่วไป

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "<Key | Click | Wheel | Zoom>",
            "controlValue": {
                "value"  : "<controlType-specific-value>",
                "action" : "<controlType-specific-action>"
            }
        }
    },
    "etc"       : { }
}
ฟิลด์ คำอธิบาย
version กำหนดเป็น "1.02.00"
eventType เป็น "Control" เสมอ
eventValue.value.controlType ประเภทอินพุต มี 4 ประเภท: "Key" / "Click" / "Wheel" / "Zoom"
eventValue.value.controlValue.value ข้อมูลตาม controlType เช่น พิกัดหรือคีย์โค้ด
eventValue.value.controlValue.action สถานะหรือ action ตาม controlType
etc ออบเจ็กต์ว่าง

controlType = "Key" - อินพุตคีย์บอร์ด

ส่งสถานะกดหรือปล่อยของคีย์เดี่ยวบนคีย์บอร์ด ทั้งอินพุตคีย์บอร์ดและอินพุตจอยสติ๊กที่ผู้ใช้สร้างใน web client จะถูกแปลงเป็น controlType

ฟิลด์ ค่า
controlValue.value สตริงเลขฐานสิบหกของ Windows Virtual-Key Code เช่น "0x41" = 'A', "0x57" = 'W', "0x20" = Space
controlValue.action "Down" หรือ "Up"

โค้ดทิศทางของจอยสติ๊กจะถูกแยกเป็นเหตุการณ์คีย์เดี่ยวตามการตั้งค่า mapping (WASD / DIRECTION) และส่งเป็น controlType ดังนั้นฝั่งเกมจะมองเห็นอินพุตคีย์บอร์ดและจอยสติ๊กในรูปแบบเดียวกัน การเคลื่อนที่แนวทแยงจะถูกโฮสต์แยกเป็นเหตุการณ์คีย์เดี่ยว 2 รายการก่อนส่ง

ตัวอย่าง - กดคีย์ 'A'

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "Key",
            "controlValue": { "value": "0x41", "action": "Down" }
        }
    },
    "etc"       : { }
}

ตัวอย่าง - จอยสติ๊กขึ้น (คีย์ 'W' เมื่อใช้ WASD mapping)

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "Key",
            "controlValue": { "value": "0x57", "action": "Down" }
        }
    },
    "etc"       : { }
}

controlType = "Click" - อินพุตพิกัดเมาส์และทัช

ส่งพิกัดคลิกเมาส์ การเคลื่อนที่ของเมาส์ และทัช ทั้งอินพุตเมาส์และทัชที่ผู้ใช้สร้างใน web client จะถูกแปลงอัตโนมัติเป็นพิกัดพิกเซลของ client area ในหน้าต่างเกมโดยอิงจาก GetClientRect() ก่อนส่ง

ฟิลด์ ค่า
controlValue.value รูปแบบ "x#y" หน่วยคือ พิกัดพิกเซลของ client area ในหน้าต่างเกม โดยมีทศนิยม 1 ตำแหน่ง พิกัด normalized 0-1000 จาก web client จะถูกโฮสต์แปลงเป็นพิกเซลก่อนส่ง
controlValue.action "Down", "Up" หรือ "Move"

Note

สำหรับอินพุตทัช ข้อมูล touchID จะไม่ถูกเก็บไว้ระหว่างการแปลงเป็นประเภท Click

ข้อมูลอ้างอิงการแปลงพิกัด

curWidth / curHeight คือขนาด client area ของหน้าต่างเกมตาม GetClientRect() ดังนั้นในเกมให้ใช้เป็นพิกัดหน้าต่างได้โดยตรง

ตำแหน่งใน web client พิกัด normalized (inbound) ตัวอย่างพิกัดพิกเซล (curWidth=1053, curHeight=1585)
ซ้ายบน "0.0#0.0" "0.0#0.0"
ขวาบน "1000.0#0.0" "1053.0#0.0"
ซ้ายล่าง "0.0#1000.0" "0.0#1585.0"
ขวาล่าง "1000.0#1000.0" "1053.0#1585.0"
กึ่งกลาง "500.0#500.0" "526.5#792.5"

ตัวอย่าง - คลิกเมาส์ (Down -> Up)

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "Click",
            "controlValue": { "value": "526.5#792.5", "action": "Down" }
        }
    },
    "etc"       : { }
}

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "Click",
            "controlValue": { "value": "526.5#792.5", "action": "Up" }
        }
    },
    "etc"       : { }
}

ตัวอย่าง - ลาก (Move)

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "Click",
            "controlValue": { "value": "645.1#681.7", "action": "Move" }
        }
    },
    "etc"       : { }
}

controlType = "Wheel" - วีลเมาส์

ส่ง action การเลื่อนด้วยวีลเมาส์

ฟิลด์ ค่า
controlValue.value รูปแบบ "x.0#y.0" เป็น พิกัดพิกเซลปัจจุบันของเคอร์เซอร์เมาส์ โดยอิงจาก client area ของหน้าต่างเกม ข้อมูล step การเลื่อนที่ web client ส่งมาจะสูญหาย
controlValue.action "Up" (เลื่อนขึ้น) หรือ "Down" (เลื่อนลง)

ตัวอย่าง - wheel down (ตำแหน่งเมาส์ 1234.0 px, 567.0 px)

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "Wheel",
            "controlValue": { "value": "1234.0#567.0", "action": "Down" }
        }
    },
    "etc"       : { }
}

controlType = "Zoom" - ซูมเข้าและซูมออก

ส่ง action ซูมเข้าและซูมออกสำหรับกล้องหรือหน้าจอแผนที่

ฟิลด์ ค่า
controlValue.value กำหนดเป็น "1" เสมอ ข้อมูล step การเลื่อนที่ web client ส่งมาจะสูญหาย
controlValue.action "In" (ซูมเข้า) หรือ "Out" (ซูมออก)

ตัวอย่าง - ซูมเข้า

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "Zoom",
            "controlValue": { "value": "1", "action": "In" }
        }
    },
    "etc"       : { }
}

ตัวอย่าง - ซูมออก

{
    "version"   : "1.02.00",
    "eventType" : "Control",
    "eventValue": {
        "value": {
            "controlType" : "Zoom",
            "controlValue": { "value": "1", "action": "Out" }
        }
    },
    "etc"       : { }
}

สรุป mapping การแปลงประเภทอินพุต

ตารางต่อไปนี้แสดงโครงสร้าง mapping ที่แปลงประเภทอินพุตต้นทางจาก web client เป็นเหตุการณ์ callback ของโฮสต์

อินพุตจาก web client Callback controlType การแปลงหลัก / ข้อมูลที่สูญหาย
Key "Key" ค่า VK เลขฐานสิบหกคงเดิม
Joystick "Key" โค้ดทิศทาง -> WASD / DIRECTION mapping -> แยกเป็นคีย์เดี่ยว
Click "Click" พิกัด normalized -> แปลงเป็นพิกเซล
Touch "Click" พิกัด normalized -> แปลงเป็นพิกเซล, ไม่ส่ง touchID (ตัดสิน multi-touch ด้วย Down ซ้ำ)
Wheel "Wheel" ข้อมูล step -> แทนที่ด้วยพิกัดพิกเซลของเมาส์
Zoom "Zoom" ข้อมูล step -> กำหนดเป็น "1" เสมอ

เหตุการณ์สถานะ (Event) และแชท (Message) จะไม่ถูกส่งผ่านเหตุการณ์ Control นี้ แต่จะถูกส่งแยกตามข้อกำหนดโปรโตคอลเดิม

คู่มือการประมวลผลอินพุตฝั่งเกม

เมื่อเปิดใช้งานฟีเจอร์ เล่นโดยไม่ปลดล็อกหน้าจอล็อก ในการตั้งค่า Remote Play ของ Hive Console แล้ว Remote Play จะไม่ส่งอินพุตไปยังคิวอินพุตของระบบปฏิบัติการ (OS) แต่จะส่งตรงไปยังเกมผ่าน callback ที่ลงทะเบียนไว้

ดังนั้น อินพุตที่ได้รับจาก callback ต้องถูกส่งตรงไปยังระบบอินพุตภายในเกม เช่น Unity Input System หรือ Unreal Input Subsystem เพื่อประมวลผล

วิธีนี้เป็นโครงสร้างสำหรับหลีกเลี่ยงข้อจำกัดของ Windows lock screen ที่จำกัดการฉีดอินพุตระดับ OS เช่น SendInput และ SendKey เนื่องจากอินพุตถูก dispatch โดยตรงภายในโปรเซสเกม จึงแนะนำให้ใช้เส้นทางประมวลผลเดียวกันไม่ว่าสถานะจะล็อกหรือปกติ

กล่าวคือ หลังจากเปิดใช้งานฟีเจอร์ เล่นโดยไม่ปลดล็อกหน้าจอล็อก ควร implement ให้เกมรับอินพุตทั้งหมดที่ส่งผ่าน callback นี้และส่งต่อไปยังระบบอินพุตภายในเกม เพื่อให้การประมวลผลอินพุตและ Remote Play ทำงานสม่ำเสมอไม่ว่าสถานะหน้าจอล็อกจะเป็นอย่างไร

  • Unity: Dispatch ด้วย InputSystem.QueueEvent หรือ input manager ที่กำหนดเอง
  • Unreal Engine: Dispatch ด้วย Slate event เช่น FSlateApplication::OnKeyDown / OnMouseButtonDown หรือใช้ input manager ของเกมเอง

คู่มือการเรนเดอร์และเสียงเบื้องหลังในสภาพแวดล้อมหน้าจอล็อก

หากต้องการใช้ Remote Play ในสภาพแวดล้อมหน้าจอล็อกของ Windows เกมต้องสามารถทำงานได้ตามปกติแม้อยู่ใน background เกมเอนจินส่วนใหญ่จะหยุด rendering จำกัด frame หรือหยุดเสียงชั่วคราวเพื่อลดการใช้ CPU เมื่อเสีย focus หรือเข้าสู่ background ดังนั้นเพื่อรักษาการสตรีมระยะไกลให้ปกติ ต้องตั้งค่าให้โปรเซสเกมทำงานต่อใน background

1. นโยบายที่แนะนำตามสถานะการเชื่อมต่อ Remote Play

เกมต้องตรวจสอบสถานะการเชื่อมต่อ Remote Play เพื่อ判断ว่ามีผู้ใช้ระยะไกลเชื่อมต่ออยู่หรือไม่ พฤติกรรมที่แนะนำมีดังนี้

สถานะการเชื่อมต่อ พฤติกรรมที่แนะนำ
ไม่ได้เชื่อมต่อ คงนโยบายเดิมของเกมไว้ สามารถหยุด rendering และเสียงเมื่อเข้าสู่ background ได้
กำลังเชื่อมต่อ คง rendering ไว้แม้อยู่ใน background และคงการส่งออกเสียงไว้แม้อยู่ใน background

สามารถ判断สถานะการเชื่อมต่อได้ผ่าน callback เหตุการณ์สถานะ (eventType: "Event", value REMOTE_PLAY_CONNECTED / REMOTE_PLAY_DISCONNECTED)

2. คงการเรนเดอร์เบื้องหลัง

ต้องให้ rendering loop ทำงานต่อ เพื่อให้ Remote Play จับภาพหน้าจอได้แม้ Windows จะล็อกอยู่ ตัวอย่างรายการตรวจสอบมีดังนี้

  • อย่าหยุด frame update
  • คง render loop ไว้
  • คง camera rendering ไว้
  • คงการอัปเดต UI ไว้
  • ตรวจสอบนโยบายจำกัด frame ใน background

Warning

หาก rendering หยุดใน background Remote Play อาจส่งหน้าจอล่าสุดต่อไปหรือแสดงหน้าจอสีดำ

3. คงเอาต์พุตเสียงเบื้องหลัง

Remote Play จับเสียงที่ออกจากโปรเซสเกมและส่งให้ผู้เล่นระยะไกล ดังนั้นต้องปิดนโยบายที่บล็อกเสียงใน background เช่นรายการต่อไปนี้

  • หยุดเสียงชั่วคราวเมื่อ Focus Lost
  • หยุด Audio Engine เมื่อ Window Deactivate
  • เปิดใช้งาน Background Mute

Warning

หากเอาต์พุตเสียงหยุดใน background ผู้เล่นระยะไกลจะไม่ได้ยินเสียงเกม

รายการตรวจสอบและข้อมูลอ้างอิงตามเอนจิน

Unity

โดยค่าเริ่มต้น Unity อาจจำกัดการอัปเดตเมื่อเกมเข้าสู่ background เมื่อใช้ฟีเจอร์ Remote Play บนหน้าจอล็อก แนะนำให้เปิดใช้งานการทำงานใน background ดังนี้

Application.runInBackground = true;

หากมี logic แบบ custom ที่หยุดเสียงเมื่อเสีย focus ให้จัดการเป็นข้อยกเว้นโดยพิจารณาสถานะการเชื่อมต่อ Remote Play

Unreal Engine

ในโปรเจกต์ Unreal Engine ต้องตรวจสอบว่ามีการตั้งค่าที่จำกัด Tick, rendering หรือการประมวลผลเสียงเมื่อเสีย focus หรือไม่ แนะนำให้ตรวจสอบรายการต่อไปนี้เป็นพิเศษ

  • Background Tick ทำงานหรือไม่
  • มีการ Pause เมื่อ Focus Lost หรือไม่
  • Audio Mixer ทำงานหรือไม่
  • นโยบายจำกัด FPS ใน background

ขณะเชื่อมต่อ Remote Play ต้อง implement ให้ game loop ปกติคงอยู่แม้อยู่ใน background

สถานการณ์การทำงานที่แนะนำ

  1. ผู้ใช้เปลี่ยนเป็นหน้าจอล็อกของ Windows
  2. Remote Play client เชื่อมต่อ
  3. เกมตรวจพบสถานะการเชื่อมต่อและคง background rendering กับ audio output ไว้
    • การเรนเดอร์เกมดำเนินต่อ
    • เอาต์พุตเสียงเกมดำเนินต่อ
  4. Remote Play Plugin จับภาพหน้าจอและเสียงแล้วส่งได้ตามปกติ
  5. ผู้ใช้ระยะไกลรับภาพและเสียงเกมแบบ real time
  6. อินพุตถูกส่งไปยังระบบอินพุตภายในเกมผ่าน callback อินพุต Control ของ Remote Play

สรุปรายการตรวจสอบที่จำเป็น

เพื่อให้ Remote Play ทำงานได้ตามปกติในสภาพแวดล้อมหน้าจอล็อก ต้องเป็นไปตามเงื่อนไขทั้งสองข้อต่อไปนี้

  • เปิดใช้งานฟีเจอร์ เล่นโดยไม่ปลดล็อกหน้าจอล็อก ในการตั้งค่า Remote Play ของ Hive Console เพื่อส่งอินพุตไปยังระบบอินพุตภายในเกม
  • คง rendering และ audio processing ไว้ใน background ระหว่างเชื่อมต่อ Remote Play

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

เอกสารที่เกี่ยวข้อง