If you're seeing this message, it means we're having trouble loading external resources on our website.

Nếu bạn đang sử dụng bộ lọc web, vui lòng kiểm tra lại xem bộ lọc có chặn hai tên miền *.kastatic.org*.kasandbox.org hay không.

Nội dung chính

Sử dụng vectơ để mô phỏng chuyển động

Bạn có thể thắc mắc, tất cả những kiến thức toán học về vectơ sẽ giúp ta lập trình như thế nào? Chúng ta sẽ cần kiên nhẫn thêm một chút trước khi có thể khám phá trọn vẹn sự hữu dụng của kiểu đối tượng PVector.
Đây là vấn đề thường gặp khi ta bắt đầu học một cấu trúc dữ liệu mới. Ví dụ, khi mới học về mảng, bạn có thể cảm thấy việc sử dụng mảng tốn nhiều công sức hơn là chỉ dùng biến. Tuy nhiên, biến sẽ không khả thi khi ta cần hàng trăm, hàng nghìn, hàng vạn nội dung khác nhau.
Trường hợp của PVector cũng tương tự như vậy. Ở thời điểm hiện tại, bạn có thể thấy các thao tác hơi phức tạp, nhưng chắc chắn công sức bỏ ra lúc này sẽ được đền đáp xứng đáng. Và bạn cũng sẽ không phải đợi lâu, ta cần áp dụng những kiến thức này ngay trong chương tiếp theo.

Vận tốc

Vậy, lập trình chuyển động bằng vectơ nghĩa là như thế nào? Ta đã nghiên cứu ví dụ đầu tiên là chương trình quả bóng nảy. Một đối tượng trên màn hình luôn có vị trí position (tại bất kỳ thời điểm nào) và vận tốc velocity (chỉ dẫn về cách di chuyển từ điểm này đến điểm tiếp theo). Vận tốc được kết hợp với vị trí như sau:
position.add(velocity);
Sau đó, ta vẽ đối tượng tại vị trí mới:
ellipse(position.x, position.y, 16, 16);
Về cơ bản, thuật toán mô tả hiệu ứng chuyển động bao gồm 2 bước:
  • Kết hợp vận tốc và vị trí để có được vị trí mới
  • Vẽ đối tượng ở vị trí mới
Trong chương trình vẽ quả bóng nảy, tất cả các lệnh trên đều được đặt bên trong hàm draw. Bây giờ, ta sẽ tiến hành cải tiến chương trình bằng cách gói gọn tất cả các câu lệnh lập trình chuyển động bên trong một đối tượng. Cũng có thể nói rằng, ta đang tạo dựng nền tảng để lập trình vô số đối tượng chuyển động trong tất cả các chương trình ProcessingJS của mình.
Trong trường hợp này, ta sẽ khởi tạo một kiểu đối tượng Mover chuyên dùng để mô tả vật thể di chuyển xung quanh màn hình. Ta cần xem xét hai câu hỏi sau:
  • Đối tượng Mover chữa dữ liệu gì?
  • Đối tượng Mover có hành vi như thế nào?
Thuật toán chuyển động mà ta đã trình bày trả lời hai câu hỏi trên. Một đối tượng Mover có hai dữ liệu: position (vị trí) và velocity (vận tốc). Cả hai dữ liệu đều được mô tả bởi đối tượng PVector. Ta có thể bắt đầu bằng hàm khởi tạo với giá trị thuộc tính được lựa chọn ngẫu nhiên:
var Mover = function() {
  this.position = new PVector(random(width), random(height));
  this.velocity = new PVector(random(-2, 2), random(-2, 2));
};
Về hành vi, đối tượng Mover cần di chuyển và người xem phải nhìn thấy được đối tượng. Ta sẽ hiện thực hóa các yêu cầu đó với 2 phương thức update()display(). Phương thức update() có nhiệm vụ lập trình hiệu ứng chuyển động, còn phương thức display() sẽ vẽ đối tượng:
Mover.prototype.update = function() {
  this.position.add(this.velocity);
};

