前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >残影拖尾实现思路分析

残影拖尾实现思路分析

作者头像
ChildhoodAndy
发布2021-06-09 10:49:36
1.9K0
发布2021-06-09 10:49:36
举报
文章被收录于专栏:小菜与老鸟小菜与老鸟

残影拖尾效果实现思路分析

今天小菜给大家分享下实现残影、拖尾效果的几种实现思路,或者叫固定套路,保准大家认真看完后,以后再也不怕实现残影、拖尾效果了。

本文字数比较多,且部分内容需要阅读代码加以思考,预计阅读10-15分钟。(画外音:小菜好不容易总结的,客官读完有收获再走呀,?)

残影

啥是残影?小菜直接上图说明。

封面图

游戏人物挥剑动作

游戏人物冲刺动作

李小龙经典镜头

我们经常在影视剧、游戏中,看到残影的镜头。有武器特效方面的,也有人物动作方面的。看到这里想必大家应该了解到了什么是残影了吧。

小菜用白话描述下:

有一个运动的物体,在一段时间内,从这个位置运动到了那个位置,在我们看到的某个画面时间点上,却展示了物体在前一小段时间内的物体运动位置轨迹,这些轨迹往往以半透明的方式展现出来(还有其他表现形势,如上面的游戏中人物冲刺动作的残影),营造出视觉残留效果,起到不错的观赏效果。

拖尾

拖尾又是啥?顾名思义,拖动尾巴,尾巴跟随的效果,拖尾常常可以和残影一起说,因为残影效果往往伴随着拖尾,就是物体运动着,在之前历史时间点的位置轨迹也会展现出来,不断的消失,不断的跟随。

但拖尾也可以单独拎出来说,不说残影效果,只说尾巴的跟随效果。我们今天的例子也会讲到。

常用套路

下面我们用 Processing 来实现残影、拖尾效果,分析下如何实现。小菜将套路总结成三个:

1)半透明叠加法

2)生命流逝法

3)中学生班级晨跑法

套路1-半透明叠加法

void setup() {
  size(800, 800);
  background(0);
  noStroke();
}

void draw() {
  fill(0, 20);
  rect(0, 0, width, height);

  fill(30, 255, 255);
  circle(mouseX, mouseY, 50);
}

我们运行下看下效果

代码简单的不能再简单了,但却能实现了一种残影拖尾效果。是不是很神奇?

我们来分析下这个残影的实现原理:

1)黑色的画布背景

2)一个跟随鼠标运动的圆,填充色RGB为30,255,255

3)每一次 draw 绘制时,都会在画布上画一层和画布背景颜色的一样,但具有一定透明度的长方形(一般和画布大小一致)

如果去掉了第三步,效果是什么样?

void draw() {
  fill(30, 255, 255);
  circle(mouseX, mouseY, 50);
}

很明显,我们在画布上不断的画圆,原来的圆会一直停留在画布上。所以随着我们鼠标的运动,会形成一个圆按照鼠标运行轨迹叠加出来的一个画面。

那我们清除下画布呢?每次在 draw 中都填充下背景色,可以将之前画的圆全部擦除掉

void draw() {
  background(0); // 每一次绘制,都填充下背景色
  fill(30, 255, 255);
  circle(mouseX, mouseY, 50);
}

因为每一次绘制都把画布填充了下,会把原来绘制的圆给擦除掉,所以最终呈现的效果如上 gif 图效果。

好了。不清除画布,会导致圆按照轨迹不断叠加,形成一条圆组成的“线条“。填充背景色清除画布,会只看到一个圆跟随鼠标运动。

关键的地方来了,我们每次填充一个半透明画布大小的矩形会怎么样呢?会发生什么神奇的效果?残影 is comming!

一句话讲清原理:不断叠加的半透明矩形会越来越不透明,历史的圆圈轨迹,在半透明矩形叠加的情况下,会慢慢的消失(渐隐),跟着鼠标运动不断新绘制出来的圆,也会被后面叠加的半透明矩形给渐渐的隐藏掉。

我们来看下原理的动态演示

每次 draw 中的半透明矩形的半透明度,目前设置是20(0~255的范围),决定着残影的停留时长,设置的越低,叠加的越慢,半透明叠加到完全不透明需要的时间就越长,残影停留时间就越长。

  fill(0, 20); // 20的透明度
  rect(0, 0, width, height);

我们把 20 改成 60 看看,效果比较明显:

透明度20

透明度60

套路2-生命流逝法

小菜再次尝试用一段话来描述原理:生命流逝法使用的是面向对象编程的方式,将运动的圆抽象成一个生命体,这个生命体诞生的时候具有 255 的生命值(刚好和透明度对应),随着时间的推移,这个生命体的生命也在不断流逝,降低到 0 后就会死亡,而生命体的生命值会反映在它的透明度上。

