패키지, 저장소 및 앱 카테고리 검색

패키지 상세v0.15.3

forge2d

Dart용 2차원 물리 엔진(박스2D 기반), 플러터의 플레이임 게임 엔진과도 함께 작동

AndroidiOSWebmacOSWindowsLinux
30일 다운로드
1.8만
좋아요
139
Pub 점수
160

패키지 설치

flutter pub add forge2d

패키지 개요

공개 정보를 바탕으로 생성한 선택 가이드입니다.

Forge2D는 Box2D를 영감으로 하여 Dart용 2차원 물리 엔진으로, 특히 Flame 게임 엔진과 함께 사용하기 위해 Flutter 애플리케이션에 설계되었습니다. 강체, 조인트, 충돌 및 힘을 포함한 정확하고 효율적인 물리 시뮬레이션을 제공합니다. Forge2D는 Android, iOS, 웹, macOS, Windows, Linux 등 여러 플랫폼을 지원하여 크로스플랫폼 게임 개발에 적합합니다. Flame과의 통합은 현실적인 물리적 행동을 가진 상호작용 게임을 구축하는 것을 간편하게 만듭니다.

사용 사례

  • 2D 게임 개발
  • 인터랙티브 시뮬레이션
  • 물리 기반 애니메이션
  • 크로스 플랫폼 게임
  • 게임 프로토타이핑

주요 기능

  • Box2D에서 영감을 받은 정확성
  • 무결점 플레임 통합
  • 다중 플랫폼 지원
  • 강체 역학
  • 충돌 감지
  • 관절 제약

적합한 대상

  • Flutter 게임 개발자
  • 2D 물리 시뮬레이션 프로젝트
  • 플레임 엔진 사용자
  • 크로스 플랫폼 앱 제작자
  • 교육용 게임 제작자

고려 사항

  • 물리 개념에 대한 지식이 필요합니다
  • 플레임 엔진과 가장 잘 어울림
  • 2차원 시나리오에 한정
  • 성능은 장면의 복잡도에 따라 달라집니다
  • 3D 물리에 적합하지 않음

원본 README

연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.

flame

Forge2D - Dart bindings for the Box2D physics engine

Test

Forge2D provides an idiomatic Dart API for the native Box2D v3 physics engine. The C library is bundled, compiled by the Dart/Flutter build through native assets, and called over dart:ffi, so simulations run at native speed with the real, actively maintained Box2D.

You can use it independently in Dart or in your Flame project with the help of flame_forge2d.

Requirements

  • Dart 3.12+ (Flutter 3.38+), which builds the bundled C library automatically through build hooks.
  • A C toolchain on native platforms: Xcode on macOS/iOS, Visual Studio Build Tools on Windows, clang or gcc on Linux, and the NDK on Android.
  • Supported platforms: Android, iOS, macOS, Windows, Linux, and the web.

On the web the same API runs against a bundled WebAssembly build of Box2D (about 220 KB). No setup is needed: initializeForge2D() finds the module at the package asset path in Dart web apps, and at the bundled package asset in Flutter web apps. For custom hosting setups the location can be overridden with initializeForge2D(wasmUri: ...), and dart run forge2d:setup_web copies the module into a web/ directory.

Getting started

import 'package:forge2d/forge2d.dart';

Future<void> main() async {
  await initializeForge2D();

  final world = World();

  // Static ground: a wide box whose top surface is at y = 0.
  world
      .createBody(BodyDef(position: Vector2(0, -1)))
      .createShape(Polygon.box(50, 1));

  // A dynamic box dropped from above.
  final box = world.createBody(
    BodyDef(type: BodyType.dynamic, position: Vector2(0, 10)),
  )..createShape(Polygon.square(0.5));

  for (var i = 0; i < 90; i++) {
    world.step(1 / 60);
  }
  print(box.position); // The box has landed on the ground.

  world.destroy();
}

Highlights of the API:

  • World, Body, Shape, Chain, and the joints are cheap value-like handles over native ids; destroy things explicitly with destroy().
  • Contact, sensor, and body-move events are polled from the world after each step (world.contactEvents, world.sensorEvents, world.bodyMoveEvents).
  • Ray casts (castRayClosest, castRayAll, castRay), AABB overlap queries, and explosions are available on World.
  • DebugDraw can be implemented to render the physics world for debugging.

Units

Box2D is tuned for meters, kilograms and seconds, so lay your world out in meters and aim to keep moving objects roughly between 0.1 and 10 of them, with 1 being the sweet spot. Rendering scale is a separate concern: decide how many pixels a meter is worth in your renderer, not in the simulation.

Some of the tolerances are absolute lengths rather than fractions of the shapes they apply to, so a world laid out at a much smaller scale behaves oddly. The most visible one is the speculative distance: Box2D creates contact points for shapes that are approaching but not yet touching, which is what stops fast objects from passing through each other, and it means beginContact fires while there is still a gap of up to 0.02 meters. A shape that is only a couple of centimeters across is therefore permanently in contact with its neighbors. Tolerances exposes these values:

Tolerances.linearSlop;           // 0.005
Tolerances.speculativeDistance;  // 0.02
Tolerances.aabbMargin;           // 0.05

