A Godot plugin that provides a unified GDScript interface for ML Kit face mesh detection on Android and iOS. Pass any Image to the Vision node and receive rich per-face data including 468 3-D landmark points, triangle mesh indices, and named facial contours — all in a single async call.
Key Features:
- Detect one or more faces in any
Imageusing ML Kit's Face Mesh API; configure the maximum number of faces reported per frame withset_max_faces()(iOS) or rely on the SDK default of 5 (Android) - Receive 468 normalised 3-D landmark points per face (
x/y∈ [0, 1],z= relative depth) - Access the full triangle mesh (indices into the landmark array) for each detected face
- Query individual named contours — face oval, eyes, eyebrows, lips, and nose bridge — via
FaceMeshInfoconstants - Built-in wireframe drawing utilities: overlay a mesh on an existing image or generate a transparent mesh layer for compositing
- Typed result and error classes (
FaceScanResult,FaceMeshInfo,ScanError) for clean, idiomatic GDScript
- Demo
- Installation
- Usage
- Signals
- Methods
- Classes
- Platform-Specific Notes
- Links
- All Plugins
- Credits
- Contributing
Try the demo app located in the demo directory.
Before installing this plugin, make sure to uninstall any previous versions of the same plugin.
If installing both Android and iOS versions of the plugin in the same project, then make sure that both versions use the same addon interface version.
There are 3 ways to install the Vision plugin into your project:
- Through the GMP Menu (recommended)
- Through the Godot Editor's AssetLib
- Manually by downloading archives from Github
Steps:
- install GMP Menu if not installed
- enable
GMP Menuvia thePluginstab ofGodot Editor'sProject->Project Settings...menu if not enabled - select
Vision PluginfromGodot Editor'sGMPmenu - select desired plugin version and platform
- click
Downloadbutton - once the download has completed, click
Installbutton - enable
Visionvia thePluginstab ofProject->Project Settings...menu, in the Godot Editor
Steps:
- search for and select the
Visionplugin in Godot Editor - click
Downloadbutton - on the installation dialog...
- keep
Change Install Foldersetting pointing to your project's root directory - keep
Ignore asset rootcheckbox checked - click
Installbutton
- keep
- enable the plugin via the
Pluginstab ofProject->Project Settings...menu, in the Godot Editor
When installing via AssetLib, the installer may display a warning that states "[x number of] files conflict with your project and won't be installed." You can ignore this warning since both versions use the same addon code.
Steps:
- download release archive from Github
- unzip the release archive
- copy to your Godot project's root directory
- enable the plugin via the
Pluginstab ofProject->Project Settings...menu, in the Godot Editor
Add a Vision node to your main scene or an autoload global scene.
- connect
Visionnode signals before callingscan_face()face_mesh_ready(result: FaceScanResult)— emitted when detection succeedsface_mesh_failed(error: ScanError)— emitted when detection fails
- call
vision.scan_face(image)with anyImage; the image is automatically converted toFORMAT_RGBA8if needed - in the
face_mesh_readycallback, iterate overresult.get_faces()to access per-face landmark data - optionally use the static drawing helpers to render the mesh directly onto an image
Basic face scan example:
@onready var vision := $Vision
func _ready() -> void:
vision.face_mesh_ready.connect(_on_face_mesh_ready)
vision.face_mesh_failed.connect(_on_face_mesh_failed)
func scan(image: Image) -> void:
vision.scan_face(image)
func _on_face_mesh_ready(result: FaceScanResult) -> void:
print("Detected %d face(s)" % result.get_face_count())
for face in result.get_faces():
print(" Points: %d, Triangles: %d" % [face.get_points().size(), face.get_triangles().size()])
func _on_face_mesh_failed(error: ScanError) -> void:
print("Scan failed [%s]: %s" % [error.get_code(), error.get_description()])Setting the maximum number of faces (iOS only):
@onready var vision := $Vision
func _ready() -> void:
vision.face_mesh_ready.connect(_on_face_mesh_ready)
vision.face_mesh_failed.connect(_on_face_mesh_failed)
# Detect up to 10 faces per frame.
# On Android this call is accepted but has no effect;
# ML Kit's face mesh detector always uses its SDK default (5).
vision.set_max_faces(10)
func scan(image: Image) -> void:
vision.scan_face(image)Drawing the mesh overlay onto an image:
func _on_face_mesh_ready(result: FaceScanResult) -> void:
# Returns a new Image with the wireframe painted on top
var annotated: Image = Vision.draw_face_mesh_on_image(original_image, result, Color.CYAN)
$TextureRect.texture = ImageTexture.create_from_image(annotated)Generating a transparent mesh layer for compositing:
func _on_face_mesh_ready(result: FaceScanResult) -> void:
var mesh_layer: Image = Vision.generate_face_mesh_image(
result, result.get_image().get_width(), result.get_image().get_height(), Color.LIME)
$MeshOverlay.texture = ImageTexture.create_from_image(mesh_layer)Accessing individual contours:
func _on_face_mesh_ready(result: FaceScanResult) -> void:
var face: FaceMeshInfo = result.get_face(0)
if face:
var oval: Array = face.get_contour(FaceMeshInfo.CONTOUR_FACE_OVAL)
for point in oval:
print("Oval point: ", point) # Vector3 — x/y normalised, z = depthRegister listeners to the following signals of the Vision node:
| Signal | Description |
|---|---|
face_mesh_ready(result: FaceScanResult) |
Emitted when face mesh detection succeeds. result contains the full ImageInfo for the processed image and an array of FaceMeshInfo objects, one per detected face. |
face_mesh_failed(error: ScanError) |
Emitted when detection fails. error contains a ScanError.Code enum value and a human-readable description. |
| Method | Description |
|---|---|
scan_face(a_image: Image) -> void |
Sends a_image to the native plugin for ML Kit face mesh detection. The image is automatically converted to FORMAT_RGBA8 internally. Results are delivered asynchronously via the face_mesh_ready or face_mesh_failed signals. |
set_max_faces(a_max_faces: int) -> void |
Sets the maximum number of faces the detector will report per frame. Accepts values in [1, ABSOLUTE_MAX_FACES]; values outside this range are clamped. Defaults to DEFAULT_MAX_FACES (5). iOS: fully supported — MediaPipe rebuilds the landmarker immediately with the new limit. Android: accepted without error but has no effect; ML Kit's FaceMeshDetectorOptions.Builder does not expose a max-faces setting and the SDK default of 5 is always used. The value is persisted and automatically re-applied if the plugin singleton is reconnected after an app-resume. |
| Constant | Value | Description |
|---|---|---|
DEFAULT_MAX_FACES |
5 |
Default value used by set_max_faces() and both native plugins on startup. |
ABSOLUTE_MAX_FACES |
20 |
Hard upper bound accepted by set_max_faces(); values above this are clamped. Applies to iOS only (see above). |
These are pure-GDScript helpers that operate on a FaceScanResult returned by the face_mesh_ready signal. They require no scene nodes or rendering server calls.
| Method | Description |
|---|---|
Vision.generate_face_mesh_image(a_result: FaceScanResult, a_width: int, a_height: int, a_color: Color) -> Image |
Creates a new FORMAT_RGBA8 image of the given dimensions with a fully transparent background, then draws the face mesh wireframe in a_color. Use this to composite the mesh as a separate layer on top of your camera frame. |
Vision.draw_face_mesh_on_image(a_original: Image, a_result: FaceScanResult, a_color: Color) -> Image |
Returns a new FORMAT_RGBA8 copy of a_original with the face mesh wireframe painted on top in a_color. The original image is not modified. |
Encapsulates the full result of a scan_face() call. Received via the face_mesh_ready signal.
| Method | Return type | Description |
|---|---|---|
get_image() |
ImageInfo |
Returns the full ImageInfo for the image that was scanned. Use get_image().get_width(), get_image().get_height(), get_image().get_buffer(), etc. to access individual properties. Returns null if the result is malformed. |
get_face_count() |
int |
Number of faces detected in the image. |
get_face(a_index: int) |
FaceMeshInfo |
Returns the FaceMeshInfo at the given index, or null if out of range. |
get_faces() |
Array |
Returns all detected faces as an Array of FaceMeshInfo objects. |
is_valid() |
bool |
Returns true when all required fields are present in the result. |
get_raw_data() |
Dictionary |
Returns the underlying raw data dictionary. |
Encapsulates the mesh data for a single detected face.
| Method | Return type | Description |
|---|---|---|
get_points() |
Array |
All 468 landmark points as Vector3. x and y are normalised to [0, 1]; z is relative depth. |
get_triangles() |
Array |
All mesh triangles as Vector3i. Each component (x, y, z) is an index into the get_points() array. |
get_contours() |
Dictionary |
Raw contours dictionary keyed by contour name. Values are arrays of [x, y, z] sub-arrays. |
get_contour(a_contour_name: String) |
Array |
Points of a named contour as Array of Vector3. Use the CONTOUR_* constants as the name argument. |
is_valid() |
bool |
Returns true when points, triangles, and contours are all present. |
get_raw_data() |
Dictionary |
Returns the underlying raw data dictionary. |
Contour name constants (pass to get_contour()):
| Constant | Facial region |
|---|---|
CONTOUR_FACE_OVAL |
Outer boundary of the face |
CONTOUR_LEFT_EYE |
Left eye outline |
CONTOUR_LEFT_EYEBROW_BOTTOM |
Bottom edge of the left eyebrow |
CONTOUR_LEFT_EYEBROW_TOP |
Top edge of the left eyebrow |
CONTOUR_LOWER_LIP_BOTTOM |
Outer bottom edge of the lower lip |
CONTOUR_LOWER_LIP_TOP |
Inner top edge of the lower lip |
CONTOUR_NOSE_BRIDGE |
Bridge of the nose |
CONTOUR_RIGHT_EYE |
Right eye outline |
CONTOUR_RIGHT_EYEBROW_BOTTOM |
Bottom edge of the right eyebrow |
CONTOUR_RIGHT_EYEBROW_TOP |
Top edge of the right eyebrow |
CONTOUR_UPPER_LIP_BOTTOM |
Inner bottom edge of the upper lip |
CONTOUR_UPPER_LIP_TOP |
Outer top edge of the upper lip |
Encapsulates error information delivered via the face_mesh_failed signal.
| Method | Return type | Description |
|---|---|---|
get_code() |
ScanError.Code |
One of the Code enum values below. |
get_description() |
String |
Human-readable description of the error. |
ScanError.Code enum values:
| Value | Meaning |
|---|---|
NONE |
No error. |
INVALID_IMAGE |
The supplied image could not be processed. |
NO_CODE_DETECTED |
No face was detected in the image. |
SCANNER_FAILURE |
The underlying ML Kit scanner returned a failure. |
INTERNAL_ERROR |
An unexpected internal error occurred. |
Wraps a Godot Image as a serialisable dictionary for transfer to the native plugin. Used internally by scan_face() — you do not normally need to use this class directly.
| Method | Return type | Description |
|---|---|---|
ImageInfo.create_from_image(a_image: Image) (static) |
ImageInfo |
Creates an ImageInfo from a Godot Image. |
get_buffer() |
PackedByteArray |
Raw pixel bytes of the image. |
get_width() |
int |
Image width in pixels. |
get_height() |
int |
Image height in pixels. |
get_format() |
Image.Format |
Pixel format of the image. |
has_mipmaps() |
bool |
Whether the image has mipmaps. |
is_valid() |
bool |
Returns true when width, height, and buffer are all present. |
get_raw_data() |
Dictionary |
Returns the underlying raw data dictionary. |
- Download Android export template and enable gradle build from export settings
- The face mesh detector uses ML Kit Face Mesh Detection. The underlying
FaceMeshDetectorOptions.Builderdoes not expose a max-faces setting; the SDK default of 5 faces is always used regardless of anyset_max_faces()call. - Troubleshooting:
- Logs:
adb logcat | grep 'godot'(Linux),adb.exe logcat | select-string "godot"(Windows) - You may find the following resources helpful:
- Follow instructions on Exporting for iOS
- The face mesh detector uses MediaPipe Tasks Vision. The maximum number of faces is fully configurable via
set_max_faces()at any time; the default is 5 and the hard ceiling is 20. - View XCode logs while running the game for troubleshooting.
- See Godot iOS Export Troubleshooting.
| ✦ | Plugin | Android | iOS | Latest Release | Downloads | Stars |
|---|---|---|---|---|---|---|
| Admob | ✅ | ✅ | ||||
| Connection State | ✅ | ✅ | ||||
| Deeplink | ✅ | ✅ | ||||
| Firebase | ✅ | ✅ | ||||
| In-App Review | ✅ | ✅ | ||||
| Native Camera | ✅ | ✅ | ||||
| Notification Scheduler | ✅ | ✅ | ||||
| OAuth 2.0 | ✅ | ✅ | ||||
| QR | ✅ | ✅ | ||||
| Share | ✅ | ✅ | ||||
| Vision | ✅ | ✅ |
Developed by Cengiz
Based on Godot Mobile Plugin Template v7
Original repository: Godot Vision Plugin
Contributions are welcome. Please see the contributing guide in the repository for details.
If this plugin has helped you, consider supporting its development! Every bit of support helps keep the plugin updated and bug-free.
| ✦ | Ways to Help | How to do it |
|---|---|---|
| ✨⭐ | Spread the Word | Star this repo to help others find it. |
| 💡✨ | Give Feedback | Open an issue or suggest a feature. |
| 🧩 | Contribute | Submit a PR to help improve the codebase. |
| ❤️ | Buy a Coffee | Support the maintainers on GitHub Sponsors or other platforms. |