Mover.prototype.display = function() {
  stroke(0);
  strokeWeight(2);
  fill(127);
  ellipse(this.position.x, this.position.y, 48, 48);
};
Nếu chưa tìm hiểu về lập trình hướng đối tượng thì có thể bạn sẽ cảm thấy hơi khó hiểu. Ta đã dành các bài đọc đầu tiên của chương để thảo luận về PVector. Đối tượng PVector là nguyên mẫu để khởi tạo các đối tượng vị trí và vận tốc. Vậy vai trò của đối tượng PVector trong một đối tượng khác, cụ thể là Mover, là gì? Trên thực tế, đây là điều khá bình thường. Đối tượng được sử dụng để lưu trữ dữ liệu (và hành vi). Dữ liệu có thể là số, chuỗi ký tự, mảng hoặc đối tượng khác. Ta sẽ gặp cách thức này nhiều lần trong suốt khóa học. Ví dụ, trong phần về hạt, ta sẽ cần tạo đối tượng để mô tả hệ thống các hạt. Đối tượng ParticleSystem có dữ liệu là một mảng các đối tượng Particle và mỗi đối tượng Particle có dữ liệu là các đối tượng PVector!
Để hoàn thành phần khai báo kiểu đối tượng Mover, ta kết hợp một hàm nhằm chỉ định hành vi của đối tượng khi chạm đến mép của khung kết quả. Ở thời điểm hiện tại, ta sẽ chỉ thực hiện thao tác đơn giản như là cho đối tượng xuất hiện trở lại từ cạnh đối diện:
Mover.prototype.checkEdges = function() {

  if (this.position.x > width) {
    this.position.x = 0;
  } 
  else if (this.position.x < 0) {
    this.position.x = width;
  }

  if (this.position.y > height) {
    this.position.y = 0;
  } 
  else if (this.position.y < 0) {
    this.position.y = height;
  }
};
Sau khi đã hoàn thành khởi tạo kiểu đối tượng Mover mới, ta sẽ xem xét những điều cần làm trong chương trình chính. Đầu tiên, ta khai báo và khởi tạo một thực thể Mover mới:
var mover = new Mover();
Sau đó, ta gọi các hàm phù hợp trong phần định nghĩa hàm draw:
draw = function() {
  background(255, 255, 255);

  mover.update();
  mover.checkEdges();
  mover.display(); 
};
Dưới đây là chương trình hoàn chỉnh. Hãy thử điều chỉnh các con số, soạn thảo thêm câu lệnh và xem điều gì sẽ xảy ra:

Gia tốc

Đến đây, các bạn cần đảm bảo nắm vững hai kiến thức sau: (1) PVector là gì và (2) cách sử dụng PVector bên trong một đối tượng khác để theo dõi vị trí và chuyển động của đối tượng đó. Tiếp theo, ta sẽ thực hiện một bước tiến lớn hơn. Chương trình ví dụ trên có phần nhàm chán vì hình tròn không bao giờ tăng tốc hay giảm tốc, cũng không chuyển hướng. Để có thể lập trình được những chuyển động thú vị hơn, mô phỏng chính xác hơn chuyển động trong thế giới thực, ta cần thêm một PVector nữa vào đối tượng Mover để kiểm soát gia tốc.
Khái niệm gia tốc mà ta đang sử dụng ở đây được định nghĩa là: tốc độ thay đổi của vận tốc. Liệu đây có phải một khái niệm mới không? Không hẳn. Vận tốc được định nghĩa là tốc độ thay đổi của vị trí. Về bản chất, ta có thể mô tả bằng cụm từ "phản ứng dây chuyền". Gia tốc ảnh hưởng đến vận tốc, vận tốc ảnh hưởng đến vị trí (đây là vấn đề rất quan trọng trong chương tiếp theo vì ta sẽ nghiên cứu xem lực ảnh hưởng đến gia tốc như thế nào). Trong một chương trình máy tính, phản ứng dây chuyền này được hiện thực hóa như sau:
velocity.add(acceleration);
position.add(velocity);
Để việc học được hiệu quả hơn, kể từ bây giờ, hãy cùng tuân thủ một quy tắc: Lập trình tất cả các chương trình liên quan tiếp theo mà không động đến giá trị của vận tốc và vị trí (trừ thao tác khởi tạo ban đầu). Nói cách khác, mục tiêu của chúng ta là: Có được một thuật toán tính gia tốc và để "phản ứng dây chuyền" lo phần còn lại (dĩ nhiên, sẽ có những thời điểm mà quy tắc này không phải là phương án tốt nhất, thay vào đó bạn hãy coi đây là cơ hội để hiểu rõ "hậu trường" phía sau thuật toán chuyển động). Để hiện thực hóa quy tắc trên, ta cần có một số cách thức để tính toán gia tốc như sau:
  1. Gia tốc là hằng số
  2. Gia tốc là giá trị ngẫu nhiên
  3. Gia tốc theo vị trí chuột
