local libraries = { colorlib = "colorlib", image = "image", buffer = "doubleBuffering", doubleHeight = "doubleHeight", files = "files", computer = "computer", event = "event", } for library in pairs(libraries) do if not _G[library] then _G[library] = require(libraries[library]) end end; libraries = nil local rayEngine = {} ---------------------------------------------------------------------------------------------------------------------------------- local function round(num) if num >= 0 then return math.floor(num + 0.5) else return math.ceil(num - 0.5) end end local function convertRadiansToDegrees(rad) return rad * (180 / 3.14) end local function convertDegreesToRadians(ang) return ang * (3.14 / 180) end local function constrainAngle(value) if ( value < 0 ) then value = value + 360 elseif ( value > 360 ) then value = value - 360 end return value end local function getSkyColorByTime() return rayEngine.world.colors.sky[rayEngine.world.dayNightCycle.currentTime > 0 and math.ceil(rayEngine.world.dayNightCycle.currentTime / rayEngine.world.dayNightCycle.length * #rayEngine.world.colors.sky) or 1] end local function getBrightnessByTime() return rayEngine.world.colors.brightnessMultiplyer[rayEngine.world.dayNightCycle.currentTime > 0 and math.ceil(rayEngine.world.dayNightCycle.currentTime / rayEngine.world.dayNightCycle.length * #rayEngine.world.colors.brightnessMultiplyer) or 1] end local function getTimeDependentColors() rayEngine.world.colors.sky.current = getSkyColorByTime() rayEngine.world.colors.brightnessMultiplyer.current = math.floor(getBrightnessByTime() * 2.55) rayEngine.world.colors.groundByTime = colorlib.alphaBlend(rayEngine.world.colors.ground, 0x000000, rayEngine.world.colors.brightnessMultiplyer.current) rayEngine.world.colors.tile.byTime = {} for i = 1, #rayEngine.world.colors.tile do rayEngine.world.colors.tile.byTime[i] = colorlib.alphaBlend(rayEngine.world.colors.tile[i], 0x000000, rayEngine.world.colors.brightnessMultiplyer.current) end end local function doDayNightCycle() if rayEngine.world.dayNightCycle.enabled then local computerUptime = computer.uptime() if (computerUptime - rayEngine.world.dayNightCycle.lastComputerUptime) >= rayEngine.world.dayNightCycle.speed then rayEngine.world.dayNightCycle.currentTime = rayEngine.world.dayNightCycle.currentTime + rayEngine.world.dayNightCycle.speed if rayEngine.world.dayNightCycle.currentTime > rayEngine.world.dayNightCycle.length then rayEngine.world.dayNightCycle.currentTime = 0 end rayEngine.world.dayNightCycle.lastComputerUptime = computerUptime getTimeDependentColors() end end end local function correctDouble(number) return string.format("%.1f", number) end ---------------------------------------------------------------------------------------------------------------------------------- rayEngine.tileWidth = 32 function rayEngine.loadWorld(pathToWorldFolder) rayEngine.world = files.loadTableFromFile(pathToWorldFolder .. "/World.cfg") rayEngine.map = files.loadTableFromFile(pathToWorldFolder .. "/Map.cfg") rayEngine.player = files.loadTableFromFile(pathToWorldFolder .. "/Player.cfg") rayEngine.map.width = #rayEngine.map[1] rayEngine.map.height = #rayEngine.map rayEngine.player.position.x = rayEngine.tileWidth * rayEngine.player.position.x - rayEngine.tileWidth / 2 rayEngine.player.position.y = rayEngine.tileWidth * rayEngine.player.position.y - rayEngine.tileWidth / 2 getTimeDependentColors() rayEngine.world.dayNightCycle.lastComputerUptime = computer.uptime() rayEngine.distanceToProjectionPlane = (buffer.screen.width / 2) / math.tan(convertDegreesToRadians(rayEngine.player.fieldOfView / 2)) end ---------------------------------------------------------------------------------------------------------------------------------- local function convertWorldCoordsToMapCoords(x, y) return round(x / rayEngine.tileWidth), round(y / rayEngine.tileWidth) end local function getBlockCoordsByLook(distance) local radRotation = math.rad(rayEngine.player.rotation + 90) return convertWorldCoordsToMapCoords(rayEngine.player.position.x + distance * math.sin(radRotation) * rayEngine.tileWidth, rayEngine.player.position.y + distance * math.cos(radRotation) * rayEngine.tileWidth) end function rayEngine.move(distanceForward, distanceRight) local forwardRotation = math.rad(rayEngine.player.rotation + 90) local rightRotation = math.rad(rayEngine.player.rotation + 180) local xNew = rayEngine.player.position.x + distanceForward * math.sin(forwardRotation) + distanceRight * math.sin(rightRotation) local yNew = rayEngine.player.position.y + distanceForward * math.cos(forwardRotation) + distanceRight * math.cos(rightRotation) local xWorld, yWorld = convertWorldCoordsToMapCoords(xNew, yNew) if rayEngine.map[yWorld][xWorld] == nil then rayEngine.player.position.x, rayEngine.player.position.y = xNew, yNew end end function rayEngine.rotate(angle) rayEngine.player.rotation = constrainAngle(rayEngine.player.rotation + angle) end function rayEngine.destroy(distance) local xBlock, yBlock = getBlockCoordsByLook(distance) rayEngine.map[yBlock][xBlock] = nil end function rayEngine.place(distance, blockColor) local xBlock, yBlock = getBlockCoordsByLook(distance) rayEngine.map[yBlock][xBlock] = blockColor or 0x0 end ---------------------------------------------------------------------------------------------------------------------------------- local function drawFieldOfViewAngle(x, y, distance, color) local fieldOfViewHalf = rayEngine.player.fieldOfView / 2 local firstAngle, secondAngle = math.rad(-(rayEngine.player.rotation - fieldOfViewHalf + 90)), math.rad(-(rayEngine.player.rotation + fieldOfViewHalf + 90)) local xFirst, yFirst = math.floor(x + math.sin(firstAngle) * distance), math.floor(y + math.cos(firstAngle) * distance) local xSecond, ySecond = math.floor(x + math.sin(secondAngle) * distance), math.floor(y + math.cos(secondAngle) * distance) doubleHeight.line(x, y, xFirst, yFirst, color) doubleHeight.line(x, y, xSecond, ySecond, color) end function rayEngine.drawMap(x, y, width, height, transparency) buffer.square(x, y, width, height, 0x000000, 0x000000, " ", transparency) local xHalf, yHalf = math.floor(width / 2), math.floor(height / 2) local xMap, yMap = convertWorldCoordsToMapCoords(rayEngine.player.position.x, rayEngine.player.position.y) local xPos, yPos = x, y for i = yMap - yHalf + 1, yMap + yHalf + 1 do for j = xMap + xHalf + 1, xMap - xHalf + 1, -1 do if rayEngine.map[i] and rayEngine.map[i][j] then buffer.square(xPos, yPos, 1, 1, 0xEEEEEE) end xPos = xPos + 1 end xPos = x; yPos = yPos + 1 end --Поворот local xPlayer, yPlayer = x + xHalf, (y + yHalf) * 2 --Поле зрения drawFieldOfViewAngle(xPlayer, yPlayer, 5, 0xCCFFBF) --Игрок doubleHeight.set(xPlayer, yPlayer, 0x66FF40) --Инфа y = y + height buffer.square(x, y, width, 1, 0x000000, 0x000000, " ", transparency + 10) x = x + 1 buffer.text(x, y, 0xFFFFFF, "POS: " .. correctDouble(rayEngine.player.position.x) .. " x " .. correctDouble(rayEngine.player.position.y)) end ---------------------------------------------------------------------------------------------------------------------------------- local function hRaycast(player, angle) local rayInTop = math.sin( angle ) > 0 local tanAng = math.tan( angle ) local Ay = math.floor(rayEngine.player.position.y / rayEngine.tileWidth) * rayEngine.tileWidth; Ay = Ay + ( (rayInTop) and -1 or rayEngine.tileWidth ) local Ax = rayEngine.player.position.x + (rayEngine.player.position.y - Ay) / tanAng local Ya = (rayInTop) and -rayEngine.tileWidth or rayEngine.tileWidth local Xa = rayEngine.tileWidth / tanAng; Xa = Xa * ( (rayInTop) and 1 or -1 ) local x, y = math.floor(Ax / rayEngine.tileWidth), math.floor(Ay / rayEngine.tileWidth) while (rayEngine.map[y + 1] and not rayEngine.map[y + 1][x + 1]) do Ax = Ax + Xa; Ay = Ay + Ya if (Ax < 0 or Ax > rayEngine.tileWidth * rayEngine.map.width or Ay < 0 or Ay > rayEngine.tileWidth * rayEngine.map.height) then break end x, y = math.floor(Ax / rayEngine.tileWidth), math.floor(Ay / rayEngine.tileWidth) end return math.abs(rayEngine.player.position.x - Ax) / math.abs(math.cos( angle )) end local function vRaycast(player, angle) local rayInRight = math.cos( angle ) > 0 local tanAng = math.tan( angle ) local Bx = math.floor(rayEngine.player.position.x / rayEngine.tileWidth) * rayEngine.tileWidth; Bx = Bx + ( (rayInRight) and rayEngine.tileWidth or -1 ) local By = rayEngine.player.position.y + (rayEngine.player.position.x - Bx) * tanAng local Xa = (rayInRight) and rayEngine.tileWidth or -rayEngine.tileWidth local Ya = rayEngine.tileWidth * tanAng; Ya = Ya * ( (rayInRight) and -1 or 1 ) local x, y = math.floor(Bx / rayEngine.tileWidth), math.floor(By / rayEngine.tileWidth) while (rayEngine.map[y + 1] and not rayEngine.map[y + 1][x + 1]) do Bx = Bx + Xa; By = By + Ya if (Bx < 0 or Bx > rayEngine.tileWidth * rayEngine.map.width or By < 0 or By > rayEngine.tileWidth * rayEngine.map.height) then break end x, y = math.floor(Bx / rayEngine.tileWidth), math.floor(By / rayEngine.tileWidth) end return math.abs(rayEngine.player.position.y - By) / math.abs(math.sin( angle )) end function rayEngine.drawWorld() buffer.clear(rayEngine.world.colors.groundByTime) buffer.square(1, 1, buffer.screen.width, math.floor(buffer.screen.height / 2), rayEngine.world.colors.sky.current) local startColumn = rayEngine.player.rotation - (rayEngine.player.fieldOfView / 2) local endColumn = rayEngine.player.rotation + (rayEngine.player.fieldOfView / 2) local step = rayEngine.player.fieldOfView / buffer.screen.width local startX = 1 local distanceLimit = buffer.screen.height * 0.8 local hDist, vDist, dist, height, startY, tileColor for angle = startColumn, endColumn, step do hDist = hRaycast(player, convertDegreesToRadians(angle) ) vDist = vRaycast(player, convertDegreesToRadians(angle) ) -- local dist = math.min( hDist, vDist ) * math.cos( convertDegreesToRadians(angle) ) dist = math.min( hDist, vDist ) height = rayEngine.tileWidth / dist * rayEngine.distanceToProjectionPlane startY = buffer.screen.height / 2 - height / 2 + 1 --Рисуем сценку tileColor = height > distanceLimit and rayEngine.world.colors.tile.byTime[#rayEngine.world.colors.tile.byTime] or rayEngine.world.colors.tile.byTime[math.floor(#rayEngine.world.colors.tile.byTime * height / distanceLimit)] buffer.square(math.floor(startX), math.floor(startY), 1, height, tileColor, 0x000000, " ") -- buffer.square(math.floor(startX), math.floor(startY), 1, height, 0x000000, 0x000000, " ") startX = startX + 1 end doDayNightCycle() end function rayEngine.intro() local logo = image.fromString("17060000FF 0000FF 0000FF 0000FF 007EFF▄007EFF▄007EFF▄007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▄007EFF▄007EFF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 007EFF▄007EFF▀007EFF▀0000FF 0000FF 0000FF 0000FF 0053FF▄0053FF▀0053FF▀0053FF▀0053FF▄0000FF 0000FF 0000FF 0000FF 007EFF▀007EFF▀007EFF▄0000FF 0000FF 0000FF 007EFF▀007EFF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 530000 0000FF 0078FF▀0000FF 537800▀0078FF▀0078FF▀0078FF▀0078FF▀0078FF▀0078FF▀7E7800▀0078FF▀0000FF 0078FF▀0000FF 0000FF 007EFF▀007EFF▀007EFF▄007EFF▄007EFF▄0000FF 0000FF 0053FF▀0053FF▀0053FF▀0000FF 0000FF 007EFF▄007EFF▄007EFF▄007EFF▀007EFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 007EFFP007EFFo007EFFw007EFFe007EFFr007EFFe007EFFd0000FF 007EFFb007EFFy0000FF 007EFFR007EFFa007EFFy007EFFE007EFFn007EFFg007EFFi007EFFn007EFFe007EFF™0000FF 0000FF ") local x, y = math.floor(buffer.screen.width / 2 - logo.width / 2), math.floor(buffer.screen.height / 2 - logo.height / 2) local function draw(transparency) buffer.clear(0xF0F0F0); buffer.image(x, y, logo) buffer.square(1, 1, buffer.screen.width, buffer.screen.height, 0x000000, 0x000000, " ", transparency) buffer.draw() os.sleep(0) end for i = 20, 100, 20 do draw(i) end os.sleep(1.5) for i = 100, 20, -20 do draw(i) end end ---------------------------------------------------------------------------------------------------------------------------------- return rayEngine