Translating a 2D world into a retro 3D first person perspective

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
togFox
Party member
Posts: 779
Joined: Sat Jan 30, 2021 9:46 am
Location: Brisbane, Oztralia

Translating a 2D world into a retro 3D first person perspective

Post by togFox »

I'll start by being clear I'm not talking 3D models with lighting etc. I'm talking Wolfenstein flat 2D images that are placed on the screen in a way that gives you that first person vibe.

I have this top-down view of the world where the submarine, when looking through the periscope, has a very limited and narrow view of the world:

Image

It can see the destroyer when the periscope faces it (the periscope can rotate) it but not the one's on either side. I want to build a 3D 'scene' that is very simple, very retro and very flat. Specifically, I'd like to show that one destroyer, as a flat 2D image that is positioned correctly (a little left of centre) and scaled down to the right size to give the perception of distance. Obviously, when the periscope "moves" left and right, the 2D flat image moves in the opposite direction.

I have a few things to work with. I know the x/y of the periscope. I know which way it is facing (degrees from north). I know the periscope has a 40 degree field of view (20 degrees left/right), I know the x/y of the destroyer(s), the viewable area (periscope viewport) is physically 350 pixels radius and I don't care about the destroyers facing/orientation (for now).

I've had a few failed attempts at the math and to project the image in the right position laterally with the right scale. This is complicated by the fact each destroyer has an x/y but the physical shape obviously is a rectangle that is more than a single point.

I'm not looking for a full-blown 3D immersive environment. My ambitions are minimal and was hoping someone has done math like this before, in terms of translating relative position from the viewer into correct placement in an X/Y sense and then scale to right size based on distance. I'm not even trying to get the y value right (on the horizon or below the horizon) at this stage.

This is the vision:

Image

Even some journals on how Wolfenstein or Doom3D pulled this off would be great reading.
Current project:
https://togfox.itch.io/backyard-gridiron-manager
American football manager/sim game - build and manage a roster and win season after season
User avatar
darkfrei
Party member
Posts: 1181
Joined: Sat Feb 08, 2020 11:09 pm

Re: Translating a 2D world into a retro 3D first person perspective

Post by darkfrei »

1. Create the same, but just with circles.
Each circle has x,y position on map, radius on map. By the given player position and periscope direction, it can be recalculated as x position on the screen and radius un the screen.

Code: Select all

function love.load()
	Width, Height = love.graphics.getDimensions ()
	MapCanvas = love.graphics.newCanvas (Width, Height, {dpiscale = 0.25})

	Ships = {}
	for i = 1, 4 do
		local x = math.random (Width)
		local y = math.random (Height)
		local r = 20
		local ship = {x=x, y=y, r=r}
		table.insert (Ships, ship)
	end

	Player = {
--		x=Width/2, y=Height/4, 
		x=750, y=435, 
		r=20, 
		angle = 0, 
		angle = math.rad(183), 
		aov = math.rad(30), -- angle of view
		lineLeft = {0,0,0,0}, lineRight = {0,0,0,0}, ships = {}}
	-- 
	ViewWidth = Width / 4 -- view width in pixels
	ViewtoWidthFactor = ViewWidth / Player.aov -- factor pixels pro angle
end

function love.update(dt)
	local w = love.keyboard.isScancodeDown ('w')
	local a = love.keyboard.isScancodeDown ('a')
	local s = love.keyboard.isScancodeDown ('s')
	local d = love.keyboard.isScancodeDown ('d')
	local speed = w and 1 or s and -1 or 0
	local dangle = d and 1 or a and -1 or 0
	Player.angle = (Player.angle + dt * dangle * 2)
	Player.x = Player.x + dt * speed * 2*120 * math.cos (Player.angle)
	Player.y = Player.y + dt * speed * 2*120 * math.sin (Player.angle)
	
	love.window.setTitle ('sea battle '.. Player.x..' '.. Player.y..' '..math.deg(Player.angle))

	-- update field of view
	Player.lineLeft[1] = Player.x
	Player.lineLeft[2] = Player.y
	Player.lineLeft[3] = Player.x + 500*math.cos(Player.angle - Player.aov/2)
	Player.lineLeft[4] = Player.y + 500*math.sin(Player.angle - Player.aov/2)
	Player.lineRight[1] = Player.x
	Player.lineRight[2] = Player.y
	Player.lineRight[3] = Player.x + 500*math.cos(Player.angle + Player.aov/2)
	Player.lineRight[4] = Player.y + 500*math.sin(Player.angle + Player.aov/2)
	
	Player.ships = {}
	for i, ship in ipairs (Ships) do
		local vShip = {}
		vShip.y = Height/2
		local angle = (Player.angle - math.atan2 (ship.y-Player.y, ship.x-Player.x) + math.pi/2) % (2*math.pi) - math.pi/2
		vShip.x = Width/2 - ViewtoWidthFactor * (angle)
		vShip.r = 100* ship.r / math.sqrt((Player.x-ship.x)^2 + (Player.y-ship.y)^2)
		if math.abs(Width/2 - vShip.x) < ViewWidth/2 then
			table.insert (Player.ships, vShip)
		end
	end
end

function love.draw()
	love.graphics.setCanvas (MapCanvas)
		love.graphics.clear (0.1,0.1,0.3)
		love.graphics.setColor (0.8,0.2,0.2)
		for i, ship in ipairs (Ships) do
			love.graphics.circle ('fill', ship.x, ship.y, ship.r)
		end
		love.graphics.setColor (0.2,0.8,0.2)
		love.graphics.circle ('fill', Player.x, Player.y, Player.r)
		love.graphics.setLineWidth (4)
		love.graphics.line (Player.lineLeft)
		love.graphics.line (Player.lineRight)
	love.graphics.setCanvas ()

	-- sky
	love.graphics.setColor (0.6,0.7,0.8)
	love.graphics.rectangle ('fill', 0,0, Width, Height/2)
	
	-- sea
	love.graphics.setColor (0.1,0.2,0.5)
	love.graphics.rectangle ('fill', 0, Height/2, Width, Height/2)

	-- ships in player's periscope
	love.graphics.setColor (1,1,1)
	for i, ship in ipairs (Player.ships) do
		love.graphics.circle ('fill', ship.x, ship.y, ship.r)
	end
	
	-- periscope circle
	love.graphics.setLineWidth (1)
	love.graphics.circle ('line', Width/2, Height/2, ViewWidth/2)

	-- map overlay
	love.graphics.draw (MapCanvas, 0, 0, 0, 0.25, 0.25)
end

function love.keypressed(key, scancode, isrepeat)
	if key == "escape" then
		love.event.quit()
	end
end
Screenshot 2023-10-29 152335.png
Screenshot 2023-10-29 152335.png (14.5 KiB) Viewed 3670 times
:awesome:
:cool: (Press WASD to move)
:3
Attachments
sea-battle-01.love
(1.17 KiB) Downloaded 19 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
Post Reply

Who is online

Users browsing this forum: Gopmyc and 136 guests