【PHP】RPGゲームを作る3(マップエディタ)

MySQL

map_chipで作成したマップ素材を配置して、マップを編集する管理者ページを作成します。あらかじめmap_chipが登録されている必要があります。

環境

 OS ubuntu22.04 
 Webサーバ apache2.4.52
(自身のIP 192.168.19.128、公開フォルダ /var/www/html/php/rpg)
 PHP 8.1.2-1ubuntu2.14
 DB MySQL Ver 8.0.34-ubuntu0.22.04.1

マップ編集画面の作成

まずは次のSQLでマップ用データの保存テーブルを作成します。

create table map (map_no int,x int ,y int ,chip_no int ,mask int ,event text,primary key (map_no, x,y));

 次は、下のPHPを作成して
   /var/www/html/php/rpg/
に配置し、パーミッションを705にしました。
 ファイル名 map_edit.php

<?php
// MySQL接続情報
$servername = "192.168.19.128"; // データベースのホスト名
$username = "********"; // データベースのユーザー名
$password = "********"; // データベースのパスワード
$dbname = "****"; // 使用するデータベース名

$DEF_MAKE = "make";
$DEF_INSERT = "insert";
$DEF_OPEN = "open";
$DEF_UPDATE = "update";
?>

<!DOCTYPE html>
<html>
<body>
<style>
  .image_pass {
    border: 2px solid blue; /* 赤い枠線を追加 */
    display: inline-block; /* インラインブロック要素として表示 */
  }
  .image_block {
    border: 2px solid red; /* 赤い枠線を追加 */
    display: inline-block; /* インラインブロック要素として表示 */
  }
</style>
<script type="text/javascript">
<!--
function makeForm(param){
	if(document.getElementById("x_"+param).value =="" || document.getElementById("y_"+param).value =="") {
		alert("XとYの最大値を入力してください");
		exit;
	}
	if(Number.isSafeInteger(document.getElementById("x_"+param).value) || Number.isSafeInteger(document.getElementById("y_"+param).value)) {
		alert("XとYは数字で入力して下さい");
		exit;
	}
	if(document.getElementById("x_"+param).value < 5 || document.getElementById("x_"+param).value  > 100 || document.getElementById("y_"+param).value < 5 || document.getElementById("y_"+param).value  > 100) {
		alert("X,Yは5~100の整数で指定してください");
		exit;
	}
	document.getElementById("do").value="<?php echo $DEF_MAKE ?>";
	document.getElementById("do_no").value=param;
	document.getElementById("x").value=Math.floor(document.getElementById("x_"+param).value);
	document.getElementById("y").value=Math.floor(document.getElementById("y_"+param).value);
	document.update.submit();
}

function insertForm(){
	document.getElementById("do").value="<?php echo $DEF_INSERT ?>";
	document.update.submit();
}
function openForm(param){
	document.getElementById("do").value="<?php echo $DEF_OPEN ?>";
	document.getElementById("do_no").value=param;
	document.getElementById("x").value=Math.floor(document.getElementById("x_"+param).value);
	document.getElementById("y").value=Math.floor(document.getElementById("y_"+param).value);
	document.update.submit();
}

function updateForm(x_max,y_max){
	if(document.getElementById("edit_x").value =="" || document.getElementById("edit_y").value =="") {
		alert("XとYの値を入力してください");
		exit;
	}
	if(Number.isSafeInteger(document.getElementById("edit_x").value) || Number.isSafeInteger(document.getElementById("edit_y").value)) {
		alert("XとYは数字で入力して下さい");
		exit;
	}
	if(document.getElementById("edit_x").value > x_max || document.getElementById("edit_x").value  <= 0 || document.getElementById("edit_y").value > y_max || document.getElementById("edit_y").value <= 0) {
		alert("Xは"+x_max+"以下、Yは"+y_max+"以下で指定してください");
		exit;
	}
	document.getElementById("do").value="<?php echo $DEF_UPDATE ?>";
	document.getElementById("edit_x").value=Math.floor(document.getElementById("edit_x").value);
	document.getElementById("edit_y").value=Math.floor(document.getElementById("edit_y").value);
	document.update.submit();
}

-->
</script>
<h1>MAP_編集画面</h1>
<?php

$do="";
$do_no="";
$do_x="";
$do_y="";

//db読込
// MySQLサーバーへの接続
$conn = new mysqli($servername, $username, $password, $dbname);

// 接続エラーの確認
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

