跳轉到內容

建立自己的模擬遊戲/簡易地圖編輯器

來自華夏公益教科書,開放的書籍,開放的世界

在上一課中,我們取得了實質性的進展。現在,我們不再僅僅是在遊戲結束前有一個簡單的按鈕點選,而是擁有了一個角色,可以在更大的區域內移動,被牆壁阻擋,掉入深淵而死亡,然後找到寶藏後獲勝。

這一切都很好,但我們的遊戲需要更多內容。是時候新增更多地圖、地圖等級以及我們自己的簡易地圖編輯器,這樣我們就可以快速建立地圖、生成地圖檔案,並以全新的複雜程度玩我們的遊戲了。

包含檔案

[編輯 | 編輯原始碼]

程式設計最棒的事情之一就是能夠將更大更復雜的問題分解成更小更易於管理的片段。包含檔案就是實現這種功能的一種方式。讓我們看看我們在上一課中編寫的用來在螢幕上顯示地圖的自定義函式。您是否注意到我們的 character.php 檔案變得有點擁擠,難以閱讀?如果我們能夠從該頁面上的程式碼中刪除 displayMap() 函式,但仍然可以在該頁面上使用它,那不是很好嗎?

包含檔案的作用正是如此。使用包含檔案非常簡單。首先,您建立一個新檔案。我喜歡將包含許多函式的檔案命名為一些通用的東西:functions.php。這樣,當您以後返回尋找東西時,如果您發現一個不在當前檔案中的函式被使用,您就知道在哪裡檢查才能找到它。

接下來,您在 functions.php 中放置一些程式碼。在本例中,就是 displayMap 函式。所以我們只需要將該函式從 character.php 檔案剪下/貼上到 functions.php 檔案中。

最後但同樣重要的是,我們返回 character.php,並在頁面頂部寫下

<?php

session_start();
include('functions.php');

//rest of the code here like normal....

?>

這做了什麼?它實際上將 functions.php 檔案的內容轉儲到 character.php 頁面上。這樣,您在 functions.php 中包含的任何程式碼片段現在都可以在該頁面上訪問,就像我們直接在 character.php 頁面中編寫 displayMap() 函式一樣。多麼整潔!

我相信您已經看到了這樣做的益處。它使一切都更容易閱讀。

問題:何時適合建立函式?

函式是非常棒的東西。我的經驗法則就是,如果您有一段程式碼,甚至是一些您計劃反覆使用的頁面格式,將其變成一個函式。這樣,您就不需要一遍又一遍地重複輸入相同的程式碼,只需要呼叫您的函式並傳遞唯一的引數。

以我們的角色移動為例。每次有人點選一個按鈕時,我們都需要檢查他們按下了哪個按鈕、他們在移動的哪個方向以及如何對該方向上的內容做出反應。您是否注意到每個方向的程式碼中都有相似之處?如果您遇到了這樣的情況,那麼是時候停下來考慮函數了。

試試看!您能將角色移動轉換成一個函式嗎?以下是一個提示:每次玩家移動時,您真正需要知道的只是移動的方向。然後,根據該方向,您可以檢查地圖的 x 和 y 值以檢視那裡有什麼。

自動檔案下載

[編輯 | 編輯原始碼]

在本課中,我們將建立一個地圖編輯器,我們可以用它來相當快地建立地圖檔案。在我們的地圖編輯器中,我們希望在完成地圖編輯後,將建立的地圖檔案自動下載或匯出。要做到這一點,您需要了解一些關於檔案和 http 標頭的資訊。儘管這些東西聽起來很難,但實際上只需要幾行程式碼。

讓我們談談 HTTP 標頭。HTTP 代表超文字傳輸協議,是我們用來線上檢視檔案的一種方式。您可能熟悉 FTP、檔案傳輸協議或 TCP/IP、傳輸控制協議/網際網路協議等其他協議。但我們只關注其中一種。

每次您從網際網路上的某個地方請求一個檔案時,都會發生很多事情。如果您想了解所有內容的工作原理,我強烈建議您訪問維基百科的網路部分。但是,您需要了解的一件更重要的事情是,對於您線上檢視的任何檔案,它都以標頭資訊開頭,這些資訊描述了檔案的型別、檔案長度和檔案內容。

