如何在Unity中使游戏帧率独立?

huangapple go评论67阅读模式
英文:

How to make a game frame rate independent in Unity?

问题

以下是脚本的翻译部分:

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

public class playerScript : MonoBehaviour
{
    public GameObject Player;
    public Sprite piranha_mouthOpen;
    public Sprite piranha_mouthClosed;
    public float playerPositionY;
    public float speed = 0;
    public float deltaTime;
    public float deltaTimeTouch;
    public float passiveSpeed = 0;
    public GameObject UpperBorder;
    public GameObject LowerBorder;
    public bool playerEliminated;
    public GameObject UpperObstacle;
    public GameObject LowerObstacle;
    private Rigidbody2D rb;
    public float normalGravityScale = 1.0f; // the default gravity scale for the object
    public float collisionGravityScale = 5.0f;
    private Collider2D col;
    public bool gameStart;

    // 旋转的角速度(每秒的度数)
    public float rotationSpeed = 120f;

    // 目标旋转角度(绕Z轴的度数)
    public float targetRotation = 55f;

    // 认为旋转“足够接近”目标的阈值
    public float rotationThreshold = 0.1f;

    // 负目标旋转角度(绕Z轴的度数)
    public float negativeTargetRotation = -55f;

    public void OnTriggerEnter2D(Collider2D col)
    {
        if (col.gameObject.name == "UpperBorder")
        {
            //playerEliminated = true;
            Debug.Log("gameObject.SetActive(false)");
        }

        if (col.gameObject.name == "LowerBorder")
        {
            //playerEliminated = true;
            Debug.Log("gameObject.SetActive(false)");
        }

        if (col.gameObject.name == "UpperObstacle")
        {
            playerEliminated = true;
            rb.constraints = RigidbodyConstraints2D.None;
            rb.gravityScale = collisionGravityScale;
            col.isTrigger = false;
        }

        if (col.gameObject.name == "LowerObstacle")
        {
            playerEliminated = true;
            rb.constraints = RigidbodyConstraints2D.None;
            rb.gravityScale = collisionGravityScale;
            col.isTrigger = false;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        playerEliminated = false;
        transform.position = new Vector3(0, 0, 0);
        transform.Rotate(0, 0, 0);
        playerPositionY = transform.position.y;
        deltaTime = 0;
        StartCoroutine(changeSprite());
        rb = GetComponent<Rigidbody2D>();
        rb.gravityScale = normalGravityScale;
        col = GetComponent<Collider2D>();
        gameStart = false;
    }

    IEnumerator changeSprite()
    {
        while (playerEliminated == false)
        {
            yield return new WaitForSeconds(0.25f);
            Player.gameObject.GetComponent<SpriteRenderer>().sprite = piranha_mouthClosed;
            yield return new WaitForSeconds(0.25f);
            Player.gameObject.GetComponent<SpriteRenderer>().sprite = piranha_mouthOpen;
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (playerEliminated == false)
        {
            if (gameStart == true)
            {
                // 移动
                if (Input.touchCount > 0)
                {
                    Touch touch = Input.GetTouch(0);
                    if (touch.phase == TouchPhase.Began)
                    {
                        speed = 0.1f;
                        deltaTimeTouch = 1;
                        deltaTime = 0;
                    }
                    else if (touch.phase == TouchPhase.Moved)
                    {
                        deltaTimeTouch += 0.01f;
                        speed = speed + 0.001f * deltaTimeTouch;
                    }
                    else if (touch.phase == TouchPhase.Ended)
                    {
                        speed = 0;
                        deltaTimeTouch = 1;
                    }

                    playerPositionY = transform.position.y;
                    transform.position = new Vector3(0, playerPositionY - speed, 0);

                    if (Mathf.Abs(transform.rotation.eulerAngles.z - negativeTargetRotation) > rotationThreshold)
                    {
                        // 朝向目标旋转
                        float step = rotationSpeed * Time.deltaTime;
                        transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, 0, negativeTargetRotation), step);
                    }
                }
                else if (Input.touchCount == 0)
                {
                    deltaTime += 0.001f;
                    speed += 0.008f * deltaTime;
                    playerPositionY = transform.position.y;
                    transform.position = new Vector3(0, playerPositionY + speed, 0);

                    // 旋转(90度)
                    if (Mathf.Abs(transform.rotation.eulerAngles.z - targetRotation) > rotationThreshold)
                    {
                        // 朝向目标旋转
                        float step = rotationSpeed * Time.deltaTime;
                        transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, 0, targetRotation), step);
                    }
                }

                // 边界
                if (transform.position.y >= 20.5f)
                {
                    transform.position = new Vector3(0, 20.5f, 0);
                }

                if (transform.position.y <= -20f)
                {
                    transform.position = new Vector3(0, -20f, 0);
                }
            }
        }

        if (gameStart == false)
        {
            if (Input.touchCount > 0)
            {
                Touch touch = Input.GetTouch(0);
                if (touch.phase == TouchPhase.Began)
                {
                    speed = 0.1f;
                    deltaTimeTouch = 1;
                    deltaTime = 0;
                    gameStart = true;
                }
            }
        }
    }
}
英文:

So, I have a Unity game intended for mobile use, but the problem that I am currently facing is that it performs differently on seperate devices, with some it goes super slow, and with others it goes super fast. I just want my game to run at the same speed as it did in the Unity editor, in the downloaded Google Play version. One of the solutions I found online was to make it frame rate independent, so my question is: How can I make the following script frame rate independent?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class playerScript : MonoBehaviour
{
public GameObject Player;
public Sprite piranha_mouthOpen;
public Sprite piranha_mouthClosed;
public float playerPositionY;
public float speed = 0;
public float deltaTime;
public float deltaTimeTouch;
public float passiveSpeed = 0;
public GameObject UpperBorder;
public GameObject LowerBorder;
public bool playerEliminated;
public GameObject UpperObstacle;
public GameObject LowerObstacle;
private Rigidbody2D rb;
public float normalGravityScale = 1.0f; // the default gravity scale for the object
public float collisionGravityScale = 5.0f;
private Collider2D col;
public bool gameStart;
// Speed of rotation in degrees per second
public float rotationSpeed = 120f;
// Target rotation on the Z-axis in degrees
public float targetRotation = 55f;
// Threshold for considering the rotation &quot;close enough&quot; to the target
public float rotationThreshold = 0.1f;
// Target rotation on the Z-axis in degrees (negative)
public float negativeTargetRotation = -55f;
public void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.name == &quot;UpperBorder&quot;)
{
//playerEliminated = true;
Debug.Log(&quot;gameObject.SetActive(false)&quot;);
}
if (col.gameObject.name == &quot;LowerBorder&quot;)
{
//playerEliminated = true;
Debug.Log(&quot;gameObject.SetActive(false)&quot;);
}
if (col.gameObject.name == &quot;UpperObstacle&quot;)
{
playerEliminated = true;
rb.constraints = RigidbodyConstraints2D.None;
rb.gravityScale = collisionGravityScale;
col.isTrigger = false;
}
if (col.gameObject.name == &quot;LowerObstacle&quot;)
{
playerEliminated = true;
rb.constraints = RigidbodyConstraints2D.None;
rb.gravityScale = collisionGravityScale;
col.isTrigger = false;
}
}
// Start is called before the first frame update
void Start()
{
playerEliminated = false;
transform.position = new Vector3 (0, 0, 0);
transform.Rotate(0, 0, 0);
playerPositionY = transform.position.y;
deltaTime = 0;
StartCoroutine(changeSprite());
rb = GetComponent&lt;Rigidbody2D&gt;();
rb.gravityScale = normalGravityScale;
col = GetComponent&lt;Collider2D&gt;();
gameStart = false;
}
IEnumerator changeSprite()
{
while (playerEliminated == false)
{
yield return new WaitForSeconds(0.25f);
Player.gameObject.GetComponent&lt;SpriteRenderer&gt;().sprite = piranha_mouthClosed;
yield return new WaitForSeconds(0.25f);
Player.gameObject.GetComponent&lt;SpriteRenderer&gt;().sprite = piranha_mouthOpen;
}
}
// Update is called once per frame
void Update()
{
if (playerEliminated == false)
{
if (gameStart == true)
{
// movement
if (Input.touchCount &gt; 0)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
speed = 0.1f;
deltaTimeTouch = 1;
deltaTime = 0;
}
else if (touch.phase == TouchPhase.Moved)
{
deltaTimeTouch += 0.01f;
speed = speed + 0.001f * deltaTimeTouch;
// * 35
}
else if (touch.phase == TouchPhase.Ended)
{
speed = 0;
deltaTimeTouch = 1;
}
playerPositionY = transform.position.y;
transform.position = new Vector3(0,playerPositionY - speed, 0);
if (Mathf.Abs(transform.rotation.eulerAngles.z - negativeTargetRotation) &gt; rotationThreshold)
{
// Rotate towards the target
float step = rotationSpeed * Time.deltaTime;
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, 0, negativeTargetRotation), step);
}
}
else if (Input.touchCount == 0 )
{
deltaTime += 0.001f;
speed += 0.008f * deltaTime;
// * 35
playerPositionY = transform.position.y;
transform.position = new Vector3(0,playerPositionY + speed, 0);
// Rotation (90 degrees)
if (Mathf.Abs(transform.rotation.eulerAngles.z - targetRotation) &gt; rotationThreshold)
{
// Rotate towards the target
float step = rotationSpeed * Time.deltaTime;
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, 0, targetRotation), step);
}
}
//borders
if (transform.position.y &gt;= 20.5f)
{
transform.position = new Vector3(0, 20.5f, 0);
}
if (transform.position.y &lt;= -20f)
{
transform.position = new Vector3(0, -20f, 0);
}
}
}
if (gameStart == false)
{
if (Input.touchCount &gt; 0 )
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
speed = 0.1f;
deltaTimeTouch = 1;
deltaTime = 0;
gameStart = true;
}
}
}
}
}

