Flutter Flame教程10 -- Particles粒子效果

我跑步前腳先著地發表於2020-05-04

Particle -- 粒子


Flame提供了基本的,同時也很強大的,可擴充套件的粒子系統。這個系統的核心概念是Particle類,在行為上和ParticleComponent很相似。

ParticleBaseGame的最基本使用如下:

import 'package:flame/components/particle_component.dart';

game.add(
    ParticleComponent(
        particle: CircleParticle()
    );
);
複製程式碼

Particle和自定義的Game實現一起使用時,請確保在每次遊戲迴圈幀時,Particleupdaterender生命週期鉤子被呼叫。

實現想要的粒子效果的主要方式:

  • 現有行為的組合
  • 使用行為鏈(只是一個語法糖)
  • 使用ComputedParticle

通過從上到下的效果,合成的工作方式與Flutter小部件的工作方式類似。通過定義自底向上的行為,鏈式允許更流暢的表達相同的組合樹。計算粒子又將行為的實現完全委派給您的程式碼。在需要的地方,任何方式都可以和現存的行為組合使用。

下面你可以看到顯示使用上面定義的三種方式從(0,0)加速到隨機方向的一個圓圈的效果示例。

Random rnd = Random();
Function randomOffset = () => Offset(
    rnd.nextDouble() * 200 - 100,
    rnd.nextDouble() * 200 - 100,
);

game.add(
    ParticleComponent(
        particle: Particle.generate(
            count: 10,
            generator: (i) => AcceleratedParticle(
                acceleration: randomOffset(),
                child: CircleParticle(
                    paint: Paint()..color = Colors.red
                )
            )
        )
    )
);

game.add(
    Particle
        .generate(
            count: 10,
            generator: (i) => CircleParticle(paint: Paint()..color = Colors.red)
                .accelerating(randomOffset())
        )
        .asComponent()
);

game.add(
    Particle
        .gnerate(
            count: 10,
            generator: (i) {
                var position = Offset.zero;
                var speed = Offset.zero;
                final acceleration = randomOffset();
                final paint = Paint()..color = Colors.red;
                
                return ComputedParticle(
                    renderer: (canvas, _) {
                        speed += acceleration;
                        position += speed;
                        canvas.drawCircle(position, 10, paint);
                    }
                );
            }
        )
)
複製程式碼

你可以在這裡找到更多以各種組合使用不同的內建粒子的示例。

Lifecycle 生命週期


所有Particle的普遍行為是它們都接受lifespan引數。一旦其內部粒子達到其使用壽命時,該值用於使ParticleComponent自我銷燬。Particle內部的時間使用Flame的Timer。可以將其配置為double,通過將其傳遞到相應的Particle構造器來表示秒(精確到微妙)。

Particle(lifespan: .2);
Particle(lifespan: 4);
複製程式碼

通過使用setLifespan方法,也可以重置Particle壽命,接收double型別的秒。

final particle = Particle(lifespan: 2);
particle.setLifespan(2);
複製程式碼

在存活時間內,Particle追蹤它存活的時間,並暴露progess進度,這是一個浮點型單位,值從0跨度到1.它的值與Flutter的AnimationController的value值類似。

final duration = const Duration(seconds: 2);
final particle = Particle(lifespan: duration.inMicroseconds / Duration.microsecondsPerSecond);

game.add(ParticleComponent(particle: particle));

Timer.periodic(duration * .1, () => print(particle.progress));
複製程式碼

如果支援任何一個內建行為,生命週期會被傳遞給定Particle的所有後代。

內建粒子


Flame附帶了一些內建的粒子行為:

  • TranslatedParticle,通過給定的Offset平移child
  • MovingParticle,在兩個預定義的Offset移動它的child,支援Curve
  • AcceleratedParticle,允許基本的基於物理的效果,比如重力或者速度衰減
  • CircleParticle,渲染所有形狀和大小的圓形
  • SpriteParticle,在粒子效果中渲染Flame精靈
  • ImageParticle,在粒子效果中渲染dart:ui的Image
  • ComponentParticle,在粒子效果中渲染Flame的Component
  • FlareParticle,在粒子效果中渲染Flare動畫

一起使用這些行為的更多粒子可以在這裡找到。所有的可用實現都可以在Flame原始碼的particles資料夾中找到。

Translated Particle


只需平移粒子到渲染畫布的指定便宜即可。不改變或修改位置。如果需要改變位置,考慮使用MovingParticle或者AcceleratedParticle。通過平移畫布也可以達到相同的效果。

game.add(
    ParticleComponent(
        particle: TranslatedParticle(
            offset: game.size.center(Offset.zero),
            child: Particle(),
        )
    )
);
複製程式碼

Moving Particle 移動粒子


在粒子的生命週期中,從from移動到to。通過CurvedParticle支援Curve

game.add(
    ParticleComponent(
        particle: MovingParticle(
            from: game.size.topLeft(Offset.zero),
            to: game.size.bottomRight(Offset.zero),
            child: Particle(),
        )
    )
);
複製程式碼

Accelerated Particle 加速粒子


