p5.js完成星際穿越特效 歡迎關註我的 "博客" ,⬅️點他即可。 星際穿越,是模仿漫天星辰撲面而來的感覺。 最關鍵的在於對透視的掌握。 參考資料:The Coding Train 00 思路構想 1. 星星是一個圓,會隨機的出現在屏幕的任何位置; 2. 星星會從遠處到眼前: 圓的大小 來表示遠近 ...
p5.js完成星際穿越特效
歡迎關註我的博客,⬅️點他即可。
星際穿越,是模仿漫天星辰撲面而來的感覺。
最關鍵的在於對透視的掌握。
參考資料:The Coding Train
00 思路構想
- 星星是一個圓,會隨機的出現在屏幕的任何位置;
- 星星會從遠處到眼前:圓的大小來表示遠近;
- 星星的運動軌跡:連接星星與中心點的射線,向外運動。
01 創建星星
我們可以使用一個 Star 類,來用它表示我們的星星。
class Star { }
星星的成員變數有哪些?星星的位置、大小和運動的速度:
class Star {
constructor() {
this.x = random(-width / 2, width / 2) // 隨機x坐標
this.y = random(-width / 2, width / 2) // 隨機y坐標
this.z = random(0, width) // 隨機z坐標
this.r = 25 + random(-2, 3) // 隨機半徑
}
// 在當前位置畫圓
show() {
fill(255)
noStroke()
ellipse(this.x, this.y, this.r, this.r)
}
}
這裡為什麼是-width/2 到 width/2 呢?
而且為什麼 y 的值也要取決於 width 呢?
因為 p5 的原點是在左上角,我們的星星從原點計算起會很方便,一會兒我們會通過一條語句,將整個畫面往右下挪動,使原點在畫面中呈現!
y 的值之所以取決於 width,是因為電腦一般都是長方形,並且 width>height。
如果是分別根據 width 和 height 隨機值,就會導致分佈不均勻,變成蝴蝶型(左右兩邊集中,上下很稀疏)。
我們還需要什麼?
因為星星會不斷的移動,所以說我們需要繪製它移動的軌跡。但是首先,我們需要初始化星星:
let stars = [] // 存放星星的數組
function setup() {
const starsNumber = 100 // 星星的個數
for (let i = 0; i < starsNumber; i++) {
const temp = new Star()
stars.push(temp)
}
}
現在我們初始化了 100 個星星,但是畫布忘記繪製了。我們不妨利用 css 和 js,讓用戶瀏覽器無論是多大,我們都剛剛好全屏顯示。
讓我們來寫一點點 CSS:
* {
margin: 0;
padding: 0;
}
body {
width: 100vw;
height: 100vh;
}
然後需要用到:document.body.offsetWidth
獲取寬度,同理,offsetHeight
可以獲取高度:
function setup() {
const wid = document.body.offsetWidth
const heig = document.body.offsetHeight
createCanvas(wid, heig)
// 這裡放剛剛新建星星的代碼
}
註意,這裡不要使用width
與height
作為變數名。因為 p5 中,width 與 height 就代表當前畫布寬高。
所以歸納一下,第一階段,在 setup 方法和 draw 方法中,應該這麼寫:
let stars = []
function setup() {
const wid = document.body.offsetWidth
const heig = document.body.offsetHeight
createCanvas(wid, heig)
const starsNumber = 100
for (let i = 0; i < starsNumber; i++) {
const temp = new Star()
stars.push(temp)
}
}
function draw() {
background('#000')
for (let i = 0; i < stars.length; i++) {
stars[i].show()
}
}
這樣就在屏幕上繪製出了 100 個靜態的星星。
02 移動星星
移動星星,意味著改變位置,連續的改變位置就會變成移動。因為我們前面說到過,使用一個對象的方法創建的圓,再調用該對象的該方法時,便會重新創建,原來的圓就會消失。
這一步看起來很複雜,但其實非常簡單。
我們在更新函數裡面,只需要做兩件事情:
- 減少 z,因為 z 是星星離我們的距離;
- 如果星星跑到了我們背後,他們就該重置位置在離我們最遠處了。
update(speed) {
z = z - seed;
if(z <= 1) {
z = width;
x = random(0, width);
y = random(0, height);
}
}
這裡可能有人會疑惑:為什麼第一次初始化星星的時候,是random(0, width)
,現在讓 z 的值直接等於 width 呢?
因為我們第一打開網頁的時候,星星出現,這個時候星星應該是有遠的有近的。
可是當我們星星飛走了,重新生成的時候,他就該從無限遠處進入視野,而不是直接出現在眼前。
接下來我們不需要關心 x 和 y 的值應該怎樣變化,因為可以通過某種計算,將 z 的變化,線性的表現在 x,y 上。
這裡需要提到一點,我們在這裡利用了透視的原理,實際上,x 與 y 的值是不會改變的,只是我們的透視視角,讓他們看起來,是一條斜線。
更新完了位置,我們需要在 show 方法中,通過計算表示出新的 x、y 了。
show() {
fill(255); // 上色
noStroke()
const nowX = map(this.x / this.z, -1, 1, -width / 2, width / 2)
const nowY = map(this.y / this.z, -1, 1, -width / 2, width / 2)
const nowR = map(this.z, 0, width, this.r, 0)
ellipse(nowX, nowY, nowR, nowR)
}
緊接著我們只需要在主方法里,不斷的迴圈update()
和show()
即可!
03 大功告成
function draw() {
background(0);
translate(width / 2, height / 2);
for(int i = 0; i < stars.length; i++) {
stars[i].update(speed);
stars[i].show();
}
04 附錄:完整代碼
let stars = []
function setup() {
const wid = document.body.offsetWidth
const heig = document.body.offsetHeight
createCanvas(wid, heig)
let starsNumber = parseInt((width * height) / 6500)
for (let i = 0; i < starsNumber; i++) {
const temp = new Star()
stars.push(temp)
}
}
function draw() {
translate(wid / 2, heig / 2)
background('#000')
for (let i = 0; i < stars.length; i++) {
stars[i].update(15)
stars[i].show()
}
}
class Star {
constructor() {
this.x = random(-width / 2, width / 2)
this.y = random(-width / 2, width / 2)
this.z = random(0, width)
this.r = 25 + random(-2, 3)
this.isMiss = false
this.sx
this.sy
}
update(speed) {
this.z -= speed
if (this.z <= 1) {
this.x = random(-width / 2, width / 2)
this.y = random(-width / 2, width / 2)
this.z = width
this.isMiss = false
}
}
show() {
fill(255)
noStroke()
const nowX = map(this.x / this.z, -1, 1, -width / 2, width / 2)
const nowY = map(this.y / this.z, -1, 1, -width / 2, width / 2)
if (!this.isMiss) {
this.sx = nowX
this.sy = nowY
this.isMiss = true
}
const nowR = map(this.z, 0, width, this.r, 0)
ellipse(nowX, nowY, nowR, nowR)
stroke(255)
triangle(nowX + nowR / 3, nowY + nowR / 3, nowX - nowR / 3, nowY - nowR / 3, this.sx, this.sy)
}
}
這裡我加上了射線,其實非常簡單,就是在星星的新位置和起始點畫三角形即可。
(完)