337p人体粉嫩胞高清图片,97人妻精品一区二区三区在线 ,日本少妇自慰免费完整版,99精品国产福久久久久久,久久精品国产亚洲av热一区,国产aaaaaa一级毛片,国产99久久九九精品无码,久久精品国产亚洲AV成人公司
網易首頁 > 網易號 > 正文 申請入駐

用 200 行 MoonBit 構建原生移動端游戲

0
分享至


一、引言

當你想做一個簡單的手機游戲 , 比如 Flappy Bird、2048、貪吃蛇——你的第一反應可能是打開 Unity 或者 Godot。但你有沒有想過:對于一個只需要畫幾個矩形和圓的游戲,你真的需要一個完整的游戲引擎嗎?

引擎內部數十萬行的 C++ 代碼 帶來的不只是便利, 或許還有冗余 。如果我們換一種思路 : 不用引擎,不依賴運行時,直接用一門現代語言編寫游戲邏輯,編譯為原生機器碼,再搭配一個極簡的圖形庫 , 結果會怎樣?

這正是本文要探討的命題 : 使用 MoonBit (一門編譯到原生代碼的現代語言)和 Raylib (一個僅提供最基本圖形能力的 C 庫),從零構建一個可以在 Android 手機上運行的 Flappy Bird。

在這個過程中,你會看到:一個完整的移動游戲,可以只有幾百行代碼、幾 MB 的 APK,以及零引擎依賴。

Web 游戲試玩鏈接:

https://moonbit-community.github.io/tonyfettes-raylib-android-games

Android 游戲 APK 下載鏈接:

https://moonbit-community.github.io/tonyfettes-raylib-android-games/

二、移動游戲開發的技術選型 演進

2015年前后,手機游戲開發主要依賴三大技術路線:

  1. 引擎時代(Unity/Godot):提供一站式開發環境,大幅降低門檻,但存在包體臃腫、底層黑箱、版本更新風險等問題。Godot 雖開源,思路仍類似。

  2. 跨平臺框架(React Native/Flutter):承諾一套代碼多端運行,適合 UI 應用。但在游戲中暴露出額外抽象層、GC 停頓、非為高頻渲染優化等性能瓶頸。

  3. 原生 NDK(C/C++):性能最優,無中間開銷,但開發體驗差,手動內存管理易引發段錯誤等 bug,對業余項目成本過高。

有沒有一種方案,既能獲得原生性能,又能享受現代語言的開發體驗?

這正是 MoonBit 和 Raylib 的組合所提供的。

MoonBit 是一門為性能而設計的現代編程語言。它擁有強類型系統、模式匹配、類型推導,編寫體驗接近 Rust 或 OCaml,但編譯目標是 C——這意味著它可以直接對接 Android NDK 的工具鏈,最終生成和手寫 C 一樣高效的原生代碼。

Raylib 則是游戲圖形庫的極簡主義代表。它不是引擎,不幫你管理場景,不提供編輯器——它只做四件事: 開窗口、畫圖形、讀輸入、放聲音 。用戶面對的核心 API 集中在一個頭文件 raylib.h 中,沒有復雜的依賴關系,沒有狀態機,沒有回調地獄。

把它們組合在一起,你得到的是:


這不是說 MoonBit + Raylib 適合所有場景——如果你在做一款需要物理引擎、粒子系統、骨骼動畫的大型游戲,Unity 仍然是更合理的選擇。但如果你的目標是一款邏輯清晰的 2D 游戲,這套"極簡主義"方案可能是最干凈的路徑。

讓我們來看看它是怎么工作的。

三、理解構建鏈路:從源碼到 APK

在動手寫代碼之前,有一個問題值得想清楚: 你寫的 MoonBit 代碼,是如何變成手機上可以運行的 APK 的?

理解構建鏈路不是為了背誦流程——而是為了在出問題時,知道該往哪里看。

1、 構建鏈路

整個過程可以用一條鏈來描述:


讓我們逐步拆解。

