前面我們花了點時間介紹了 HTML 的歷史故事、並搭配一些語法進行說明。但各位看到這邊,多半會好奇:「那要如何架構一個基本的 HTML 網頁呢?」這部份我們會藉由逐步實做一個遊戲專案,搭配整體鐵人賽的進度。
後來決定實作網頁版的「2D 打磚小蜜蜂遊戲」。那位何選定這遊戲呢?原因是因為這遊戲的實做,比起其他遊戲元素來說還相對容易一些。這兩個遊戲分別是 打磚塊(Breakout Game)與 小蜜蜂(Galaxian)兩個遊戲的結合,其中 Breakout Game 是玩家控制底部的球拍擊球,球彈跳後撞擊上面的磚塊;而 Galaxian 則是玩家控制一隻飛船或是蜜蜂等去射擊上方降落的敵人(可能是其他蜜蜂或飛碟之類的)。這兩個遊戲的相同之處,就在於都屬於「由上往下」的遊戲,況且我想大家都玩過這兩個遊戲(如果有經歷 GBA 又名為 Gameboy 年代的各位),那不彷就結合這兩個遊戲,並加入一些現代遊戲的元素吧~
Source: https://www.coolmathgames.com/blog/how-to-play-atari-breakout
Source: https://www.thepinballgameroom.com/product/galaxian-arcade-machine/
那一個遊戲會包含哪些內容呢?我想從我第二個遊戲開始說起吧(第一個是彈水阿給,但後來被遊戲橘子收購變成爆爆王,直接導致我練到坦克等級的帳號直接消失...)~ 那是 2006 年時的楓之谷 Maplestory,當時這遊戲是一個可愛畫風的 2D 角色扮演遊戲,遊戲內有任務、打怪升等、裝備、遊戲付費裝備或道具,玩家最開心的就是升級點技能數值跟能力數值的那一刻,因為可以看到自己的攻擊數字不斷增高。除此之外,更還有公會、組隊等,這些社交功能讓枯燥的打怪練功不再那麼無聊,每次假日都可以在自由市場內看到一對人聚集聊天。當時最特別的還有「結婚」等社交功能,我們以前也常常看到很多人分享從在楓之谷結婚到現實世界結婚的故事;同時而當年的遊戲橘子經常在中秋節、聖誕節、農曆新年等節日推出各種限時活動。總之,當年 Maplestory 能成為代表作,我想是因為他不僅是一個打怪練等的遊戲,更是一個社交與互動的虛擬世界。
Source:我本人
我還記得當年我那到的第一隻 Android 智慧型手機是 Sony Ericsson X10i,那時最流行的遊戲就是 2009 年推出的 Doodle Jump 與 2011 年的 Temple Run。那時這兩個遊戲最大的特徵,就是「簡單、容易上癮」,因為玩這些遊戲最大的目標,就是盡可能活更久、獲得更高分,但幾乎不會讓我們在遊玩過程中造成太大壓力。而當時的遊戲都是免費的、遊戲公司就會在網頁或遊戲裡面,嵌入一些廣告或是一些「商店」的機制來賺取收入。
Source: https://chromewebstore.google.com/detail/doodle-jump-game/gpildgokohnggieonmcldaaioafabnji
而在 2010~13 年左右,Facebook 上開始流行 「開心農場」,也就是大家常常在聊的偷菜遊戲,而 Facebook 本身就是一個社群網站,所以透過各位分享自己在 Facebook 上分享遊戲經驗、送禮物、傳好友邀請等,這時開心農場散播的速度應該比病毒還要快,當年不少新聞也在報。這類的免費遊戲在社群平台上面傳播,本身入坑就沒什麼門檻;而接下來就是鼓勵廣大玩家做小額消費,並加入一些「限時優惠」等機制,很多人看到「限時」就馬上掏出信用卡刷下去換 Farm Cash 了。
Source: https://www.4gamers.com.tw/news/detail/32472/facebook-game-happy-farm-close-at-september-25th
再接下來,可能大家最常聽到的就是 2013 年由香港 Madhead 推出的轉珠遊戲 神魔之塔(Tower of Savior),但注意我們不在這討論神魔之塔與龍族拼圖的相似度。早期的神魔之塔並沒有什麼劇情,就是以打敗敵人與提昇卡牌能力值為主,當年大家也都很熟悉付費買鑽石抽「五星超稀有級」的白金卡。而後在 2014 年開始加入劇情融入遊戲主線(副本)任務,開始融入有關於 涉及神族、魔族、人類之間史詩戰爭的宏大世界觀,加入劇情元素;很多 YouTuber 也在每次官方推出新 BOSS 關卡之後,開直播抽卡組戰隊、努力在第一時間破關後幫大家講解破關技巧的日子。畢竟我們也都知道嗑金玩家這種 Pay-to-win 的打法,讓這些遊戲公司海賺不少錢呢~
Source: https://forum.gamer.com.tw/Co.php?bsn=23805&sn=1804450
神魔之塔應該算是我認真玩過的最後一個手遊了。我們單從這些內容來看,遊戲元素從簡單的角色養成遊戲,逐漸轉變為融入社交功能以及大量主線劇情主導融入破關策略的遊戲。但我們回到一個最核心的問題,就是一個基本的網頁遊戲,可以融入哪些基礎架構?
當我們要從零開始製作一款 2D 網頁遊戲時,首先需要思考的是整個遊戲框架。這不僅僅是包含遊戲本身的玩法,還涉及到玩家的使用體驗,包括如何讓玩家方便地登入、註冊、選擇遊戲難度、查看成就、管理個人資料等等。以下是我們將要實作的幾個主要架構元素囉
我們首先需要先思考:「在一個網頁遊戲系統中,有哪些重點頁面?」。我先用以下這些頁面開始,分述這些頁面對應到的功能。我想這些頁面要做哪些事情,應該也蠻直觀的,因此簡單列出:
我們就從這些內容開始,逐步開始吧!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="This is the 2D web game contains Galaxian Breakout game elements">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- link rel="stylesheet" href="styles.css" -->
<!-- script src="game.js"></script -->
<title>Galaxian Breakout Game</title>
</head>
...
從前面介紹 HTML 的內文可以知道,HTML 是網頁的最基本架構,我們首先從建立基本的網頁結構開始。我們使用 <!DOCTYPE html>
讓瀏覽器知道他準備要執行 HTML5,而 <html>
標籤則是定義了網頁的範圍。
<html>
標記中包含了兩個部份,分別是 <head>
與 <body>
。你可能會問:「為何要區分出這兩部份?」其實主要目的是定義功能範圍:<head>
頁面的 Metadata,<body>
負責顯示前端網頁的可見內容,這樣 SEO 工程師跟純網頁工程師就會知道他們要集中負責哪些區塊。
<meta>
標記呈現頁面的關鍵資訊,而你會看到他對應不同的標記,我們先就以下案例說明:
<meta name="viewport">
:這個標籤用於定義頁面在移動設備上的顯示比例,確保響應式設計的效果。<meta** charset="UTF-8">
:讓頁面直接使用可以解析全球語言的萬國碼(UTF-8)。<meta name="description">
:這是你在搜尋引擎中會看到的「敘述內容」。<head>
標記呈現網頁的標題、引用的 CSS 樣式表、JavaScript 網頁功能、字形、以及有關 SEO 的內容。我們利用 <title>
來定義網頁的標題,你會在瀏覽器的分頁(Tab)上面看到。<body>
區塊要放「使用者可以在網頁中看見」的內容,包含文字、圖片、表格、表單、按鈕等。我們待會會繼續延伸。...
<body>
<header>
<h1>Galaxian Breakout Game</h1>
</header>
<main>
<!-- Main content goes here -->
</main>
<footer>
<p>© 2024 Galaxian Breakout Game. All rights reserved.</p>
</footer>
</body>
</html>
而在 <body>
裡面,則是包含了 <header>
、<main>
、以及 <footer>
。這些內容分別對應 標題區、主要內容區、以及頁尾區。
<header>
:通常用來在頁面頂端放網站的標題、LOGO 或主要選單(Navi-bar),目的是讓使用者一進入頁面,就能看到關於這網站的重要與關鍵資訊。<main>
:代表頁面的最核心內容,基本上在一個頁面中只會使用一個 <main>
標記。<footer>
:是頁面的底部區域,用來顯示版權資訊、聯絡方式、隱私權政策等內容,當你看到這些一般就代表頁面滑到底了。那我個人習慣上,就會放一個空的前端專案範本,也就是裡面有一個做好的空 HTML 檔案,這樣待會要做出其他頁面時,只需要直接複製來改就好了~ 大概就這麼空
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Game Page</title>
</head>
<body>
<header>
</header>
<main>
</main>
<footer>
</footer>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="This is the 2D web game contains Galaxian Breakout game elements">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Page</title>
</head>
<body>
<main>
<section id="login-section">
<h2>Login</h2>
... (a)
... (b)
那我們知道,現在就連單機遊戲都要登入,因此我們需要一個登入頁面。現在的遊戲都會儲存與同步玩家的遊戲進度、遊戲設定等,避免意外導致資料遺失。
你在 <main>
之下就先看到 <section>
標記了,這主要功能就是把頁面內有相關或主題的內容進行分區。我們在這裡寫 <section id="login-section">
來將登入相關的表單內容集中在一個區塊,瀏覽器和網頁工程師就會知道這個區域是進行關於使用者登入的功能。一般我們會在 <section>
後面放一個標題,例如 <h2>
來把方便玩家操作的資訊顯示在前端網頁中。
你可能會好奇為何使用 <section>
而不是其他標籤?原因是,相比於 <div>
或是 <article>
或是 <aside>
,使用 <section>
更適合用來表達一個主題功能的區塊;而 <div>
是一個通用的容器,沒有任何語義上的含義,僅用於分隔內容等佈局功能;另一方面,<article>
通常用來表示一個獨立的文章(例如你現在看到的這篇)或資料區塊;而 <aside>
則是表示主要內容,但不一定必須要附加的資訊,像是 Side bar 或是廣告。因此綜上所述,<section>
比較適合我們用在登入為主要功能的區塊,並與其他部分區分開來,做到「網頁結構化」。
那我們可以先在 <section>
之下,使用 <form>
標記來實做一個簡單的登入表單。
... (a)
<form action="index.html" method="post">
<!-- form action="/login" method="post"-->
<div>
<label for="username">Username:</label>
<input type="email" id="username" name="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
你可能會注意到上下都夾了 <div>
標記,其中包含 <label>
和 <input>
。其中
<label>
標記是該輸入框前方的文字。<input>
標記是定義輸入框。action
和 method
,這主要是用來規範表單提交時的行為。
action
屬性用來指定使用者提交表單時,資料要傳送到的目的地。我們需要注意的是,action
會指向一個伺服器端的處理程式(例如送往 PHP 或 Node.js 進行處理),而不是直接傳送到 HTML 檔案裡面。我們在這個簡單範例中使用 index.html
只是為了展示,但在真實實作中,我們會將此改為伺服器端(後端)對應的路徑來處理表單的輸入資料,可以考註解的部份。method
屬性是指定表單在提交資料時所會使用的 HTTP 方法。而在這裡使用 post
代表使用 HTTP POST 方法來傳輸資料。一般來說,前端網頁工程師會對 <div>
進行 CSS 排版控制,讓 <label>
與 <input>
可以在頁面上對齊。如果我們要在 <div>
裡面再做分區樣式控制,那就會用到 <span>
了,這我們後續可能也會用到。
而我們要讓玩家輸入帳號與密碼,因此表單會包含兩個 input
元素;而輸入完帳號密碼就要執行登入,因此放上 submit
按鈕。
type
屬性主要是定義 <input>
的類型
<input type="email">
:代表一個用來放 Email 帳號的「單行」的文字框。任何沒有 '@’ 的字串都會跳出警告。<input type="password">
:用來輸入密碼,而你輸入的密碼就會被隱藏起來。id
屬性主要是對 <input>
元素指定一個唯一的識別碼(Unique ID),代表在一個網頁頁面中,這樣的元素名稱只能出現一次。這目的是你在 CSS 或 JavaScript 要針對個別元素進行控制時,瀏覽器才會找得到你指定的元素。name
屬性用來標記這個輸入文字框的表單欄位名稱。當使用者提交表單時,表單中的資料會以鍵值對(Keypair)的形式傳送,其中 name
是鍵,輸入的內容是值。舉例來說,當表單提交時,伺服器會收到 username="<你輸入的文字>"
。required
屬性是 HTML5 的一個功能,用來指定該輸入框為必填欄位。這代表你必須在這個文字框中輸入內容,否則表單無法送出。具體呈現方式就看 type
屬性。... (b)
<p>Don't have an account? <a href="register.html">Register here</a>.</p>
<div>
<p>Or login with:</p>
<button onclick="googleLogin()">Login with Google</button>
<button onclick="facebookLogin()">Login with Facebook</button>
</div>
</section>
</main>
</body>
</html>
這是接續上面 …(a)
後的內容。你會發現這邊有 <p>
的元素,這主要是用來顯示段落的標籤。我們在這裡使用 <p>
是分別讓使用者知道「沒有帳號嗎?點擊這裡註冊」以及「有其他登入選項」。而這些文字屬於引導性文字,讓使用者可以知道這些按鍵的功能。
<a href="register.html">Register here</a>
的內容,這功能是把這個文字設定成超連結,使用者看到的是 “Register here” 的文字,點擊後會導向到 “register.html”
的網頁路徑中。你會發現上方使用<button>
建立按鈕,就是你看到就會點擊的那個按鈕。在這裡是讓使用者可以透過第三方帳號登入 API 執行快速登入,這樣就可以簡化登入流程,因為使用者就不用手動輸入帳號跟密碼囉。
onclick=”googleLogin()"
的 JavaScript 函數,這代表你點擊這按鈕時,就會觸發 googleLogin()
的 JS 函數。而後續這功能,也就是「透過 OAuth 認證機制取得使用者資訊並進行登入」的功能,就要在 JS 檔案裡面實作了,下方按鈕亦同。實務上,註冊頁面的設計與登入頁面非常相似,但可能會多一些要填入的註冊資訊,例如密碼確認等等的。我們在這裡先簡單實做,因此就不多做說明了:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="This is the 2D web game contains Galaxian Breakout game elements">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register Page</title>
</head>
<body>
<main>
<section id="register-section">
<h2>Register</h2>
<form action="index.html" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<label for="confirm-password">Confirm Password:</label>
<input type="password" id="confirm-password" name="confirm-password" required>
</div>
<div>
<button type="submit">Register</button>
</div>
</form>
<p>Already have an account? <a href="login.html">Login here</a>.</p>
<div>
<p>Or register with:</p>
<button onclick="googleRegister()">Register with Google</button>
<button onclick="facebookRegister()">Register with Facebook</button>
</div>
</section>
</main>
</body>
</html>
接下來就到遊戲的主畫面了。我們設計三個區塊,僅就上方未提及部份,以及功能區塊進行說明:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout Game</title>
</head>
<body>
<header>
<h1>Breakout Game</h1>
... (a)
</header>
<main>
... (b)
... (c)
</main>
<footer>
<p>© 2024 Breakout Game. All rights reserved.</p>
</footer>
</body>
</html>
你會發現我們在這邊實作了 <nav>
標記,這是專門使用一群超連結組成的重新導向區塊,你可以從這裡跳轉到其他頁面(無論是網頁內部或外部)。在這裡我們會實做四個功能,分別對應「主畫面」、「成就」、「分數與統計」、以及「玩家設定」等。其中
<li>
代表列表項目,在這裡每一個 <li>
都對應到一個超連結,由每個獨立項目組合成一個選單。這樣我們要增減這些超連結,就只需要在這些固定區域即可。... (a)
<nav>
<ul>
<li><a href="index.html">Home</a></li>
<li><a href="accomplishments.html">Accomplishments</a></li>
<li><a href="statistics.html">Score & Statistics</a></li>
<li><a href="settings.html">Settings</a></li>
</ul>
</nav>
而一般在這種單機遊戲中,都可以設定難度,因此我在這裡定義了三種難度。你會發現這下面導向的網址,包含 .html?difficult={難度}
的語法,這主要是實作 ”URL with Query Parameter” 的功能簡單來說,這是一個查詢參數,用來傳送給後端或前端有關使用者選擇的 {難度}
,這樣系統就可以根據網址中的參數來判斷玩家選擇的難度等級,進而載入相對應的遊戲設定或內容。
... (b)
<section id="game-difficults">
<h2>Select Difficulty</h1>
<nav>
<ul>
<li><a href="game.html?difficulty=easy">Easy</a></li>
<li><a href="game.html?difficulty=hard">Hard</a></li>
<li><a href="game.html?difficulty=asian">Asian</a></li>
</ul>
</nav>
</section>
最後這就是登出功能了,我們在上面有提到這邊的語法。但要注意,因為系統在玩家點擊登出之後,就會執行一些相對應的操作,完成使用者登出手續後再回到主畫面(就是你點擊登出後,網頁在等待跳轉的這段時間)。那因為我們還沒有用 JavaScript 實作相關功能,因此在這裡簡單使用單純的 html 網頁導向。
... (c)
<section id="logout">
<div>
<p>Logout</p>
<!-- <button onclick="logout()">Logout</button> -->
<li><a href="login.html">Logout</a></li>
</div>
<section>
接下來,我們要開始設計真正的遊戲頁面 (Game Page)。這是遊戲的核心頁面,玩家將在這裡進行遊戲,包含了遊戲畫面、玩家狀態以及一些基本的使用者介面元素。這個頁面的重點是讓遊戲能夠被呈現,並提供玩家即時的遊戲資料,像是分數、生命值等。這一步會讓我們更深入了解網頁遊戲的架構,同時也學習如何組織遊戲的視覺內容。
首先,讓我們來看看基本的 HTML 結構。這和我們之前設計的頁面類似,但這次我們要著重在如何安排遊戲的主要內容和玩家資料的顯示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Game Page</title>
</head>
<body>
<main>
... (a)
... (b)
</main>
</body>
</html>
遊戲區域 (Game Area)
... (a)
<section id="game-area">
<header>
<h2>Game Area</h2>
<p>Difficulty: <span id="difficulty-level"></span></p>
</header>
<div id="game-canvas">
<p>[Game canvas will go here]</p>
</div>
</section>
這部份就是整個遊戲頁面中,最為核心的部份,因為遊戲畫面就是放在這裡的。
我們在 <header>
這邊顯示「遊戲區域」的標題,並顯示對應到玩家在遊戲中選擇的難度。具體是就是我們會用 JavaScript 來設定難度等級,並透過 <span id="difficulty-level">
顯示出來給玩家。
而 <div id="game-canvas">
代表遊戲的區域,就是我們之後會放遊戲畫面的區塊。一般來說我們會用 HTML5 的 <canvas>
標記來渲染並呈現遊戲畫面。而我們就先在這裡用 <div>
標記「佔位」,之後實作遊戲時,我們會把這裡換成實際的遊戲畫面。
這個區塊未來會包含遊戲的所有視覺元素,例如球、磚塊、敵人等。接下來我們會用 JavaScript 來操作和繪製這些元素。
玩家狀態 (Player Status)
... (b)
<section id="player-status">
<h3>Player Status</h3>
<p>Lives: <span id="lives-count">3</span></p>
<p>Score: <span id="current-score">0</span></p>
</section>
在任何遊戲中,能讓玩家一眼就知道自己的當前狀態,是相當很重要的一件事情,你總不希望要靠大腦記得這些「數字」,因此可以簡單設計一個顯示玩家狀態的區域,一般以 生命值 和 分數 為主。
<p>Lives: <span id="lives-count">3</span></p>
:用來顯示玩家的剩餘生命值。在這裡我們先將玩家的生命值初始狀態設定為 3,之後在遊戲調適(Tweaking)的時候,可以再修改、或是用 JavaScript 根據玩家的遊戲狀態做動態更新。<p>Score: <span id="current-score">0</span></p>
:用來顯示目前的遊戲分數。基本上初始分數設定都是設定為 0,而後透過 JavaScript 函數更新分數。到目前為止,我們已經完成了遊戲頁面(Game Page)的基本架構設計。在接下來的進度中,除了會撰寫「成就」、「玩家設定」、「分數與統計」、以及「玩家資料」等內容之外,還會使用 JavaScript 來實現控制遊戲畫面。雖然這過程中還會有很多細節需要完善,但目前的 HTML 結構先定義號,之後在逐步完善的過程中,才會比較輕鬆。