Can a Flame game object such as PositionComponent be Collidable and Draggable - flutter

Successfully implemented a Draggable component. When adding the Hitbox and Collidable mixins to the class extended by PositionComponent the drag functionality stops working.
Is it possible to have a draggable component that is also collidable?
Flutter version: 2.2.3
Flame version: 1.0.0-releasecandidate.13
main.dart
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'DraggablesGame.dart';
void main() {
runApp(
GameWidget(
game: DraggablesGame(),
),
);
}
DraggablesGame.dart
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'DraggableSquare.dart';
class DraggablesGame extends BaseGame with HasDraggableComponents, HasCollidables {
#override
Future<void> onLoad() async {
add(DraggableSquare());
add(DraggableSquare()..y = 350);
}
}
DraggableSquare.dart
import 'package:flame/components.dart';
import 'package:flame/extensions.dart';
import 'package:flame/game.dart';
import 'package:flame/gestures.dart';
import 'package:flutter/material.dart' show Colors;
import 'DraggablesGame.dart';
class DraggableSquare extends PositionComponent
with Draggable, HasGameRef<DraggablesGame>, Hitbox, Collidable {
#override
bool debugMode = true;
DraggableSquare({Vector2? position})
: super(
position: position ?? Vector2.all(100),
size: Vector2.all(100),
);
Vector2? dragDeltaPosition;
bool get isDragging => dragDeltaPosition != null;
#override
void update(double dt) {
super.update(dt);
debugColor = isDragging ? Colors.greenAccent : Colors.purple;
}
#override
bool onDragStart(int pointerId, DragStartInfo info) {
dragDeltaPosition = info.eventPosition.game - position;
return false;
}
#override
bool onDragUpdate(int pointerId, DragUpdateInfo event) {
final dragDeltaPosition = this.dragDeltaPosition;
if (dragDeltaPosition == null) {
return false;
}
position.setFrom(event.eventPosition.game - dragDeltaPosition);
return false;
}
#override
bool onDragEnd(int pointerId, _) {
dragDeltaPosition = null;
return false;
}
#override
bool onDragCancel(int pointerId) {
dragDeltaPosition = null;
return false;
}
}
Update based on answer
Spydon's answer suggested using addHitbox(HitboxRectangle());. This resulted in the following error:
The method 'addHitbox' isn't defined for the type 'DraggableSquare'.
Instead this modified constructor allows for both the dragging and colliding.
Updated DraggableSquare Constructor
DraggableSquare({Vector2? position})
: super(
position: position,
size: Vector2.all(100),
) {
final hitBox = HitboxRectangle();
addShape(hitBox);
}

When you add the Hitbox mixin you also have to add some hitboxes, otherwise it will not be able to know what it should count as a "hit".
The simplest solution is to add an empty hitbox of either the HitboxRectangle or HitboxCircle type. These hitbox will will the full size of the component if you don't define anything more specific in them.
So to add a HitboxRectangle, you modify your constructor to this:
DraggableSquare({Vector2? position})
: super(
position: position ?? Vector2.all(100),
size: Vector2.all(100),
) {
addShape(HitboxRectangle());
}
If you set debugMode = true you will be able to visually see the hitboxes that you add.

Related

Flame-engine, Draggable component doesn't work