第一步:MoonBit → C 。MoonBit 編譯器將你的 `.mbt` 源文件編譯為標準 C 代碼。MoonBit 的強類型系統在編譯期就排除了大量常見錯誤,生成的 C 代碼是高效的、確定性的。你可以把它理解為:MoonBit 幫你寫了人類不太愿意手寫的那種高質量 C 代碼。

第二步:C → .so 。Android NDK 中的交叉編譯器(通常是 clang)接手,將 C 代碼連同 Raylib 的源碼一起編譯為目標架構的共享庫(`.so` 文件)。這一步和你用 NDK 編譯任何 C/C++ 項目一樣。

第三步:打包成 APK 。Gradle 構建系統將 .so 文件打包進 APK。同時,那個極輕量的 Kotlin 入口點(僅僅是加載庫和隱藏系統 UI)會經過標準的 Android 編譯流程:Kotlin 編譯器將其編譯為 JVM 字節碼,再由 D8 工具轉換為 Android 運行時使用的 classes.dex 。最終的 APK 結構非常簡單:

APK
├── classes.dex ← 極小的 Kotlin 膠水代碼
├── lib/
│ ├── arm64-v8a/
│ │ └── libflappybird.so ← 你的游戲 + Raylib
│ └── armeabi-v7a/
│ └── libflappybird.so
└── AndroidManifest.xml

如果用一個類比:傳統引擎方案就像你寫了一封信,然后把它裝進一個帶有自動翻譯器、排版引擎和朗讀功能的智能信封里寄出去。而 MoonBit + Raylib 的方案就像你直接把信折好,塞進一個普通信封——信的內容沒有變,但信封輕了十倍。

2、 腳手架:一鍵搭建項目

理解了鏈路之后,實際操作反而很簡單。MoonBit 生態提供了一個腳手架工具,可以一鍵生成上述所有構建配置:

moon install tonyfettes/create-moonbit-raylib-android-app
create-moonbit-raylib-android-app MyFlappyBird

生成的項目結構看起來像這樣:

MyFlappyBird/
├── gradlew # Gradle 構建包裝器
├── app/
│ ├── build.gradle.kts # Android 構建配置 (NDK, ABI 目標)
│ ├── src/main/
│ │ ├── AndroidManifest.xml # 應用清單 (NativeActivity)
│ │ ├── java/.../MainActivity.kt # 輕量 Kotlin 入口點
│ │ ├── moonbit/ # 你的游戲代碼存放處
│ │ │ ├── main.mbt # 游戲代碼
│ │ │ ├── moon.mod.json # MoonBit 模塊配置
│ │ │ └── moon.pkg # 包聲明
│ │ └── cpp/
│ │ └── CMakeLists.txt # 構建管道膠水代碼
│ └── ...
└── gradle/

這里關鍵的只有一個目錄: app/src/main/moonbit/ ——你的所有游戲邏輯都寫在這里。其余的 Gradle 配置、CMake 文件、Kotlin 入口點,腳手架已經幫你處理好了。

模塊配置( moon.mod.json )聲明了對 Raylib 綁定的依賴:

{
"name": "username/myflappybird",
"version": "0.1.0",
"deps": {
"tonyfettes/raylib": "0.2.2"
},
"preferred-target": "native"
}

構建和部署也是一行命令:

cd MyFlappyBird
./gradlew assembleDebug --no-daemon

第一次構建需要幾分鐘(它會從源碼編譯 Raylib),之后的增量構建會快得多。你也可以在 Android Studio 中打開項目,點擊 Run 按鈕一鍵編譯部署。


在運行時,輕量的 MainActivity 加載 .so 庫,NDK 膠水代碼啟動原生端,Raylib 初始化 OpenGL ES 上下文,然后調用 main() ——也就是你用 MoonBit 寫的那個 fn main 。

基礎設施講完了。現在讓我們進入真正有趣的部分:游戲邏輯。

四、構建 Flappy Bird

1、游戲循環

從《超級馬里奧》到《原神》,所有實時游戲在最底層都共享同一個結構——初始化(Init)、循環執行更新(Update)與繪制(Draw)、最后清理(Cleanup):


