unit untFireworks;
interface
uses
System.Types,
System.UITypes,
System.Classes,
System.UIConsts,
FMX.Filter.Effects,
FMX.Controls,
FMX.Effects,
FMX.Ani,
FMX.Types,
FMX.Objects;
type
TFireWorksStatus = record
private
public
StartX: Single;
FireY: Single;
FireRadius: Single;
FireColorHue: Single; // -1 から 1 までの値を取る
LauncherDelay: Single;
LauncherDuration: Single;
FireDuration: Single;
BallRadius: Single;
BallCount: Integer;
BallBitmap: TBitmap;
LauncherWidth: Single;
LauncherHeight: Single;
end;
type
TFireworks = class
private
FTimer: TTimer;
FParentImage: TImage;
FOwner: TComponent;
FStatus: TFireWorksStatus;
FLauncher: TRectangle;
FLauncherPositionYAnimation: TFloatAnimation;
FLauncherBlur: TBlurEffect;
FBall: Array of TCircle;
FBallImage: Array of TImage;
FBallPositionAnimation: Array of TPathAnimation;
FBallOpacityAnimation: Array of TFloatAnimation;
FBallHueAdjustEffect: Array of THueAdjustEffect;
procedure OnTimer(Sender: TObject);
procedure OnFLauncherPositionYAnimationFinish(Sender: TObject);
procedure OnBallOpacityAnimationFinish(Sender: TObject);
function getScreenHeight: Single;
function getScreenWidth: Single;
property ScreenWidth: Single read getScreenWidth;
property ScreenHeight: Single read getScreenHeight;
public
constructor Create(aOwner: TComponent;
aParent: TImage;
aFireWorksStatus: TFireWorksStatus);
destructor Destroy(); override;
procedure Fire();
end;
const
START_POSITION_V_MARGIN = 0.1;
START_POSITION_H_MARGIN = 0.1;
FINISH_POSITION_V_MARGIN = 0.1;
implementation
{ TFireworks }
constructor TFireworks.Create(aOwner: TComponent;
aParent: TImage;
aFireWorksStatus: TFireWorksStatus);
var
i: Integer;
tmpRad: Single;
begin
FTimer := TTimer.Create(nil);
FTimer.OnTimer := OnTimer;
FTimer.Enabled := False;
FOwner := aOwner;
FParentImage := aParent;
FParentImage.ClipChildren := True;
FStatus := aFireWorksStatus;
//
FTimer.Interval := Round(FStatus.LauncherDelay * 1000);
FTimer.Enabled := False;
//
FLauncher := TRectangle.Create(FOwner);
FLauncher.Parent := FParentImage;
FLauncher.Visible := False;
FLauncherBlur := TBlurEffect.Create(FOwner);
FLauncherBlur.Parent := FLauncher;
FLauncherBlur.Softness := 0.1;
with FStatus do
begin
FLauncherPositionYAnimation := TFloatAnimation.Create(FOwner);
FLauncherPositionYAnimation.Parent := FLauncher;
FLauncherPositionYAnimation.PropertyName := 'Position.Y';
FLauncherPositionYAnimation.OnFinish := OnFLauncherPositionYAnimationFinish;
FLauncher.Width := LauncherWidth;
FLauncher.Height := LauncherHeight;
FLauncher.Position.X := StartX - LauncherWidth / 2;
FLauncher.Position.Y := Self.ScreenHeight * 0.95 - LauncherHeight / 2;
FLauncherPositionYAnimation.StartValue := FLauncher.Position.Y;
FLauncherPositionYAnimation.StopValue := FireY - LauncherHeight / 2;
FLauncherPositionYAnimation.Duration := LauncherDuration;
FLauncherPositionYAnimation.AnimationType := TAnimationType.atOut;
FLauncherPositionYAnimation.Interpolation := TInterpolationType.itCubic;
end;
//
SetLength(FBall, FStatus.BallCount);
SetLength(FBallPositionAnimation, FStatus.BallCount);
SetLength(FBallOpacityAnimation, FStatus.BallCount);
SetLength(FBallImage, FStatus.BallCount);
SetLength(FBallHueAdjustEffect, FStatus.BallCount);
for i := 0 to FStatus.BallCount - 1 do
begin
FBallImage[i] := TImage.Create(FOwner);
FBallImage[i].Parent := FParentImage;
FBallImage[i].Bitmap.Assign(FStatus.BallBitmap);
FBallImage[i].Width := FStatus.BallRadius * 2;
FBallImage[i].Height := FStatus.BallRadius * 2;
FBallImage[i].Position.X := FStatus.StartX - FStatus.BallRadius;
FBallImage[i].Position.Y := FStatus.FireY - FStatus.BallRadius;
FBallImage[i].RotationAngle := (360 / FStatus.BallCount) * i + 90;
FBallImage[i].Visible := False;
//
FBallPositionAnimation[i] := TPathAnimation.Create(FOwner);
FBallPositionAnimation[i].Parent := FBallImage[i];
FBallPositionAnimation[i].Duration := FStatus.FireDuration * 0.8;
FBallPositionAnimation[i].AnimationType := TAnimationType.atOut;
FBallPositionAnimation[i].Interpolation := TInterpolationType.itCubic;
FBallPositionAnimation[i].Path.Clear;
FBallPositionAnimation[i].Path.MoveTo(PointF(0, 0));
tmpRad := (2 * PI / FStatus.BallCount) * i;
FBallPositionAnimation[i].Path.LineTo(PointF(Cos(tmpRad) * FStatus.FireRadius,
Sin(tmpRad) * FStatus.FireRadius));
//
FBallOpacityAnimation[i] :=TFloatAnimation.Create(FOwner);
FBallOpacityAnimation[i].Parent := FBallImage[i];
FBallOpacityAnimation[i].PropertyName := 'Opacity';
FBallOpacityAnimation[i].Duration := FStatus.FireDuration;
FBallOpacityAnimation[i].StartValue := 1;
FBallOpacityAnimation[i].StopValue := 0.1;
FBallOpacityAnimation[i].AnimationType := TAnimationType.atIn;
FBallOpacityAnimation[i].Interpolation := TInterpolationType.itCubic;
//
FBallHueAdjustEffect[i] := THueAdjustEffect.Create(FOwner);
FBallHueAdjustEffect[i].Parent := FBallImage[i];
FBallHueAdjustEffect[i].Hue := FStatus.FireColorHue;
end;
// 代表として Index 0 を基準にする
FBallOpacityAnimation[0].OnFinish := OnBallOpacityAnimationFinish;
end;
destructor TFireworks.Destroy;
begin
FTimer.Free;
inherited;
end;
function TFireworks.getScreenHeight: Single;
begin
Result := FParentImage.Height;
end;
function TFireworks.getScreenWidth: Single;
begin
Result := FParentImage.Width;
end;
procedure TFireworks.OnBallOpacityAnimationFinish(Sender: TObject);
var
i: Integer;
begin
for i := 0 to FStatus.BallCount - 1 do
begin
FBallImage[i].Visible := False;
end;
self.Free;
end;
procedure TFireworks.OnFLauncherPositionYAnimationFinish(Sender: TObject);
var
i: Integer;
begin
FLauncher.Visible := False;
for i := 0 to FStatus.BallCount - 1 do
begin
FBallImage[i].Visible := True;
FBallPositionAnimation[i].Start();
FBallOpacityAnimation[i].Start();
end;
end;
procedure TFireworks.OnTimer(Sender: TObject);
begin
FLauncher.Visible := True;
FLauncherPositionYAnimation.Start();
FTimer.Enabled := False;
end;
procedure TFireworks.Fire;
begin
// Start
FTimer.Enabled := True;
end;
end.
花火 アニメーションサンプル
コメントをどうぞ