答案1

得分: 2

以下是您要翻译的内容:

让我们将这个问题讨论得更广泛一些,以便我们可以看到程序员是如何最终到达他们现在的地步的:

首先,想象一下,你的角色可以在每一帧移动100个单位(单位可以是米、像素,取决于上下文)。您的游戏目前以60帧每秒(每秒帧数)运行,这将导致6000个单位的移动(100*60 = 6000)。但我们不想每秒移动这个角色6000个单位,相反,我们只想移动那100个单位

你需要得到多少这个数字,以便角色可以在每一帧中移动"只是一点点"?

我们必须将当前的步长与"一秒钟的一部分"相乘,它的大小取决于帧速率,如下所示:

1/60 ~ 0.016666666666

你会如何实现这个?

void Update()
{
    float FPS = 60f;                   // 60每秒帧数
    float stepModifier = 1 / FPS;      // 1/60的帧

    float stepSize = 100;              // 100单位

    // 本帧中的1.6666单位
    float stepSizeInCurrentFrame = stepSize * stepModifier; 

    MakeStep(stepSizeInCurrentFrame);  
}

void MakeStep(float stepSize) { }

这很好,但是如果我的FPS一直在变化,而不是严格的60帧每秒,该怎么办呢?Unity(通常所有游戏引擎都是如此)有一种机制可以获取"上一帧经过的时间" ~ 自上一帧完成以来经过了多长时间。这个变量从数学上真正称为delta,因为它是差异:

var delta = currentTime - previousFrameTime;

在Unity中,可以这样获得:

var delta = Time.deltaTime;

这样,我们可以更新我们的代码,在所有需要移动"实际运动的一部分"的地方,我们将乘以移动的大小:

void Update()
{
    float stepSize = 100;              // 100单位(与之前相同)

    // 结果值取决于上一帧经过的时间
    float stepSizeInCurrentFrame = stepSize * Time.deltaTime; 

    MakeStep(stepSizeInCurrentFrame);  
}

