Hi,
So, a few pointers:
1. On weather
The rain is centered around the player. It's spawned on a per-tile basis, based on the weatherRadius parameter in the defineAmbiance. So if it's 2, you'll get a 5x5 area, with 3, a 7x7 area, and so on. When the party moves, a new row of fx are spawned at the end of the "weather radius square" in that direction, and the fx are destroyed on the back row of where the party moved from.
Code for moving wheater elements:
Code: Select all
function moveWeather(dir)
local dx, dy = getForward(dir)
local r = getAmbiance().weatherRadius
for w = 0, r * 2 do
wX = party.x - r * dx + (w - 2) * dy
wY = party.y - r * dy + (w - 2) * dx
if weatherObjects[wX.."."..wY] then
local weather = findEntity(weatherObjects[wX.."."..wY])
if weather then
weather:destroy()
end
weatherObjects[wX.."."..wY] = nil
end
wX = party.x + (r + 1) * dx + (w - 2) * dy
wY = party.y + (r + 1) * dy + (w - 2) * dx
spawnWeather(party.level, wX, wY)
end
end
Wether FX are not spawned on wall tiles, or on "interior tiles". Interior tiles are automatically detected based on the presence of a roof object.
The system is flexible and will allow us to add snow, for example, or just simple fog, or whatever. For the rain you see, I made a custom 16 frame particle animation with photoshop, and Neikun tweaked it further.
2. On skybox
A lot of the techniques on the forum people tried were putting big elements far away and using negative depthBias to bring them to view. This caused a lot of clipping issues. The breakthrough was when I tought of doing the opposite: using a small element near the party, with
positive depthBias to have it drawn behind other geometry. So the skySphere here is a unified color sphere around the party, about 1 tile big. It is a projectile, and moves at the speed of the party (thanks pferguso for pioneering that techinque). As it's a unique color (blue/grey) it doesn't matter if it's not perfectly in sync with the party. Actually what happens is that the first projectile is destroyed, then one shot in the direction, then when the party arrives, that moving projectile is destroyed and another static one is generated.
Code for creating/moving the skySphere:
Code: Select all
function createSkySphere(level, x, y)
if getAmbiance().skySphere then
skySphereId = shootProjectileWithId(getAmbiance().skySphere, level, x, y, 0, 0, 0, 0, 0, 3, 0, 1, party, true)
end
end
function destroySkySphere()
if skySphereId and findEntity(skySphereId) then
findEntity(skySphereId):destroy()
end
end
function moveSkySphere(dir)
local dx, dy = getForward(dir)
local newX = party.x+dx
local newY = party.y+dy
destroySkySphere()
skySphereId = shootProjectileWithId(getAmbiance().skySphere, party.level, party.x, party.y, dir, 6.7, 0, 0, -dx*0.6, 3, dy*0.6, 1, party, true)
delay(0.5,
function(self, newX, newY)
if skySphereId then
findEntity(skySphereId):destroy()
skyScript.setSkySphereId(nil)
end
createSkySphere(party.level, newX, newY)
end,
{newX, newY}
)
end
function setSkySphereId(id)
skySphereId = id
end
3. On clouds
Clouds are large textured planes (about 20x20 tiles) which are created as static projectiles. They have a very slow rotation in their definition, which creates the moving cloud effect. The cloudsFrequence parameter determines how many clouds to create, but they are dynamically spawned with some randomness (on x, y & z axis), so you never get the same cloud coverage in a scene.
Code for creating clouds:
Code: Select all
function createClouds()
local cloudsGrid = math.ceil(getAmbiance().cloudsFrequence)
for x = 0, cloudsGrid do
for y = 0, cloudsGrid do
local cloudsInterval = math.ceil(32/cloudsGrid)
local cloudsX = clampCoord(x * cloudsInterval + math.random(5) - 3)
local cloudsY = clampCoord(y * cloudsInterval + math.random(5) - 3)
local clouds = shootProjectileWithId(getAmbiance().clouds, party.level, cloudsX, cloudsY, 2, 0, 0, 0, 0, 9 + math.random() * 2, 0, 1, party, true)
table.insert(skyClouds,clouds)
end
end
end
4. On global light
The global light is an fx high above, slightly off the party (in a diagonal adjacent square), with a great range. The fx is animated in sync with party movement.
Code for moving light with party:
Code: Select all
function moveSkyLight(dir)
local dx, dy = getForward(dir)
local interval = 0.3
dx = dx * interval
dy = dy * interval
local fxTimer = timers:create(randomTimerId("lightTimer"))
fxTimer:setTimerInterval(0.05)
fxTimer:setTickLimit(10, true)
fxTimer:addCallback(
function(self, dx, dy)
local skyLight = findEntity("skyLight")
if skyLight then
skyLight:translate(dx,0,-dy)
end
end,
{dx, dy}
)
fxTimer:activate()
end
5. On stars
Stars are a particle field, about 6x6x5 tiles wide. It's spawned above the player, with positive depthBias to be rendered behind clouds and architecture. There are some custom particles in there.
All the ambiances are switchable with setAmbiance(ambiance). For example, setAmbiance("day") or setAmbiance("night"). You see how it switches instantly in the video.
When ready (there's still a few issues to fix) we'll release this as standalone so that community can use this ambiance system.
EDIT: 6. On Projectiles
Aa you can see, I have a custom function for shooting projectiles that returns the id of the created projectile. Very useful for managing them afterwards. It scans the tile for existing items with a numerical (random) id, shoots the projectile, then rescans the tile for items with a numerical (random) id and returns the new one that just appeared. Here's the function:
Code: Select all
function shootProjectileWithId(projName,level,x,y,dir,speed,gravity,velocityUp,offsetX,offsetY,offsetZ,attackPower, ignoreEntity,fragile,championOrdinal)
local pIds = {}
for i in entitiesAt(level, x, y) do
if i.class == "Item" and string.find(i.id, "^%d+$") then
pIds[i.id] = i.name
end
end
shootProjectile(projName,level,x,y,dir,speed,gravity,velocityUp,offsetX,offsetY,offsetZ,attackPower, ignoreEntity,fragile,championOrdinal)
for i in entitiesAt(level, x, y) do
if i.class == "Item" and string.find(i.id, "^%d+$") and not(pIds[i.id]) then
return i.id
end
end
end