A triple-tile match puzzle, rendered in real 3D. Tap tiles from layered stacks into a 7-slot tray. Match three of the same icon and they vanish. Clear the board to win. No ads, no IAP, no analytics, no tracking.
Since v1.0 the game renders as a true 3D scene: rounded beveled tiles
(procedural meshes), a perspective camera, a directional sun with real-time
shadows, arc fly-to-tray animations with landing squash, and particle
bursts on matches. The 12 cartoon icons are still authored as 2D _draw()
vector art (inspired by Tile Trip) and are baked into textures at startup —
there are no external assets anywhere in the project.
Direct APK download: https://github.com/Matswm86/tile-explorer/releases/download/latest/tile-explorer.apk
- Open that link in your phone's browser and tap to download.
- When you tap the downloaded file, Android may say "For your security, your phone is not allowed to install unknown apps from this source." Tap Settings, toggle Allow from this source, then go back and install.
- The app appears as Tile Explorer.
The APK is debug-signed with a stable key (stored as the
ANDROID_DEBUG_KEYSTORE_BASE64GitHub secret), so reinstalling a newer build over an older one Just Works — no uninstall needed.
Permanent versioned downloads are also published to the
Releases page when a
vX.Y.Z tag is pushed.
- Tap any unblocked tile (one not covered by another tile above it). It flies into the wooden tray at the bottom of the screen. Blocked tiles are dimmed and wiggle when tapped.
- The tray holds 7 tiles. Tiles auto-sort by icon so groups stay together.
- When three tiles of the same icon end up in the tray, they clear.
- Win by emptying the board with the tray empty too.
- Lose when the tray fills with 7 tiles that can't form a triple.
| Button | What it does |
|---|---|
| Undo | Sends the last tile you tapped back to the board. |
| Clear 3 | Deletes the leftmost 3 tiles in the tray, plus enough matching board tiles of the same icons to keep every icon's remaining count a multiple of 3 (so the level stays solvable). |
| Shuffle | Randomly re-assigns the icons on remaining board tiles. |
- Install Godot 4.6.x from https://godotengine.org/ (single binary, no install needed — just extract and run).
- Open the editor → Import → pick
project.godotin this folder. - Press F5 (or hit ▶). Mouse acts as touch on desktop.
Each level is a JSON file in data/levels/. Coordinates are absolute pixels
referenced to a 1080×1920 viewport (the project stretch mode handles scaling
on other resolutions).
{
"level": 1,
"tile_size": 130,
"tiles": [
{"icon": 0, "x": 380, "y": 580, "layer": 0},
{"icon": 1, "x": 540, "y": 580, "layer": 0}
]
}iconis an integer index intoIcons.TABLEinscripts/Icons.gd(12 themed icons: soccer ball, apple, cat, fish, cherry, watermelon, bone, mushroom, ghost, banana, carrot, donut — all drawn procedurally).layeris z-order: higher layers cover lower layers. A tile is tappable only when no tile on a strictly higher layer overlaps its bounding box.- Each icon must appear in a multiple of 3 (so triples can clear).
Generate or hand-author additional levels and bump max_level in the Game
scene to expose them.
30 levels with progressive difficulty:
| Range | Tiles | Layers | Icons | Notes |
|---|---|---|---|---|
| L1–L5 | 12 → 36 | 1 → 4 | 4 → 6 | Intro, simple grids |
| L6–L11 | 42 → 72 | 3 → 5 | 7 → 12 | All 12 icons unlocked at L11 |
| L12–L20 | 81 → 150 | 5 → 6 | 12 | Denser stacked piles |
| L21–L30 | 156 → 216 | 6 → 7 | 12 | Deep centred pyramids |
Levels are generated by gen_levels.py (centred-pyramid layouts, where
each upper layer is smaller and centred over the layer below — peeling
back outer rings reveals the inner core).
Every push to main triggers .github/workflows/build-android.yml, which:
- Spins up
ubuntu-latest, installs Java 17 + Android SDK. - Downloads Godot 4.6.2 headless + Android export templates.
- Decodes the stable debug keystore from the
ANDROID_DEBUG_KEYSTORE_BASE64secret (falls back to generating an ephemeral keystore if the secret isn't set, so forks still build). - Writes
editor_settings-4.6.tresand the build template marker files. - Runs
godot --headless --export-debug "Android" tile-explorer.apk. - Uploads the APK as a workflow artifact, and updates the rolling
latestpre-release on the Releases page.
For a permanent versioned APK: git tag v0.1.0 && git push --tags.
The full debugging history of this workflow lives in ball-connect's
docs/godot-android-ci-notes.md
— same workflow, same gotchas.
project.godot Engine settings (1080×1920 portrait, GL Compat)
export_presets.cfg Android export preset (gradle build, arm64-v8a)
icon.svg App icon
.github/workflows/
build-android.yml CI workflow that produces the APK
scenes/
Game3D.tscn Root 3D scene: camera, sun, sky, UI
scripts/
GameManager3D.gd State machine, level loader, power-ups, win/lose
Board3D.gd Tile collection, blocking detection, ray picking
Tray3D.gd 7-slot tray, triple-match logic, arc animations
Tile3D.gd Rounded-box tile body + icon quad + materials
RoundedBox.gd Procedural rounded-box ArrayMesh generator
IconBaker.gd Bakes the 2D icon art into 3D textures at startup
Fx3D.gd Match-burst and win-confetti particles
Icons.gd 12 themed icon definitions, 2D draw helpers
Drawing.gd Rounded-rect 2D draw helpers (used by the baker)
data/levels/
level_01.json …through level_30.json (30 levels)
Set TILE_DEVSHOT=/path/shot.png to boot the game, screenshot and exit.
Optional: TILE_DEVLEVEL=N (jump to level), TILE_DEVTAPS=N (simulate N
taps through the real ray-picking path and assert triples clear), and
TILE_DEVMODE=power|lose (exercise the power-ups / force the lose path).
Inert unless the env var is set.
- Tile size: 130 px square. Viewport: 1080×1920 portrait.
- Tile-blocking inset: 6 px (tiles must overlap by more than this to count
as covering) — see
Board3D.OVERLAP_INSET. Blocking runs in the original 130 px level space, so 2D-era levels behave identically in 3D. - Tray sort: tiles auto-sort by
icon_id, so same-icon tiles always end up adjacent. Triple-match resolves on count, not on adjacency. - Power-ups reset to 3 charges at the start of each level.
- Lose condition fires the moment the tray is full after triple resolution; players who anticipate this can pre-emptively use Clear 3 or Undo on the previous turn.