void MakeStep(float stepSize) { }

关于deltaTime的更多信息可以在官方文档中找到。

英文:

Lets cover this question a bit broader, so that we can see the evolution of how programmers end up where they are:

For start, imagine that your character can move 100 units every tick (unit can be meter, pixel, ... depends on context). Your game is currently running in 60 FPS (frames per second), which would lead to 6000 units movement (100*60 = 6000). But we don't want to move this character 6000 units every second, instead we want to move it only those 100 units.

What is the number you need to get, so that the character can move "just a little" every tick?

We must multiply current step size with a "fraction" of the second, and its size depends on the framerate as:

1/60 ~ 0.016666666666

How would you implement that?

void Update()
{
    float FPS = 60f;                   // 60 frames per second
    float stepModifier = 1 / FPS;      // 1/60 of frame

    float stepSize = 100;              // 100 units

    // 1.6666 units in this frame
    float stepSizeInCurrentFrame = stepSize * stepModifier; 

    MakeStep(stepSizeInCurrentFrame);  
}

void MakeStep(float stepSize) { }

This is nice, but what to do if my FPS changes all the time and is not strictly 60 fps? Unity (and usually all game engines) have mechanism to get "time since last frame" ~ how long did it take since last frame has finished. This variable is genuinelly called delta (coming from math), as it is difference:

var delta = currentTime - previousFrameTime;

In Unity this can be obtained as:

var delta = Time.deltaTime;

This way we can update our code and in all places where we need to move only "fraction of actual movement", we will multiply the movement size:

void Update()
{
    float stepSize = 100;              // 100 units (same as before)

    // Resulting value depends on time since last frame
    float stepSizeInCurrentFrame = stepSize * Time.deltaTime; 

    MakeStep(stepSizeInCurrentFrame);  
}

void MakeStep(float stepSize) { }

More about the deltaTime can be found in official documentation.

答案2

得分: 0

Unity有自己的Time.deltaTime,用于使游戏的帧率独立。我建议你使用Unity内置的Time.deltaTime,而不是自己定义的deltaTime变量,因为它是在类中定义并在Update()或其他方法中更新,它不是帧率独立的。

英文:

Unity has it's own Time.deltaTime, which is used to make the games frame rate independent, I would suggest you to use Unity's built in Time.deltaTime instead of having of your own, since your deltaTime variable is defined in class and is getting updated in the Update() or some other method, it is not frame rate independent.

答案3

得分: 0

当您应用您的移动时,您可以使用FixedUpdate方法而不是Update方法。您可以在链接中了解更多区别。

这将有助于您的一个快速答案是,Update方法会尽量快地运行,取决于您的设备性能。这意味着如果您在性能良好的硬件上运行游戏,以每秒超过70fps的速度运行,那么Update方法将每秒执行70次;如果您的硬件性能不足,可能只以40fps运行,以此类推。这就是为什么速度会因设备而异的原因。

另一方面,FixedUpdate保证在任何设备上以相同的fps运行,我认为是50fps,并建议在需要应用任何物理效果时使用此方法。

此外,您还可以手动设置游戏的fps如下:

void Start()
{      
    Application.targetFrameRate = 60;
}
英文:

When you apply your movement, instead of the Update method you can use the FixedUpdate method. You can read more about the difference in the links.

A quick answer why this will help you is that, the Update method it runs as fast as your device can handle. What that means is that if you run the game with a good hardware that runs with more than 70fps, that means the Update method will be executed 70 times per second and if your hardware is not good enough it might be running with 40fps and so on. That's why your speed will be different based on the device.

On the other hand FixedUpdate is guarantee to run the same amount of fps which I think is 50fps in any device and is recommended to use this method when you have to apply any kind of physics.

Also you can set manually the amount of fps your game will run like this:

void Start()
{      
Application.targetFrameRate = 60;
}

huangapple
  • 本文由 发表于 2023年5月21日 06:59:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76297641.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定