OK, tôi đã làm mọi thứ hoạt động, phải mất mãi mãi, vì vậy tôi sẽ đăng giải pháp chi tiết của mình ở đây.
Lưu ý: Tất cả các mẫu mã đều bằng JavaScript.
Vì vậy, hãy chia nhỏ vấn đề thành các phần cơ bản:
Bạn cần tính toán độ dài, cũng như các điểm giữa 0..1
trên đường cong bezier
Bây giờ bạn cần điều chỉnh tỷ lệ của bạn T
để tăng tốc con tàu từ tốc độ này sang tốc độ khác
Bắt Bezier đúng
Tìm một số mã để vẽ đường cong Bezier rất dễ dàng, có một số cách tiếp cận khác nhau, một trong số đó là Thuật toán DeCasteljau , nhưng bạn cũng có thể chỉ sử dụng phương trình cho các đường cong Bézier:
// Part of a class, a, b, c, d are the four control points of the curve
x: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
+ 3 * ((1 - t) * (1 - t)) * t * this.b.x
+ 3 * (1 - t) * (t * t) * this.c.x
+ (t * t * t) * this.d.x;
},
y: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
+ 3 * ((1 - t) * (1 - t)) * t * this.b.y
+ 3 * (1 - t) * (t * t) * this.c.y
+ (t * t * t) * this.d.y;
}
Với điều này, giờ đây người ta có thể vẽ một đường cong bezier bằng cách gọi x
và y
với t
phạm vi từ đó 0 to 1
, hãy xem:
Uh ... đó không thực sự là một sự phân phối đồng đều của các điểm, phải không?
Do tính chất của đường cong Bézier, các điểm trên 0...1
có khác nhau arc lenghts
, vì vậy các đoạn gần đầu và cuối, dài hơn các điểm nằm gần giữa đường cong.
Ánh xạ T đồng đều trên tham số hóa chiều dài cung AKA
Vậy lam gi? Vâng trong thuật ngữ đơn giản chúng ta cần một chức năng để lập bản đồ của chúng tôi T
vào t
của đường cong, do đó chúng tôi T 0.25
kết quả trong t
tại đó 25%
chiều dài của đường cong.
làm sao chúng ta làm việc đó bây giờ? Vâng, chúng tôi Google ... nhưng nó chỉ ra rằng thuật ngữ này là không phải là googleable , và tại một số điểm bạn sẽ nhấn này PDF . Chắc chắn đó là một bài đọc tuyệt vời, nhưng trong trường hợp bạn đã quên tất cả những thứ toán học bạn đã học ở trường (hoặc bạn không thích những biểu tượng toán học đó) thì nó khá vô dụng.
Gì bây giờ? Hãy đi và tìm hiểu thêm về Google (đọc: 6 giờ) và cuối cùng bạn cũng tìm thấy một bài viết tuyệt vời về chủ đề này (bao gồm cả những bức ảnh đẹp! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingTextToSplines.html
Làm mã thực tế
Trong trường hợp bạn không thể cưỡng lại việc tải xuống bản PDF đó mặc dù bạn đã mất kiến thức toán học từ rất lâu rồi (và bạn đã bỏ qua liên kết bài viết tuyệt vời ), bây giờ bạn có thể nghĩ: "Chúa ơi, việc này sẽ mất hàng trăm dòng mã và hàng tấn CPU "
Không nó sẽ không như vậy. Bởi vì chúng tôi làm những gì tất cả các lập trình viên làm, khi nói đến công cụ toán học:
Chúng tôi chỉ đơn giản là gian lận.
Tham số chiều dài vòng cung, cách lười biếng
Hãy đối mặt với nó, chúng ta không cần độ chính xác vô tận trong trò chơi của mình, phải không? Vì vậy, trừ khi bạn làm việc tại Nasa và dự định gửi cho mọi người Sao Hỏa, bạn sẽ không cần một 0.000001 pixel
giải pháp hoàn hảo.
Vậy làm thế nào để chúng ta ánh xạ T
lên t
? Nó đơn giản và chỉ bao gồm 3 bước:
Tính toán N
các điểm trên đường cong bằng cách sử dụng t
và lưu trữ arc-length
(còn gọi là chiều dài của đường cong) tại vị trí đó thành một mảng
Để ánh xạ T
lên t
, đầu tiên nhân T
với tổng chiều dài của đường cong để có được u
và sau đó tìm kiếm mảng độ dài cho chỉ số của giá trị lớn nhất nhỏ hơnu
Nếu chúng ta có một lần truy cập chính xác, hãy trả về giá trị mảng tại chỉ số đó chia cho N
, nếu không nội suy một chút giữa điểm chúng ta tìm thấy và điểm tiếp theo, hãy chia điều đó một lần nữa N
và trả lại.
Đó là tất cả! Vì vậy, bây giờ hãy xem mã hoàn chỉnh:
function Bezier(a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.len = 100;
this.arcLengths = new Array(this.len + 1);
this.arcLengths[0] = 0;
var ox = this.x(0), oy = this.y(0), clen = 0;
for(var i = 1; i <= this.len; i += 1) {
var x = this.x(i * 0.05), y = this.y(i * 0.05);
var dx = ox - x, dy = oy - y;
clen += Math.sqrt(dx * dx + dy * dy);
this.arcLengths[i] = clen;
ox = x, oy = y;
}
this.length = clen;
}
Điều này khởi tạo đường cong mới của chúng tôi và tính toán arg-lenghts
, nó cũng lưu trữ độ dài cuối cùng là total length
đường cong, yếu tố chính ở đây this.len
là của chúng tôi N
. Cao hơn, ánh xạ sẽ càng chính xác, đối với một đường cong kích thước trong hình trên 100 points
dường như là đủ, nếu bạn chỉ cần ước tính độ dài tốt, một cái gì đó giống như 25
sẽ thực hiện công việc chỉ với 1 pixel trong chúng tôi ví dụ, nhưng sau đó bạn sẽ có một ánh xạ ít chính xác hơn, điều này sẽ dẫn đến việc phân phối không đồng đều T
khi được ánh xạ tới t
.
Bezier.prototype = {
map: function(u) {
var targetLength = u * this.arcLengths[this.len];
var low = 0, high = this.len, index = 0;
while (low < high) {
index = low + (((high - low) / 2) | 0);
if (this.arcLengths[index] < targetLength) {
low = index + 1;
} else {
high = index;
}
}
if (this.arcLengths[index] > targetLength) {
index--;
}
var lengthBefore = this.arcLengths[index];
if (lengthBefore === targetLength) {
return index / this.len;
} else {
return (index + (targetLength - lengthBefore) / (this.arcLengths[index + 1] - lengthBefore)) / this.len;
}
},
mx: function (u) {
return this.x(this.map(u));
},
my: function (u) {
return this.y(this.map(u));
},
Mã ánh xạ thực tế, đầu tiên chúng ta thực hiện đơn giản binary search
trên các độ dài được lưu trữ của chúng tôi để tìm độ dài lớn nhất nhỏ hơn sau đó targetLength
, sau đó chúng ta chỉ cần trả về hoặc thực hiện phép nội suy và trả về.
x: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
+ 3 * ((1 - t) * (1 - t)) * t * this.b.x
+ 3 * (1 - t) * (t * t) * this.c.x
+ (t * t * t) * this.d.x;
},
y: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
+ 3 * ((1 - t) * (1 - t)) * t * this.b.y
+ 3 * (1 - t) * (t * t) * this.c.y
+ (t * t * t) * this.d.y;
}
};
Một lần nữa điều này tính toán t
trên đường cong.
Thời gian cho kết quả
Bây giờ sử dụng mx
và my
bạn nhận được một phân phối đồng đều T
trên đường cong :)
Không khó lắm phải không? Một lần nữa, hóa ra một giải pháp đơn giản (mặc dù không hoàn hảo) sẽ đủ cho một trò chơi.
Trong trường hợp bạn muốn xem mã hoàn chỉnh, có sẵn Gist:
https://gist.github.com/670236
Cuối cùng, tăng tốc cho tàu
Vì vậy, tất cả những gì còn lại bây giờ là tăng tốc cho các con tàu dọc theo con đường của chúng, bằng cách ánh xạ vị trí T
mà sau đó chúng ta sử dụng để tìm t
đường cong của chúng ta.
Đầu tiên chúng ta cần hai trong số các phương trình chuyển động , cụ thể là ut + 1/2at²
và(v - u) / t
Trong mã thực tế sẽ trông như thế này:
startSpeed = getStartingSpeedInPixels() // Note: pixels
endSpeed = getFinalSpeedInPixels() // Note: pixels
acceleration = (endSpeed - startSpeed) // since we scale to 0...1 we can leave out the division by 1 here
position = 0.5 * acceleration * t * t + startSpeed * t;
Sau đó, chúng tôi giảm quy mô xuống 0...1
bằng cách làm:
maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;
Và ở đó bạn đi, những con tàu đang di chuyển trơn tru dọc theo con đường.
Trong trường hợp nó không hoạt động ...
Khi bạn đọc nó, mọi thứ đều hoạt động tốt và bảnh bao, nhưng ban đầu tôi có một số vấn đề với phần tăng tốc, khi giải thích vấn đề cho ai đó trong phòng chat gamedev tôi đã tìm thấy lỗi cuối cùng trong suy nghĩ của mình.
Trong trường hợp bạn chưa quên hình ảnh trong câu hỏi ban đầu, tôi đề cập s
ở đó, hóa ra đó s
là tốc độ tính bằng độ , nhưng các con tàu di chuyển dọc theo con đường tính bằng pixel và tôi đã quên mất thực tế đó. Vì vậy, những gì tôi cần làm trong trường hợp này là chuyển đổi độ dịch chuyển theo độ thành độ dịch chuyển theo pixel, hóa ra điều này khá dễ dàng:
function rotationToMovement(planetSize, rotationSpeed) {
var r = shipAngle * Math.PI / 180;
var rr = (shipAngle + rotationSpeed) * Math.PI / 180;
var orbit = planetSize + shipOrbit;
var dx = Math.cos(r) * orbit - Math.cos(rr) * orbit;
var dy = Math.sin(r) * orbit - Math.sin(rr) * orbit;
return Math.sqrt(dx * dx + dy * dy);
};
Vì vậy, đó là tất cả! Cảm ơn vì đã đọc ;)