Chỉ cần chọn một tốc độ khung hình và bám vào nó. Đó là điều mà rất nhiều game cũ đã làm - chúng chạy ở tốc độ cố định 50 hoặc 60 FPS, thường được đồng bộ hóa với tốc độ làm mới màn hình và chỉ thiết kế logic trò chơi của chúng để làm mọi thứ cần thiết trong khoảng thời gian cố định đó. Nếu, vì một số lý do, điều đó đã không xảy ra, trò chơi sẽ phải bỏ qua một khung (hoặc có thể bị sập), làm chậm hiệu quả cả bản vẽ và vật lý trò chơi xuống một nửa tốc độ.
Đặc biệt, các trò chơi sử dụng các tính năng như phát hiện va chạm sprite phần cứng khá nhiều phải hoạt động như thế này, bởi vì logic trò chơi của chúng gắn chặt với kết xuất, được thực hiện trong phần cứng ở tốc độ cố định.
Sử dụng một dấu thời gian thay đổi cho vật lý trò chơi của bạn. Về cơ bản, điều này có nghĩa là viết lại vòng lặp trò chơi của bạn để trông giống như thế này:
long lastTime = System.currentTimeMillis();
while (isRunning) {
long time = System.currentTimeMillis();
float timestep = 0.001 * (time - lastTime); // in seconds
if (timestep <= 0 || timestep > 1.0) {
timestep = 0.001; // avoid absurd time steps
}
update(timestep);
draw();
// ... sleep until next frame ...
lastTime = time;
}
và, bên trong update()
, điều chỉnh các công thức vật lý để giải thích cho dấu thời gian biến, ví dụ như thế này:
speed += timestep * acceleration;
position += timestep * (speed - 0.5 * timestep * acceleration);
Một vấn đề với phương pháp này là có thể khó để giữ cho vật lý (phần lớn) không phụ thuộc vào dấu thời gian ; bạn thực sự không muốn khoảng cách người chơi có thể nhảy phụ thuộc vào tốc độ khung hình của họ. Công thức tôi đã trình bày ở trên hoạt động tốt cho gia tốc không đổi, ví dụ như dưới trọng lực (và công thức trong bài được liên kết hoạt động khá tốt ngay cả khi gia tốc thay đổi theo thời gian), nhưng ngay cả với các công thức vật lý hoàn hảo nhất có thể, làm việc với phao có khả năng tạo ra một chút "nhiễu số", đặc biệt, có thể làm cho các replay chính xác là không thể. Nếu đó là điều bạn nghĩ rằng bạn có thể muốn, bạn có thể muốn các phương pháp khác.
Tách rời các bản cập nhật và vẽ các bước. Ở đây, ý tưởng là bạn cập nhật trạng thái trò chơi của mình bằng dấu thời gian cố định, nhưng chạy một số lượng cập nhật khác nhau giữa mỗi khung. Đó là, vòng lặp trò chơi của bạn có thể trông giống như thế này:
long lastTime = System.currentTimeMillis();
while (isRunning) {
long time = System.currentTimeMillis();
if (time - lastTime > 1000) {
lastTime = time; // we're too far behind, catch up
}
int updatesNeeded = (time - lastTime) / updateInterval;
for (int i = 0; i < updatesNeeded; i++) {
update();
lastTime += updateInterval;
}
draw();
// ... sleep until next frame ...
}
Để làm cho chuyển động nhận thức mượt mà hơn, bạn cũng có thể muốn draw()
phương thức của mình nội suy mọi thứ như vị trí đối tượng một cách trơn tru giữa các trạng thái trò chơi trước và tiếp theo. Điều này có nghĩa là bạn cần truyền bù nội suy chính xác cho draw()
phương thức, ví dụ như thế này:
int remainder = (time - lastTime) % updateInterval;
draw( (float)remainder / updateInterval ); // scale to 0.0 - 1.0
Bạn cũng cần phải có update()
phương pháp của mình thực sự tính toán trạng thái trò chơi trước một bước (hoặc có thể là một vài, nếu bạn muốn thực hiện phép nội suy spline bậc cao) và để nó lưu các vị trí đối tượng trước đó trước khi cập nhật chúng, để draw()
phương thức có thể nội suy giữa họ. (Cũng có thể ngoại suy các vị trí dự đoán dựa trên vận tốc và gia tốc của vật thể, nhưng điều này có thể trông giật, đặc biệt nếu các vật thể di chuyển theo những cách phức tạp, khiến cho các dự đoán thường thất bại.)
Một lợi thế của nội suy là, đối với một số loại trò chơi, nó có thể cho phép bạn giảm đáng kể tốc độ cập nhật logic trò chơi, trong khi vẫn duy trì ảo giác về chuyển động mượt mà. Ví dụ: bạn chỉ có thể cập nhật trạng thái trò chơi của mình, giả sử, 5 lần mỗi giây, trong khi vẫn vẽ 30 đến 60 khung hình nội suy mỗi giây. Khi bạn làm điều này, bạn cũng có thể muốn xem xét xen kẽ logic trò chơi của mình với bản vẽ (nghĩa là có một tham số cho update()
phương thức của bạn để chỉ cho nó chạy x % bản cập nhật đầy đủ trước khi quay lại) và / hoặc chạy vật lý trò chơi / logic và mã kết xuất trong các luồng riêng biệt (hãy cẩn thận với sự cố đồng bộ hóa!).