這就是 游戲循環(Game Loop) 。它揭示了實時游戲的本質: 游戲不是一系列事件的響應,而是一幀又一幀的持續模擬 。和 Web 應用的事件驅動模型不同,游戲代碼每秒執行 60 次,無論用戶是否操作——用戶輸入不是觸發器,而是被每一幀"采樣"的信號。

一個良好的游戲架構應該將 狀態更新(update) 和 畫面繪制(draw) 嚴格分離:update 只修改數據,draw 只讀取數據,不存在交叉副作用。讓我們用這個原則來構建 Flappy Bird。

2、定義游戲世界

首先,用結構體描述游戲中的所有對象:

///|
privstructBird {
muty : Float
mutvelocity : Float
}

///|
privstructPipe {
mutx : Float
mutgap_y : Float
mutscored : Bool
}

///|
privstructGame {
sw : Float
sh : Float
bird_x : Float
bird : Bird
bird_radius : Float
gravity : Float
jump_force : Float
pipes : Array[Pipe]
pipe_width : Float
gap_size : Float
pipe_speed : Float
pipe_spacing : Float
mut score : Int
mut game_over : Bool
}

Bird 只保存 每幀變化 的值(位置和速度),而 不變的屬性 (水平位置、半徑)由 Game 持有—— 可變狀態越少,bug 越少 。每個 Pipe 記錄水平位置 x 、空隙中心點 gap_y (開口的垂直中點)和計分標志 `scored`。

另一個值得關注的細節:所有大小都從屏幕尺寸( sw 、 sh )派生——鳥的半徑是 sh / 25.0 ,重力加速度是 sh * 1.5 。這意味著游戲在任何分辨率的設備上都能保持相同的視覺比例和手感,不需要額外的適配邏輯。

3、游戲邏輯

update 函數處理所有狀態變化——物理模擬、水管移動、碰撞檢測和計分:

///|
fn update(game : Game, dt : Float) -> Unit {
if game.game_over {
if@raylib.is_gesture_detected(@raylib.GestureTap) {
reset(game)
}
return
}

if@raylib.is_gesture_detected(@raylib.GestureTap) {
game.bird.velocity = game.jump_force
}

game.bird.velocity += game.gravity * dt
game.bird.y += game.bird.velocity * dt

// 限制在屏幕邊緣內
if game.bird.y < game.bird_radius {
game.bird.y = game.bird_radius
game.bird.velocity = 0.0
}
if game.bird.y > game.sh - game.bird_radius {
game.bird.y = game.sh - game.bird_radius
game.bird.velocity = 0.0
}

for pipe in game.pipes {
pipe.x -= game.pipe_speed * dt
// 水管滾出左邊緣后回收到右側
if pipe.x < -game.pipe_width {
pipe.x += Float::from_int(game.pipes.length()) * game.pipe_spacing
pipe.gap_y = random_gap_y(game)
pipe.scored = false
}

// AABB 碰撞檢測
if game.bird_x + game.bird_radius > pipe.x &&
game.bird_x - game.bird_radius < pipe.x + game.pipe_width {
if game.bird.y - game.bird_radius < pipe.gap_y - game.gap_size / 2.0 ||
game.bird.y + game.bird_radius > pipe.gap_y + game.gap_size / 2.0 {
game.game_over = true
}
}

// 飛過水管時計分
if not(pipe.scored) && pipe.x + game.pipe_width < game.bird_x {
game.score += 1
pipe.scored = true
}
}
}

這段代碼有幾個值得注意的設計決策:

  • 幀率無關性 :所有涉及"隨時間變化"的量都乘以 `dt`(自上一幀經過的秒數)。`game.bird.velocity += game.gravity * dt` 意味著"每秒增加 `gravity` 這么多速度"——無論設備是 60fps 還是 30fps,物理效果一致。

  • 對象回收 :整個游戲只有 4 個水管對象。當一根水管滾出左邊緣,直接把 `x` 坐標加上偏移量"傳送"到最右邊,重新隨機空隙位置。不需要對象池框架——一個 `if` 和一次坐標重置就夠了。

  • AABB 碰撞檢測 :將圓形小鳥近似為外接矩形,檢測它與水管矩形是否重疊——先查水平方向重疊,再查小鳥是否在空隙之外。不是像素級精確,但對休閑游戲完全足夠。

  • 游戲結束檢查 :`update` 頂部的 `game_over` 檢查攔截一切后續邏輯,讓游戲"凍結"在撞擊瞬間,只允許點擊重啟。

