欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

网页视觉特效-p5.js完成星际穿越

程序员文章站 2022-04-16 07:49:07
p5.js完成星际穿越特效 欢迎关注我的 "博客" ,⬅️点他即可。 星际穿越,是模仿漫天星辰扑面而来的感觉。 最关键的在于对透视的掌握。 参考资料:The Coding Train 00 思路构想 1. 星星是一个圆,会随机的出现在屏幕的任何位置; 2. 星星会从远处到眼前: 圆的大小 来表示远近 ......

p5.js完成星际穿越特效

欢迎关注我的,⬅️点他即可。

星际穿越,是模仿漫天星辰扑面而来的感觉。

网页视觉特效-p5.js完成星际穿越

最关键的在于对透视的掌握。

参考资料:the coding train

00 思路构想

  1. 星星是一个圆,会随机的出现在屏幕的任何位置;
  2. 星星会从远处到眼前:圆的大小来表示远近;
  3. 星星的运动轨迹:连接星星与中心点的射线,向外运动。

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)

	// 这里放刚刚新建星星的代码
}

注意,这里不要使用widthheight作为变量名。因为 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 移动星星

移动星星,意味着改变位置,连续的改变位置就会变成移动。因为我们前面说到过,使用一个对象的方法创建的圆,再调用该对象的该方法时,便会重新创建,原来的圆就会消失。

这一步看起来很复杂,但其实非常简单。

我们在更新函数里面,只需要做两件事情:

  1. 减少 z,因为 z 是星星离我们的距离;
  2. 如果星星跑到了我们背后,他们就该重置位置在离我们最远处了。
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)
	}
}

这里我加上了射线,其实非常简单,就是在星星的新位置和起始点画三角形即可。

(完)