2010-07-22 4 views
4

달 착륙선 데모에서 내 게임을 크게 변경했지만 40-50fps를 얻을 수는 있지만 문제는 40-50fps 사이에서 너무 많이 변동하므로 문제가 발생합니다. 그래픽을 지터로 이동! 그것의 성가신 및 내 게임을 정말 젠장 정말 실제로 좋은 프레임 속도로 실행. ... 안드로이드 2d 캔버스 게임 : FPS 지터 문제

나는 스레드 우선 순위 높은 설정을 시도했지만 그것은 단지 지금은 40-60fps 사이에서 변동 할 것입니다 ...이 악화

나는 그것이 될 수 있도록 약 30에 FPS를 제한하는 생각 일정한. 이것은 좋은 생각입니까? 다른 누구에게도 경험이나 다른 해결책이 있습니까?

감사합니다.

이 프레임 속도에 속도를 업데이트 (등 개체 이동,) 게임의 논리를 기반으로하지 마십시오 내 실행 루프

@Override 
    public void run() { 
     while (mRun) { 
      Canvas c = null; 
      try { 
       c = mSurfaceHolder.lockCanvas(null); 
       synchronized (mSurfaceHolder) { 
        if(mMode == STATE_RUNNING){ 

         updatePhysics(); 
        } 
        doDraw(c); 
       } 
      } finally { 
       // do this in a finally so that if an exception is thrown 
       // during the above, we don't leave the Surface in an 
       // inconsistent state 
       if (c != null) { 
        mSurfaceHolder.unlockCanvasAndPost(c); 
       } 
      } 
     } 
     } 

private void updatePhysics() { 

     now = android.os.SystemClock.uptimeMillis(); 

     elapsed = (now - mLastTime)/1000.0; 

     posistionY += elapsed * speed; 
     mLastTime = now; 
} 

답변

20

입니다. 즉, 드로잉 및 로직 업데이트 코드를 별도의 두 구성 요소/스레드에 배치하십시오. 이렇게하면 게임 논리가 프레임 속도와 완전히 독립적입니다.

논리 업데이트는 마지막 업데이트 이후 경과 한 시간을 기준으로해야합니다 (delta라고 부름). 따라서 개체가 개체 이런 식으로 뭔가를해야 할 각 업데이트하는 동안 다음, 1 x 1 픽셀/밀리 초에 이동이있는 경우 :

public void update(int delta) { 
    this.x += this.speed * delta; 
} 

그래서 지금 FPS가 지연 되더라도, 그것은 개체의 이동 속도에 영향을주지 않습니다, 델타가 커질 것이기 때문에 객체를 멀리 옮겨서 보정해야합니다 (경우에 따라서는 합병증이 있지만 그것이 그 요지입니다).

그리고 이것은 로직 업데이트 개체 내에서 델타를 계산하는 방법 중 하나는 (일부 스레드 루프에서 실행)입니다 : 도움이

private long lastUpdateTime; 
private long currentTime; 

public void update() { 
    currentTime = System.currentTimeMillis(); 
    int delta = (int) (currentTime - lastUpdateTime); 
    lastUpdateTime = currentTime; 
    myGameObject.update(delta); // This would call something like the update method above. 
} 

희망을! 다른 질문이 있으면 물어보십시오. 나는 안드로이드 게임을 직접 만들고있다. :)


샘플 코드 :

복사 두 조각 (1 개 활동과 1 개보기) 및 코드를 실행합니다. 결과는 FPS가 무엇이든 관계없이 화면 아래로 부드럽게 떨어지는 흰색 점이어야합니다. 코드는 좀 복잡하고 길어 보이지만 실제로는 아주 간단합니다. 의견은 모든 것을 설명해야합니다.

이 활동 등급은 그다지 중요하지 않습니다. 당신은 그 안에있는 대부분의 코드를 무시할 수 있습니다.

public class TestActivity extends Activity { 

    private TestView view; 

    public void onCreate(Bundle savedInstanceState) { 
     // These lines just add the view we're using. 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.randomimage); 
     RelativeLayout rl = (RelativeLayout) findViewById(R.id.relative_layout); 
     view = new TestView(this); 
     RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
       10000, 10000); 
     rl.addView(view, params); 

