2013-02-26 29 views
6

Gần đây tôi đã cố gắng tham gia lập trình trò chơi. Tôi khá kinh nghiệm với Java, nhưng không phải với lập trình game. Tôi đọc http://www.koonsolo.com/news/dewitters-gameloop/ và thực hiện vòng lặp trò chơi được đề nghị đó với đoạn mã sau:"Vòng xoay trò chơi deWiTTERS" có giả định UPS không đổi không?

private static int UPDATES_PER_SECOND = 25; 
private static int UPDATE_INTERVAL = 1000/UPDATES_PER_SECOND * 1000000; 
private static int MAX_FRAMESKIP = 5; 

public void run() { 
    while (true) { 
     int skippedFrames = 0; 
     while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) { 
      this.updateGame(); 
      this.nextUpdate += UPDATE_INTERVAL; 
      skippedFrames++; 
     } 

     long currentNanoTime = System.nanoTime(); 
     double interpolation = (currentNanoTime + UPDATE_INTERVAL - this.nextUpdate)/UPDATE_INTERVAL; 
     this.repaintGame(interpolation); 
    } 
} 

Vòng lặp nhìn đầy hứa hẹn và dễ dàng, nhưng bây giờ mà tôi đang thực sự cố gắng để làm điều gì đó với nó tôi không như vậy chắc chắn nữa. Nếu tôi không hoàn toàn nhầm lẫn updateGame() sẽ xử lý những việc như tính toán vị trí, di chuyển kẻ thù, tính toán va chạm, ...? Vì nội suy không được chuyển đến updateGame(), điều đó có nghĩa là chúng tôi giả định rằng updateGame() được gọi chính xác và đều đặn UPDATES_PER_SECOND lần mỗi giây? Điều đó có nghĩa là tất cả các tính toán của chúng tôi dựa trên giả định đó? Và điều đó sẽ không khiến chúng tôi gặp nhiều rắc rối nếu - vì bất kỳ lý do gì - lệnh gọi updateGame() bị trì hoãn? Ví dụ, nếu nhân vật của tôi có nghĩa vụ phải đi bộ và chúng tôi di chuyển nó theo tốc độ của nó trên mỗi updateGame() - nếu phương pháp bị trì hoãn, điều đó có nghĩa là tính toán của chúng tôi chỉ đơn giản là tắt và nhân vật sẽ tụt hậu?

Trên trang web, ví dụ sau đây cho phép nội suy được ghi:

Nếu trong 10 gametick vị trí là 500, và tốc độ là 100, sau đó trong 11 gametick vị trí sẽ là 600. Vì vậy, nơi bạn sẽ đặt xe của bạn khi bạn render nó? Bạn chỉ có thể lấy vị trí của gametick cuối cùng (trong trường hợp này là 500). Nhưng một cách tốt hơn là để dự đoán vị trí chiếc xe sẽ có mặt tại chính xác 10,3, và điều này xảy ra như thế này:
view_position = position + (speed * interpolation)
Chiếc xe sau đó sẽ được trả lại ở vị trí 530.

Tôi biết nó chỉ là một ví dụ, nhưng điều đó không làm cho tốc độ xe phụ thuộc vào số UPDATES_PER_SECOND? Vì vậy, nhiều UPS sẽ có nghĩa là một chiếc xe nhanh hơn? Điều này không thể đúng ...?

Bất kỳ trợ giúp, hướng dẫn, bất kỳ điều gì được đánh giá cao. (! Thanks)

UPDATE/SOLUTION

Sau khi tất cả sự giúp đỡ, đây là những gì tôi hiện đang sử dụng - hoạt động khá tốt cho đến nay (đối với di chuyển một sprite nhân vật), nhưng chúng ta hãy chờ đợi những thứ trò chơi thực sự phức tạp để quyết định điều này. Tuy nhiên, tôi muốn chia sẻ điều này.
trò chơi vòng lặp của tôi bây giờ trông như thế này:

private static int UPDATES_PER_SECOND = 25; 
private static int UPDATE_INTERVAL = 1000/UPDATES_PER_SECOND * 1000000; 
private static int MAX_FRAMESKIP = 5; 

private long nextUpdate = System.nanoTime(); 