draw 函數只負責將當前狀態繪制到屏幕:

///|
fn draw(game : Game) -> Unit {
@raylib.begin_drawing()
@raylib.clear_background(@raylib.skyblue)
for pipe in game.pipes {
let px = pipe.x.to_int()
let pw = game.pipe_width.to_int()
let gap_top = (pipe.gap_y - game.gap_size / 2.0).to_int()
let gap_bottom = (pipe.gap_y + game.gap_size / 2.0).to_int()
@raylib.draw_rectangle(px, 0, pw, gap_top, @raylib.darkgreen)
@raylib.draw_rectangle(
px,
gap_bottom,
pw,
@raylib.get_screen_height() - gap_bottom,
@raylib.darkgreen,
)
}
@raylib.draw_circle_v(
@raylib.Vector2::new(game.bird_x, game.bird.y),
game.bird_radius,
@raylib.yellow,
)
@raylib.end_drawing()
}

先畫水管再畫小鳥,確保小鳥總在最上層。所有繪圖調用必須在 begin_drawing() 和 end_drawing() 之間。

輔助函數和初始化邏輯:

///|
fn random_gap_y(game : Game) -> Float {
Float::from_int(
@raylib.get_random_value(
(game.gap_size / 2.0 + 50.0).to_int(),
(game.sh - game.gap_size / 2.0 - 50.0).to_int(),
),
)
}


///|
fn reset(game : Game) -> Unit {
game.bird.y = game.sh / 2.0
game.bird.velocity = 0.0
game.score = 0
game.game_over = false
for i in 0..
game.pipes[i].x = game.sw + Float::from_int(i) * game.pipe_spacing
game.pipes[i].gap_y = random_gap_y(game)
game.pipes[i].scored = false
}
}

最后, main 將一切連接起來:

///|
fn main {
@raylib.init_window(0, 0, "Flappy Bird")
@raylib.set_target_fps(60)
@raylib.set_exit_key(0)
letsw = Float::from_int(@raylib.get_screen_width())
letsh = Float::from_int(@raylib.get_screen_height())

let game : Game = {
sw,
sh,
bird_x: sw * 0.2,
bird: { y: 0.0, velocity: 0.0 },
bird_radius:sh / 25.0,
gravity: sh * 1.5,
jump_force: sh * -0.65,
pipes: Array::make(4, fn() { { x: 0.0, gap_y: 0.0, scored: false } }),
pipe_width: sw / 8.0,
gap_size: sh / 4.0,
pipe_speed: sw * 0.4,
pipe_spacing:sw / 2.5,
score: 0,
game_over: false,
}
reset(game)

while not(@raylib.window_should_close()) {
let dt = @raylib.get_frame_time()
update(game, dt)
draw(game)
}
@raylib.close_window()
}

init_window(0, 0, ...) 表示使用屏幕全尺寸——在 Android 上就是全屏。游戲循環本身只有三行:獲取 dt、更新、繪制。 is_gesture_detected(GestureTap) 同時響應觸屏和鼠標點擊,可以在桌面開發測試后無縫部署到手機。

構建并部署:

cd MyFlappyBird
./gradlew assembleDebug --no-daemon
adb install -r app/build/outputs/apk/debug/app-debug.apk


重力、水管、碰撞檢測、計分、游戲結束和重啟——全在一個文件、約 200 行代碼里。沒有引擎,沒有運行時,沒有框架。

五 、總結與展望

讓我們回顧一下整個技術脈絡。