     // This starts our view's logic thread 
     view.startMyLogicThread(); 
    } 

    public void onPause() { 
     super.onPause(); 
     // When our activity pauses, we want our view to stop updating its logic. 
     // This prevents your application from running in the background, which eats up the battery. 
     view.setActive(false); 
    } 
} 

이 수업은 재미있는 부분입니다!

public class TestView extends View { 

    // Of course, this stuff should be in its own object, but just for this example.. 
    private float position; // Where our dot is 
    private float velocity; // How fast the dot's moving 

    private Paint p; // Used during onDraw() 
    private boolean active; // If our logic is still active 

    public TestView(Context context) { 
     super(context); 
     // Set some initial arbitrary values 
     position = 10f; 
     velocity = .05f; 
     p = new Paint(); 
     p.setColor(Color.WHITE); 
     active = true; 
    } 

    // We draw everything here. This is by default in its own thread (the UI thread). 
    // Let's just call this thread THREAD_A. 
    public void onDraw(Canvas c) { 
     c.drawCircle(150, position, 1, p); 
    } 

    // This just updates our position based on a delta that's given. 
    public void update(int delta) { 
     position += delta * velocity; 
     postInvalidate(); // Tells our view to redraw itself, since our position changed. 
    } 

    // The important part! 
    // This starts another thread (let's call this THREAD_B). THREAD_B will run completely 
    // independent from THREAD_A (above); therefore, FPS changes will not affect how 
    // our velocity increases our position. 
    public void startMyLogicThread() { 
     new Thread() { 
      public void run() { 
       // Store the current time values. 
       long time1 = System.currentTimeMillis(); 
       long time2; 

       // Once active is false, this loop (and thread) terminates. 
       while (active) { 
        try { 
         // This is your target delta. 25ms = 40fps 
         Thread.sleep(25); 
        } catch (InterruptedException e1) { 
         e1.printStackTrace(); 
        } 

        time2 = System.currentTimeMillis(); // Get current time 
        int delta = (int) (time2 - time1); // Calculate how long it's been since last update 
        update(delta); // Call update with our delta 
        time1 = time2; // Update our time variables. 
       } 
      } 
     }.start(); // Start THREAD_B 
    } 

    // Method that's called by the activity 
    public void setActive(boolean active) { 
     this.active = active; 
    } 
} 
+0

안녕하세요 Andy, 게임 루프를 포함하도록 제 질문을 업데이트했습니다. 이것이 당신이 그것을 구현하는 것에 대해 말하는 방식입니까? 필자에게는 물리학을위한 것과, 그림을위한 것의 두 가지 기능이 있습니다. 물리학 내부에는 경과 시간 또는 "델타 (delta)"라는 답이 나와 있습니다. 어떤 이유로 그래픽을 지터 롭습니다. 델타가 계속 변동하기 때문에 떨어지는 물체가 일정한 속도로 떨어지지 않는다고 생각합니다. – Cameron

+0

물리 및 드로잉 기능이 두 개의 개별 스레드에 있는지 확인하십시오. 별도의 스레드 두 개를 사용하면 서로 독립적으로 시간을 실행할 수 있습니다. 지 터링에 관해서는 델타를 올바르게 계산하고 있습니까? (현재와 마지막 업데이트 사이의 시간)? 또한 업데이트가 호출 된 횟수 대신 게임의 모든 물리가 해당 델타에 의존하는지 확인하십시오. 괜찮 으면 더 자세한 조언을 제공 할 수 있도록 해당 코드 일부를 게시하십시오. 게시 할 코드를 찾으려고 노력할 것입니다. 감사! –

+0

그들은 2 개의 분리 된 스레드에 있지 않으며 동일한 스레드의 다른 기능을 가지고 있지 않습니다. 동일한 활동에 2 개의 스레드를 추가 할 수 있습니까? – Cameron

0

가 나는 그것이 가비지 컬렉터에 대해

+0

예, 일반적으로 문제는 있지만 런타임 중에 할당되거나 삭제 된 항목이 없도록 조치를 취했습니다. 어쨌든 나는 그것을 고쳐 썼다. 그것은 나의 타이밍과 관련이 있었고, 잘못된 시간에 잘못된 시스템 시간을 사용하여 일관성없는 타이밍을 초래했다. 또한 단위 변환 문제가 있었는데, double -> int로 바꾸기가 너무 일찍 일어나서 반올림 오류가 발생했습니다. 그래서 onCanvas.draw에서 변환하기 전에 바로 기다렸습니다. – Cameron