public void run() { 
    while (true) { 
     int skippedFrames = 0; 
     while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) { 
      long delta = UPDATE_INTERVAL; 
      this.currentState = this.createGameState(delta); 
      this.newPredictedNextState = this.createGameState(delta + UPDATE_INTERVAL, true); 

      this.nextUpdate += UPDATE_INTERVAL; 
      skippedFrames++; 
     } 

     double interpolation = (System.nanoTime() + UPDATE_INTERVAL - this.nextUpdate)/(double) UPDATE_INTERVAL; 
     this.repaintGame(interpolation); 
    } 
} 

Như bạn có thể thấy, một vài thay đổi:

  • delta = UPDATE_INTERVAL? Vâng. Đây là thử nghiệm cho đến nay, nhưng tôi nghĩ rằng nó sẽ làm việc. Vấn đề là, ngay sau khi bạn thực sự CALCULATE một delta từ hai dấu thời gian, bạn đang giới thiệu các lỗi tính toán float. Đó là nhỏ, nhưng xem xét cập nhật của bạn được gọi là hàng triệu lần, họ thêm lên. Và kể từ khi vòng lặp thứ hai đảm bảo chúng tôi bắt kịp các cập nhật bị mất (trong trường hợp kết xuất mất nhiều thời gian), chúng tôi có thể khá chắc chắn rằng chúng tôi nhận được 25 cập nhật mỗi giây. Trường hợp xấu nhất: Chúng tôi bỏ lỡ hơn MAX_FRAMESKIP cập nhật - trong trường hợp này, các bản cập nhật sẽ bị mất và trò chơi sẽ bị chậm. Tuy nhiên, như tôi đã nói, thử nghiệm. Tôi có thể thay đổi điều này thành một đồng bằng thực tế một lần nữa.
  • dự đoánTrạng thái trò chơi tiếp theo? Vâng. GameState là đối tượng chứa tất cả thông tin trò chơi có liên quan, trình kết xuất được truyền đối tượng này để hiển thị trò chơi lên màn hình.Trong trường hợp của tôi, tôi quyết định cung cấp cho hai trạng thái kết xuất: Một trạng thái chúng ta thường vượt qua anh ta, với trạng thái trò chơi hiện tại và trạng thái tương lai được dự đoán, UPDATE_INTERVAL trong tương lai. Bằng cách này, trình kết xuất có thể sử dụng giá trị nội suy để dễ dàng nội suy giữa cả hai. Tính toán trạng thái trò chơi trong tương lai thực sự khá dễ dàng - vì phương thức cập nhật của bạn (createGameState()) cũng có giá trị delta, chỉ cần tăng delta lên UPDATE_INTERVAL - theo cách này, trạng thái tương lai sẽ được dự đoán. Trạng thái tương lai, tất nhiên, giả định đầu vào của người dùng, v.v. vẫn giữ nguyên. Nếu không, cập nhật trạng thái trò chơi tiếp theo sẽ xử lý các thay đổi.
  • Phần còn lại vẫn khá giống nhau và được lấy từ vòng lặp trò chơi deWiTTERS. MAX_FRAMESKIP là khá nhiều một failsafe trong trường hợp phần cứng là REALLY chậm, để đảm bảo rằng chúng tôi làm cho một cái gì đó theo thời gian. Nhưng nếu cú ​​đá này vào, chúng tôi sẽ có những sai lầm cực kỳ dù tôi đoán thế nào. Nội suy cũng giống như trước đây - nhưng bây giờ trình kết xuất có thể chỉ đơn giản là nội suy giữa hai gamestates, nó không phải có bất kỳ logic ngoại trừ các số nội suy. Thật tuyệt!

Có thể để làm rõ, ví dụ. Dưới đây là cách tôi tính toán vị trí nhân vật (giản một chút):

public GameState createGameState(long delta, boolean ignoreNewInput) { 
    //Handle User Input and stuff if ignoreNewInput=false 

    GameState newState = this.currentState.copy(); 
    Sprite charSprite = newState.getCharacterSprite(); 
    charSprite.moveByX(charSprite.getMaxSpeed() * delta * charSprite.getMoveDirection().getX()); 
    //getMoveDirection().getX() is 1.0 when the right arrow key is pressed, otherwise 0.0 
} 

