game/lib/widgets/test/sprite_animation_widget.dart

156 lines
4.3 KiB
Dart

import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class SpriteAnimationWidget extends StatefulWidget {
final String assetPath;
final double tileWidth;
final double tileHeight;
final int frameCount;
final double scale;
const SpriteAnimationWidget({
super.key,
required this.assetPath,
this.tileWidth = 100.0,
this.tileHeight = 100.0,
this.frameCount =
6, // Default guess, will adjust logic to use actual image width if possible
this.scale = 1.0,
});
@override
State<SpriteAnimationWidget> createState() => _SpriteAnimationWidgetState();
}
class _SpriteAnimationWidgetState extends State<SpriteAnimationWidget>
with SingleTickerProviderStateMixin {
ui.Image? _image;
late AnimationController _controller;
bool _isLoading = true;
int _calculatedFrameCount = 6;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600), // 100ms per frame approx
);
_loadImage();
}
Future<void> _loadImage() async {
try {
final ByteData data = await rootBundle.load(widget.assetPath);
final List<int> bytes = data.buffer.asUint8List();
final Completer<ui.Image> completer = Completer();
ui.decodeImageFromList(Uint8List.fromList(bytes), (ui.Image img) {
completer.complete(img);
});
final image = await completer.future;
if (mounted) {
setState(() {
_image = image;
_isLoading = false;
// Use provided frameCount, but clamp to available frames in image
int maxFrames = (image.width / widget.tileWidth).floor();
_calculatedFrameCount = widget.frameCount > maxFrames
? maxFrames
: widget.frameCount;
// Adjust duration based on frame count
_controller.duration = Duration(
milliseconds: _calculatedFrameCount * 100,
);
_controller.repeat();
});
}
} catch (e) {
debugPrint('Failed to load sprite image: $e');
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_isLoading || _image == null) {
return SizedBox(
width: widget.tileWidth * widget.scale,
height: widget.tileHeight * widget.scale,
child: const Center(child: CircularProgressIndicator()),
);
}
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
size: Size(
widget.tileWidth * widget.scale,
widget.tileHeight * widget.scale,
),
painter: SpriteSheetPainter(
image: _image!,
currentFrame:
(_controller.value * _calculatedFrameCount).floor() %
_calculatedFrameCount,
tileWidth: widget.tileWidth,
tileHeight: widget.tileHeight,
scale: widget.scale,
),
);
},
);
}
}
class SpriteSheetPainter extends CustomPainter {
final ui.Image image;
final int currentFrame;
final double tileWidth;
final double tileHeight;
final double scale;
SpriteSheetPainter({
required this.image,
required this.currentFrame,
required this.tileWidth,
required this.tileHeight,
required this.scale,
});
@override
void paint(Canvas canvas, Size size) {
// Correct src rect calculation
// Assuming horizontal strip for the animation row.
// Ideally we would want to select which 'row' (Y) to animate, but for now assuming row 0.
// If the image is a single row, srcY is 0.
final double srcX = currentFrame * tileWidth;
final double srcY = 0.0; // Default to first row
final Rect src = Rect.fromLTWH(srcX, srcY, tileWidth, tileHeight);
final Rect dst = Rect.fromLTWH(0, 0, tileWidth * scale, tileHeight * scale);
canvas.drawImageRect(
image,
src,
dst,
Paint()..filterQuality = FilterQuality.none,
);
}
@override
bool shouldRepaint(covariant SpriteSheetPainter oldDelegate) {
return oldDelegate.currentFrame != currentFrame ||
oldDelegate.image != image;
}
}