登入畫面與選角畫面修正
本篇定位
這是 Phase 5 的第 4 篇,解析
Client::FixLoginScreen()和Client::FixSelectCharScreen()。 登入畫面和選角畫面的 UI 修正比一般 HUD 更複雜,因為它們有動畫、背景圖、文字輸入框等需要特殊處理的元素。
一、登入畫面有哪些元素需要修正?
MapleStory v83 的登入畫面元素(在 800×600 座標系):
┌──────────────────── 800 ────────────────────────┐
│ │
│ [遊戲標誌 / Logo] │
│ ↑ │
│ Y = 150 │
│ │
│ 帳號輸入框 (X=280, Y=330, W=240, H=25) │
│ 密碼輸入框 (X=280, Y=365, W=240, H=25) │
│ │
│ [登入按鈕] (X=280, Y=400) │
│ [新手按鈕] (X=400, Y=400) │
│ │
│ 版本號文字 (X=700, Y=580) │
└──────────────────────────────────────── 600 ──┘
這些元素在改成 1280×720 後,都會擠在左上角。
二、FixLoginScreen() 實作
void Client::FixLoginScreen() {
int newW = Client::m_nGameWidth;
int newH = Client::m_nGameHeight;
// ── 背景圖層 ──────────────────────────────────────────────────────────────
// 登入背景要拉伸填滿整個新視窗
Memory::Write<int>(addys::LoginBG_Width, newW);
Memory::Write<int>(addys::LoginBG_Height, newH);
// ── 遊戲 Logo ────────────────────────────────────────────────────────────
// Logo 水平置中,垂直位置等比例縮放
Memory::Write<int>(addys::LoginLogo_X, (newW / 2) - 200); // Logo 寬約 400px,置中
Memory::Write<int>(addys::LoginLogo_Y, ScaleY(150));
// ── 帳號 / 密碼輸入框 ─────────────────────────────────────────────────────
// 輸入框整體置中
int inputBoxLeft = (newW / 2) - 120; // 240px 寬的輸入框,置中
Memory::Write<int>(addys::IDInput_X, inputBoxLeft);
Memory::Write<int>(addys::IDInput_Y, ScaleY(330));
Memory::Write<int>(addys::IDInput_W, 240); // 輸入框寬度固定,不等比
Memory::Write<int>(addys::IDInput_H, 25);
Memory::Write<int>(addys::PWInput_X, inputBoxLeft);
Memory::Write<int>(addys::PWInput_Y, ScaleY(365));
Memory::Write<int>(addys::PWInput_W, 240);
Memory::Write<int>(addys::PWInput_H, 25);
// ── 按鈕列 ───────────────────────────────────────────────────────────────
int btnBaseY = ScaleY(400);
Memory::Write<int>(addys::LoginBtn_X, (newW / 2) - 120);
Memory::Write<int>(addys::LoginBtn_Y, btnBaseY);
Memory::Write<int>(addys::NewUserBtn_X, (newW / 2));
Memory::Write<int>(addys::NewUserBtn_Y, btnBaseY);
// ── 版本號文字(右下角錨點)─────────────────────────────────────────────
Memory::Write<int>(addys::VersionText_X, newW - ScaleX(800 - 700)); // 右邊距固定
Memory::Write<int>(addys::VersionText_Y, newH - ScaleY(600 - 580)); // 下邊距固定
}為什麼輸入框寬度固定不縮放?
輸入框(TextBox)的寬度代表「可輸入文字的視覺長度」,和解析度大小是獨立的。 帳號最多 13 個字元,對應 240px 就夠了。等比例放大反而讓它看起來過寬、不美觀。
三、登入畫面的特殊問題:動態背景
MapleStory v83 的登入畫面有一個會移動的雲朵背景動畫,實作是循環捲動一張 800px 寬的背景圖:
// 遊戲內的背景捲動邏輯(偽碼)
bgScrollX += scrollSpeed;
if (bgScrollX >= 800) bgScrollX = 0;
DrawBackground(bgScrollX, 0, 800, 600);解析度改成 1280 後,這個捲動最大值還是 800,所以背景只能覆蓋左邊 800px,右邊 480px 是黑色。
修正方式:
// 把捲動邊界從 800 改成新寬度
Memory::WriteProtected<int>(addys::BgScrollBound, newW);
// 把背景渲染寬度從 800 改成新寬度
Memory::WriteProtected<int>(addys::BgDrawWidth, newW);副作用
背景圖本身只有 800px 寬,強制拉伸到 1280px 會讓紋理模糊。這是 v83 登入器開發的已知視覺問題,無法在不修改美術資源的情況下完全解決。
四、FixSelectCharScreen():選角畫面的特殊挑戰
選角畫面比登入畫面複雜,因為它同時顯示:
- 最多 3 個角色(站在不同位置)
- 每個角色的名稱、等級、職業圖示
- 左右箭頭按鈕(翻頁)
- 「開始遊戲」、「建立角色」、「刪除角色」按鈕
void Client::FixSelectCharScreen() {
int newW = Client::m_nGameWidth;
int newH = Client::m_nGameHeight;
// ── 三個角色的站立位置 ─────────────────────────────────────────────────
// 原始:三個角色分別在 X = 200, 400, 600(平均分布於 800px 內)
// 新位置:平均分布於新寬度
int charSpacing = newW / 4; // 四等分,角色站在 1/4, 2/4, 3/4 的位置
Memory::Write<int>(addys::CharSlot1_X, charSpacing * 1);
Memory::Write<int>(addys::CharSlot2_X, charSpacing * 2);
Memory::Write<int>(addys::CharSlot3_X, charSpacing * 3);
// 所有角色的 Y 座標等比例換算
int charBaseY = ScaleY(380);
Memory::Write<int>(addys::CharSlot1_Y, charBaseY);
Memory::Write<int>(addys::CharSlot2_Y, charBaseY);
Memory::Write<int>(addys::CharSlot3_Y, charBaseY);
// ── 角色名稱 / 等級標籤(跟隨角色位置) ─────────────────────────────────
Memory::Write<int>(addys::CharName1_X, charSpacing * 1);
Memory::Write<int>(addys::CharName2_X, charSpacing * 2);
Memory::Write<int>(addys::CharName3_X, charSpacing * 3);
int nameY = ScaleY(420);
Memory::Write<int>(addys::CharName1_Y, nameY);
Memory::Write<int>(addys::CharName2_Y, nameY);
Memory::Write<int>(addys::CharName3_Y, nameY);
// ── 底部按鈕列(水平置中) ────────────────────────────────────────────
int btnCenterX = newW / 2;
int btnY = newH - ScaleY(600 - 520); // 下邊距固定
Memory::Write<int>(addys::StartGameBtn_X, btnCenterX - 120);
Memory::Write<int>(addys::StartGameBtn_Y, btnY);
Memory::Write<int>(addys::CreateCharBtn_X, btnCenterX);
Memory::Write<int>(addys::CreateCharBtn_Y, btnY);
Memory::Write<int>(addys::DeleteCharBtn_X, btnCenterX + 120);
Memory::Write<int>(addys::DeleteCharBtn_Y, btnY);
// ── 翻頁箭頭(貼近視窗左右邊緣) ─────────────────────────────────────
Memory::Write<int>(addys::PrevPageArrow_X, ScaleX(30));
Memory::Write<int>(addys::NextPageArrow_X, newW - ScaleX(30) - 20); // 減去箭頭寬度
Memory::Write<int>(addys::PrevPageArrow_Y, charBaseY);
Memory::Write<int>(addys::NextPageArrow_Y, charBaseY);
}五、呼叫時機總覽
flowchart TD A[DllMain 載入] --> B[MainMain 讀設定] B --> C[安裝 Hook] C --> D[Client::UpdateGameStartup] D --> E[Client::UpdateResolution] E --> F[遊戲啟動] F --> G{Hook_CWvsApp_SetUp 觸發} G --> H[Client::FixLoginScreen] H --> I{玩家登入成功} I --> J{Hook_CWvsApp_SelectChar 觸發} J --> K[Client::FixSelectCharScreen] K --> L{玩家選擇角色進遊戲} L --> M{Hook_CWvsApp_EnterField 觸發} M --> N[Client::FixUIElements] N --> O[正常遊玩]
六、Phase 5 回顧:Client 模組的四個任務
| 函式 | 時機 | 任務 |
|---|---|---|
UpdateGameStartup() | DLL 載入後立即 | 視窗模式、停用解析度警告 |
UpdateResolution() | DLL 載入後立即 | 全域變數、機器碼 Patch、Viewport |
FixLoginScreen() | 觸發 SetUp Hook 後 | 登入畫面 UI 重新置中定位 |
FixSelectCharScreen() | 觸發選角 Hook 後 | 角色位置重分配、按鈕置中 |
FixUIElements() | 觸發 EnterField Hook 後 | HUD 各元素的等比例換算 |
七、常見問題
Q:為什麼登入按鈕要「水平置中」而不是等比例換算?
等比例換算
ScaleX(280) = 448,但新畫面寬 1280,中心是 640。把輸入框和按鈕放在 448 會明顯偏左,視覺上很奇怪。「置中」才符合使用者對「登入表單在畫面中央」的直覺。不同元素有不同的設計意圖,不能一律等比例換算。
Q:角色動畫(行走、揮劍)的位置也需要修正嗎?
不需要。角色動畫是遊戲引擎用角色物件的
position座標渲染的,而 position 是以地圖座標系(而非螢幕座標系)為基礎。解析度改變只影響「攝影機把哪個範圍的地圖投影到螢幕上」,不影響角色在地圖裡的位置。
Q:如果私服改了登入畫面的美術(不同背景圖),修正還有效嗎?
大部分情況有效,因為我們修改的是 UI 元素的位置座標,和背景圖的內容無關。但如果私服的登入畫面使用了完全不同的 UI 結構(不同的元素數量或佈局),就需要重新逆向找到新的地址。
延伸閱讀
- 03_UI元素位置修正 — 遊戲內 HUD 的通用修正模式
- 01_Client.cpp整體架構導覽 — Client 模組的全貌
- 02_解析度修改原理與實作 — 這些 UI 修正的先決條件
- 01_config.ini設定系統 — Phase 7:解析度設定如何從設定檔讀入