//
if ($_SERVER['REQUEST_METHOD'] === 'POST' 
	&& isset($_POST['do'])
	&& $_POST['do'] === $DEF_MAKE
	&& isset($_POST['do_no'])
	&& isset($_POST['x'])
	&& isset($_POST['y'])){
	
	//マップ編集画面(新規)
	edit_map($conn,$_POST);
	

} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' 
	&& isset($_POST['do'])
	&& $_POST['do'] === $DEF_INSERT
	&& isset($_POST['do_no'])
	&& isset($_POST['x'])
	&& isset($_POST['y'])){
	
	//DB登録(新規)
	create_map_ondb($conn,$_POST);

} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' 
	&& isset($_POST['do'])
	&& $_POST['do'] === $DEF_OPEN
	&& isset($_POST['do_no'])
	&& isset($_POST['x'])
	&& isset($_POST['y'])){

	//マップ編集画面(更新)
	update_map($conn,$_POST);

} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' 
	&& isset($_POST['do'])
	&& $_POST['do'] === $DEF_UPDATE
	&& isset($_POST['do_no'])
	&& isset($_POST['sel_update'])
	&& isset($_POST['edit_x'])
	&& isset($_POST['edit_y'])
	&& isset($_POST['x'])
	&& isset($_POST['y'])){

	//DB登録(更新)
	update_map_ondb($conn,$_POST);
	//マップ編集画面(更新)
	update_map($conn,$_POST);
} else {
	//初期表示画面
	first_menu($conn);

}

//Footer
echo '<A href="./map_chip.php">マップチップ編集画面へ</a><br>';
echo '<A href="./map_edit.php">reload</a><br>';
$conn->close();

?>

</body>
</html>
<?PHP

//*********************************************************************************************************************
//最初に表示されるページ
//*********************************************************************************************************************
function first_menu($conn){
	//既存マップ一覧の読み込み(初期画面) 
	echo "編集するマップを選んでください。<br>";
	echo "新しいマップを作成する場合は、マップのサイズ(5<=X<=100,5<=Y<100)を指定してから「MAKE」ボタンを押してください。<br>";
	echo "マップのサイズは後から変更できません。";

	$count=0;
	$sql = "SELECT map_no,max(x) as x,max(y) as y from map group by map_no ";
	$result = $conn->query($sql);
	echo '<form action="" name="update" method="post">';
	echo '<input type="hidden" name="do" id="do" value="">';
	echo '<input type="hidden" name="do_no" id="do_no" value="">';
	echo '<input type="hidden" name="x" id="x" value ="">';
	echo '<input type="hidden" name="y" id="y" value ="">';
	echo "<table border=1><tr><td>map_no</td><td>MAX_X</td><td>MAX_Y</td><td>BUTTON</td></tr>";
	if ($result->num_rows > 0) {
		while($row = $result->fetch_assoc()) {
	    	$count++;
	        echo "<tr><td>";
	        echo "<input type='text' value='" . $row["map_no"] . "' size=3 readonly>";
	        echo "</td><td>";
	        echo "<input type='text' id='x_".$row["map_no"]."' name='x_".$row["map_no"]."' value='" . $row["x"] . "' size=3 readonly>";
	        echo "</td><td>";
	        echo "<input type='text' id='y_".$row["map_no"]."' name='y_".$row["map_no"]."' value='" . $row["y"] . "' size=3 readonly>";
	        echo "</td><td>";
	        echo "<button type='button' onclick='openForm(".$row["map_no"].");' >OPEN</button>";
	        echo "</td></tr>";
	    }
	}

	$count++;
	echo "<tr><td>";
    echo "<input type='text' value='".$count."' size=3 readonly>";
    echo "</td><td>";
    echo "<input type='text' id='x_".$count."' name='x_".$count."' value='' size=3 >";
    echo "</td><td>";
    echo "<input type='text' id='y_".$count."' name='y_".$count."' value='' size=3 >";
    echo "</td><td>";
    echo "<button type='button' onclick='makeForm(".$count.");' >MAKE</button>";
    echo "</td></tr>";
	echo "</table></form>";
}

