Developing a Player Controller for Mobile Games
The player controller is the most tactile component of any game. A poor controller is immediately noticeable: input lag, inaccuracy, character drifting after releasing your finger. A good one is invisible because it does exactly what was expected.
Architecture: Separate Input, Logic, and Presentation
A common mistake: PlayerController.cs contains touch reading, movement physics, and animation calls all mixed together. This works until the first non-standard requirement—freezing the player in a cutscene, supporting a gamepad, adding auto-aim.
The correct structure:
-
InputReader— only reads touch/keyboard/gamepad via Input System Package. Publishes events (OnMove,OnJump,OnAttack), knows nothing about the player. -
PlayerLocomotion— handles movement. AcceptsVector2 moveInput, managesCharacterControllerorRigidbody. No direct Input reading. -
PlayerAnimator— reads state fromPlayerLocomotion(speed, isGrounded, isAttacking), managesAnimator. UsesAnimator.SetFloatwith damping:animator.SetFloat("Speed", targetSpeed, 0.1f, Time.deltaTime).
This separation enables: testing logic without Input, plugging in AI controllers instead of the player, implementing replays by swapping InputReader.
Touch Control: Virtual Joystick vs Swipe vs Tap-to-Move
The choice of control scheme is a critical design decision that affects level design for the entire game.
Virtual joystick (floating joystick): best for action and platformers. Implementation: IPointerDownHandler captures the touch point, IDragHandler calculates offset, normalizes to Vector2. Important: don't pin the joystick position—a floating joystick (centered at the first touch point) is more ergonomic than fixed, reduces thumb fatigue.
Swipe controls for runners and puzzle-action: Vector2 delta = currentPos - startPos. If delta.magnitude > threshold && Time.time - touchStartTime < maxSwipeTime—it's a swipe. Direction—Mathf.Atan2(delta.y, delta.x), quantized to 4 or 8 directions.
Tap-to-move for isometric RPGs and strategy games: Camera.main.ScreenToWorldPoint(touch.position) → NavMesh Sample Position → NavMeshAgent.SetDestination. On mobile, showing a "destination marker" is critical—without it, players don't understand whether their tap registered.
Input Buffering and Game Feel
For action games: if the player pressed "attack" 2 frames before it's technically possible (character still in previous attack animation), the action should execute at the first opportunity—this is input buffering.
Implementation: Queue<PlayerAction> with TTL (time to live). On each FixedUpdate, check: is there a valid command in the buffer + can we execute it now? A 3–6 frame buffer (50–100ms at 60fps) makes controls significantly more responsive without changing game mechanics.
Timeline: a complete player controller with one control scheme, animations, and basic physics—2–4 weeks within a project.