0

게임 액션 무거운 경우 내가 대신보기의 서피스 뷰 SurfaceView를 사용하는 것 같아요. GUI를 빠르게 업데이트 할 필요가 없다면 View는 괜찮지 만 2D 게임의 경우 SurfaceView를 사용하는 것이 더 좋습니다.

3

나는 위의 코드 중 일부가 실제로는 잘못된 것이 아니라 다소 비효율적이라고 생각합니다. 나는이 코드에 대해 이야기하고있다 ...

// The important part! 
// This starts another thread (let's call this THREAD_B). THREAD_B will run completely 
// independent from THREAD_A (above); therefore, FPS changes will not affect how 
// our velocity increases our position. 
public void startMyLogicThread() { 
    new Thread() { 
     public void run() { 
      // Store the current time values. 
      long time1 = System.currentTimeMillis(); 
      long time2; 

      // Once active is false, this loop (and thread) terminates. 
      while (active) { 
       try { 
        // This is your target delta. 25ms = 40fps 
        Thread.sleep(25); 
       } catch (InterruptedException e1) { 
        e1.printStackTrace(); 
       } 

       time2 = System.currentTimeMillis(); // Get current time 
       int delta = (int) (time2 - time1); // Calculate how long it's been since last update 
       update(delta); // Call update with our delta 
       time1 = time2; // Update our time variables. 
      } 
     } 
    }.start(); // Start THREAD_B 
} 

특히, 때 사실, 귀중한 처리 시간의 낭비이다 나는 ... 다음 행에 대해 생각하고

// This is your target delta. 25ms = 40fps 
Thread.sleep(25); 

그냥 스레드가 아무것도하지 않고 놀고 갖는 날 것으로 보인다 당신이하고 싶은 일은 업데이트를 수행하는 것입니다. 그런 다음 업데이트가 25 밀리 초보다 짧은 시간을 보낸다면, 업데이트 중에 사용 된 것과 25 밀리 (또는 선택한 프레임 속도가). 이 방법으로 현재 프레임이 렌더링되는 동안 업데이트가 발생하고 다음 프레임 업데이트가 업데이트 된 값을 사용하도록 완료됩니다.

내가 여기서 생각할 수있는 유일한 문제는 일종의 동기화가 발생해야 현재 프레임 렌더링이 부분적으로 업데이트 된 값을 사용하지 않는다는 것입니다. 아마도 값 집합의 새 인스턴스로 업데이트 한 다음 렌더링 직전에 새 인스턴스를 현재 인스턴스로 만듭니다.

그래픽 카드에서 원하는 프레임 속도를 유지하면서 가능한 한 많은 업데이트를 수행한다는 목표를 읽은 다음 화면 업데이트를 수행하는 것을 기억합니다.

물론 이것은 하나의 스레드로 업데이트를 구동해야합니다. SurfaceView를 사용하면 캔버스를 잠글 때 렌더링이이 스레드에 의해 제어됩니다 (이론적으로는 어쨌든 이해합니다).

그래서, 코드, 그것은 ... 더 같이

// Calculate next render time 
nextRender = System.currentTimeInMillis() + 25; 

while (System.currentTimeInMillis() < nextRender) 
{ 
    // All objects must be updated here 
    update(); 

    // I could see maintaining a pointer to the next object to be updated, 
    // such that you update as many objects as you can before the next render, and 
    // then continue the update from where you left off in the next render... 
} 

// Perform a render (if using a surface view) 
c = lockCanvas() blah, blah... 
// Paint and unlock 

// If using a standard view 
postInvalidate(); 

행운을 확실하게 우리 모두가 뭔가를 배울 도움이 될이 사용하는 모든 사용자의 피드백 ...

rpbarbati

0

비슷한 문제가있어 지터로 인해 대형 물체가 고르지 않게 보입니다. 비록 "속도"가 동일하더라도, 다양한 길이의 스텝이 움직임을 비약적으로 보입니다. Broody - 당신은 SurfaceView가 더 좋다고 말합니다. 그러나 Android 3.0 이후에는 View가 HW 가속화되지만 .lockCanvas에 의해 반환 된 캔버스는 그렇지 않습니다. Steven - 예, 이것은 poroblems을 유발할 수 있지만 감지하기 쉽습니다. /Jacob