PHP 作為一種伺服器端程式語言,可以用來在其他人線上檢視檔案之前設定您建立的任何檔案的標頭。透過這種方式,我們可以使用 PHP 告訴瀏覽器,我們在 HTML 檔案中寫入的任何內容都應該像音樂檔案、應用程式、PDF 或文字檔案一樣對待。我們想要什麼都可以!

建立一個檔案並將其命名為 testdownload.php。現在將以下內容放入其中

<?php

//let's create a name for the file you'll download

$filename = "My Funky File.txt";

//Tell PHP that we want to change the header information for this .php file and instead it'll become a .txt file

header("Content-Type: text");

//Now we add an additional statement that's meant to make your browser automatically download this file's content

header('Content-Disposition: attachment; filename="' . $filename .'"');

//now anything we echo on this page will become a part of the file we're downloading! think of all the possibilities!

echo "This is my totally awesome, funky cool file that I made download automatically using .php! You can't even tell this was actually inside a .php script because the headers are being set to text :)";

?>

就是這樣!您看,只需幾行程式碼,您就可以建立一個檔案,該檔案會自動下載您在頁面上 echo 的任何內容,並使用您想要的檔名和副檔名。我們可以將此概念與我們的地圖編輯器一起使用,在設定標頭後將使用者選擇的所有選項 echo 到頁面,併為地圖檔案建立自動下載。

從檔案讀取

[編輯 | 編輯原始碼]

從檔案讀取有點複雜。我不會介紹這個概念,而是直接給出我為地圖編輯器編寫的實際程式碼,並新增大量註釋來解釋我做了什麼以及原因。請注意,我編寫了此檔案讀取器來處理地圖編輯器生成的檔案格式。它可能不適用於您自己建立的任何地圖檔案,因此您需要先使用地圖編輯器重新建立它們。

我還想提醒您一件事。我將我的地圖檔案儲存在另一個名為 maps 的目錄中。如果您將地圖檔案儲存在其他位置,則需要更改 $directory 變數以反映這一點。如果您將地圖檔案儲存在與遊戲和地圖編輯器檔案相同的位置,則取消註釋第一個 $directory 行,並註釋掉第二個 $directory 行。

<?php

/**************
* Load Maze File
* NOTE: This was designed specifiically for files made with the map editor
*	 	it's not guaranteed to work for a file you made and formatted yourself.
*		Please use the editor to generate these files for you.
**************/
function loadMap($filename, $level)
{
	$map = array();

	//if you keep the map files in a particular directory
	//fill that in below. Otherwise you can leave this blank

	//$directory = "";
	$directory = "maps/";

	if (file_exists($directory . $filename . "_" . $level . ".txt"))
	{
		//open our file for reading the contents
		$fileline = file($directory . $filename . "_" . $level . ".txt");

		$x = 0;

		//while there is data in the file
		//read it one line at a time
		foreach ($fileline as $line_num => $line)
		{
			$i = 1;
			$y = 0; //we need to reset this each time
				//so our row always starts at zero

				//if this data is the start of our map
				//it should always be a wall
				if (substr($line, $i, 1) == "W")
				{
					//start pulling the info for the first row
					//keep loading in info until we reach the end of the line
					//in the row
					while (substr($line, $i, 1) != "\n")
					{
						if ($i % 2 != 0) //we do this so we don't load
								//in any of the spaces between characters
						{
							$map[$x][$y] = substr($line, $i, 1);
							$y++;
						}
						$i++; //take the next character in the row
					}
					$x++; //increment to the next row
				}
		}
		return $map; //return the array with all the map data
	}
	else
		return "File not found!";
}

?>

此函式主要做三件事。首先,它檢查檔案是否存在,如果不存在,則會顯示一條錯誤訊息。否則,它將開啟檔案,並搜尋該檔案,直到找到地圖的第一行。我們知道何時找到地圖的第一行,因為它將是一個 W 字元,即圍繞地圖外側的所有牆壁。一旦它從地圖檔案中找到了一行,它就會將其插入陣列並按 1 遞增陣列變數。這樣,地圖的每一行和每一列都會被讀取,直到它不再找到任何內容為止。