//*********************************************************************************************************************
//マップデータ上にマップチップを配置するページ(新規)
//*********************************************************************************************************************
function edit_map($conn,$PO){
	//MAKE
	$do = $PO['do'];
	$do_no=$PO['do_no'];
	$do_x=$PO['x'];
	$do_y=$PO['y'];
	$count=0;
	
	$sql = "SELECT * from map_chip";
	$result = $conn->query($sql);

	if ($result->num_rows > 0) {
		$chip = array();
		while ($row = $result->fetch_assoc()) {
    		$chip[] = $row;
		}
		echo '<form action="" name="update" method="post">';
		echo "<input type=hidden name='do' id='do' value='' >";
		echo "max x<input type=text name='x' id='x' value='".$do_x."' readonly><br>";
		echo "max y<input type=text name='y' id='y' value='".$do_y."' readonly><br>";
		echo "map no<input type=hidden name='do_no' id='do_no' value='".$do_no."'>$do_no<br>";

		for ($y = 1; $y <= $do_y; $y++) {
			 for($x = 1; $x <= $do_x ; $x++) {
			 	//echo "(".$x.":".$y.")";
			 	echo "<select name='sel_".$x."_".$y."' id='sel_".$x."_".$y."'>";
			 	
			 	foreach ($chip as $sel) {
			 	
			 		echo "<option value='".$sel["chip_no"]."_0' >〇".$sel["descript"]."</option>";
			 		echo "<option value='".$sel["chip_no"]."_1' >×".$sel["descript"]."</option>";
			 	}
			 	echo "<select>";
			 }
			 echo "<br>";
		}
		echo "<button type='button' onclick='insertForm();' >登録</button>";
		echo "</form>";
		echo "〇は通過できるチップ、×は通過できないチップ<br>";
	} else {
		echo "先にマップチップデータを登録して下さい。";
	}
}

//*********************************************************************************************************************
//マップデータをDB上に登録する
//*********************************************************************************************************************
function create_map_ondb($conn,$PO){
	$do = $PO['do'];
	$do_no=$PO['do_no'];
	$do_x=$PO['x'];
	$do_y=$PO['y'];

	$map = array();
	for ($y = 1; $y <= $do_y; $y++) {
		for ($x = 1; $x <= $do_x; $x++) {
			 $map[$x][$y] = $PO['sel_'.$x.'_'.$y];
        }
    }
	
    for ($y = 1; $y <= $do_y; $y++) {
		for ($x = 1; $x <= $do_x; $x++) {
			list($map_chip, $mask) = explode("_", $map[$x][$y]);
			
			$sql="insert into map(map_no,x,y,chip_no,mask) VALUES(?,?,?,?,?) ON DUPLICATE KEY UPDATE chip_no = VALUES(chip_no), mask = VALUES(mask)";
			$stmt = $conn->prepare($sql);
			$stmt->bind_param("iiiii", $do_no, $x, $y, $map_chip, $mask);
			if ($stmt->execute()) {
		    	//echo "レコードに追加されました(".$x.",".$y.")";
			} else {
			    echo "Error: " . $stmt->error;
			}
			
		}
	}
}

