Game Unity bị đen màn hình khi build và chạy trên điện thoại
Vì sao game Unity bị đen màn hình khi build / chạy trên điện thoại?
Màn đen trên máy thật là bốn lỗi khác nhau đội chung một vỏ: scene khởi động không nằm trong Build Scenes, scene không có camera nào đang bật, một Image phủ kín đè lên tất cả, hoặc shader máy từ chối chạy. Tách bốn loại đó trong sáu mươi giây bằng âm thanh và thời điểm, rồi để adb logcat -s Unity gọi đúng nguyên nhân.
Màn hình đen trên điện thoại không phải một lỗi, mà là bốn lỗi nhìn từ ngoài giống hệt nhau. Ba trong số đó tách được ra trước khi mở bất kỳ file nào: âm thanh có chạy tiếp không, hình có tự trở lại sau vài giây không, và logcat nói gì. Xác định đúng loại rồi sửa đúng loại đó, thay vì build mười lần theo cảm giác.
1 — Tách màn đen ra làm ba loại
Sau logo, bản thân Unity cũng vẽ màu đen một tới hai giây, và điện thoại tới được scene đầu chậm hơn editor rất nhiều. Trước khi đổi thứ gì, hãy dành mười giây cho câu hỏi bạn đang thật sự có loại đen nào. Mỗi loại mang theo danh sách nguyên nhân riêng.
Đen vĩnh viễn, không bao giờ thấy scene đầu tiên: hoặc scene điện thoại khởi động vào không phải cái bạn nghĩ, hoặc trong đó chẳng có gì được vẽ. Mục 2 và 3.
Đen vài giây rồi game hiện ra: khởi động bình thường nhưng thiếu màn hình chờ che trước. Bộ nhớ điện thoại và lần mở IL2CPP nguội chậm hơn cache của editor — thứ bạn chưa từng phải chờ.
Đen trong khi nhạc vẫn chạy: logic còn sống, chỉ mất hình. Đó là camera, hoặc giao diện đè lên, hoặc shader máy từ chối chạy — mục 3, 4 và 5.
2 — Scene đầu tiên không có trong bản build
Scene không nằm trong Build Scenes thì không có trong game. Không có trong APK, không có trên máy, gọi tên cũng không tới — chạy bản build là file đó đơn giản không tồn tại. Chính cái bảng này khiến câu 'trong editor ngon, lên điện thoại thì đen' tồn tại.
- 1
Mở (trước Unity 6 là Build Settings), chọn nền tảng, nhìn Scene List. Mọi scene game đi tới được đều phải có — không chỉ scene bạn khởi động vào.
- 2
Dòng ở index 0 là scene bản build khởi động vào. Editor bỏ qua số đó và mở cái bạn đang để trước mặt — nên lỗi này chỉ lộ ra trên điện thoại.
- 3
Thêm tường minh từng scene, rồi kéo menu hoặc cảnh khởi động về index 0. Scene bạn chưa thêm vẫn có thể đang mở trong editor và trông hoàn hảo.
Nếu code gọi LoadScene với tên không có trong danh sách, Unity ghi một dòng lỗi và việc nạp không xảy ra — im lặng, không hộp thoại, không ngoại lệ. Mọi thứ code đã làm vẫn còn nguyên: menu đã ẩn, camera đã tắt, object đã huỷ. Bạn không nhìn thấy lỗi render, mà là đống đổ nát của một lần nạp chưa từng tới.
Chứng minh scene nào đã mở
Một dòng là chấm dứt cả hạng đoán mò này. Đặt Debug.Log trong Awake của một object ở mỗi scene, in ra tên chính scene đó kèm SceneManager.sceneCountInBuildSettings — số scene bản build thật sự mang theo. logcat sẽ trả lời cả hai: điện thoại đã mở gì, và cái bạn muốn có được đóng gói hay không.
Che chỗ chờ, thay vì phơi nó ra
Loại đen thứ hai không hỏng, chỉ chưa trau: engine đang làm việc và màn hình không có gì trong lúc đó. Khuôn mẫu chỉ là một object — lớp phủ đen sống lâu hơn lần đổi scene, chờ nạp xong, rồi nhạt dần trên scene mới.
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
// CanvasGroup và script này đặt lên Canvas GỐC — DontDestroyOnLoad không tác
// dụng với object có cha, nên thứ phải sống sót là chính Canvas. Object con duy
// nhất của nó là một Image màu đen phủ kín màn hình.
[RequireComponent(typeof(CanvasGroup))]
public class FadeLoader : MonoBehaviour
{
public float fadeTime = 0.35f;
CanvasGroup veil;
void Awake()
{
veil = GetComponent<CanvasGroup>();
veil.alpha = 0f;
// Lớp phủ đã nhạt ra thì không được chặn thao tác chạm của người chơi.
veil.interactable = false;
veil.blocksRaycasts = false;
DontDestroyOnLoad(gameObject);
}
public void Load(string sceneName)
{
StartCoroutine(Transition(sceneName));
}
IEnumerator Transition(string sceneName)
{
// 1. Kéo hết màu đen trong lúc scene cũ vẫn còn đó.
yield return To(1f);
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
// Scene không có trong Build Scenes thì bản build không nạp được: Unity
// ghi lỗi và trả về null. Không chặn dòng này thì dòng sau ném lỗi và
// lớp phủ ở nguyên màu đen mãi mãi.
if (op == null)
{
Debug.LogError("Not in Build Profiles: " + sceneName);
yield return To(0f);
yield break;
}
// 2. Chờ. Lớp phủ đang phủ nên khoảng chờ đọc thành màu đen, chứ không
// thành hỏng.
while (!op.isDone)
yield return null;
// 3. Nhạt dần để lộ scene mới.
yield return To(0f);
}
IEnumerator To(float target)
{
float from = veil.alpha;
for (float t = 0f; t < fadeTime; t += Time.deltaTime)
{
veil.alpha = Mathf.Lerp(from, target, t / fadeTime);
yield return null;
}
veil.alpha = target;
}
}progress của AsyncOperation dừng ở 0.9 trong lúc scene chờ tới lượt hiện ra, nên thanh phần trăm phải dùng op.progress / 0.9f, còn allowSceneActivation = false là cách giữ scene đã nạp xong lại tới khi thanh chạy hết. Bản có gắn slider nằm ở bài nạp scene khác.
3 — Camera không có gì để vẽ
Màn hình là thứ camera nói ra. Scene được nạp mà không có camera nào đang bật sẽ vẽ màu đen — dòng 'No cameras rendering' trong editor không tồn tại khi chạy game. Khi đã biết chắc có camera, câu hỏi chuyển từ 'nó có chạy không' sang 'nó thấy gì'.
Một camera đang bật, gắn tag MainCamera. Object hay component bị bỏ tick thì không vẽ gì, và camera đang con của thứ bị code tắt lúc khởi động thì sập theo. NullReferenceException trên Camera.main nghĩa là không object nào mang tag đó.
Nhân vật sinh ra dưới sàn: capsule bắt đầu bên dưới Plane rơi mãi, camera bám theo rơi cùng, mọi object rời khỏi far plane. Trong editor bạn còn kịp thấy cú rơi bắt đầu; lên điện thoại chỉ có màu đen. Một mặt sàn thiếu collider cũng cùng câu chuyện, và sửa ở scene chứ không phải camera.
Mặt phẳng và mask: far plane 100 trong màn chơi 500 mét thì chỉ thấy bầu trời, còn camera có Culling Mask bỏ layer của bạn thì không vẽ gì ngoài skybox. Cả hai đều nằm trên component Camera và đều mất vài giây để loại trừ.
Có một cách tách chúng trong năm giây, đáng làm trước khi đọc tiếp. Đặt Clear Flags của camera thành Solid Color, Background Color chọn màu chói — hồng magenta, thứ bạn không nhầm với phòng trống — rồi build. Màn hình đổi màu chói: camera vẫn vẽ bình thường và màu đen của bạn chỉ là góc nhìn trống, hãy xem nó đứng đâu và cull gì. Màn hình vẫn đen: scene mà điện thoại mở không có camera nào đang bật, bạn quay lại mục 2.
Nếu manager của bạn là singleton mà phần khởi tạo trong Awake phụ thuộc scene nào chạy trước, đó mới là lỗi thật; bốn nguyên tắc giữ GameManager tử tế chính là thứ sửa nó.
4 — Giao diện bị vẽ phủ kín màn hình
Canvas ở Screen Space - Overlay luôn vẽ lên trên mọi thứ camera tạo ra, bất kể mọi thứ nằm đâu trong scene. Một Image phủ kín màn hình trong đó — ảnh nền, lớp phủ fade, panel bạn quên — là game chạy dưới cái vung. Có âm thanh mà vẫn đen thì là mục này hoặc mục camera, không có thứ ba.
Lớp phủ không bao giờ nhấc lên: Image đen mà code dự định trả alpha về 0. Code nằm trên object vừa bị lần nạp scene huỷ đi, hoặc chờ sự kiện input mà chuột gửi còn ngón tay thì không — đọc Mouse.current trong Input System mới thì ngón tay không gửi gì, nên fade chạy trên laptop và không bao giờ trên điện thoại.
Thứ tự anh em là thứ tự vẽ: object con cuối cùng của Canvas vẽ đè lên các object trước, anchor nói gì cũng mặc. Image đen thêm sau các nút bấm, kiểu ảnh nền để tính sau, phủ luôn lên nút và mọi thứ phía sau.
Screen Space - Camera với plane distance nằm trong near clip plane, hoặc chĩa vào camera không phải cái bạn nghĩ: Canvas thành một mặt phẳng trước ống kính, thường là đen, và không thèm quan tâm thế giới phía sau.
Hai lần build là xong. Bỏ tick Canvas trong Hierarchy rồi build: thế giới hiện ra thì đúng là giao diện, và bạn biết cần mở object nào. Vẫn đen: chạy phép thử Clear Flags ở trên, câu trả lời nằm ở camera hoặc scene. Cả hai không đổi gì — giao diện vô tội, hai nguyên nhân còn lại là hai mục tiếp theo.
5 — Shader mà điện thoại không chạy: hồng, và đen
Hồng và đen cùng một nguồn — GPU nhận thứ nó không dùng được — nhưng nói hai điều khác nhau, và chính khác biệt đó cho biết cần sửa gì. Màu hồng ầm lên và thành thật: không subshader nào trong file hỗ trợ nền tảng này, nên Unity vẽ chất liệu lỗi. Đen hoặc vô hình ít nói hơn: shader đã được nhận, chỉ có phần bạn cần đúng lúc đó không ở trong bản build.
Hồng mọi nơi, kể cả trong editor: lệch pipeline. Project Settings > Graphics gọi tên pipeline mà dự án dùng — có URP Asset ở đó thì material chuẩn Built-in hoá hồng; không có gì ở đó thì dự án đầy URP/Lit cũng vậy. Sửa ở material hoặc pipeline, đừng sửa ở đèn.
Hồng trên điện thoại mà sạch trong editor: do graphics API. Các driver không thống nhất về thứ mình hỗ trợ, và subshader đòi shader model 4.5, compute shader hay tessellation hoàn toàn có thể bị một chiếc máy vui vẻ với OpenGLES3 nhưng khó tính với Vulkan từ chối — hoặc ngược lại.
Editor thì bình thường mà trên máy vô hình hoặc đen: đúng biến thể lúc chạy cần đã bị cắt khỏi build. Unity chỉ giữ biến thể nó thấy được dùng, còn keyword code bật ở giây thứ năm thì chưa ai thấy. Đưa shader vào Project Settings > Graphics > Always Included Shaders, hoặc warm ShaderVariantCollection trong lúc màn hình chờ đang hiện.
- 1
Mở , chuyển sang tab Android, vào Other Settings và bỏ tick Auto Graphics API.
- 2
Xoá Vulkan, giữ OpenGLES3, build rồi chạy. Hết đen: con chipset đó không chịu nổi thứ shader của bạn đòi. Đặt Vulkan về cuối danh sách và thử thêm một lần trước khi chốt.
- 3
Vẫn đen trên OpenGLES3: không phải do graphics API. Năm phút loại được hai khả năng — hãy đọc log, mục 7.
6 — Máy yếu, và những khung hình đầu không tới
Điện thoại cấp phát render target ngay khi scene đầu tiên nạp, cùng lúc kéo texture, shadow map và hậu kỳ — trên máy tầm trung là vài trăm megabyte đòi trong một khung hình. Máy bàn không trả nổi thì đưa bạn một exception; nhiều driver di động đưa bạn một surface không ai bao giờ vẽ lên, và không một dòng thông báo.
HDR và MSAA trong URP asset: cả hai đều đắt trên GPU mobile kiểu tile, và cả hai đều có tiền sử gây đen ở khung hình đầu trên một số driver Adreno, Mali. Bỏ tick cả hai trong URP asset cho mobile rồi build. Màn 6 inch gần như không mất gì, còn việc tìm lỗi có thể kết thúc luôn.
Bóng thời gian thực và mức chất lượng: Max Distance 40, Cascade Count 1, độ phân giải 1024, và nhớ rằng bản build dùng mức được tick cho Android trong Project Settings > Quality, không phải mức trên thanh công cụ editor. Mức khác trên điện thoại nghĩa là bạn đã thử một game khác.
Bộ nhớ texture: hoàn hảo trên flagship mà đen trên máy tầm trung bốn năm tuổi thì thường vượt ngân sách. Max Size 1024, ASTC 6x6 cho Android, tắt Read/Write Enabled — vẫn là những con số phần còn lại của khoá học dùng.
Xin 30 và đừng đuổi 60: đặt Application.targetFrameRate = 30 trong Awake của object khởi động game, kèm Screen.sleepTimeout. Máy chạm giới hạn mà giữ 30 đều thì vẫn chơi được; máy khựng giữa 60 và 15 lúc khởi động có thể trông như đã chết đủ lâu để bị báo là màn đen.
7 — logcat là cách duy nhất để thôi đoán
Tất cả ở trên đều là giả thuyết. Điện thoại đã biết cái nào là thật và nói to điều đó ngay khoảnh khắc bạn cắm cáp: một lệnh, và hai mươi dòng đầu nêu tên scene đã nạp, graphics API máy chọn, cùng ngoại lệ đã ăn mất khung hình đầu tiên.
# xoá log cũ, sau đó chỉ hiện dòng của Unity
adb logcat -c && adb logcat -s Unity
# khi app tự đóng: thêm cả báo cáo văng của Android
adb logcat -c && adb logcat Unity:V CRASH:V AndroidRuntime:E '*:S'
# xuất ra file để bình tĩnh tìm
adb logcat -s Unity > phone.txtDòng nêu Vulkan hoặc OpenGLES3 ngay sau tên GPU — API mà điện thoại thật sự chọn. Không trùng với cái bạn thử thì chỗ cần quay lại là danh sách graphics API, chứ không phải lightmap.
Dòng log khởi động của chính bạn từ mục 2. Không có dòng nào thì không có scene: bản build đã không mở cái bạn nghĩ.
NullReferenceException trong Awake hoặc Start, hoặc Scene ... has not been added to the build settings. Cái đầu nghĩa là manager với tới thứ chỉ tồn tại trong scene bạn quên thêm. Cái sau là mục 2, và là dòng duy nhất trong bài này đã là câu trả lời trọn vẹn chứ không còn là manh mối.
Out of memory, GL_OUT_OF_MEMORY, Failed to create — mục 6. Hoặc bức tường im lặng sau dòng version của Unity: app chết trước khi việc vẽ kịp bắt đầu, tức vấn đề cài đặt, ABI hoặc ký số, không phải màn đen.
Sáu mươi giây để biết đó là màn đen nào
- ✓Âm thanh vẫn chạy? Vậy logic còn sống, lỗi ở phần vẽ: camera, rồi giao diện, rồi shader.
- ✓Vài giây rồi nó tự hiện lại? Không hỏng gì — bạn thiếu màn hình chờ. Lớp phủ ở mục 2 là chỗ sửa.
- ✓App tự đóng? Đó là văng, không phải màn đen — chạy logcat trước khi đổi bất kỳ thiết lập nào.
- ✓Scene có trong Build Scenes, và ở index 0? Tên phân biệt hoa thường, viết đúng như file .unity.
- ✓Clear Flags Solid Color, nền hồng magenta, build một lần: màn hồng = camera chạy và không thấy gì; đen = không có camera nào bật.
- ✓Bỏ tick Canvas mà thế giới bỗng hiện: giao diện là cái vung. Còn xoá Vulkan mà hết đen là lỗi driver, không phải scene.
Khi đã có hình, điện thoại sẽ phàn nàn về thứ khác: tốc độ khung hình sau năm phút nóng máy, và giao diện dựng theo đúng màn hình của bạn. Hai bài nối tiếp là bài về mọi loại màn hình và bài build kèm gỡ lỗi.
bài học làm ra thứ này
Build, cài và gỡ lỗi trên máy thật
Bài này là một công thức đứng riêng. Trong khoá học, cùng thứ đó được dựng như một phần của dự án xuyên suốt năm tab — Xuất lên điện thoại, bài 05.