Thuật toán tương ứng với cách thức thứ 1, gia tốc là hằng số, nhìn chung không thú vị nhưng là thuật toán đơn giản nhất và sẽ giúp ta biết cách kết hợp yếu tố gia tốc vào chương trình của mình.
Đầu tiên, ta cần thêm một thuộc tính PVector nữa vào hàm khởi tạo Mover để biểu thị gia tốc. Ta cũng sẽ khởi tạo giá trị gia tốc là (0.001,0.01) và giữ giá trị đó trong suốt chương trình vì thuật toán mà ta đang nghiên cứu là gia tốc không đổi. Bạn có thể cho rằng giá trị này có vẻ hơi nhỏ. Cần lưu ý rằng, gia tốc (đo bằng pixel) sẽ cộng dồn theo thời gian (để cho ra vận tốc tại một thời điểm). Tần suất cộng dồn là khoảng 30 lần mỗi giây, tùy thuộc vào tốc độ khung hình của chương trình. Để đảm bảo độ lớn của vectơ vận tốc nằm trong phạm vi hợp lý, giá trị gia tốc cần được khởi tạo và duy trì ở mức rất nhỏ.
var Mover = function() {
  this.position = new PVector(width/2,height/2);
  this.velocity = new PVector(0, 0);
  this.acceleration = new PVector(-0.001, 0.01);
};
Lưu ý, ở đoạn mã trên, ta cũng khởi tạo giá trị vận tốc ở mức 0 vì vận tốc sẽ tăng khi chương trình chạy nhờ có gia tốc. Ta lập trình phần tăng tốc trong phương thức update() như sau:
Mover.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);  
};
Bởi vì vận tốc liên tục tăng, giá trị vận tốc sẽ trở nên quá lớn nếu ta để chương trình chạy đủ lâu. Do vậy, ta cần giới hạn vận tốc ở mức phù hợp bằng cách sử dụng phương thức limit đính kém với kiểu đối tượng PVector. Phương thức này sẽ giới hạn độ lớn của vectơ ở một mức nhất định.
Mover.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.velocity.limit(10);
  this.position.add(this.velocity);  
};
Đoạn mã trên có thể được diễn giải như sau:
Độ lớn của vectơ vận tốc là bao nhiêu? Nếu nhỏ hơn 10 thì không sao. Nếu lớn hơn 10 thì cần giảm xuống 10!
Hãy xem chương trình dưới đây và so sánh với đối tượng Mover ở chương trình trước, khi chưa có accelerationlimit():
Tiếp đến là thuật toán tương ứng với cách thức thứ 2, gia tốc là giá trị ngẫu nhiên. Trong trường hợp này, thay vì khởi tạo gia tốc trong hàm khởi tạo đối tượng, ta sẽ chọn một giá trị gia tốc mới trong mỗi chu kỳ, hay chính là mỗi lần gọi hàm update().
Mover.prototype.update = function() {
  this.acceleration = PVector.random2D();
  this.velocity.add(this.acceleration);
  this.velocity.limit(10);
  this.position.add(this.velocity);  
};
Bởi vì vectơ ngẫu nhiên là vectơ đã được chuẩn hóa nên ta có thể phóng to/thu nhỏ bằng hai kỹ thuật sau:
  1. phóng to/thu nhỏ gia tốc thành vectơ có độ lớn là hằng số:
    acceleration = PVector.random2D();
    acceleration.mult(0.5);
    
  1. phóng to/thu nhỏ gia tốc thành vectơ có độ lớn ngẫu nhiên:
    acceleration = PVector.random2D();
    acceleration.mult(random(2));
    
Một lưu ý quan trọng cần nhớ là, gia tốc không chỉ đơn thuần phản ánh sự tăng tốc hay giảm tốc của một đối tượng đang chuyển động mà là bất kỳ sự thay đổi nào của vectơ vận tốc theo cả độ lớn và hướng. Gia tốc được sử dụng để điều hướng đối tượng khi cần và ta sẽ thấy điều này rất nhiều lần trong các chương sau, khi chúng ta bắt đầu lập trình những đối tượng có thể tự di chuyển trên màn hình.
Khóa học "Mô phỏng tự nhiên" này được biên soạn dựa trên cuốn "The Nature of Code" (tạm dịch: Bản chất của lập trình) của tác giả Daniel Shiffman, được sử dụng theo giấy phép Creative Commons Attribution-NonCommercial 3.0 Unported License.

Tham gia cuộc thảo luận?

Chưa có bài đăng nào.
Bạn có hiểu Tiếng Anh không? Bấm vào đây để thấy thêm các thảo luận trên trang Khan Academy Tiếng Anh.