WorldDef.restitutionThreshold (1 m/s), WorldDef.hitEventThreshold (1 m/s), WorldDef.maxContactPushSpeed (3 m/s), WorldDef.maximumLinearSpeed (400 m/s) and BodyDef.sleepThreshold (0.05 m/s) are absolute in the same way, but they are per world or per body, so they can simply be set.

When a world genuinely cannot be laid out at that scale, tell Box2D how many of your length units make up a meter and every tolerance above moves with it:

await initializeForge2D(lengthUnitsPerMeter: 100);

A good rule of thumb is to pass the height of your player character. You are then on the hook for gravity, densities and forces being sensible at that scale. For a length scale factor of S, velocities and accelerations scale by S, masses by S², forces and impulses by S³ and torques by S⁴, while densities, friction, restitution and damping stay as they are. Scaling lengths and gravity together leaves the timing of the simulation unchanged.

The length unit is process-wide and cannot change once a World exists, which is why it is set through initializeForge2D.

Performance

The standard bench2d benchmark (a 40-high pyramid of boxes, 256 frames), on the same machine:

Engine ms/frame (mean)
forge2d 0.14 (pure Dart port) 9.37
forge2d with native Box2D v3 0.51

Migrating from forge2d 0.14

Forge2D 0.15 is a ground-up rewrite on the Box2D v3 API. For the full walkthrough, see the Forge2D migration guide. The high-level concepts map as follows:

  • Dart 3.12+ and a C toolchain are required, see Requirements. This is usually the biggest practical hurdle of the upgrade.
  • On the web, await initializeForge2D() has to run before the first World is created. On native it is a no-op (the backend is created lazily), but cross-platform code should always await it.
  • World, Body, Shape, Chain, and joints are value-like handles over native ids rather than Dart objects owning their state. Nothing is garbage collected: destroy things explicitly, and check isValid if a handle may have outlived what it points at.
  • Fixture is gone: bodies carry Shapes directly, created with body.createShape(geometry, ShapeDef(...)) where the geometry is a Circle, Capsule, Segment, or Polygon. Chains have their own body.createChain(ChainDef(points: ...)).
  • EdgeShape is replaced by Segment (standalone) and one-sided chain shapes for level geometry.
  • Friction, restitution, and rolling resistance live on ShapeDef(material: SurfaceMaterial(...)) instead of being fields on the old FixtureDef.
  • ContactListener callbacks are replaced by events polled after each step: world.contactEvents, world.sensorEvents, and world.bodyMoveEvents. Contact and sensor events are opted in per shape with ShapeDef(enableContactEvents: true) and ShapeDef(isSensor: true, enableSensorEvents: true); hit events need enableHitEvents. Custom filtering is a world.customFilterCallback.
  • Query and ray-cast callback classes are gone: world.castRayClosest, world.castRayAll, and world.overlapAabb return their results directly, and world.castRay takes a plain closure. AABB is now Aabb.
  • Joints are created with type-specific methods such as world.createRevoluteJoint(RevoluteJointDef(...)), and the set is now distance, filter, motor, mouse, prismatic, revolute, weld, and wheel. Gear, pulley, rope, friction, and constant-volume joints do not exist in Box2D v3.
  • Rotations are a Rot rather than a raw angle: BodyDef(rotation: ...) and body.setTransform(position, rotation) take one, built with Rot.fromAngle(radians). body.angle still reads back a double.
  • body.applyForce and body.applyLinearImpulse take the point of application as a named argument, applyForce(force, point: ..., wake: true), and applying at the centre of mass is just leaving point out. applyForceToCenter is gone.
  • DebugDraw is an abstract class you implement and pass to world.draw(debugDraw), with colors as 0xRRGGBB ints.
  • The particle system (LiquidFun) is not part of Box2D v3 and has been removed; stay on forge2d 0.14 if you depend on it.
  • Worlds default to subStepCount: 4 in step instead of velocity and position iterations.
  • A bare World() now has the Box2D default gravity of (0, -10); the old API defaulted to zero gravity. Top-down games should pass World(gravity: Vector2.zero()).
  • Destroying bodies, shapes, chains, or joints while the world is stepping (from a collision callback) is deferred until the step ends; creating them mid-step throws a StateError instead of the old silent queueing. world.destroy() itself is not deferred and throws mid-step.

Timeline

Box2D was first written in C++ and released by Erin Catto in 2007, and it is still actively maintained.

It was ported to Java (jbox2d) by Daniel Murphy around 2015, then from that Java port it was ported to Dart by Dominic Hamon and Kevin Moore.

A few years after that Lukas Klingsbo refactored the code to better follow the Dart standard and the project was renamed to Forge2D.

Since Box2D v3 rewrote the engine in C with a first-class embedding API, Forge2D moved from being a port to being bindings: the same idiomatic Dart surface, powered by the real engine.

There have also been countless other contributors which we are very thankful to!

Credits

  • The Flame engine team who is continuously working on maintaining and improving Forge2D.
  • Erin Catto for Box2D itself, which this package embeds.
  • Special thanks to Lukas Klingsbo, a Flame team member who took this project under his wing and greatly improved the project!
  • The Dart port of Box2D that earlier versions of Forge2D were built on.

카테고리

의존성

의존성 목록 7 개

소스 저장소

이 패키지를 사용하는 프로젝트