我們從一個簡單的問題出發: 一個休閑小游戲,真的需要一個完整的游戲引擎嗎? 然后沿著"從重到輕"的路徑,審視了移動游戲開發的幾種技術選型——從引擎(Unity/Godot)到跨平臺框架,再到原生 NDK,最終到達 MoonBit + Raylib 這個極簡組合。

在構建鏈路層面,我們看到 MoonBit 編譯到 C、C 通過 NDK 編譯到 .so 、 .so 打包進 APK 的清晰路徑——每一步都是確定性的,沒有黑箱。

在游戲架構層面,我們理解了游戲循環這個"所有游戲的共同骨架",以及為什么 update/draw 分離、幀率無關的物理模擬是重要的設計原則。

在具體實現層面,我們用約 200 行代碼構建了一個完整的 Flappy Bird,其中涉及了對象回收(窮人版對象池)和 AABB 碰撞檢測等實用技巧。

這套方案不是萬能的。如果你需要 3D 渲染管線、物理引擎、骨骼動畫、熱更新——使用 Unity 或 Godot 仍然是更務實的選擇。但如果你的目標是一款輕量的 2D 游戲,追求的是 小包體、高性能、完全可控的代碼 ,那么"做減法"的思路值得一試。

這里有一些可以繼續探索的方向:

  • tonyfettes/raylib —— MoonBit 的 Raylib 綁定庫,涵蓋圖形、紋理、音頻、3D 模型、著色器等完整功能

  • selene —— 一個用 MoonBit 編寫的實驗性游戲引擎,支持 WebGPU 和 Raylib 后端,專為網頁和原生游戲設計

  • MoonBit 文檔 —— 語言詳細文檔

另外值得注意一點的是,本文中所有示例代碼均由 AI 生成,甚至包括 Raylib 綁定庫本身。我們利用 AI Agent 的 Subagent 并行化地在桌面、Web 和 Android 平臺上產出了超過 150 款游戲。更多詳情可參見 tonyfettes/raylib 下的 examples/ 目錄。

從 Unity 的"給你一切"到 MoonBit + Raylib 的"只給你需要的",這不僅是技術選型的變化,更是一種開發哲學的轉變—— 最好的代碼不是寫出來的,而是不需要寫的 。

特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關推薦
熱點推薦
1982年血色使館:中國外交官唐健生為了生存殺光了所有同事

1982年血色使館:中國外交官唐健生為了生存殺光了所有同事

阿校談史
2026-03-20 11:03:27
Shams:東契奇遭遇左腿筋二級拉傷,常規賽報銷

Shams:東契奇遭遇左腿筋二級拉傷,常規賽報銷

懂球帝
2026-04-04 07:28:07
10萬億窟窿!比恒大更坑的民企來了,曾力壓許家印,位居第一

10萬億窟窿!比恒大更坑的民企來了,曾力壓許家印,位居第一

孤單是寂寞的毒
2026-03-04 15:38:03
29萬戶股東跌懵了,陽光電源去年第四季度凈利腰斬!

29萬戶股東跌懵了,陽光電源去年第四季度凈利腰斬!

每日經濟新聞
2026-04-04 22:26:52
清純得不像動作片女一號!

清純得不像動作片女一號!

貴圈真亂
2026-03-26 11:33:33
1:2!姆巴佩陷球荒,皇馬91分鐘被絕殺,落后巴薩7分西甲冠軍危矣

1:2!姆巴佩陷球荒,皇馬91分鐘被絕殺,落后巴薩7分西甲冠軍危矣

阿超他的體育圈
2026-04-05 05:17:54
浙江東陽,33 歲的男子,在母親長眠的公墓旁,在車里結束了生命

浙江東陽,33 歲的男子,在母親長眠的公墓旁,在車里結束了生命

老貓觀點
2026-04-02 13:02:45
被美毒打后,一架專機降落北京,新總理亮明底牌,親華派開始行動

被美毒打后,一架專機降落北京,新總理亮明底牌,親華派開始行動

超喜歡我
2026-04-03 17:56:49
局勢再度升級!首艘開往中國的油輪遭到襲擊,是誤傷還是警告