我們知道何時到達了一行的末尾(或地圖中一行的末尾),因為我們遇到了一個 \n 字元。這與檔案中的回車符或換行符相同。這告訴我們這一行沒有其他內容需要讀取,因此我們遞增陣列以開始從下一行讀取。

如果這看起來有點超出您的理解範圍,請不要擔心。當您在下一課中學習讀取或插入資料庫中的值時,這實際上要容易得多。

一些簡單的 Javascript

[編輯 | 編輯原始碼]

我知道我說過我不會深入 AJAX 和 Javascript,但是當我建立地圖編輯器時,我意識到如果新增一些 Javascript 來進行自動顏色編碼,會更容易實現視覺效果。因此,我將介紹我編寫的兩個函式及其工作原理。

Javascript 使用與 PHP 中不同的標籤開頭,但它有開始和結束標籤。

<script language="javascript">
function setColor(object)
{
	object.style.backgroundColor = object.options[object.selectedIndex].style.backgroundColor;
	object.style.color = object.options[object.selectedIndex].style.color;
}

function resetColors(object)
{
	var i;

        //we need to make sure we skip the buttons at the
        //bottom of the page, so we have to subtract 2 from
        //all the items in the form
	for (i = 0; i < (object.length-2); i++)
			setColor(object[i]);
}
</script>

第一個函式很簡單。它獲取 select 框中選項的顏色,並將 select 框更改為與該顏色匹配。

第二個函式稍微複雜一些。當我們呼叫它時,我們傳遞給它一個表單,即地圖編輯器檔案中的 <form> 和 </form> 標籤之間的所有內容。這包括我們用來讓您建立地圖的所有 select 框。然後,它遍歷所有這些 select 框,並呼叫 setColor 函式。

我們使用第二個函式的原因與第一次載入編輯器有關。第一次載入編輯器時,我們執行一個 PHP 指令碼,以便地圖的邊框自動變成牆壁。當我們這樣做時,我們仍然希望更新我們設定的牆壁的顏色。這就是 resetColors 函式的作用,它遍歷頁面上的每個 select 框,並將它們設定為適當的顏色。您會注意到,當您第一次啟動編輯器時,它會顯示所有白色的 select 框,幾秒鐘後,地圖的邊框會變為灰色。這種變化是由 javascript 呼叫來重置顏色造成的。嘗試從地圖編輯器中刪除 resetColors 呼叫。您現在嘗試執行它時是否看到了區別?

厄運深淵地圖編輯器

[編輯 | 編輯原始碼]

我知道以前沒有這樣做過,但我感覺我們已經到了需要更多解釋的地步了。現在,我將討論地圖編輯器、它的一些功能以及如何使用它。

地圖編輯器是一個非常棒且實用的工具,我們可以用它來快速有效地構建這個遊戲。它具有三個主要功能。您開啟它時看到的第一個頁面是設定地圖的名稱和等級。如果您正在同一張地圖上製作多個等級,那麼它們都需要相同的名稱。然後,您可以輸入地圖的寬度和高度。我建議使用 15 x 15 或更小的尺寸,否則您需要進行大量的滾動操作,但無論您選擇什麼尺寸都可以。

完成地圖詳細資訊填寫後,點選開始地圖編輯器。接下來你會看到一個擁有很多實用功能的螢幕。

  • 為了加快地圖構建速度,所有邊緣會自動變成牆壁。為了地圖檔案載入器的順利進行,請確保地圖邊緣始終有牆壁。
  • 地圖編輯器也採用了顏色編碼,使一切都更具視覺美感。
  • S 代表角色的起始位置。你可以設定多個起始位置,但我提供的檔案總是選擇離棋盤頂端最近的起始位置。如果你想新增隨機選擇起始位置的功能,歡迎嘗試。
  • 地圖編輯器的另一個功能是 L,代表梯子。當角色站在梯子上時,可以上下移動。當角色處於最高地圖層級(第 1 層)時,無法向上移動。反之,當角色處於最低地圖層級時,無法向下移動。目前,我們假設所有地圖有 5 個層級。
  • 地圖編輯器的最後一個(也是最好的)功能是匯出檔案按鈕。它可以讓你自動將地圖以 .txt 檔案的形式下載。然後,你可以使用這些 .txt 檔案將地圖載入到你的遊戲中並進行遊玩!

梯子、陷阱和層級變化

