- shadows demo
- scaling - math fix - refactoring
5
.vscode/settings.json
vendored
@ -9,5 +9,8 @@
|
||||
"Lua.runtime.special": {
|
||||
"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,
|
||||
quads = nil,
|
||||
fps = 12,
|
||||
currentTime = 0
|
||||
}
|
||||
|
||||
function Animation(image, width, height)
|
||||
@ -21,16 +20,8 @@ function Animation(image, width, height)
|
||||
return setmetatable(animation, { __index = __Animation })
|
||||
end
|
||||
|
||||
function __Animation:getQuad()
|
||||
local frametime = 1 / self.fps
|
||||
local frame = math.floor(self.currentTime / frametime)
|
||||
function __Animation:getQuad(t)
|
||||
local duration = #self.quads / self.fps
|
||||
local frame = math.floor(t * duration * #self.quads)
|
||||
return self.quads[frame + 1]
|
||||
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")
|
||||
return
|
||||
Some(img)
|
||||
elseif (ext == "glsl") then
|
||||
return Some(love.graphics.newShader(path));
|
||||
end
|
||||
|
||||
return None
|
||||
end
|
||||
|
||||
|
||||
@ -9,7 +9,8 @@ __Entity = {
|
||||
rotation = 0, -- clockwise radians
|
||||
friction = 0.98,
|
||||
speed = 3, -- m/s
|
||||
rotation_speed = 1 --rad/sec
|
||||
rotation_speed = 1, --rad/sec
|
||||
state = "walk"
|
||||
}
|
||||
|
||||
function Entity(id)
|
||||
@ -23,8 +24,6 @@ end
|
||||
function __Entity:update(dt)
|
||||
self:processMovement()
|
||||
if self.sprite then
|
||||
local dir = self:namedDirection()
|
||||
self.sprite.playing = "walk_" .. dir
|
||||
self.sprite:update(dt)
|
||||
end
|
||||
end
|
||||
@ -35,29 +34,12 @@ function __Entity:processMovement()
|
||||
self.rotation = self.velocity:direction()
|
||||
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)
|
||||
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
|
||||
|
||||
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'
|
||||
|
||||
|
||||
__AnimatedSprite = {}
|
||||
__AnimatedSprite = {
|
||||
t = 0,
|
||||
}
|
||||
|
||||
function AnimatedSprite(id)
|
||||
local table = {}
|
||||
local bundle = AssetBundle.files.sprites[id]
|
||||
for key, value in pairs(bundle) do
|
||||
table[key] = Animation(value, 96, 96)
|
||||
table[key] = Animation(value, value:getHeight(), value:getHeight())
|
||||
end
|
||||
|
||||
return setmetatable(table, { __index = __AnimatedSprite })
|
||||
end
|
||||
|
||||
function __AnimatedSprite:update(dt)
|
||||
if self.playing then
|
||||
self[self.playing]:update(dt)
|
||||
end
|
||||
self.t = (self.t + dt) % 1
|
||||
end
|
||||
|
||||
function __AnimatedSprite:getQuad()
|
||||
if self.playing then
|
||||
return self[self.playing]:getQuad()
|
||||
function __AnimatedSprite:getQuad(key)
|
||||
if key then
|
||||
return self[key]:getQuad(self.t)
|
||||
end
|
||||
return love.graphics.newQuad(0, 0, 235, 235, AssetBundle.files.sprites.fallback)
|
||||
end
|
||||
|
||||
function __AnimatedSprite:getTexture()
|
||||
if self.playing then
|
||||
return self[self.playing].spriteSheet
|
||||
function __AnimatedSprite:getTexture(key)
|
||||
if key then
|
||||
return self[key].spriteSheet
|
||||
end
|
||||
return AssetBundle.files.sprites.fallback
|
||||
end
|
||||
|
||||
@ -52,7 +52,7 @@ function __Vec3:normalize()
|
||||
end
|
||||
|
||||
function __Vec3:direction()
|
||||
return -math.atan2(self.y, self.x)
|
||||
return math.atan2(self.y, self.x)
|
||||
end
|
||||
|
||||
function __Vec3:dot(other)
|
||||
@ -62,3 +62,7 @@ end
|
||||
function __Vec3:__tostring()
|
||||
return "Vec3{" .. self.x .. ", " .. self.y .. ", " .. self.z .. "}"
|
||||
end
|
||||
|
||||
function __Vec3:angle_to(other)
|
||||
return (other - self):direction()
|
||||
end
|
||||
|
||||
59
main.lua
@ -1,12 +1,18 @@
|
||||
require "lib.asset_bundle"
|
||||
require "lib.player"
|
||||
require "lib.math2"
|
||||
|
||||
PIXEL_PER_METER = 48
|
||||
|
||||
P = nil
|
||||
|
||||
function love.load()
|
||||
AssetBundle:load()
|
||||
love.window.setMode(1080, 720, { resizable = true, msaa = 4 })
|
||||
|
||||
P = Player('fox')
|
||||
P.entity.position = Vec3 { 200, 200 }
|
||||
print('ready')
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
@ -14,11 +20,58 @@ function love.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.draw(P.entity.sprite:getTexture(), P.entity.sprite:getQuad(), P.entity.position.x, P.entity.position.y,
|
||||
P.entity.direction,
|
||||
2, 2, 48, 48)
|
||||
love.graphics.clear(1, 1, 1)
|
||||
local spriteCanvas = love.graphics.newCanvas()
|
||||
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
|
||||
|
||||
function love.conf(t)
|
||||
t.console = true
|
||||
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
|
||||
|
||||