At the moment I'm experimenting with Flutter and the Flame game engine.
To do so I'm extending Flame's BaseGame class and do some heavy processing inside it's constructor.
The heavy processing includes composing an Image out of other images and ultimatively drawing it onto a temporary Canvas and the result is stored in a Picture object.
ui.PictureRecorder rec = new ui.PictureRecorder();
Canvas tempCanvas = new Canvas(rec, bgRect);
// left out picture operations
ui.Picture pic = rec.endRecording();
To finally get an Image object, I need to call the asynchronous .toData() method which returns a Future.
I'm wrapping the call in an async method getImage()
getImage(ui.Picture pic, Rect bgRect) async {
background = await pic.toImage(bgRect.width.toInt(), bgRect.height.toInt());
done = true;
}
(background is a class variable of type Image which is used inside the render() method of the BaseGame class)
Problem is, because it's asynchronous the rest of my statements inside the game's constructor get executed and after it finishes, the render() method fires but the background might not be available yet.
To workaround, I added a class variable done of type bool which gets set to true inside the getImage() method.
Now I modified the render() to wait for done to be true.
void render(Canvas canvas) {
if (done) {
canvas.drawImage(background, new Offset(0.0, 0.0), new Paint());
}
}
Of course this ain't to elegant.
Is there a way to wait for the .toImage() method to finish inside the constructor function of the extended BaseGame class?
I tried making the constructor async like:
class TheGame extends BaseGame {
Image background;
bool done = false;
TheGame(DeviceOrientation orientation) async {
}
}
but this gives me the error:
The modifier 'async' can't be applied to the body of a constructor
What else could I try to make it 'synchronous'?
If you really need the image before the first frame is rendered, you can just create a static method which is responsible for creating TheGame
class TheGame extends BaseGame {
final Image background;
TheGame._(DeviceOrientation orientation, this.background);
static Future<TheGame> create(DeviceOrientation orientation) async {
return TheGame._(orientation, await generateImage());
}
}
but I assume it doesn't really hurt if you render a few frames without a background image, then I would suggest you simply check background != null instead of the done property, which feels a bit redundant.
Related
I'm playing around with Forge2d on Flutter Flame and created a Ninja which throws Kanuies (a character shooting bullets basically).
If I create Kanuies and the Ninja separately then add them separately to the game world, all will be fine. However, I want the Ninja class to be responsible for login for throwing the Kanui. So I was wondering what is the right way to add a bullet component to a character component in Forge2d.
In my current code, inside Ninja Class, if I call add(Kanui) there will be no graphics shown about Kanuie NOT even with debugMode=true .
However, if use addToParrent(Kanui) it will be fine again.
Below is the link to my code. Please have a look and suggest corrections.
I'll provide some snippets here as well.
https://github.com/bmd007/flame_playground/blob/154cc0a9a99cc4bd5732e8d0c94bfa38093b0298/lib/my_girl.dart#L134
Ninja Class:
class MyGirl extends BodyComponent {
late SpriteAnimationComponent component;
Queue<MyGirlKanui> kanuies = Queue<MyGirlKanui>();
#override
Future<void> onLoad() async {
await super.onLoad();
idleAnimation = await gameRef.loadSpriteAnimation("red_girl/idle_spriteSheet.png", idleAnimationData);
component = SpriteAnimationComponent()
..animation = idleAnimation
..size = Vector2.all(6)
..anchor = Anchor.center;
add(component);
kanuies.add(MyGirlKanui(initialPosition));
}
#override
Body createBody() {
final shape = PolygonShape()..setAsBoxXY(3, 3);
final fixtureDefinition = FixtureDef(shape, density: 2, restitution: 0.1, friction: 2);
final bodyDefinition = BodyDef(position: initialPosition, type: BodyType.dynamic)..fixedRotation = true;
return world.createBody(bodyDefinition)..createFixture(fixtureDefinition);
}
throwKanui() async {
if (kanuies.isNotEmpty) {
var kanui = kanuies.removeFirst();
// await parent?.add(kanui);
await add(kanui);
kanui.component.position = component.position;
kanui.body.linearVelocity.x = 30;
}
}
I call the throw method when a UI button is pressed.
In Forge2D you shouldn't add any bodies as children to other components.
You can add the HasGameRef<Forge2DGame> mixin to the component and then you can add the bullets directly to the game.
Also don't forget to put the body of the bullet to isBullet = true if the "bullet" is moving very fast, otherwise you could end up with tunneling (where the body passes through the object that it is supposed to hit).
Does removeFromParent destroys object? I mean Garbage collects it, I am looking for destroy method but couldn't find.
update:
import 'package:flame/components.dart';
class Enemy extends SpriteAnimationComponent with HasGameRef {
#override
Future<void>? onLoad() async {
position = Vector2(200, 100);
size = Vector2(100, 100);
var spriteAnimationData = SpriteAnimationData.sequenced(
amount: 12,
stepTime: 0.05,
textureSize: Vector2(30, 30),
);
animation =
await gameRef.loadSpriteAnimation('enemy/pig.png', spriteAnimationData);
anchor = Anchor.center;
return super.onLoad();
}
#override
void update(double dt) {
position += Vector2(-2, 0);
if (position.x < -20) {
removeFromParent();
}
super.update(dt);
}
}
Since Dart is a garbage collected language it will garbage collect unused objects automatically once there are no references to the object anymore.
However, Sprites are a bit special since they have a loaded image in them. In Flame 1.0.0 the image cache (or the sprite class) does not call dispose of these images when it is cleared (but on main, and in the next version this will be done).
So to properly free up memory you'll have to call image.dispose() after you have removed the SpriteComponent, you could do sprite.image.dispose() in onRemove of the SpriteComponent for example, or call spriteComponent.sprite.image.dispose() after it has been removed.
EDIT: Since the question is now updated.
To call dispose on all Images loaded into a SpriteAnimationComponent you would have to do something like this:
component.animation.frames.forEach((f) => f.sprite.image.dispose());
Since those images are also loaded into the image cache they will also be removed when you remove an entry (or clear the whole cache) in the next version.
Reference: https://api.flutter.dev/flutter/dart-ui/Image/dispose.html
I want to create a game in flutter with flame. For this game I want to detect swipes.
I could implement a tap recognition with the help of a tutorial. But I could not implement it with swipe detection.
my main with Taprecognition looks like this:
My main function is
void main() async{
Util flameUtil = Util();
await flameUtil.fullScreen();
await flameUtil.setOrientation(DeviceOrientation.portraitUp);
GameManager game = GameManager();
runApp(game.widget);
TapGestureRecognizer tapper = TapGestureRecognizer();
tapper.onTapDown = game.onTapDown;
flameUtil.addGestureRecognizer(tapper);
}
In my GameManager class I do have:
class GameMAnager extends Game{
// a few methods like update, render and constructor
void onTapDown(TapDownDetails d) {
if (bgRect.contains(d.globalPosition)) { //bgRect is the background rectangle, so the tap works on the whole screen
player.onTapDown();
}
}
And my player class contains:
void onTapDown(){
rotate();
}
Now I want to change this to rotate in the direction of the swipe instead of onTapDown.
I tried to somehow add
GestureDetector swiper = GestureDetector();
swiper.onPanUpdate = game.onPanUpdate;
to my main and
void onPanUpdate() {
}
to my gameManager class. But I cannot find anything similar to TapDownDetails for panning.
Any suggestions on this?
I saw some help for this to wrap the widget in a GestureDetector and use it like this:
GestureDetector(onPanUpdate: (details) {
if (details.delta.dx > 0) {
// swiping in right direction
}
});
But I couldn't make it work on my project.
You can use the HorizontalDragGestureDetector (or PanGestureRecognizer if you need both axes)
use the following in your main method
HorizontalDragGestureRecognizer tapper = HorizontalDragGestureRecognizer();
tapper.onUpdate = game.dragUpdate;
and then the following in your GameManager
void dragUpdate(DragUpdateDetails d) {
// using d.delta you can then track the movement and implement your rotation updade here
}
That should do the trick :D
If you need both axes, you can use PanGestureRecognizer (as #Marco Papula said).
Use this in your main method:
PanGestureRecognizer panGestureRecognizer = PanGestureRecognizer();
panGestureRecognizer.onEnd = game.onPanUpdate;
flameUtil.addGestureRecognizer(panGestureRecognizer);
and your onPanUpdate method:
void onPanUpdate(DragEndDeatils d) {
if(d.velocity.pixelsPerSecond.dx.abs()>d.velocity.pixelsPerSecond.dy.abs()) {
// X Axis
snake.velocity = d.velocity.pixelsPerSecond.dx<0 ? LeftSwipe : RightSwipe;
} else {
// Y Axis
snake.velocity = d.velocity.pixelsPerSecond.dy<0 ? UpSwipe : DownSwipe;
}
}
If you are using a newer (v1) version of Flame, you no longer need to wrap it in your own GestureDetector (thought you can). Now Flame has built in wrappers for all events, including panning! You can mix your game with:
class MyGame extends BaseGame with PanDetector {
// ...
}
And implement onPanUpdate to get the desired behaviour; or use a myriad of any other detectors (check the documentation for more options and details on how to use it).
I am making my first learning steps with CN1 graphics.
I encounter difficulties when calling the paint method of the following class in the start () method.
public class DrawingCanvas extends Component {
public void myMethod(Graphics g) {
// some code
}
}
In the start() method, I call the method like this:
public void start() {
DrawingCanvas drawingCanvas = new DrawingCanvas();
drawingCanvas.myMethod(g);
}
The error message I get is : Cannot find symbol : variable g
In my understanding, I now have to define g somehow.
How do I do that?
Graphics g =
You can't and don't initialize graphics. Graphics is created internally within Codename One and passed to the paint method as a callback. You should never invoke paint(Graphics) on your own.
If you want to invoke paint(Graphics) you can invoke cmp.repaint() which will call paint for you. Notice that you don't need to invoke it normally as paint is implicitly invoked when needed e.g. when layout changes etc.
I've been working on switching my applications from Swing to JavaFX. I've been working on a room escape game which displays a description of the item on which the user clicks. In Swing, I'd subclass JComponent and override the paintComponent(Graphics) method. I could draw the text there, knowing that the method is constantly called to update the screen. However, using the JavaFX Canvas, there is no method that is called constantly, which makes this task harder. I attempted save()ing the GraphicsContext after I drew the images and called restore() when I wanted to remove the text, but to no avail. Here's the important code:
package me.nrubin29.jescape;
import javafx.application.Platform;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.shape.Rectangle;
import java.util.Timer;
import java.util.TimerTask;
public class RoomPane extends Canvas {
private Room room;
private Toast toast;
public RoomPane() {
super(640, 480);
setOnMouseClicked(e -> {
for (JObject o : room.getObjects()) {
if (o.getBounds().contains(e.getX(), e.getY())) {
toast = new Toast(o.getDescription());
}
}
});
new Timer().schedule(new TimerTask() {
#Override
public void run() {
if (toast == null) {
return;
}
if (toast.decrement()) { // Decrements the internal counter. If the count is 0, this method returns true.
toast = null;
Platform.runLater(() -> getGraphicsContext2D().restore());
}
else {
Platform.runLater(() -> getGraphicsContext2D().strokeText(toast.getText(), 300, 100));
}
}
}, 0, 1000);
}
public void changeRoom(Room room) {
this.room = room;
GraphicsContext g = getGraphicsContext2D();
g.drawImage(room.getBackground(), 0, 0);
for (JObject o : room.getObjects()) {
g.drawImage(o.getImage(), getCenterX(o.getBounds()), getCenterY(o.getBounds()));
}
g.save();
}
}
I attempted save()ing the GraphicsContext after I drew the images and called restore() when I wanted to remove the text, but to no avail.
save and restore have nothing to with removing things like text, what they do is save in a stack the state of various settings like a stroke or fill to use to draw shapes and allow them to be popped off the stack for application later. Those routines don't effect the pixels drawn on the canvas at all.
To remove something from a GraphicsContext, you can either draw over the of it, or clear it. For your code, what you could do is snapshot the canvas node where you are trying to save it, then draw your snapshot image onto the canvas where you are trying to restore it. It is probably not the most efficient way of handling drawing (a smarter routine which just draws only damaged area where the text is would be better, but probably not required for your simple game).
However, using the JavaFX Canvas, there is no method that is called constantly
Rather than using a timer to trigger canvas calls, use a AnimationTimer or a Timeline. The AnimationTimer has a callback method which is invoked every pulse (60 times a second, or as fast as JavaFX can render frames, whichever is the lesser), so it gives you an efficient hook into the JavaFX pulse based rendering system. The Timeline can have keyframes which are invoked at user specified durations and each keyframe can have an event handler callback which is invoked at that duration.
Using the built-in JavaFX animation framework, you don't have to worry about multi-threading issues and doing things like Platform.runLater which overly complicate your code and can easily lead to subtle and serious errors.
On a kind of unrelated note, for a simple game like this, IMO you are probably better off recoding it completely to use the JavaFX scene graph rather than a canvas. That way you will be working at a higher level of abstraction rather than clip areas and repainting damaged paint components.