局勢再度升級!首艘開往中國的油輪遭到襲擊,是誤傷還是警告

李橑在北漂
2026-04-03 21:08:03
來了!英超魔咒,輪到阿森納,曼聯輸得一點都不冤,曼城一直不勝

來了!英超魔咒,輪到阿森納,曼聯輸得一點都不冤,曼城一直不勝

嗨皮看球
2026-04-04 15:56:12
CBA最新排名!廣東深圳鎖前四,7-11激烈,5隊緊咬北控爭12名!

CBA最新排名!廣東深圳鎖前四,7-11激烈,5隊緊咬北控爭12名!

籃球資訊達人
2026-04-05 01:56:50
比賴清德更狂的人出現了,只要她當上臺灣領導人,解放軍必定收臺

比賴清德更狂的人出現了,只要她當上臺灣領導人,解放軍必定收臺

共工之錨
2026-03-29 18:26:19
綠滿中原 河南產業升級背后的“硬核”與“溫情”

綠滿中原 河南產業升級背后的“硬核”與“溫情”

人民網
2026-04-03 16:04:31
歷史竟開了個玩笑:先倒下的不是烏克蘭,而是百年中立的瑞士?

歷史竟開了個玩笑:先倒下的不是烏克蘭,而是百年中立的瑞士?

通鑒史智
2026-04-02 10:27:20
巴爾韋德妻子吐槽巴薩紅牌改判:這都不算紅,球場真該關門了

巴爾韋德妻子吐槽巴薩紅牌改判:這都不算紅,球場真該關門了

懂球帝
2026-04-05 05:14:24
“給你女兒買件好內衣吧!”中學女孩鍛煉視頻,網友都看不下去了

“給你女兒買件好內衣吧!”中學女孩鍛煉視頻,網友都看不下去了

妍妍教育日記
2026-03-30 18:38:24
雷扎伊,身亡

雷扎伊,身亡

農民日報
2026-04-03 19:45:39
回加拿大生活的大山,60歲須發皆白很滄桑,重慶妻子仍風韻猶存

回加拿大生活的大山,60歲須發皆白很滄桑,重慶妻子仍風韻猶存

素衣讀史
2026-03-31 15:11:31
皇馬輸球元兇找到了!3 大核心集體擺爛,巨星親手送球隊翻車

皇馬輸球元兇找到了!3 大核心集體擺爛,巨星親手送球隊翻車

奶蓋熊本熊
2026-04-05 03:21:05
中俄意見出現分歧,俄羅斯帶頭反對,帶著朝鮮對華“唱反調”

中俄意見出現分歧,俄羅斯帶頭反對,帶著朝鮮對華“唱反調”

霽寒飄雪
2026-04-04 11:59:05
2026-04-05 06:40:49
開源中國 incentive-icons
開源中國
每天為開發者推送最新技術資訊
7667文章數 34522關注度
往期回顧 全部

游戲要聞

這些游戲全來和騰訊搶同一賽道?盤點五款童年情懷捉寵游戲

頭條要聞

特朗普發布視頻宣稱“打死多名伊朗軍事領導人”

頭條要聞

特朗普發布視頻宣稱“打死多名伊朗軍事領導人”

體育要聞

剎不住的泰格·伍茲,口袋里的兩粒藥丸

娛樂要聞

Q女士反擊,否認逼宋寧峰張婉婷離婚

財經要聞

中微董事長,給半導體潑點冷水

科技要聞

內存一年漲四倍!國產手機廠商集體漲價

汽車要聞

17萬級海豹07EV 不僅續航長還有9分鐘滿電的快樂

態度原創

藝術
親子
數碼
本地
軍事航空

藝術要聞

周恩來唯一草書題碑,8個字快一半都不認識!

親子要聞

優生優育,從科學孕前準備開始

數碼要聞

今年新款AirPods Pro、Apple TV值得等嗎?升級方向曝光

本地新聞

跟著歌聲游安徽,聽古村回響

軍事要聞

美軍又一架戰機墜毀 此前F-15E被擊落

無障礙瀏覽 進入關懷版