- vừa được xem lúc

Xử lý input cho multiplatform trong Unity

0 0 18

Người đăng: Hoa Truong

Theo Viblo Asia

Tiêu đề nghe thực sự khá nguy hiểm nhưng đây là một kinh nghiệm mình học hỏi được qua quá trình thực hiện các project cần support nhiều platform (PC, WebGL, Mobile). Việc handle input của các platform có thể khác nhau ví dụ như Mobile (touch screen, sensor), PC (keyboard, mouse), console (gamepad).

Để lấy ví dụ cho việc xử lý input cho multiplatform, mình lấy một ví dụ khá tiêu biểu: hệ thống điều khiển nhân vật. Hệ thống bao gồm các hành động như: di chuyển, chạy, bắn, nhảy, ngồi, hệ thống cần chơi được cho cả PC, Mobile. Ví dụ cho hệ thống này có thể kể đến như Genshin Impact, PUBG, Leage of Legend mobile,...

  • PC: sử dụng keyboard với các phím A,W,D,S để di chuyển, Space nhảy, Shift chạy, Chuột trái để tấn công
  • Mobile: Các phím trên UI

Hướng giải quyết:

Bắt đầu với cách tiếp cận mà chúng ta thường xuyên sử dụng, bên dưới là đoạn code mà trước đây mình hay sử dụng để implement chức năng này:

public class CharacterMoveController: MonoBehaviour { private void Update() { if (Input.GetKey(KeyCode.A)) { MoveLeft(); } if (Input.GetKey(KeyCode.D)) { MoveRight(); } if (Input.GetKey(KeyCode.W)) { MoveForward(); } if (Input.GetKey(KeyCode.S)) { MoveBackward(); } if (Input.GetKey(KeyCode.RightShift)) { Run(); } if (Input.GetKeyDown(KeyCode.Space)) { Jump(); } if (Input.GetMouseButton(0)) { Shoot(); } } public void MoveLeft() { } public void MoveRight() { } public void MoveForward() { } public void MoveBackward() { } public void Run() { } public void Shoot() { } public void Jump() { } public void Attack() { }
}

Dựa vào đoạn code trên thì chúng ta nhận ra được có 2 bước trong việc xử lý input là lắng nghe các Input mà player nhập vào và thực thi hành động đúng với Input đó.

Vậy cách giải quyết là gì? Chúng ta tách chúng ra thành 2 thành phần riêng biệt là Input HandlerInput Executor

  • Input Handler: là object đảm nhận nhiệm vụ lắng nghe các input của player và kiểm tra các input đó thỏa mãn các điều kiện để thực thi các hành động tương ứng. Với vị dụ trên thì đoạn code ở hàm Update() đang hoặt động như một Input Handler và đây cũng là nơi để kiểm tra xem người chơi có đang nhấn phím di chuyển, nhảy, hay click chuột để bắn hay không. Nếu ở trên console thì có thể là kiểm tra cần analog hoặc nút trigger trên tay cầm.
  • Input Executor: là object nhận các sự kiện được raise lên từ Input Handler để thực thi hành động nào đó.

Việc tách rời 2 thực thể này giúp code của chúng ta bảo toàn được nguyên lý Single Responsibility trong SOLID.

Input Handler chỉ đảm nhiệm lắng nghe input từ player, không cần quan tâm đến các logic được thực thi bởi các input đó và chỉ bắn ra các sự kiện để những Input Executor lắng nghe và thực thi logic.

Input Executor không cần quan tâm đến Input của player là loại gì, Keyboard, Mouse, Touch Screen hay Gamepad. Chỉ cần output các function hay interface để thực hiện các hành động nhằm phản hồi lại các Input mà player nhập vào.

Vì vậy khi có sự thay đổi từ Input ta chỉ việc xử lý các thay đổi đó ở phía Input Handler cũng như các thay đổi logic ở phía Input Executor mà không làm ảnh hưởng hoặc bị phụ thuộc lẫn nhau.

Cơ bản là vậy, ý tưởng rất hay nhưng show code please !!

public static class InputEvents
{ public static UnityEvent moveLeftEvent; public static UnityEvent moveRightEvent; public static UnityEvent moveForwardEvent; public static UnityEvent moveBackwardEvent; public static UnityEvent runEvent; public static UnityEvent jumpEvent; public static UnityEvent shootEvent;
}

public class PCInputHandler : MonoBehaviour
{ [SerializeField] private KeyCode moveLeftKey; [SerializeField] private KeyCode moveRightKey; [SerializeField] private KeyCode moveForwardKey; [SerializeField] private KeyCode moveBackwardKey; [SerializeField] private KeyCode runKey; [SerializeField] private KeyCode jumpKey; [SerializeField] private int shootMouseButton; private void Update() { if (Input.GetKey(moveLeftKey)) { InputEvents.moveLeftEvent.Invoke(); } if (Input.GetKey(moveRightKey)) { InputEvents.moveRightEvent.Invoke(); } if (Input.GetKey(moveForwardKey)) { InputEvents.moveForwardEvent.Invoke(); } if (Input.GetKey(moveBackwardKey)) { InputEvents.moveBackwardEvent.Invoke(); } if (Input.GetKeyDown(runKey)) { InputEvents.runEvent.Invoke(); } if (Input.GetKeyDown(jumpKey)) { InputEvents.jumpEvent.Invoke(); } if (Input.GetMouseButton(shootMouseButton)) { InputEvents.shootEvent.Invoke(); } }
}

