Tutorial:Gridlocked Player (日本語)

このチュートリアルでは、固定された間隔で"自機"が画面内を移動できる非常に基礎的なゲームを作成します。これはタイルベースのゲームを作成しているとき、自機がグリッド内に同調して滞在して欲しいのに、それらが転送されて周囲に出現して欲しくはない場合に有用です。

コードの開始

最初に、自機を作成する必要があります。完了後に自機は様相を持つようになるため、従って player 変数を love.load() 内へテーブルとして作成します。

function love.load()
	player = {}
end

次に何を行っているのか確認する必要があるでしょう。 love.draw() にて love.graphics.rectangle() を使用することで自機に見立てた長方形を表示することができます。

function love.draw()
	love.graphics.rectangle("fill", 0, 0, 32, 32)
end

自機の位置を保持するために player テーブルへ少し書き加えます。テーブルへ変数を記入するために速記法を使用していることに注意してください。それらをカンマを使用して分離を行い、カーリー・ブラケットの内側で定義します (さらに、これらの新規値を使用するには love.graphics.rectangle にある引数を変更する必要があります)。

function love.load()
	player = {
		x = 256,
		y = 256
	}
end
 
function love.draw()
	love.graphics.rectangle("fill", player.x, player.y, 32, 32)
end

Grid player 1.gif

これで自機は位置を保持するようになったので、 love.keypressed() を使用して操作の一部を追加します。していることは key の参照を行い一致するかどうかを確認して、一致すれば player テーブルにある値を変更します。

function love.keypressed(key)
	if key == "down" then
		player.y = player.y + 32
	end
end

従って、これで小型の正方形は1グリッド単位ずつ下方向へテレポートします (この場合は 32 ピクセルであり、 player は同じ長さは進みます)。

拡張

これは elseif を使用してより多くの key を拡張することができます。全て 32 単位のままであることに注意してください。タイルにおいて共通の大きさは PO2_Syndrome を利用します。

function love.keypressed(key)
	if key == "up" then
		player.y = player.y - 32
	elseif key == "down" then
		player.y = player.y + 32
	elseif key == "left" then
		player.x = player.x - 32
	elseif key == "right" then
		player.x = player.x + 32
	end
end

従って、これでキーボードを打鍵するのと同じくらい高速に画面の中を飛び越えることを満たすことができる小型の正方形があります。さて滑らかな遷移を追加します。これを行うには、 player テーブルにある方向担当の変数を変更する必要があります。

現在必要な物が二つあります。一つ目は、現在において転送先の座標として有用なグリッドに固定された (gridlocked) とは異なる実際の X, Y 座標が必要です。二つ目は、転送先の座標へ移動するために love.update() にある実際の座標を更新する必要があります。これは単純な数学の問題により行うことができます:

actual_x = actual_x - (actual_x - destination_x)

あるいは、この場合 Lua では:

function love.load()
	player = {
		grid_x = 256,
		grid_y = 256,
		act_x = 200,
		act_y = 200
	}
end
 
function love.update(dt)
	player.act_y = player.act_y - (player.act_y - player.grid_y)
	player.act_x = player.act_x - (player.act_x - player.grid_x)
end
 
function love.draw()
	love.graphics.rectangle("fill", player.act_x, player.act_y, 32, 32)
end
 
function love.keypressed(key)
	if key == "up" then
		player.grid_y = player.grid_y - 32
	elseif key == "down" then
		player.grid_y = player.grid_y + 32
	elseif key == "left" then
		player.grid_x = player.grid_x - 32
	elseif key == "right" then
		player.grid_x = player.grid_x + 32
	end
end

恐らく気付いているでしょうが、多分ですが違いを一切見ることはできません。速度を制御するために love.updatedt 引数を使用して、位置の差に dt を乗算する必要があります:

function love.update(dt)
	player.act_y = player.act_y - ((player.act_y - player.grid_y) * dt)
	player.act_x = player.act_x - ((player.act_x - player.grid_x) * dt)
end

しかしながら全く正しくないままです。 player は我慢できないほど遅いです (が、少なくとも計算機から計算機まで一貫しています!)。速く移動するか制御するために dt へ速度を乗算することで player に speed 属性を与えます:

function love.load()
	player = {
		grid_x = 256,
		grid_y = 256,
		act_x = 200,
		act_y = 200,
		speed = 10
	}
end
 
function love.update(dt)
	player.act_y = player.act_y - ((player.act_y - player.grid_y) * player.speed * dt)
	player.act_x = player.act_x - ((player.act_x - player.grid_x) * player.speed * dt)
end

対象の全配置

こちらは完全なコードです:

function love.load()
	player = {
		grid_x = 256,
		grid_y = 256,
		act_x = 200,
		act_y = 200,
		speed = 10
	}
end
 
function love.update(dt)
	player.act_y = player.act_y - ((player.act_y - player.grid_y) * player.speed * dt)
	player.act_x = player.act_x - ((player.act_x - player.grid_x) * player.speed * dt)
end
 
function love.draw()
	love.graphics.rectangle("fill", player.act_x, player.act_y, 32, 32)
end
 
function love.keypressed(key)
	if key == "up" then
		player.grid_y = player.grid_y - 32
	elseif key == "down" then
		player.grid_y = player.grid_y + 32
	elseif key == "left" then
		player.grid_x = player.grid_x - 32
	elseif key == "right" then
		player.grid_x = player.grid_x + 32
	end
