Back to Hideout
dev
Game Development

游戏开发-Day1

2026-05-03
game-day1
TOP
SECRET

一、输入读取,脚本 PlayerInput.cs

MoveX 水平移动输入;JumpPressed 跳跃输入;JumpHeld 蓄力跳跃输入; 公开参数,私有修改。

public float MoveX { get; private set; }
public float JumpPressed { get; private set; }
public float JumpHeld { get; private set; }

void Update(){
    MoveX = Input.GetAxisRaw("Horizontal");

    JumpPressed = Input.GetButtonDonw("Jump");
    JumpHeld = Input.GetButton("Jump");
}

二、玩家控制,脚本 PlayerController.cs

完成了基础的玩家控制:移动,转身,跳跃,土狼和预输入,下落。

1.玩家输入以及本体组件

先从输入脚本(PlayerInput.cs)中获取玩家的输入和其他必须的组件。

private PlayerInput input;
private Rigidbody2D rb; // 玩家物体刚体
private SpriteRenderer sr;// 玩家物体图片渲染

void Awake(){
    input = GetComponent<PlayerInput>();
    rb = GetComponent<Rigidbody2D>();
    sr = GetComponent<SpriteRenderer>();
}

2.移动

[Header("Movement")]
[SerializeField]private float moveSpeed = 4f;
[SerializeField]private float jumpForce = 7.5f; // 这里顺便写上跳跃的速度

void HandleMove(){
    rb.velocity = new Vector2(input.MoveX * moveSpeed, rb.velocity.y);
}

3.角色转身

这里单纯是角色的图片进行翻转,不包括任何子物体,后续可能修改。

void HandleFlip(){
    if(input.MoveX != 0){
        sr.flipX = input.MoveX < 0;
    }
}

4.跳跃

跳跃必须检测是否在地面,不能无限跳。

[Header("Ground Check")]
[SerializeField]private Transform groundCheck; // 角色脚下检测地面的子物体中心
[SerializeField]private float groundCheckRadius = 0.2f; // 检测半径
[SerializeField]private LayerMask groundLayer; // 检测的目标图层

private bool isGrounded;

void CheckGround(){
    if(Physics2D.OverlapCircle(groundCheck.positon, groundCheckradius, groundLayer)){
        isGrounded = true;
    }
    else{
        isGrounded = false;
    }
}

void HandleJump(){
    if(input.JumpPressed && isGrounded){
        rb.velocity = new Vector2(rb.velocity.x, JumpForce);
    }
}

5.跳跃的土狼与预输入

很明显上面实现的跳跃极其僵硬,玩家的输入只要稍快或稍慢都会使跳跃失败,出现按下跳跃键却没能成功跳跃的失灵感。 如果输入提前了,设置一定量的预输入,使得落地后自动跳跃。 如果输入延后了,设置一定量的延迟,使得在滞空后一小段时间也能跳跃。

[Header("Better Jump")]
[SerializeField]private float coyoteTime = 0.1f;
[SerializeField]private float jumpBufferTime = 0.1f;

private bool isGrounded;
private float coyoteTimer;
private float jumpBufferTimer;

void Update(){
    if(input.JumpPressed){
        jumpBufferTimer = jumpBufferTime; // 监听到跳跃输入,记录预输入
    }
    else if(jumpBufferTimer > 0){
        jumpBufferTimer -= Time.deltaTime;
    }
}

void CheckGround(){
    if(Physics2D.OverlapCircle(groundCheck.positon, groundCheckradius, groundLayer)){
        isGrounded = true;
        coyoteTimer = coyoteTime; // 接触地面后允许跳跃
    }
    else{
        isGrounded = false;
        coyoteTimer -= Time.deltaTime; // 流逝该段时间,结束后不允许跳跃
    }
}

void HandleJump(){
    // 在 预输入时间内 和 允许跳跃时间内 触发跳跃
    if(jumpBufferTimer > 0 && coyoteTimer > 0){
        rb.velocity = new Vector2(rb.velocity.x, JumpForce);

        jumpBufferTimer = 0;
        coyoteTimer = 0;
    }
}

6.竖直方向的物理优化