... sau đó, trong phương pháp sơn của cửa sổ ...

public void paint(Graphics g) { 
    super.paint(g); 

    Graphics2D g2d = (Graphics2D) g; 

    Sprite currentCharSprite = currentGameState.getCharacterSprite(); 
    Sprite nextCharSprite = predictedNextState.getCharacterSprite(); 
    Position currentPos = currentCharSprite.getPosition(); 
    Position nextPos = nextCharSprite.getPosition(); 
    //Interpolate position 
    double x = currentPos.getX() + (nextPos.getX() - currentPos.getX()) * this.currentInterpolation; 
    double y = currentPos.getY() + (nextPos.getY() - currentPos.getY()) * this.currentInterpolation; 
    Position interpolatedCharPos = new Position(x, y); 
    g2d.drawImage(currentCharSprite.getImage(), (int) interpolatedCharPos.getX(), (int) interpolatedCharPos.getY(), null); 
} 

Trả lời

2

Đừng căn trò chơi logic của bạn trên giả định rằng khoảng thời gian cập nhật là không đổi. Bao gồm đồng hồ trò chơi đo chính xác thời gian trôi qua giữa hai lần cập nhật. Sau đó, bạn có thể căn cứ tất cả các tính toán của bạn về sự chậm trễ và không phải lo lắng về tỷ lệ cập nhật thực tế.

Trong trường hợp này vận tốc xe sẽ được đưa ra trong đơn vị/giây và vùng đồng bằng sẽ là tổng số giây kể từ khi cập nhật lần cuối:

car.position += car.velocity * delta; 

Đó là một thực tế phổ biến để tách cập nhật các trò chơi logic từ vẽ khung. Như bạn đã nói, điều này làm cho nó có thể giữ một tỷ lệ cập nhật ổn định bằng cách bỏ qua rendering một khung tất cả bây giờ và sau đó.

Nhưng không quá nhiều về việc giữ các khoảng thời gian cập nhật liên tục. Chỉ cần có số lượng cập nhật tối thiểu mỗi đơn vị thời gian. Hãy tưởng tượng một chiếc xe di chuyển thật nhanh về phía một chiếc ghế bành. Nếu tần suất cập nhật quá thấp, khoảng cách di chuyển giữa hai bản cập nhật có thể lớn hơn tổng kích thước của vật cản. Chiếc xe sẽ di chuyển qua nó.

+0

Cảm ơn bạn đã phản hồi. Điều gây nhầm lẫn cho tôi là deWITTER đề xuất trong vòng lặp trò chơi của mình để vượt qua delta (hoặc nội suy, về cơ bản được tính từ delta) chỉ với hàm render, nhưng không phải là hàm cập nhật sẽ xử lý logic trò chơi. Vì vậy, bạn có nói rằng cả hai chức năng sẽ nhận được giá trị delta bằng cách này hay cách khác? – BlackWolf

+0

Tôi sẽ nói rằng đồng bằng chỉ cần thiết trong phương thức cập nhật, vì đây là nơi bạn sửa đổi thế giới trò chơi. Phải đủ rằng phương thức render chỉ * đọc * [mô hình thế giới trò chơi] (http://stackoverflow.com/tags/model/info). Nó tạo ra một đại diện của thế giới, nhưng nên * không thay đổi * trạng thái của nó; vì vậy vùng đồng bằng trong phương thức render có vẻ không cần thiết. – Lucius

+1

vâng, vâng, tôi nghĩ tôi sẽ thực hiện nó theo cách đó, vì điều này cũng có ý nghĩa hơn với tôi. Là một sidenote, tôi vẫn nghĩ rằng việc đi qua vùng đồng bằng để dựng hình vẫn có thể hữu ích - nó làm cho nó có thể nội suy chuyển động giữa hai bản cập nhật trò chơi. Nếu không, nếu có 25 cập nhật trò chơi mỗi giây nhưng 200 hiển thị cập nhật mỗi giây, 175 lần hiển thị về cơ bản sẽ bị lãng phí. Như bạn đã nói, _could_ này có nghĩa là một chiếc xe lái "qua" một chướng ngại vật, nhưng có khả năng phương thức cập nhật được gọi là đủ thường để điều này không xảy ra/không được chú ý. – BlackWolf

Các vấn đề liên quan