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
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).
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.
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.
I have a working collision system to sprites that i dont want "player" to pass. Problem is that i have no idea what should i execute on collision to make player not pass sprites.
wallCollision() method is currently empty.
if(tmxTileProperties.containsTMXProperty("collision", "1")) {
Rectangle rect = new Rectangle(tmxTile.getTileX(), tmxTile.getTileY() ,128, 128, mEngine.getVertexBufferObjectManager())
{
#Override
protected void onManagedUpdate(float pSecondsElapsed)
{
if (player.collidesWith(this))
{
wallCollision();
}
}
};
rect.setVisible(false);
mainScene.attachChild(rect);
}
The question located here addresses this. The method below creates a JBox2D body at the same position as the blocked tile. I'm not sure how this works in conjunction with the pathfinding to exclude blocked tiles, but I've seen the same approach used in other places, assuming you're using GLES2. Hope this helps.
private void createUnwalkableObjects(TMXTiledMap map){
// Loop through the object groups
for(final TMXObjectGroup group: this.mTMXTiledMap.getTMXObjectGroups()) {
if(group.getTMXObjectGroupProperties().containsTMXProperty("wall", "true")){
// This is our "wall" layer. Create the boxes from it
for(final TMXObject object : group.getTMXObjects()) {
final Rectangle rect = new Rectangle(object.getX(), object.getY(),object.getWidth(), object.getHeight());
final FixtureDef boxFixtureDef = PhysicsFactory.createFixtureDef(0, 0, 1f);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, rect, BodyType.StaticBody, boxFixtureDef);
rect.setVisible(false);
mScene.attachChild(rect);
}
}
}
}
It is working fine for me for the first time it is rendered.
But, If I change anything over the map or recreate it, its broken.
Here is the screen shot for how it looks.
Here is a screen shot after I changed the results per page value.
This is my code.
#UiField DivElement mapPanel;
private GoogleMap googleMap;
public void loadAllMarkers(final List<LatLng> markers)
{
if(!markers.isEmpty())
{
final MapOptions options = MapOptions.create();
options.setMapTypeId(MapTypeId.ROADMAP);
googleMap = GoogleMap.create(mapPanel, options);
final LatLngBounds latLngBounds = LatLngBounds.create();
for(LatLng latLng : markers)
{
final MarkerOptions markerOptions = MarkerOptions.create();
markerOptions.setPosition(latLng);
markerOptions.setMap(googleMap);
final Marker marker = Marker.create(markerOptions);
latLngBounds.extend(marker.getPosition());
}
googleMap.setCenter(latLngBounds.getCenter());
googleMap.fitBounds(latLngBounds);
}
}
I am calling the loadAllMarkers() method whenever new results needs to be loaded.
Can someone point out what I am doing wrong here.
This seems to come from the following (which I pulled from a Google+ Community - GWT Maps V3 API):
Brandon DonnelsonMar 5, 2013
I've had this happen and forgotten why it is, but
mapwidget.triggerResize() will reset the tiles. This seems to happen
when the onAttach occurs and animation exists meaning that the div
started smaller and increases in side, but the map has already
attached. At the end of the animation, the map doesn't auto resize.
I'v been meaning to investigate auto resize but I haven't had time to
attack it yet.
In your case, you would call googleMap.triggerResize() after you finish your changes. this solved my problem when I had the exact same issue. I know it's a little late, but I hope it helps!
Another answer there was to extend the Map widget with the following:
#Override
protected void onAttach() {
super.onAttach();
Timer timer = new Timer() {
#Override
public void run() {
resize();
}
};
timer.schedule(5);
}
/*
* This method is called to fix the Map loading issue when opening
* multiple instances of maps in different tabs
* Triggers a resize event to be consumed by google api in order to resize view
* after attach.
*
*/
public void resize() {
LatLng center = this.getCenter();
MapHandlerRegistration.trigger(this, MapEventType.RESIZE);
this.setCenter(center);
}