I am completely newbie in flutter world specially flame-engine. Maybe it's a simple issue. But currently I couldn't figure it out. So please help me!
The main issue is my Draggable component doesn't work all event handler functions seem like doesn't not work correctly. Do you have any idea?
Draggable component code:
import 'package:flame/components.dart';
import 'package:flame/input.dart';
// Zooh bolomjtoi component
class DrawerComponent extends PositionComponent with Draggable {
late final mainRectangle;
Vector2? dragDeltaPosition;
bool get isDragging => dragDeltaPosition != null;
#override
Future<void>? onLoad() async {
final bg = await Sprite.load('drawer.png');
final localSize = Vector2(350, 210);
final mainRectangle = SpriteComponent(size: localSize, sprite: bg);
mainRectangle.position = isDragging ? (dragDeltaPosition)! : Vector2(0, 0);
add(mainRectangle);
}
#override
bool onDragStart(DragStartInfo info) {
print("Drag ehelleee..");
dragDeltaPosition = info.eventPosition.game - position;
return false;
}
#override
bool onDragUpdate(DragUpdateInfo info) {
print("Drag update..");
if (isDragging) {
final localCoords = info.eventPosition.game;
position.setFrom(localCoords - dragDeltaPosition!);
}
return false;
}
#override
bool onDragEnd(DragEndInfo info) {
print("Drag end...");
dragDeltaPosition = null;
return false;
}
#override
bool onDragCancel() {
dragDeltaPosition = null;
return false;
}
}
Main FlameGame class:
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:furniture/drawer.dart';
class Core extends FlameGame with HasDraggables {
late final SpriteComponent mainRectangle;
#override
Future<void>? onLoad() async {
final bg = await Sprite.load('example.jpeg');
final localSize = Vector2(super.size.x * 0.7, super.size.y * 0.6);
final mainRectangle = SpriteComponent(size: localSize, sprite: bg);
mainRectangle.position = Vector2(super.size.x * 0.15, super.size.y * 0.2);
mainRectangle.angle = 0;
add(mainRectangle);
add(DrawerComponent());
}
}
Flutter main class:
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:furniture/core.dart';
void main() {
final game = Core();
runApp(GameWidget(game: game));
}
I think your problem is into your onLoad() because you don't declare the values correctly, I have this example for you:
class Sushi extends SpriteComponent with Draggable, HasGameRef<JapaneseChef>{
/// Sushi constructor
Sushi( this._img, this._position, this._isMove, this._isDraggable,){
position = _position;
size = Vector2(250,250);
}
late final String _img;
late final Vector2 _position;
late Vector2 _dragDeltaPosition;
late final bool _isMove;
late final bool _isDraggable;
/// Get delta position
Vector2 get dragDeltaPosition => _dragDeltaPosition;
set dragDeltaPosition(Vector2? dragDeltaPosition) {
if(dragDeltaPosition != null){
_dragDeltaPosition = dragDeltaPosition;
}
}
#override
Future<void>? onLoad() async{
final spriteSushi = await Sprite.load(_img);
sprite = spriteSushi;
changePriorityWithoutResorting(5);
return super.onLoad();
}
#override
void update(double dt) {
if (_isMove) {
position.x -= 0.6;
if (position.x < -size.x) {
remove(this);
}
}
super.update(dt);
}
#override
bool onDragStart(int pointerId, DragStartInfo info) {
if(_isDraggable) {
dragDeltaPosition = info.eventPosition.game - position;
}
return false;
}
#override
bool onDragCancel(int pointerId) {
dragDeltaPosition = null;
return super.onDragCancel(pointerId);
}
#override
bool onDragUpdate(int pointerId, DragUpdateInfo info) {
if (parent is! JapaneseChef) {
return true;
}
final dragDeltaPosition = this.dragDeltaPosition;
position.setFrom(info.eventPosition.game - dragDeltaPosition);
return false;
}
#override
bool onDragEnd(int pointerId, DragEndInfo info) {
dragDeltaPosition = null;
return false;
}
}
Also, you can see the example from Flame engine
Example result

Why removing other sprite after collision not working in Flame?

I have two sprites in the game e.g Player and Enemy. After collision detection why remove(Component) method is not working?
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';
import 'package:flutter/material.dart';
void main() async {
runApp(GameWidget(
game: CGame(),
));
}
class CGame extends FlameGame with HasCollidables {
#override
Future<void>? onLoad() async {
add(Enemy());
add(Player());
return super.onLoad();
}
}
class Enemy extends SpriteComponent with HasGameRef, HasHitboxes, Collidable {
Enemy() : super(priority: 1);
#override
Future<void>? onLoad() async {
sprite = await Sprite.load('crate.png');
size = Vector2(100, 100);
position = Vector2(200, gameRef.size.y / 3 * 2);
anchor = Anchor.center;
addHitbox(HitboxRectangle());
return super.onLoad();
}
}
class Player extends SpriteComponent with HasGameRef, HasHitboxes, Collidable {
#override
Future<void>? onLoad() async {
sprite = await Sprite.load('player.png');
size = Vector2(100, 100);
position = Vector2(0, gameRef.size.y / 3 * 2);
anchor = Anchor.center;
addHitbox(HitboxRectangle());
return super.onLoad();
}
#override
void update(double dt) {
position += Vector2(1, 0);
}
#override
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {
if (other is Enemy) {
print('Player hit the Enemy!');
remove(Enemy()); //<== Why this line is not working?
}
}
}
You are trying to remove a new instance of an enemy component, you have to remove the specific one that you collided with, which in this case is other.
You also want to remove other from the game where it is added, if you do remove on the component you are trying to remove a subcomponent.
#override
void onCollision(Set<Vector2> intersectionPoints, Collidable other) {
if (other is Enemy) {
other.removeFromParent();
// It can also be done like this since you have the `HasGameRef` mixin
// gameRef.remove(other);
}
}
}