一個允許你指定它的初始位置,速度和加速度的基本物理粒子,update週期完成剩餘工作。三個指定的都是Offset,你可以認為是向量。對於基於物理的爆發尤其有效,但不侷限於此。Offset的單位是每秒邏輯畫素。所以Offset(0, 100)的速度會在遊戲時間的每秒移動子粒子100個裝置畫素。

final rnd = Random();
game.add(
    ParticleComponent(
        particle AcceleratedParticle(
            position: game.size.center(Offset.zero),
            speed: Offset(rnd.nextDouble() *200 - 100, -rnd.nextDouble() * 100),
            acceleration: Offset(0, 100),
            child:Particle(),
        )
    )
);
複製程式碼

Circle Particle 原型粒子


傳遞給畫布的0值偏移的畫筆上渲染圓形的粒子。為了達到想要的位置,組合使用TranslatedParticle,MovingParticle或者AcceleratedParticle

game.add(
    ParticleComponent(
        particle: CircleParticle(
            radius: game.size.width / 2,
            paint: Paint()..color = Colors.red.withOpacity(.5),
        )
    )
);
複製程式碼

Sprite Particle 精靈粒子


允許你在粒子效果中插入Flame的精靈。使用SpriteSheet效果的圖形時很有用

game.add(
    ParticleComponent(
        particle: SpriteParticle(
            sprite: Sprite('sprite.png'),
            size: Position(64, 64),
        )
    )
);
複製程式碼

Image Particle 圖片粒子


在粒子樹種渲染給定的dart:ui圖片。

await Flame.images.loadAll(const [
    'image.png'
]);

game.add(
    ParticleComponent(
        particle: ImageParticle(
            size: const Size.square(24),
            image: Flame.images.loadedFiles['image.png'],
        )
    )
);
複製程式碼

Animation Particle 動畫粒子


嵌入Flame動畫的粒子。預設情況下,對齊“動畫” stepTime,以便在“粒子”生命週期中完全播放。可以使用alignAnimationTime引數覆蓋此行為.

final spritesheet = SpriteSheet(
    imageName: 'spritesheet.png',
    textureWidth: 16,
    textureHeight: 16,
    columns: 10,
    rows: 2
);

game.add(
  ParticleComponent(
    particle: AnimationParticle(
        animation: spritesheet.createAnimation(0, stepTime: 0.1),
    )
  )  
);
複製程式碼

Component Particle 元件粒子


Particle允許你將Flame Component嵌入到粒子效果中。Component可以有自己的update生命週期,並且在不同的效果樹種被重用。如果您唯一需要的是向某些Component的例項新增一些動態效果,請考慮將其直接新增到Game中,而中間不包含Particle

var longLivingRect = RectComponent();

game.add(
    ParticleComponent(
        particle: ComponentParticle(
            component: longLivingRect
        )
    )
);

class RectComponent extends Component {
    void render(Canvas c) {
        c.drawRect(
            Rect.fromCenter(center: Offset.zero, width: 100, height: 100),
            Paint()..color = Colors.red
        );
    }
    
    void update(double dt) {
        
    }
}

複製程式碼

Flare Particle


FlareAnimation的容器,向它的子代傳遞updaterender鉤子。

const flareSize = 32.0;
final flareAnimation = await FlareAnimation.load('assets/sparkle.flr');
flareAnimation.updateAnimation('Shine');
flareAnimation.width = flareSize;
flareAnimation.height = flareSize;

game.add(
    ParticleComponent(
        particle: FlareParticle(flare: flareAnimation),
    )
);
複製程式碼

Computed Particle 計算的粒子


Particle可以在如下情況幫助你:

  • 預設的行為不夠
  • 複雜效果優化
  • 簡化自定義

當建立的時候,提供的ParticleRenderDelegate代理所有的渲染,在每一幀被呼叫,並且執行必要的計算,渲染到畫布上。 建立後,將所有渲染委託給提供的ParticleRenderDeletegate,在每一幀上呼叫該類,以執行必要的計算並將某些內容渲染到Canvas上。

game.add(
    ParticleComponent(
        particle: ComputedParticle(
            renderer: (canvas, particle) => canvas.drawCircle(
                Offset.zero,
                particle.progress * 10,
                Paint()
                    ..color = Color.lerp(
                        Colors.red,
                        Colors.blue,
                        particle.progress,
                    )
            )
        )
    )
)

複製程式碼

Nesting behavior 內嵌行為


Flame的粒子實現遵循與Flutter 小元件相同的極端合成模式。封裝每個粒子的小部分行為,然後嵌入到一起來達到想要的視覺效果來實現的。

允許粒子內嵌彼此的兩個實體是:SingleChildParticle mixin 和ComposedParticle類。

SingleChildParticle可以幫助你建立自定義行為的粒子。 比如,在每幀中隨機放置子元件的位置:

var rnd = Random();
class GlitchParticle extends Particle with SingleChildParticle {
    @override
    Particle child;
    
    GlitchParticle({
        @required this.child,
        double lifespan,
    }) : super(lifespan: lifespan);
    
    @override
    render(Canvas canvas) {
        canvas.save();
        canvas.translate(rnd.nextDouble(0 * 100, rnd.nextDouble() *100);
        
        super.render();
        
        canvas.restore();
    }
}
複製程式碼

ComposedParticle 可以單獨使用,也可以使用在已存的粒子樹種。

相關文章