- shadows demo
- scaling - math fix - refactoring
5
.vscode/settings.json
vendored
@ -9,5 +9,8 @@
|
|||||||
"Lua.runtime.special": {
|
"Lua.runtime.special": {
|
||||||
"love.filesystem.load": "loadfile"
|
"love.filesystem.load": "loadfile"
|
||||||
},
|
},
|
||||||
"Lua.workspace.checkThirdParty": false
|
"Lua.workspace.checkThirdParty": false,
|
||||||
|
"Lua.diagnostics.disable": [
|
||||||
|
"undefined-field"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
25
assets/shaders/shadow.glsl
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
extern vec2 light_dir; // направление света, нормализованное
|
||||||
|
extern float flatten; // насколько тень сплющена (0.2 - норм)
|
||||||
|
extern float softness; // степень мягкости по краям
|
||||||
|
|
||||||
|
vec4 effect(vec4 color, Image tex, vec2 tex_coords, vec2 screen_coords)
|
||||||
|
{
|
||||||
|
// Получаем оригинальный цвет спрайта
|
||||||
|
vec4 tex_color = Texel(tex, tex_coords);
|
||||||
|
|
||||||
|
// Преобразуем цвет в "тень"
|
||||||
|
float alpha = tex_color.a;
|
||||||
|
vec3 shadow_color = vec3(0.0); // черная тень
|
||||||
|
|
||||||
|
// Модифицируем UV, чтобы проецировать вниз и вбок
|
||||||
|
vec2 offset = tex_coords;
|
||||||
|
offset.y -= tex_coords.y * flatten; // сплющиваем вниз
|
||||||
|
offset.x += tex_coords.y * flatten * light_dir.x; // сдвигаем в сторону
|
||||||
|
|
||||||
|
vec4 proj = Texel(tex, offset);
|
||||||
|
|
||||||
|
// Мягкость края тени (можно отключить, если не нужно)
|
||||||
|
float edge_fade = smoothstep(0.0, softness, proj.a);
|
||||||
|
|
||||||
|
return vec4(shadow_color, proj.a * edge_fade * 0.6); // альфа настраивается
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 26 KiB |
@ -2,7 +2,6 @@ local __Animation = {
|
|||||||
spriteSheet = nil,
|
spriteSheet = nil,
|
||||||
quads = nil,
|
quads = nil,
|
||||||
fps = 12,
|
fps = 12,
|
||||||
currentTime = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Animation(image, width, height)
|
function Animation(image, width, height)
|
||||||
@ -21,16 +20,8 @@ function Animation(image, width, height)
|
|||||||
return setmetatable(animation, { __index = __Animation })
|
return setmetatable(animation, { __index = __Animation })
|
||||||
end
|
end
|
||||||
|
|
||||||
function __Animation:getQuad()
|
function __Animation:getQuad(t)
|
||||||
local frametime = 1 / self.fps
|
local duration = #self.quads / self.fps
|
||||||
local frame = math.floor(self.currentTime / frametime)
|
local frame = math.floor(t * duration * #self.quads)
|
||||||
return self.quads[frame + 1]
|
return self.quads[frame + 1]
|
||||||
end
|
end
|
||||||
|
|
||||||
function __Animation:update(dt)
|
|
||||||
self.currentTime = (self.currentTime + dt) % (#self.quads / self.fps)
|
|
||||||
end
|
|
||||||
|
|
||||||
function __Animation:reset()
|
|
||||||
self.currentTime = 0
|
|
||||||
end
|
|
||||||
|
|||||||
@ -43,7 +43,10 @@ function AssetBundle.loadFile(path)
|
|||||||
img:setFilter("nearest", "nearest")
|
img:setFilter("nearest", "nearest")
|
||||||
return
|
return
|
||||||
Some(img)
|
Some(img)
|
||||||
|
elseif (ext == "glsl") then
|
||||||
|
return Some(love.graphics.newShader(path));
|
||||||
end
|
end
|
||||||
|
|
||||||
return None
|
return None
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,11 @@ __Entity = {
|
|||||||
sprite = __AnimatedSprite,
|
sprite = __AnimatedSprite,
|
||||||
position = Vec3 {},
|
position = Vec3 {},
|
||||||
velocity = Vec3 {},
|
velocity = Vec3 {},
|
||||||
rotation = 0, -- clockwise radians
|
rotation = 0, -- clockwise radians
|
||||||
friction = 0.98,
|
friction = 0.98,
|
||||||
speed = 3, -- m/s
|
speed = 3, -- m/s
|
||||||
rotation_speed = 1 --rad/sec
|
rotation_speed = 1, --rad/sec
|
||||||
|
state = "walk"
|
||||||
}
|
}
|
||||||
|
|
||||||
function Entity(id)
|
function Entity(id)
|
||||||
@ -23,8 +24,6 @@ end
|
|||||||
function __Entity:update(dt)
|
function __Entity:update(dt)
|
||||||
self:processMovement()
|
self:processMovement()
|
||||||
if self.sprite then
|
if self.sprite then
|
||||||
local dir = self:namedDirection()
|
|
||||||
self.sprite.playing = "walk_" .. dir
|
|
||||||
self.sprite:update(dt)
|
self.sprite:update(dt)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -35,29 +34,12 @@ function __Entity:processMovement()
|
|||||||
self.rotation = self.velocity:direction()
|
self.rotation = self.velocity:direction()
|
||||||
end
|
end
|
||||||
|
|
||||||
function __Entity:namedDirection()
|
|
||||||
local get_direction_index = function(rotation)
|
|
||||||
local pi = math.pi
|
|
||||||
rotation = rotation % (2 * pi)
|
|
||||||
local shifted = (rotation + pi / 8) % (2 * pi)
|
|
||||||
local index = math.floor(shifted / (pi / 4)) + 1
|
|
||||||
return index
|
|
||||||
end
|
|
||||||
|
|
||||||
local lookup = {
|
|
||||||
"e",
|
|
||||||
"ne",
|
|
||||||
"n",
|
|
||||||
"nw",
|
|
||||||
"w",
|
|
||||||
"sw",
|
|
||||||
"s",
|
|
||||||
"se",
|
|
||||||
}
|
|
||||||
|
|
||||||
return lookup[get_direction_index(self.rotation)]
|
|
||||||
end
|
|
||||||
|
|
||||||
function __Entity:lookAt(vec)
|
function __Entity:lookAt(vec)
|
||||||
self.rotation = (vec - self.position):direction()
|
self.rotation = self.position:angle_to(vec)
|
||||||
|
end
|
||||||
|
|
||||||
|
function __Entity:spriteFromAngle(angle)
|
||||||
|
local dir = math.named_direction(angle - self.rotation)
|
||||||
|
local key = self.state .. "_" .. dir
|
||||||
|
return self.sprite:getTexture(key), self.sprite:getQuad(key)
|
||||||
end
|
end
|
||||||
|
|||||||
22
lib/math2.lua
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
math.named_direction = function(rotation)
|
||||||
|
local get_direction_index = function(rotation)
|
||||||
|
local pi = math.pi
|
||||||
|
rotation = rotation % (2 * pi)
|
||||||
|
local shifted = (rotation + pi / 8) % (2 * pi)
|
||||||
|
local index = math.floor(shifted / (pi / 4)) + 1
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
|
||||||
|
local lookup = {
|
||||||
|
"s",
|
||||||
|
"se",
|
||||||
|
"e",
|
||||||
|
"ne",
|
||||||
|
"n",
|
||||||
|
"nw",
|
||||||
|
"w",
|
||||||
|
"sw",
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookup[get_direction_index(rotation)]
|
||||||
|
end
|
||||||
@ -2,34 +2,34 @@ require 'lib.asset_bundle'
|
|||||||
require 'lib.animation'
|
require 'lib.animation'
|
||||||
|
|
||||||
|
|
||||||
__AnimatedSprite = {}
|
__AnimatedSprite = {
|
||||||
|
t = 0,
|
||||||
|
}
|
||||||
|
|
||||||
function AnimatedSprite(id)
|
function AnimatedSprite(id)
|
||||||
local table = {}
|
local table = {}
|
||||||
local bundle = AssetBundle.files.sprites[id]
|
local bundle = AssetBundle.files.sprites[id]
|
||||||
for key, value in pairs(bundle) do
|
for key, value in pairs(bundle) do
|
||||||
table[key] = Animation(value, 96, 96)
|
table[key] = Animation(value, value:getHeight(), value:getHeight())
|
||||||
end
|
end
|
||||||
|
|
||||||
return setmetatable(table, { __index = __AnimatedSprite })
|
return setmetatable(table, { __index = __AnimatedSprite })
|
||||||
end
|
end
|
||||||
|
|
||||||
function __AnimatedSprite:update(dt)
|
function __AnimatedSprite:update(dt)
|
||||||
if self.playing then
|
self.t = (self.t + dt) % 1
|
||||||
self[self.playing]:update(dt)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function __AnimatedSprite:getQuad()
|
function __AnimatedSprite:getQuad(key)
|
||||||
if self.playing then
|
if key then
|
||||||
return self[self.playing]:getQuad()
|
return self[key]:getQuad(self.t)
|
||||||
end
|
end
|
||||||
return love.graphics.newQuad(0, 0, 235, 235, AssetBundle.files.sprites.fallback)
|
return love.graphics.newQuad(0, 0, 235, 235, AssetBundle.files.sprites.fallback)
|
||||||
end
|
end
|
||||||
|
|
||||||
function __AnimatedSprite:getTexture()
|
function __AnimatedSprite:getTexture(key)
|
||||||
if self.playing then
|
if key then
|
||||||
return self[self.playing].spriteSheet
|
return self[key].spriteSheet
|
||||||
end
|
end
|
||||||
return AssetBundle.files.sprites.fallback
|
return AssetBundle.files.sprites.fallback
|
||||||
end
|
end
|
||||||
|
|||||||
@ -52,7 +52,7 @@ function __Vec3:normalize()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function __Vec3:direction()
|
function __Vec3:direction()
|
||||||
return -math.atan2(self.y, self.x)
|
return math.atan2(self.y, self.x)
|
||||||
end
|
end
|
||||||
|
|
||||||
function __Vec3:dot(other)
|
function __Vec3:dot(other)
|
||||||
@ -62,3 +62,7 @@ end
|
|||||||
function __Vec3:__tostring()
|
function __Vec3:__tostring()
|
||||||
return "Vec3{" .. self.x .. ", " .. self.y .. ", " .. self.z .. "}"
|
return "Vec3{" .. self.x .. ", " .. self.y .. ", " .. self.z .. "}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function __Vec3:angle_to(other)
|
||||||
|
return (other - self):direction()
|
||||||
|
end
|
||||||
|
|||||||
59
main.lua
@ -1,12 +1,18 @@
|
|||||||
require "lib.asset_bundle"
|
require "lib.asset_bundle"
|
||||||
require "lib.player"
|
require "lib.player"
|
||||||
|
require "lib.math2"
|
||||||
|
|
||||||
|
PIXEL_PER_METER = 48
|
||||||
|
|
||||||
P = nil
|
P = nil
|
||||||
|
|
||||||
function love.load()
|
function love.load()
|
||||||
AssetBundle:load()
|
AssetBundle:load()
|
||||||
|
love.window.setMode(1080, 720, { resizable = true, msaa = 4 })
|
||||||
|
|
||||||
P = Player('fox')
|
P = Player('fox')
|
||||||
|
P.entity.position = Vec3 { 200, 200 }
|
||||||
|
print('ready')
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.update(dt)
|
function love.update(dt)
|
||||||
@ -14,11 +20,58 @@ function love.update(dt)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function love.draw()
|
function love.draw()
|
||||||
love.graphics.draw(P.entity.sprite:getTexture(), P.entity.sprite:getQuad(), P.entity.position.x, P.entity.position.y,
|
love.graphics.clear(1, 1, 1)
|
||||||
P.entity.direction,
|
local spriteCanvas = love.graphics.newCanvas()
|
||||||
2, 2, 48, 48)
|
spriteCanvas:setFilter("linear", "linear")
|
||||||
|
|
||||||
|
love.graphics.setCanvas(spriteCanvas)
|
||||||
|
love.graphics.clear(1, 1, 1)
|
||||||
|
|
||||||
|
drawShadow(P.entity, Vec3 { 200, 200 }, 200)
|
||||||
|
drawShadow(P.entity, Vec3 { 300, 200 }, 200)
|
||||||
|
drawShadow(P.entity, Vec3 { 400, 200 }, 200)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 0, 0, 1)
|
||||||
|
love.graphics.circle("fill", 200, 200, 10)
|
||||||
|
love.graphics.circle("fill", 300, 200, 10)
|
||||||
|
love.graphics.circle("fill", 400, 200, 10)
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
|
||||||
|
local tex, quad = P.entity:spriteFromAngle(math.pi / 2)
|
||||||
|
love.graphics.draw(tex, quad, P.entity.position.x, P.entity.position.y,
|
||||||
|
0,
|
||||||
|
1, 1, tex:getHeight() / 2, tex:getHeight())
|
||||||
|
|
||||||
|
love.graphics.setCanvas()
|
||||||
|
|
||||||
|
local scale = math.min(love.graphics.getDimensions()) / (15 * PIXEL_PER_METER);
|
||||||
|
love.graphics.draw(spriteCanvas, 0, 0, 0, scale, scale)
|
||||||
|
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.conf(t)
|
function love.conf(t)
|
||||||
t.console = true
|
t.console = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function drawShadow(entity, light, radius)
|
||||||
|
local shadow_vec = light - entity.position
|
||||||
|
local dist = shadow_vec:length()
|
||||||
|
if dist > radius then return end
|
||||||
|
|
||||||
|
love.graphics.setColor(0, 0, 0, math.min(0.5, 1 - (dist / radius)))
|
||||||
|
local tex, quad = P.entity:spriteFromAngle(shadow_vec:direction())
|
||||||
|
love.graphics.draw(tex, quad, P.entity.position.x, P.entity.position.y - 10,
|
||||||
|
shadow_vec:direction() - math.pi / 2,
|
||||||
|
1, math.sin(20), tex:getHeight() / 2, tex:getHeight())
|
||||||
|
|
||||||
|
-- love.graphics.push()
|
||||||
|
-- love.graphics.setColor(0, 0, 0, 0.5)
|
||||||
|
-- love.graphics.translate(entity.position.x, entity.position.y)
|
||||||
|
-- love.graphics.scale(1, 0.342)
|
||||||
|
-- love.graphics.circle("fill", 0, 0, 20)
|
||||||
|
-- love.graphics.pop()
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 1) -- сброс цвета
|
||||||
|
end
|
||||||
|
|||||||