Flutter web - Geolocator not working when uploaded to server

everyone.
I'm trying to develop a PWA with flutter 2.2.1 that shows a map using Mapbox_gl and displays the user current location using Geolocator.
So far everything works as expected while debuging the app, but when I run:
flutter build
or
flutter build --release
and then run
firebase deploy
the site gets uploaded, the map shows as intended and it asks for permissions but the user's location is never shown and Google Chrome's Console throws this error:
Uncaught TypeError: m.gfR is not a function
at Object.avh (main.dart.js:20405)
at main.dart.js:65755
at aiD.a (main.dart.js:5853)
at aiD.$2 (main.dart.js:34394)
at ahm.$1 (main.dart.js:34386)
at Rx.o1 (main.dart.js:35356)
at adi.$0 (main.dart.js:34770)
at Object.tQ (main.dart.js:5975)
at a5.mn (main.dart.js:34687)
at ada.$0 (main.dart.js:34731)
Here's the code I'm using on flutter:
mapbox.dart
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:geolocator/geolocator.dart';
import 'package:kkc/main.dart';
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:kkc/services/location_service.dart';
class Mapbox extends StatefulWidget {
const Mapbox();
#override
State createState() => MapboxState();
}
class MapboxState extends State<Mapbox> {
final Random _rnd = new Random();
Position? _currentLocation;
LatLng _currentCoordinates = new LatLng(0,0);
final List<_PositionItem> _positionItems = <_PositionItem>[];
StreamSubscription<Position>? _positionStreamSubscription;
late MapboxMapController _mapController;
List<Marker> _markers = [];
List<_MarkerState> _markerStates = [];
CameraPosition _kInitialPosition = CameraPosition(
target: LatLng(19.4274418, -99.1682147),
zoom: 18.0,
tilt: 70,
);
void _addMarkerStates(_MarkerState markerState) {
_markerStates.add(markerState);
}
void _onMapCreated(MapboxMapController controller) {
_mapController = controller;
controller.addListener(() {
if (controller.isCameraMoving) {
_updateMarkerPosition();
}
});
}
void _onStyleLoadedCallback() {
_updateMarkerPosition();
}
void _onCameraIdleCallback() {
_updateMarkerPosition();
}
void _updateMarkerPosition() {
final coordinates = <LatLng>[];
for (final markerState in _markerStates) {
coordinates.add(markerState.getCoordinate());
}
_mapController.toScreenLocationBatch(coordinates).then((points) {
_markerStates.asMap().forEach((i, value) {
_markerStates[i].updatePosition(points[i]);
});
});
}
void _addMarker(Point<double> point, LatLng coordinates) {
setState(() {
_markers.add(Marker(_rnd.nextInt(100000).toString(), coordinates, point, _addMarkerStates));
});
}
#override
void initState() {
super.initState();
_getCurrentLocation();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Stack(children: [
MapboxMap(
accessToken: Kukulcan.MAPBOX_ACCESS_TOKEN,
trackCameraPosition: true,
onMapCreated: _onMapCreated,
onCameraIdle: _onCameraIdleCallback,
onStyleLoadedCallback: _onStyleLoadedCallback,
initialCameraPosition: _kInitialPosition,
),
IgnorePointer(
ignoring: true,
child: Stack(
children: _markers,
))
]),
);
}
void _getCurrentLocation() async {
_currentLocation = await LocationService.startLocationService();
_currentCoordinates = new LatLng(_currentLocation!.latitude,_currentLocation!.longitude);
await _mapController.animateCamera(CameraUpdate.newLatLng(_currentCoordinates));
_addMarker(new Point(1, 1), _currentCoordinates);
if (_positionStreamSubscription == null) {
final positionStream = Geolocator.getPositionStream();
_positionStreamSubscription = positionStream.handleError((error) {
_positionStreamSubscription?.cancel();
_positionStreamSubscription = null;
}).listen((position) => setState(() => _positionItems.add(
_PositionItem(_PositionItemType.position, position.toString()))));
_positionStreamSubscription?.pause();
}
}
}
class Marker extends StatefulWidget {
final Point _initialPosition;
LatLng _coordinate;
final void Function(_MarkerState) _addMarkerState;
Marker(
String key, this._coordinate, this._initialPosition, this._addMarkerState)
: super(key: Key(key));
#override
State<StatefulWidget> createState() {
final state = _MarkerState(_initialPosition);
_addMarkerState(state);
return state;
}
}
class _MarkerState extends State with TickerProviderStateMixin {
final _iconSize = 80.0;
Point _position;
_MarkerState(this._position);
#override
Widget build(BuildContext context) {
var ratio = 1.0;
//web does not support Platform._operatingSystem
if (!kIsWeb) {
// iOS returns logical pixel while Android returns screen pixel
ratio = Platform.isIOS ? 1.0 : MediaQuery.of(context).devicePixelRatio;
}
return Positioned(
left: _position.x / ratio - _iconSize / 2,
top: _position.y / ratio - _iconSize / 2,
child: Image.asset('assets/img/pin.png', height: _iconSize));
}
void updatePosition(Point<num> point) {
setState(() {
_position = point;
});
}
LatLng getCoordinate() {
return (widget as Marker)._coordinate;
}
}
enum _PositionItemType {
permission,
position,
}
class _PositionItem {
_PositionItem(this.type, this.displayValue);
final _PositionItemType type;
final String displayValue;
}
Does anyone have an idea on what's the problem?
Cheers!
Anyway the solution i found is to use --no-sound-null-safety argument as stated by geolocat documentation
I quote:
NOTE: due to a bug in the dart:html library the web version of the Geolocator plugin does not work with sound null safety enabled and compiled in release mode. Running the App in release mode with sound null safety enabled results in a Uncaught TypeError (see issue #693). The current workaround would be to build your App with sound null safety disabled in release mode:

Flutter customPainter doesn't repaint

shouldRepaint doesn't repaint when there is a change:
import 'package:flutter/material.dart';
class MainCanvas extends CustomPainter{
static var penPointsToRender = [];
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()..color=Colors.black..strokeWidth=4;
renderPenPoints(paint, canvas, 2);
}
static void renderPenPoints(Paint paint, Canvas canvas, double pointsRadius){
for (int i = 0; i < penPointsToRender.length - 1; i++) {
if (penPointsToRender[i + 1] != null && penPointsToRender[i] != null) {
canvas.drawLine(penPointsToRender[i], penPointsToRender[i + 1], paint);
}
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
this list get's updated on every touch.
I have this custom gesture detector that uses a provider as it's state management system:
import 'package:flutter/material.dart';
import 'package:flutter_canvas/Widgets/Models/canvas_gesture_detector_model.dart';
class CanvasGestureDetector extends StatefulWidget {
CanvasGestureDetector(this.canvasGestureDetectorModel,{Key key,this.child}) : super(key: key);
CanvasGestureDetectorModel canvasGestureDetectorModel;
Widget child;
#override
_CanvasGestureDetectorState createState() => _CanvasGestureDetectorState();
}
class _CanvasGestureDetectorState extends State<CanvasGestureDetector> {
#override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanDown: widget.canvasGestureDetectorModel.onPanDown,
onPanUpdate: widget.canvasGestureDetectorModel.onPanUpdate,
onPanEnd: widget.canvasGestureDetectorModel.onPanEnd,
child: widget.child,
);
}
}
the model of the detector is:
import 'package:flutter/material.dart';
import 'package:flutter_canvas/Widgets/Canvas/canvas.dart';
import 'package:flutter_canvas/Widgets/Canvas/canvas_area.dart';
class CanvasGestureDetectorModel with ChangeNotifier{
Function(DragDownDetails) onPanDown;
Function(DragUpdateDetails) onPanUpdate;
Function(DragEndDetails) onPanEnd;
void changeGestureBehavior(BuildContext context,String toolName){
switch(toolName){
case "Brush":
setBrushGestureDetection(context);
break;
}
}
void setBrushGestureDetection(BuildContext context){
onPanDown = (panInfo){
MainCanvas.penPointsToRender.add(panInfo.localPosition);
};
onPanUpdate = (panInfo){
if(
panInfo.globalPosition.dx < CanvasArea.canvasPaddingHorizontal / 2 ||
panInfo.globalPosition.dx > CanvasArea.canvasPaddingHorizontal / 2 + (MediaQuery.of(context).size.width - CanvasArea.canvasPaddingHorizontal) ||
panInfo.globalPosition.dy < CanvasArea.canvasPaddingVertical / 2 - 20 ||
panInfo.globalPosition.dy > (MediaQuery.of(context).size.height - CanvasArea.canvasPaddingVertical) + (CanvasArea.canvasPaddingVertical ) / 2 -20
){return;}
if (MainCanvas.penPointsToRender.elementAt(
MainCanvas.penPointsToRender.length - 1) != panInfo.localPosition) {
MainCanvas.penPointsToRender.add(panInfo.localPosition);
}
};
onPanEnd = (panInfo){
MainCanvas.penPointsToRender.add(null);
};
notifyListeners();
}
}
from some reason,
when I update the number of penPointsToRender it doesn't cause the customPainter to repaint, even though it updates and adds the points to the static list.
I have no idea what causes this, any suggestions would be appreciated

Flutter: onTapDown function not always called

I'm new in flutter development. I add onTapdown listener if i perform any clicked action on the screen. It's worked,but the problem is sometime when i clicked,onTapdown function not get called.I don't know what problem i have done.Hope can help me solve this problem.Thank you in advance.
import 'package:flame/game.dart';
import 'package:flame/components/parallax_component.dart';
import 'package:flame/util.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Util flameUtil = Util();
await flameUtil.fullScreen();
runApp(MyGame(flameUtil).widget);
}
class MyGame extends BaseGame {
MyGame(Util flameUtil) {
final images = [
ParallaxImage("space/bg_base.png",repeat: ImageRepeat.repeat,fill: LayerFill.height),
ParallaxImage("space/bg_big_star.png",repeat: ImageRepeat.repeatY,fill: LayerFill.height),
ParallaxImage("space/bg_planet.png",repeat: ImageRepeat.repeat,fill: LayerFill.none),
];
var game = Squres(images);
add(game);
TapGestureRecognizer tapper = TapGestureRecognizer();
tapper.onTapDown = game.onTapDown;
flameUtil.addGestureRecognizer(tapper);
}
}
class Squres extends ParallaxComponent{
Squres(List<ParallaxImage> images) : super(images){
baseSpeed = const Offset(4,0);
layerDelta = const Offset(0,-50);
}
#override
void render(Canvas canvas) {
super.render(canvas);
}
#override
void resize(Size size) {
super.resize(size);
}
#override
void update(double t) {
super.update(t);
}
//not always trigger
void onTapDown(TapDownDetails tap){
print("trigger");
}
}
Make sure you use flame 0.22.0^ and use create your game class like this instead:
class MyGame extends BaseGame with TapDetector {
Squres game;
MyGame() {
final images = [
ParallaxImage("space/bg_base.png",repeat: ImageRepeat.repeat,fill: LayerFill.height),
ParallaxImage("space/bg_big_star.png",repeat: ImageRepeat.repeatY,fill: LayerFill.height),
ParallaxImage("space/bg_planet.png",repeat: ImageRepeat.repeat,fill: LayerFill.none),
];
game = Squres(images);
add(game);
}
#override
void onTapDown(TapUpDetails details) {
print("trigger");
game.onTapDown(details);
}
}
Also, remember that you don't have to override update, resize and render if you only call super on them.