Talk is cheap, show me the code!

ArrayList<Mover> movers = new ArrayList<Mover>();

void setup() {
  size(600, 600);
  colorMode(RGB);
  background(0);
}

void draw() {
  background(0);
  for (int i = movers.size() - 1; i >= 0; i--) {
    Mover mover = movers.get(i);
    mover.run();
    if (mover.isDead()) {
      movers.remove(i);
    }
  }
}

void mouseDragged() {
  movers.add(new Mover(mouseX, mouseY, 50));
}

// Mover是生命体
class Mover {
  float x;
  float y;
  float radius;
  float life;

  Mover(float x, float y, float radius) {
    this.x = x;
    this.y = y;
    this.radius = radius;

    this.life = 255;
  }

  void run() {
    update();
    display();
  }

  void update() {
    life -= 12;
    life = max(life, 0);
  } 

  boolean isDead() {
    if (life <= 0.0) { 
      return true;
    } else {
      return false;
    }
  }

  void display() {
    fill(30, 255, 255, life); 
    noStroke();
    circle(x, y, radius);
  }
}

我们描述下代码思路:

1)我们在鼠标按下的时候,生成一个生命体,生命体诞生于鼠标的位置,生命刚出生255岁,我们将生命体加入到数组中

2)我们在每一帧的绘制中,遍历生命体数组,让生命体的生命流逝,生命流逝会导致透明度逐渐降低到0,变得透明不可见(update函数)

3)我们在每一帧的绘制中,遍历生命体数组,检查生命体是否死亡,死亡的判断依据就是生命值小于等于0,当生命体死亡的时候,我们把生命体从数组中移除,避免数组无限增大,做无谓的遍历与绘制 (isDead函数)

4)我们在每一帧的绘制中,遍历生命体数组,绘制生命体的样子(display函数)

5)记得每一帧用背景色填充,将之前的绘制擦除掉,因为不再需要。在当前帧中,有所有生命体的位置和透明度信息,可以将他们全部绘制出来

我们可以在 display 函数中额外显示下生命体的生命值:

  void display() {
    fill(30, 255, 255, life); 
    noStroke();
    circle(x, y, radius);

    fill(255);
    text(life, x, y);
  }

运行下

生命体这里都是固定的生命流逝速度,update函数中每次流逝12点生命,调整流逝速度,会直接影响残影的停留时长。先诞生的生命体,先死亡,后诞生的后死亡,于是就有了上图的效果。

套路3-中学生班级晨跑法

这个套路常常用于实现拖尾效果。

小菜想了很久,怎么用通俗易懂的语言来描述这个原理。最终想到了上高中时,班级晨跑锻炼的场景。班级晨跑有以下几个特点:

1)班级的人数固定了,比如是30个同学

2)假设晨跑纵队是一列(为了贴近代码演示,咱们的晨跑是一个纵队,上学的时候一般纵队是2-3列,不然队伍太长了),队首同学不断的绕操场跑圈,队首不断的在更新位置(跟随鼠标)

3)队伍中除了队首同学,每个同学只需要跟着前面一个同学跑就行了,看着前一个同学的后脑勺,下一步将要跑到的位置就是前一个同学的位置

// 中学生班级晨跑法

int num = 100; // 100个同学
int[] x = new int[num];
int[] y = new int[num];

void setup() {
  size(600, 600);
  noStroke();
  fill(255, 100);
}

void draw() {
  background(0);

  // 从尾巴到头部,每个节点位置更新成上一个节点的位置
  // 在此帧绘制中,每一个同学的位置是上一个同学的位置
  for (int i = num - 1; i > 0; i--) {
    x[i] = x[i - 1];
    y[i] = y[i - 1];
  }

  // 队首同学跑步,跟着鼠标跑
  x[0] = mouseX;
  y[0] = mouseY;

  for (int i = 0; i < num; i++) {
    // 越靠前的位置,圆圈越大,越靠后,尾巴越小
    ellipse(x[i], y[i], (num - i) / 2, (num - i) / 2);
    // 越靠前的位置,圆圈越小,越靠后,尾巴越大
    //ellipse(x[i], y[i], i / 2, i / 2);
    // 所有圆圈固定大小
    //ellipse(x[i], y[i], 30, 30);
  }
}

至此,小菜分享了3种实现套路,你经常用哪种呢?不妨留言?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小菜与老鸟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 残影
  • 拖尾
  • 常用套路
  • 套路1-半透明叠加法
  • 套路2-生命流逝法
  • 套路3-中学生班级晨跑法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档