加快角色下落速度;增强轻跳的效果;限制下落的最大速度避免穿模。

[Header("Better Jump")]
[SerializeField]private float fallMultiplier = 2.5f;
[SerializeField]private float lowJumpMultiplier = 2f;

void BetterJump(){
    if(rb.velocity.y < 0){
        rb.velocity = rb.velocity.up * Physics2D.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
    }
    else if(rb.velocity.y > 0 && !Input.JumpHeld){
        rb.velocity = rb.velocity.up * Physics2D.gravity.y * (lowJumpMultiplier - 1) * Time.deltaTime;
    }
    if(rb.velocity.y < -20f){
        rb.velocity = new Vector2(rb.velocity.x, -20f);
    }
}

7.完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class PlayerController : MonoBehaviour
{
    [Header("Movement")]
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private float jumpForce = 10f;

    [Header("Ground Check")]
    [SerializeField] private Transform groundCheck;
    [SerializeField] private float groundCheckRadius = 0.2f;
    [SerializeField] private LayerMask groundLayer;

    [Header("Better Jump")]
    [SerializeField] private float fallMultiplier = 2.5f;
    [SerializeField] private float lowJumpMultiplier = 2f;
    [SerializeField] private float coyoteTime = 0.1f;
    [SerializeField] private float jumpBufferTime = 0.1f;

    private Rigidbody2D rb;
    private PlayerInput input;

    private bool isGrounded;
    private float coyoteTimer;
    private float jumpBufferTimer;

    private SpriteRenderer sr;

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        input = GetComponent<PlayerInput>();
        sr = GetComponent<SpriteRenderer>();
    }

    void Update()
    {
        CheckGround();

        if (input.JumpPressed)
        {
            jumpBufferTimer = jumpBufferTime;
        }
        else if (jumpBufferTimer > 0)
        {
            jumpBufferTimer -= Time.deltaTime;
        }

        HandleJump();
        HandleFlip();
    }
    
    private void FixedUpdate()
    {
        HandleMove();
        BetterJump();
    }

    void HandleMove()
    {
        rb.velocity = new Vector2(input.MoveX * moveSpeed, rb.velocity.y);
    }

    void HandleJump()
    {
        if (jumpBufferTimer > 0 && coyoteTimer > 0)
        {
            rb.velocity = new Vector2(rb.velocity.x, jumpForce);

            jumpBufferTimer = 0;
            coyoteTimer = 0;
        }
    }

    void BetterJump()
    {
        if (rb.velocity.y < 0)
        {
            rb.velocity += Vector2.up * Physics2D.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
        }
        else if (rb.velocity.y > 0 && !input.JumpHeld)
        {
            rb.velocity += Vector2.up * Physics2D.gravity.y * (lowJumpMultiplier - 1) * Time.deltaTime;
        }
        if (rb.velocity.y < -20f)
        {
            rb.velocity = new Vector2(rb.velocity.x, -20f);
        }
    }

    void CheckGround()
    {
        if (Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer))
        {
            isGrounded = true;
            coyoteTimer = coyoteTime;
        }
        else
        {
            isGrounded = false;
            coyoteTimer -= Time.deltaTime;
        }
    }

    void HandleFlip()
    {
        if (input.MoveX != 0)
        {
            //transform.localScale = new Vector3(Mathf.Sign(input.MoveX), 1, 1);
            sr.flipX = input.MoveX < 0;
        }
    }

    void OnDrawGizmos()
    {
        if (groundCheck != null)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
        }
    }
}

Feature: Player Movement System

Design

  • Input handling
  • Movement logic
  • Physics interaction

Implementation

Key scripts and logic explanation.

Challenges

  • Issue 1: Buffer time of jump

  • Issue 2: Coyote time when player is not grounded

    Solutions

  • Issue 1: Define a float parameter: jumpBufferTime, set when jump button is pressed. And it decreases through minus Time.deltaTime. Change the logic of jump, from check the button down to if parameter jumpBufferTime is positive or not.

  • Issue 2: Same as jumpBufferTime. A coyoteTime allows player jump in a time area even if player is not grounded.

Future Improvements

Animation connection will be developed in day 2.