In this section we will learn about the basic elements in Forge 2D and their combination with Flame, which is the main plugin. This chapter represents an intermediate to learn the basic characteristics of Forge 2D with Flame, therefore, it is a purely referential chapter that you can use as support at any time in case you have any questions with a real implementation that we will see in the following chapters.
Let's start by talking about the key technology that is Forge2D; Forge2D is nothing more than an adaptation for Dart (and with this, for Flutter and Flame) of the Box2D physics engine; Box2D is an open source library that implements a two-dimensional physics engine. Therefore with Forge2D we can use a 2D physics engine for our applications and/or games.
As we mentioned before, you can use Forge2D independently in Dart or Flutter with the package:
$ dart pub add forge2d
$ flutter pub add forge2d
Or with Flame with the package:
$ flutter pub add flame_forge2d
In this book, we are going to use Forge2D along with Flame, therefore, we will use the previous package in projects in Flutter with Flame.
In Flame, as the main class or Game type class, we used the Game or FlameGame classes, in Forge 2D we used Forge2DGame:
class MyGame extends Forge2DGame { }
In this way, we can interact with the world using the 2D physics engine.
For example, for the main class, we can specify the use of gravity:
MyGame() : super(gravity: Vector2(0, 15));
This means that gravity affects all dynamic bodies in the world at y=15, which means that objects will be pushed down like in real life; on the other hand, x=0 means that there is no horizontal gravity.
In Forge2D, our components are bodies and have physical qualities (unlike Flame components) and are the fundamental objects of the world in Forge2D and the main difference or addition that we have in Forge2D with respect to basic Flame.
Like any game created in Flutter that uses Flame, in Forge2D you need a "world" to position all the components that make up the game; in other words, the objects or the so-called bodies that we can create in Forge2D; we are going to know this in a little more detail when we create a body, which is a Flame component, but, with the ability that this component can be interacted physically natively through Forge2D:
return world.createBody(<BODY>)
In short, the world class allows you to manage all physical entities.
To define the world, we cannot do it using the World provided in Flame's Game type class as FlameGame since they are not compatible; to access the Forge world, we must do it as follows:
import 'package:flame/camera.dart' as camera;
***
final cameraWorld = camera.World();
And add directly in the type class Forge2DGame:
add(cameraWorld);
The way to access the world in Forge with Flame is a bit strange and this implementation may change in the future for something more consistent.
As in Flame, we need to use a camera to "see the world", using the camera to specify which part of the world is being seen at any given time.
Just like in basic Flame, we must use the camera component and add directly to the Forge2DGame type class:
cameraComponent = CameraComponent(world: cameraWorld);
cameraComponent.viewfinder.anchor = Anchor.topLeft;
add(cameraComponent);
As we already know from Flame without any addition or basic Flame, there is a close relationship between the world and the camera, since, through the camera, we define what world is going to be observed and how.
The Forge2DGame class, the camera has a zoom level set to 10 by default, so the components will be much larger than in a normal Flame game. This is due to the speed limit in the world of Forge2D, which you would reach very quickly if you use it with zoom = 1.0. The problem with this is the positioning, since, if we want to position an element in:
10x10
What is actually happening in Flame with Forge2D is that it is going to position it in:
100x100
And it is precisely because of the zoom set at 10.
To work around this behavior, we can use the screenToWorld() method that converts screen coordinates to world coordinates; for example:
cameraComponent.viewport.size
>> Vector2 ([929,918])
Going to world coordinates, we will have:
screenToWorld(cameraComponent.viewport.size)
>> Vector2 ([92.9,91.8])
You can easily change the zoom level:
class MyGame extends Forge2DGame {
MyGame() : super(zoom: 1);
}
And if we do the same calculation:
cameraComponent.viewport.size
>> Vector2 ([929,918])
We will see:
screenToWorld(cameraComponent.viewport.size)
>> Vector2 ([929,918])
Therefore, the screenToWorld() method returns the coordinates based on the zoom level; in the example above, when going from a zoom of 10 to 1, we will see that the objects in our window are 1/10 of the previous size.
In Forge2D, instances of classes of type BodyComponent are known as bodies; these bodies are the fundamental objects in the world of a Forge 2D project; unlike the components in Flame, with Forge it is possible to use the 2D physics engine, therefore, bodies are the key piece in our games to interact with the 2D physics engine; as we will see later, we can interact with these bodies in various ways and there are different body types:
In Forge, to create a body, we need to create a class and extend BodyComponent:
class MyBody extends BodyComponent {
@override
Body createBody() {
}
}
Inside the createBody() method, we create a BodyDef that will contain the body definition:
final bodyDef = BodyDef(
position: Vector2(worldSize.x / 2, 0),
type: BodyType.dynamic,
);
We can see that this will be a dynamic body, positioned vertically at the top of the screen and centered horizontally.
The creadeBody() method, which is automatically invoked internally according to the life cycle of a BodyComponent, when executed, sets the body in a property called body, also internal to the aforementioned components; therefore, when we need to do some operation with the body, we can reference this property called body.
You can change the color of the component using the paint property; for example:
paint = BasicPalette.red.paint();
In this section we will see how we can customize bodies in Forge 2D and how we can apply forces to the body to interact with the 2D physics engine; this section is somewhat abstract and you should only take it as a reference to understand how the 2D physics engine is formed; in the following chapters we will see real examples of what is explained in this section.
In Forge2D, bodies must have shape; It can be circular, rectangular or any type of polygon; you can see bodies like hitboxes in Flame only these hitboxes have the physics component; for example, to create a circular shape for our body:
final shape = CircleShape()..radius = .35;
To create a box, we have:
final shape = PolygonShape()..setAsBoxXY(.15, 1.25);
We have different types of Shapes that we can use:
https://pub.dev/documentation/flame_forge2d/latest/flame_forge2d/CircleShape-class.html
Bodies, apart from type, can have other characteristics; this is something easy to understand since, if we take bodies to the real world, a ball would be a body, just like a car would be a body, but, between them there are differences such as weight, friction or rebound, and these we can simulate characteristics with Forge2D using fixtures.
In Forge2D, a fixture has density, friction and restitution associated with it:
Following the previous code, we are going to create a fixture of our body:
final fixtureDef = FixtureDef(shape);
world.createBody(bodyDef)..createFixture(fixtureDef);
It is important to note that the fixtures are established in the shape and not directly in the body; all this logic must be defined in the createBody() method and once it is executed (which is done automatically by the Forge 2D life cycle) we can access the body and its components through a property called body.
On the body, we can apply different magnitudes or forces to move it using the following methods.
In the end, these methods allow us to move a body. To use the previous methods, 2-dimensional vectors are used indicating the movement towards where we want to move the body; for example, If we want to move only horizontally, we use a vector with a value in X (for example Vector2(50,0)) or in the vertical to make a jump, we use a vector with a value in Y (for example Vector2(0,-20)).
The first methods can be influenced according to the type of body, weight and gravity, unlike linearVelocity whose movement is automatically applied to the body regardless of the type of body, weight and gravity.
You can learn more about bodies, their properties and methods at:
https://pub.dev/documentation/forge2d/latest/forge2d_browser/Body-class.html
As happens in Flame in which we have a utility that allows us to manage collisions between components, in Forge we have a practically the same scheme, but, applied to managing contacts (collisions) between bodies and to use it from a BodyComponent, we must import the ContactCallbacks mixin and we can use it through the following methods:
Regarding the parameters:
As with collisions in Flame, you can use these methods for operations like playing sounds, displaying sprites, or any other game logic. When defining the body, it is essential that you set the userData to the bodies that we are interested in listening to the contacts through this mixin:
BodyDef bodyDef = BodyDef(
***
userData: this);
The userData corresponds to application data that is used internally by Forge2D to check contacts.
- Andrés Cruz
Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter
I agree to receive announcements of interest about this Blog.
!Courses from!
10$
On Udemy
There are 1d 01:26!
Udemy!Courses from!
4$
In Academy
View courses!Books from!
1$
See the books