public class CharacterMoveController : MonoBehaviour
{ public class CharacterMoveController : MonoBehaviour
{ private void Start() { InputEvents.moveLeftEvent.AddListener(OnMoveLeftEvent); InputEvents.moveRightEvent.AddListener(OnMoveRightEvent); InputEvents.moveForwardEvent.AddListener(OnMoveForwardEvent); InputEvents.moveForwardEvent.AddListener(OnMoveBackwardEvent); InputEvents.runEvent.AddListener(OnMoveLeftEvent); InputEvents.jumpEvent.AddListener(OnJumpEvent); InputEvents.shootEvent.AddListener(OnShootEvent); } private void OnDestroy() { InputEvents.moveLeftEvent.RemoveListener(OnMoveLeftEvent); InputEvents.moveRightEvent.RemoveListener(OnMoveRightEvent); InputEvents.moveForwardEvent.RemoveListener(OnMoveForwardEvent); InputEvents.moveForwardEvent.RemoveListener(OnMoveBackwardEvent); InputEvents.runEvent.RemoveListener(OnRunEvent); InputEvents.jumpEvent.RemoveListener(OnJumpEvent); InputEvents.shootEvent.RemoveListener(OnShootEvent); } private void OnMoveRightEvent() { MoveRight(); } private void OnMoveForwardEvent() { MoveForward(); } private void OnMoveBackwardEvent() { MoveBackward(); } private void OnMoveLeftEvent() { MoveLeft(); } private void OnRunEvent() { Run(); } private void OnJumpEvent() { Jump(); } private void OnShootEvent() { Shoot(); } public void MoveLeft() { } public void MoveRight() { } public void MoveForward() { } public void MoveBackward() { } public void Run() { } public void Shoot() { } public void Jump() { } public void Attack() { }
} 

Ở trên là code của mình sau khi phân tách Input HandlerInput Executor. Ở đây mình phân tách đống code kiểm tra player input trong hàm Update ở class CharacterMoveControl cũ ra class mới tên là PCInputHandler và xem class này như là một Input Handler cho PC.

Tại sao lại có sự xuất hiện của class static InputEvents? class này mình sử dụng như một version thu gọn của Observer Pattern đóng vai trò như cây cầu kết nối giữa Input HandlerInput Executor.

Vậy giờ muốn thêm chức năng nhận Input từ GamePad thì sao ? Có ngay thưa sếp.


public class GamePadInputHandler : MonoBehaviour
{ [SerializeField] private GamePad gamePad; [SerializeField] private int moveAnalog; [SerializeField] private int runButton; [SerializeField] private int jumButton; [SerializeField] private int shootButton; private void Update() { if (gamePad.GetAnalog(moveAnalog).x < 0) { InputEvents.moveLeftEvent.Invoke(); } if (gamePad.GetAnalog(moveAnalog).x > 0) { InputEvents.moveLeftEvent.Invoke(); } if (gamePad.GetAnalog(moveAnalog).y < 0) { InputEvents.moveBackwardEvent.Invoke(); } if (gamePad.GetAnalog(moveAnalog).y > 0) { InputEvents.moveForwardEvent.Invoke(); } if (gamePad.GetButton(runButton)) { InputEvents.runEvent.Invoke(); } if (gamePad.GetButton(jumButton)) { InputEvents.jumpEvent.Invoke(); } if (gamePad.GetButton(shootButton)) { InputEvents.shootEvent.Invoke(); } }
}

Vậy là mình đã giải thích và hướng dẫn giải quyết cách xử lý input hỗ trợ multiplatform. Với cách này các bạn có thể áp dụng để xử lý các dự án cần yêu cầu xử lý nhiều loại input mà không phải đắn đo về khả năng mở rộng hay khó khăn trong việc bảo trì code. Ngoài ra với hướng giải quyết này cũng có nhiều ứng dụng hay mà mình sẽ đề cập trong bài sau.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Học lập trình game cần những gì?

Lập trình game là làm gì. Các ngôn ngữ các bạn có thể sử dụng để lập trình game : C, C++, C#, Java, Python,... Các bước cơ bản để lập trình game. . Hiển thị: Đã là game thì hiển thị không thể thiếu, lúc đầu các bạn chỉ làm cho phần hiển thị thật đơn giản, các bạn đừng quá chú tâm vào việc làm sao ch

0 0 32

- vừa được xem lúc

Open Source Story: Agar.IO Clone

Open Source Story: Agar.IO Clone.

0 0 25

- vừa được xem lúc

Game of Life

Game of Life. Game of Life của Conway là một trò mô phỏng khá là nổi tiếng.

0 0 65

- vừa được xem lúc

Chuyện biểu diễn ma trận trên máy tính

Chuyện biểu diễn ma trận trên máy tính. Cách đây mấy hôm mình có share cái screenshot trên Facebook, khoe linh tinh vụ mình đang viết lại cái CHIP-8 emulator bằng Rust.

0 0 31

- vừa được xem lúc

Làm game Flappy Bird trên Arduino

Làm game Flappy Bird trên Arduino. Giới thiệu một tí.

0 0 28

- vừa được xem lúc

Thuật toán Minimax (AI trong Game)

Vừa qua mình có làm game dạng như caro và đã làm AI cho nó có dùng thuật toán minimax thấy hay hay nên post lên chia sẻ cho mọi người cùng tham khảo. Bài viết này mình chỉ viết về những cái cơ bản của

0 0 51