end

衝突の追加

さて、なにか詳細な世界に追加しましょう。最初に、グリッド上を歩行することができる、またはできないように指示するためにテーブルの値を作成する必要があります。

function love.load()
map = {
	{ 1, 1, 1, 1 },
	{ 1, 0, 0, 1 },
	{ 1, 0, 1, 1 },
	{ 1, 1, 1, 1 }
}
end

この map テーブルは4つの下位テーブルを保持しており、それぞれ4つの値を保持しています。それぞれの下位テーブルは map にある水平の列であり、それぞれの値は列において単一のタイルを表します。それは種類としては容易であるため 1s および 0s を使用しています。

これらの値を画面へ描画します。二種類のみ保持するため、種類のうち一つだけを描画することができます…。 1s について言及しましょう。単一ループを使用することで下位テーブルを経由してループを行い、さらにそれぞれのテーブル内の値を経由してループします。

function love.draw()
	for y=1, #map do
		for x=1, #map[y] do
			if map[y][x] == 1 then
				love.graphics.rectangle("line", x * 32, y * 32, 32, 32)
			end
		end
	end
end

この二重ループに関して数点ほど注意があります。 map テーブルは参照による方法で整列されます。この意味は座標であり通常の思考方法とは逆です。 x および y ではなく、 map は y および x です。さらに map は32 ピクセル単位です。それは歩行不能な正方形を描画する場合に、それらの位置に 32 を乗算する必要があることを意味します。

Grid player 2.gif

自機が map 上の場所を歩行試験ができる関数を作成します。この関数は場所上を歩行可能なら true を、歩行不能なら false を返します。関数には二つの値を指定します: 変更する x および 変更する y です。

function testMap(x, y)
	if map[(player.grid_y / 32) + y][(player.grid_x / 32) + x] == 1 then
		return false
	end
	return true
end

この関数に関して注意すべきこと。マップ単位で動作するため、通常時に渡される値は 1, -1 または 0 です。そのため、自機のグリッド座標とは異なり 32 での除算は行いません。

player のグリッド座標について言及すると、それらを内部へ渡す必要がなかった理由は player はグローバル変数だからです。

従って、この関数を keypressed 関数へ挿入することで使用することができます。留意しておくことは、試験関数の動作はピクセル単位ではなくマップ単位であり、よって一つのグリッド上を移動しようとする時は (0, -1) を渡します。

function love.keypressed(key)
	if key == "up" then
		if testMap(0, -1) then
			player.grid_y = player.grid_y - 32
		end
	elseif key == "down" then
		if testMap(0, 1) then
			player.grid_y = player.grid_y + 32
		end
	elseif key == "left" then
		if testMap(-1, 0) then
			player.grid_x = player.grid_x - 32
		end
	elseif key == "right" then
		if testMap(1, 0) then
			player.grid_x = player.grid_x + 32
		end
	end
end

いまこれを実行しようとすればプログラムは異常終了することに気づくでしょう。 この理由は自機がマップの範囲外から出発するからです。地図を拡張すれば不具合を修正することができます。こちらには地図に拡張を施した完全なコードがあります。

function love.load()
	player = {
		grid_x = 256,
		grid_y = 256,
		act_x = 200,
		act_y = 200,
		speed = 10
	}
	map = {
		{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
		{ 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
		{ 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1 },
		{ 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1 },
		{ 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1 },
		{ 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
		{ 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
		{ 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
		{ 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
		{ 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1 },
		{ 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
		{ 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
		{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
	}
end
 
function love.update(dt)
	player.act_y = player.act_y - ((player.act_y - player.grid_y) * player.speed * dt)
	player.act_x = player.act_x - ((player.act_x - player.grid_x) * player.speed * dt)
end
 
function love.draw()
	love.graphics.rectangle("fill", player.act_x, player.act_y, 32, 32)
	for y=1, #map do
		for x=1, #map[y] do
			if map[y][x] == 1 then
				love.graphics.rectangle("line", x * 32, y * 32, 32, 32)
			end
		end
	end
end
 
function love.keypressed(key)
	if key == "up" then
		if testMap(0, -1) then
			player.grid_y = player.grid_y - 32
		end
	elseif key == "down" then
		if testMap(0, 1) then
			player.grid_y = player.grid_y + 32
		end
	elseif key == "left" then
		if testMap(-1, 0) then
			player.grid_x = player.grid_x - 32
		end
	elseif key == "right" then
		if testMap(1, 0) then
			player.grid_x = player.grid_x + 32
		end
	end
end
 
function testMap(x, y)
	if map[(player.grid_y / 32) + y][(player.grid_x / 32) + x] == 1 then
		return false
	end
	return true
end

Grid player 3.gif

その他にも改良可能な若干の事項があります:

  • もちろん、この記事で網羅された技法を使用して、美術面を改良可能です: [1]
  • Escape キーへ love.event.push('quit') を割り当てることによるゲームの終了といった一部追加の操作を実装することは可能です。
  • 自機は閉鎖空間のマップ内に存在しますが、マップが開いており自機が脱出しようとした場合は、ゲームは異常終了します。この不具合を防止するために簡単な検査を一部追加することは可能です。
  • マップを 32, 32 から開始する場合において 0, 0 から開始することはかなり多くの意味があります。これを僅かな算術で容易に修正することはできますが、それは少しコードが複雑になり理解するのが困難になります。

しかし、この教本の用途は十分にあります。