//*********************************************************************************************************************
//マップデータ上にマップチップを配置するページ(更新)
//*********************************************************************************************************************
function update_map($conn,$PO){
	$do = $PO['do'];
	$do_no=$PO['do_no'];
	$do_x=$PO['x'];
	$do_y=$PO['y'];

	$sql = "select map_no,map.x as x,map.y as y,map.chip_no as chip_no,mask,event,filename,descript from map,map_chip where map.chip_no = map_chip.chip_no and map.map_no=? order by y,x";
	$stmt = $conn->prepare($sql);
	$stmt->bind_param("i", $do_no);
	$stmt->execute();
	$result = $stmt->get_result();
	$count=0;

	if ($result->num_rows > 0) {
		echo "<table border=1><tr>";
		echo "<td></td>";
		for ($i = 1; $i <= $do_x ;$i++){
			echo "<td>".$i."</td>";
		}
		echo "</tr>";

		while($row = $result->fetch_assoc()) {
			$count++;
			if(($count-1) % $do_x === 0){
				echo "<tr><td>".floor($count / $do_x + 1)."</td>";
			}
			echo "<td>";
			echo '<img src='.$row["filename"].'  width="20.px" alt="'.$row["descript"].'" ';
			if ($row["mask"] === 0 ){
				echo 'class="image_pass" ';
			} else {
				echo 'class="image_block" ';
			}
			echo 'onclick="mapSelectFunc('.$count.')">';
			echo '<input type="hidden" name="mask_'.$count.'" id="mask_'.$count.'" value ="'.$row["mask"].'">';
			echo '<input type="hidden" name="chip_'.$count.'" id="chip_'.$count.'" value ="'.$row["chip_no"].'">';
			echo '<input type="hidden" name="event_'.$count.'" id="event_'.$count.'" value ="'.$row["event"].'">';
			echo '<input type="hidden" name="x_'.$count.'" id="x_'.$count.'" value ="'.$row["x"].'">';
			echo '<input type="hidden" name="y_'.$count.'" id="y_'.$count.'" value ="'.$row["y"].'">';
			echo "</td>";
			if($count % $do_x === 0){
				echo "</tr>";
			}
		}
		echo "</table>";
		echo "青枠は通過可能、赤枠は通過不可能です。<br>";
		echo "地図更新  マップNo".$do_no;
		echo '<form action="" name="update" method="post">';
		echo 'X座標<input type="text" name="edit_x" id="edit_x" value ="" size=4>';
		echo 'Y座標<input type="text" name="edit_y" id="edit_y" value ="" size=4>';
		echo '<input type="hidden" name="x" id="x" value ="'.$do_x.'">';
		echo '<input type="hidden" name="y" id="y" value ="'.$do_y.'">';
		echo '<input type="hidden" name="do" id="do" value="">';
		echo '<input type="hidden" name="do_no" id="do_no" value="'.$do_no.'">';
		
		$sql = "SELECT * from map_chip";
		$result = $conn->query($sql);

		if ($result->num_rows > 0) { 
			$chip = array();
			while ($row = $result->fetch_assoc()) {
    			$chip[] = $row;
			}
		
			echo "<select name='sel_update' id='sel_update'>";
			foreach ($chip as $sel) {
					echo "<option value='".$sel["chip_no"]."_0' >〇".$sel["descript"]."</option>";
					echo "<option value='".$sel["chip_no"]."_1' >×".$sel["descript"]."</option>";
			}
			echo "<select>";
		}
		echo "〇は通過できるチップ、×は通過できないチップ<br>";
		echo 'イベント<input type="text" name="event" id="event" value ="" size=10>';
		echo "<button type='button' onclick='updateForm(". $do_x.",".$do_y .")' >更新</button><br>";
		echo "</form>";
?>
<script type="text/javascript">
<!--
function mapSelectFunc(param){
	document.getElementById("edit_x").value = document.getElementById("x_"+param).value;
	document.getElementById("edit_y").value = document.getElementById("y_"+param).value;
	document.getElementById("event").value = document.getElementById("event_"+param).value;
	var ss = document.getElementById("chip_"+param).value + "_" + document.getElementById("mask_"+param).value;
	
	var sel = document.getElementById("sel_update").options;
	for (i =0 ;i<sel.length;i++){
		if(sel[i].value === ss){
			sel[i].selected = true;
		} else {
			sel[i].selected = false;
		}
	}
}
-->
</script>

<?PHP

	} else {
		echo "地図データが見つからないか壊れています<br>";
	}
}

//*********************************************************************************************************************
//マップデータをDB上で更新する
//*********************************************************************************************************************
function update_map_ondb($conn,$PO){
	$do = $PO['do'];
	$do_no=$PO['do_no'];
	$do_x=$PO['edit_x'];
	$do_y=$PO['edit_y'];
	$sel_update=$PO['sel_update'];
	$event=$PO['event'];
	list($map_chip, $mask) = explode("_", $sel_update);
	$sql="insert into map(map_no,x,y,chip_no,mask,event) VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE chip_no = VALUES(chip_no), mask = VALUES(mask) ,event = VALUES(event)";
	$stmt = $conn->prepare($sql);
	$stmt->bind_param("iiiiis", $do_no, $do_x, $do_y, $map_chip, $mask,$event);
	if ($stmt->execute()) {
	   	//echo "レコードに追加されました";
	} else {
	    echo "Error: " . $stmt->error;
	}
}
?>

 PHPはまだ慣れていないので、おかしなところがあれば教えていただけると助かります。(以前はPerl派だったので、PHPはまだ1か月弱です)
 実行すると、次のような画面になります。

map編集画面を実行した様子。
20×20マスのマップを開くとこんな感じです。

 後々ですが、海の中の隠し通路とか、アイテムがないと入れない森の中等が作れるといいかと思い、map_chip毎に透過属性(MASK)を持たせるのではなく、マップデータにMASK属性を持たせています。
 青枠は通過できるマスで、赤枠は通過できないマスとなっています。
 フィールドマップから、他のマップへの移動はイベントを使う予定です。