[編輯 | 編輯原始碼]

在這個版本的《死亡陷阱》中,我們需要新增上下梯子和掉入陷阱的功能。首先,最簡單的做法是建立一個用於所有遊戲移動的函式。

完成上述步驟後,我們需要將我們的 $_SESSION 變數設定為函式全域性變數。這意味著該函式可以訪問我們已有的 $_SESSION 變數中的值。透過在會話中設定地圖名稱和層級,我們始終知道當前所在的地圖和層級。如果將相同的概念應用於地圖陣列值,我們始終知道載入後地圖中的內容。

現在,每當角色處於陷阱上時,我們需要更改角色所在的層級,使他們下降一層(除非他們不在地圖的最後層級!)。為了方便起見,我們將確保第 5 層的地圖上沒有陷阱。

角色的層級下降到下一張地圖檔案後,我們將載入該新層級的地圖。在角色可以再次開始遊玩該層級之前,我們需要找到該新地圖的起始位置。為此,我編寫了兩個簡單的函式。

<?php

/**************
* This finds the position of the starting location
* marked with the value S and returns the X
* coordinate. It MUST be passed an entire array
* filled with maze data for it to work.
*
* Tip!: The variable name in the function parameter
* 	(in this case its $array) does not have to match
*	the name of the variable you pass to the function
*	when you call it.
**************/
function startPositionX($array)
{
	for ($x = 0; $x < height($array); $x++)
		for ($y = 0; $y < width($array); $y++)
			if ($array[$x][$y] == "S")
				return $x;
}

/**************
* This finds the position of the starting location
* marked with the value S and returns the Y
* coordinate. It MUST be passed an entire array
* filled with maze data for it to work.
**************/
function startPositionY($array)
{
	for ($x = 0; $x < height($array); $x++)
		for ($y = 0; $y < width($array); $y++)
			if ($array[$x][$y] == "S")
				return $y;
}

?>

其中一個函式用於查詢起始位置的 X 座標,另一個函式用於查詢 Y 座標。使用這兩個函式,我們可以重置角色在新載入的地圖上的預設位置。請注意,高度和寬度是我編寫用於查詢二維陣列高度和寬度的附加函式。

梯子使用了類似的概念,但有一個區別。如果角色站在梯子上,他們可以選擇向上或向下移動。為此,我為梯子編寫了一個特殊的 if 語句。當角色站在梯子上時,可以使用向上和向下按鈕上下移動梯子,而不是在地圖上向後或向前移動。如果角色處於最底層,我們允許他們向後退,而不是向下移動梯子,如果他們處於頂層,我們允許他們向前移動,而不是向上移動一層。

我們的遊戲正在逐步完善!我對目前的成果非常滿意。不僅可以上下移動梯子、掉入陷阱、找到寶藏,而且玩起來也很有趣。現在,我認為我們為角色移動機制構建了一個非常堅實的基礎。我認為現在是時候透過新增資料庫來將遊戲的複雜度提升到新的高度了。

遊戲檔案

[編輯 | 編輯原始碼]

工作版本

character.php

mapeditor.php

原始碼

character.txt

functions.txt

mapeditor.txt

mapsymbols.txt

地圖檔案

Round Hill_1.txt

Round Hill_2.txt

Round Hill_3.txt

Round Hill_4.txt

Round Hill_5.txt

親自嘗試

character.php

functions.php

課程推理

[編輯 | 編輯原始碼]

在本課程中,我們真正開始將更多內容整合在一起。現在,我們知道如何從外部資料來源載入地圖並在地圖上的特定點切換地圖。現在,我們已經擁有了一個相當不錯的遊戲地圖/角色移動機制框架,一旦遊戲完成,就可以使用這個框架。

但這還不是我們想要的!我們需要從資料庫載入和準備所有內容,以便可以隨時根據需要進行更改。在下一課中,我們將學習 SQL 簡介,學習如何設定和連線資料庫、建立表格以及將迄今為止完成的所有內容(包括簡單的地圖編輯器)更改為從資料庫提取資料和儲存資料到資料庫。

看看!僅僅 5 堂課,我們就開始學習如何與資料庫進行互動了。

下一章

[編輯 | 編輯原始碼]

閱讀下一章

華夏公益教科書