Day 64-66

master
a327ex 2021-04-24 00:19:22 -03:00
parent 7e11fd0c0b
commit b1862400e8
52 changed files with 181 additions and 75 deletions

View File

@ -43,6 +43,7 @@ function Arena:on_enter(from, level, units, passives)
-- Spawn solids and player
self.x1, self.y1 = gw/2 - 0.8*gw/2, gh/2 - 0.8*gh/2
self.x2, self.y2 = gw/2 + 0.8*gw/2, gh/2 + 0.8*gh/2
self.w, self.h = self.x2 - self.x1, self.y2 - self.y1
self.spawn_points = {
{x = self.x1 + 32, y = self.y1 + 32, r = math.pi/4},
{x = self.x1 + 32, y = self.y2 - 32, r = -math.pi/4},
@ -89,7 +90,7 @@ function Arena:on_enter(from, level, units, passives)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
SpawnEffect{group = self.effects, x = gw/2, y = gh/2, action = function(x, y)
spawn1:play{pitch = random:float(0.8, 1.2), volume = 0.15}
self.boss = Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level, boss = boss_by_level[self.level]}
self.boss = Seeker{group = self.main, x = x, y = y, character = 'seeker', level = self.level, boss = level_to_boss[self.level]}
end}
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 1 end, function()
self.hfx:use('condition1', 0.25, 200, 10)
@ -103,7 +104,7 @@ function Arena:on_enter(from, level, units, passives)
end)
end)
end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() self.can_quit = true end)
self.t:every(function() return self.start_time <= 0 and #self.main:get_objects_by_classes(self.enemies) <= 0 end, function() self.can_quit = true end)
end)
else
-- Set win condition and enemy spawns
@ -305,9 +306,10 @@ function Arena:update(dt)
TransitionEffect{group = main.transitions, x = gw/2, y = gh/2, color = fg[0], transition_action = function()
slow_amount = 1
gold = 2
passives = {}
cascade_instance:stop()
main:add(BuyScreen'buy_screen')
main:go_to('buy_screen', 0, {})
main:go_to('buy_screen', 0, {}, passives)
end, text = Text({{text = '[wavy, bg]restarting...', font = pixul_font, alignment = 'center'}}, global_text_tags)}
end
@ -325,7 +327,7 @@ function Arena:update(dt)
end
self:update_game_object(dt*slow_amount)
-- cascade_instance.pitch = math.clamp(slow_amount*self.main_slow_amount, 0.05, 1)
cascade_instance.pitch = math.clamp(slow_amount*self.main_slow_amount, 0.05, 1)
self.floor:update(dt*slow_amount)
self.main:update(dt*slow_amount*self.main_slow_amount)
@ -388,6 +390,14 @@ function Arena:draw()
if self.choosing_passives then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end
self.ui:draw()
graphics.draw_with_mask(function()
star_canvas:draw(0, 0, 0, 1, 1)
end, function()
camera:attach()
graphics.rectangle(gw/2, gh/2, self.w, self.h, nil, nil, fg[0])
camera:detach()
end, true)
camera:attach()
if self.start_time and self.start_time > 0 and not self.choosing_passives then
graphics.push(gw/2, gh/2 - 48, 0, self.hfx.condition1.x, self.hfx.condition1.x)
@ -434,6 +444,7 @@ function Arena:draw()
end
end
camera:detach()
end
@ -589,23 +600,3 @@ function Arena:spawn_n_enemies(p, j, n)
end}
end, n, nil, 'spawn_enemies_' .. j)
end
Passives = Object:extend()
Passives:implement(GameObject)
function Passives:init(args)
self:init_game_object(args)
end
function Passives:update(dt)
self:update_game_object(dt)
end
function Passives:draw()
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@ -61,7 +61,7 @@ end
function BuyScreen:update(dt)
self:update_game_object(dt*slow_amount)
-- cascade_instance.pitch = 1
cascade_instance.pitch = 1
self.main:update(dt*slow_amount)
self.effects:update(dt*slow_amount)

View File

@ -1023,3 +1023,31 @@ thing, which should make it easier to work with. The rect cutting UI idea does s
| Spawning Pool | +1 critter health |
| Hive | +2 critter health |
| Void Rift | attacks by mages, nukers or voiders have a 20% chance to create a void rift on hit |
# Day 64-66 - 21-23/04/21
Lots of small fixes here and there and also made an attempt at getting the build accepted on Steam. It failed and now I'm waiting again until next week most likely since the guy didn't get to it by the end of Friday.
I also made some small graphical improvements to the game that I think will help. Now what I have left to do, in order of importance:
* General balance
* Enemies should have a chance to be spawned with a modifier as levels increase
* Every 3rd level should be wave only
* Remove "enemies killed" mode
* Balance playthroughs (record all balance playthroughs as they can be used for trailers)
* UI improvements
* Hover class highlight
* DPS list (right)
* HP list (bottom)
* Item list (left)
* Graphical improvements
* Further graphical improvements if there's time
* Trailers
* 3-4 pure gameplay playthroughs showcasing different builds
* Misc
* Better pause screen
* End screen
* Ascension mode (difficulty ramps up faster and goes higher than normal at the end)
* Music for bosses and shop
* Achievements
* Come up with a few new ones as I play the game more and balance the numbers
* Implement all the ones I already uploaded to Steam

View File

@ -395,7 +395,7 @@ function Seeker:hit(damage, projectile)
critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
trigger:after(0.01, function()
for i = 1, projectile.spawn_critters_on_hit do
Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 10, dmg = projectile.parent.dmg}
Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 10, dmg = projectile.parent.dmg, parent = projectile.parent}
end
end)
end
@ -448,7 +448,7 @@ function Seeker:hit(damage, projectile)
trigger:after(0.01, function()
critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
for i = 1, projectile.spawn_critters_on_kill do
Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 5, dmg = projectile.parent.dmg}
Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 5, dmg = projectile.parent.dmg, parent = projectile.parent}
end
end)
end
@ -457,7 +457,7 @@ function Seeker:hit(damage, projectile)
critter1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
trigger:after(0.01, function()
for i = 1, self.infested do
Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 10, dmg = self.infested_dmg}
Critter{group = main.current.main, x = self.x, y = self.y, color = orange[0], r = random:float(0, 2*math.pi), v = 10, dmg = self.infested_dmg, parent = projectile.parent}
end
end)
end

View File

@ -310,6 +310,19 @@ function graphics.set_stencil_test(...)
end
-- Draws the image masked by a shape, meaning that only parts inside (or outside) the shape that intersects the image are drawn.
-- By default only parts that intersect with the shape are drawn, pass the third argument as true to make it so that only parts that don't intersect are drawn.
-- action is a function that draws the image.
-- mask_action is a function that draws the shape.
function graphics.draw_with_mask(action, mask_action, invert_mask)
graphics.stencil(function() mask_action() end)
if not invert_mask then graphics.set_stencil_test('greater', 0)
else graphics.set_stencil_test('notequal', 1) end
action()
graphics.set_stencil_test()
end
local stencil_mask_shader = love.graphics.newShader[[
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 pc) {
vec4 t = Texel(texture, tc);
@ -320,7 +333,8 @@ vec4 effect(vec4 color, Image texture, vec2 tc, vec2 pc) {
}
]]
-- Draws the second image on top of the first but only the portions of it that aren't transparent are drawn.
-- Draws the second image on top of the first, but only the portions of the second image that aren't transparent are drawn.
-- This essentially applies the second image as a texture on top of the shape of the first.
-- action1 and action2 are functions that draw the images.
-- graphics.draw_intersection(function() player_image:draw(player.x, player.y) end, function() gradient_image:draw(player.x, player.y) end) -> draws the player with a gradient applied to it
function graphics.draw_intersection(action1, action2)

View File

@ -114,7 +114,7 @@ end
function system.save_state()
if not system.does_file_exist(love.filesystem.getSaveDirectory()) then love.filesystem.createDirectory("") end
binser.w(state_path, state)
binser.w(state_path, state or {})
end

View File

@ -5,6 +5,7 @@ require 'buy_screen'
require 'objects'
require 'player'
require 'enemies'
require 'media'
function init()
@ -16,8 +17,6 @@ function init()
input:bind('move_down', {'s', 'down'})
input:bind('enter', {'space', 'return'})
music.volume = 0
local s = {tags = {sfx}}
thunder1 = Sound('399656__bajko__sfx-thunder-blast.ogg', s)
flagellant1 = Sound('Whipping Horse 3.ogg', s)
@ -157,6 +156,7 @@ function init()
spawning_pool = Image('spawning_pool')
hive = Image('hive')
void_rift = Image('void_rift')
star = Image('star')
class_colors = {
['warrior'] = yellow[0],
@ -545,7 +545,7 @@ function init()
['corruptor'] = '[light_bg]Corruption',
['beastmaster'] = '[light_bg]Call of the Wild',
['launcher'] = '[light_bg]Kineticism',
['bard'] = "[red]The Bard'light_bgSong",
['bard'] = "[light_bg]The Bard's Song",
['assassin'] = '[light_bg]Toxic Delivery',
['host'] = '[light_bg]Invasion',
['carver'] = '[light_bg]World Tree',
@ -1046,41 +1046,22 @@ function init()
}
gold = 2
passives = {'void_rift'}
passives = {}
system.load_state()
main = Main()
main:add(BuyScreen'buy_screen')
main:go_to('buy_screen', 20, {
{character = 'spellblade', level = 3},
main:go_to('buy_screen', 0, {{character = 'cryomancer', level = 1}, {character = 'pyromancer', level = 1}}, passives)
--[[
{character = 'swordsman', level = 3},
{character = 'wizard', level = 3},
{character = 'scout', level = 3},
{character = 'archer', level = 3},
{character = 'host', level = 3},
{character = 'beastmaster', level = 3},
{character = 'corruptor', level = 3},
{character = 'flagellant', level = 3},
{character = 'psykino', level = 3},
{character = 'juggernaut', level = 3},
{character = 'vagrant', level = 3},
{character = 'stormweaver', level = 3},
]]--
}, passives)
--[[
main:add(Arena'arena')
main:go_to('arena', 3, {
{character = 'swordsman', level = 3},
{character = 'wizard', level = 3},
{character = 'scout', level = 3},
{character = 'archer', level = 3},
})
main:add(Media'media')
main:go_to('media')
]]--
end
function update(dt)
main:update(dt)
star_group:update(dt)
if input.n.pressed then
if sfx.volume == 0.5 then

34
media.lua 100644
View File

@ -0,0 +1,34 @@
Media = Object:extend()
Media:implement(State)
function Media:init(name)
self:init_state(name)
end
function Media:on_enter(from)
camera.x, camera.y = gw/2, gh/2
self.main = Group()
self.effects = Group()
self.ui = Group()
self.mode = 'achievements'
graphics.set_background_color(purple[0])
end
function Media:update(dt)
self.main:update(dt*slow_amount)
self.effects:update(dt*slow_amount)
self.ui:update(dt*slow_amount)
end
function Media:draw()
self.main:draw()
self.effects:draw()
self.ui:draw()
if self.mode == 'achievements' then
graphics.print_centered('Lv2', fat_font, 32, 32, 0, 1, 1, 0, 0, purple[-5])
end
end

View File

@ -1113,7 +1113,7 @@ function Player:shoot(r, mods)
if self.character == 'bard' then
self.bard_counter = self.bard_counter + 1
if self.bard_counter == 8 then
if self.bard_counter == 8 and self.level == 3 then
self.bard_counter = 0
bard2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
self.t:after(3, function()
@ -1691,7 +1691,9 @@ function DotArea:init(args)
hit2:play{pitch = random:float(0.8, 1.2), volume = 0.2}
if self.character == 'pyromancer' then
pyro1:play{pitch = random:float(1.5, 1.8), volume = 0.1}
enemy.pyrod = self
if self.level == 3 then
enemy.pyrod = self
end
end
enemy:hit((self.dot_dmg_m or 1)*self.dmg/5)
HitCircle{group = main.current.effects, x = enemy.x, y = enemy.y, rs = 6, color = fg[0], duration = 0.1}

View File

@ -17,6 +17,9 @@ function shared_init()
modal_transparent = Color(0.1, 0.1, 0.1, 0.6)
fg_transparent = Color(fg[0].r, fg[0].g, fg[0].b, 0.5)
bg_off = Color(47, 47, 47)
bg_gradient = GradientImage('vertical', Color(128, 128, 128, 0), Color(0, 0, 0, 0.3))
graphics.set_background_color(bg[0])
graphics.set_color(fg[0])
slow_amount = 1
@ -24,17 +27,50 @@ function shared_init()
sfx = SoundTag()
sfx.volume = 0.5
music = SoundTag()
music.volume = 0.5
music.volume = 0
fat_font = Font('FatPixelFont', 8)
pixul_font = Font('PixulBrush', 8)
background_canvas = Canvas(gw, gh)
main_canvas = Canvas(gw, gh, {stencil = true})
shadow_canvas = Canvas(gw, gh)
shadow_shader = Shader(nil, 'shadow.frag')
star_canvas = Canvas(gw, gh, {stencil = true})
star_group = Group()
local star_positions = {}
for i = -30, gh + 30, 15 do table.insert(star_positions, {x = -40, y = i}) end
for i = -30, gw, 15 do table.insert(star_positions, {x = i, y = gh + 40}) end
trigger:every(0.375, function()
local p = random:table(star_positions)
Star{group = star_group, x = p.x, y = p.y}
end)
end
function shared_draw(draw_action)
star_canvas:draw_to(function()
star_group:draw()
end)
background_canvas:draw_to(function()
camera:attach()
for i = 1, 32 do
for j = 1, 18 do
if j % 2 == 0 then
if i % 2 == 1 then
graphics.rectangle2(0 + (i-1)*22, 0 + (j-1)*22, 22, 22, nil, nil, bg_off)
end
else
if i % 2 == 0 then
graphics.rectangle2(0 + (i-1)*22, 0 + (j-1)*22, 22, 22, nil, nil, bg_off)
end
end
end
end
bg_gradient:draw(gw/2, gh/2, 480, 270)
camera:detach()
end)
main_canvas:draw_to(function()
draw_action()
if flashing then graphics.rectangle(gw/2, gh/2, gw, gh, nil, nil, flash_color) end
@ -47,6 +83,7 @@ function shared_draw(draw_action)
shadow_shader:unset()
end)
background_canvas:draw(0, 0, 0, sx, sy)
shadow_canvas:draw(6, 6, 0, sx, sy)
main_canvas:draw(0, 0, 0, sx, sy)
end
@ -54,6 +91,33 @@ end
Star = Object:extend()
Star:implement(GameObject)
Star:implement(Physics)
function Star:init(args)
self:init_game_object(args)
self.sx, self.sy = 0.35, 0.35
self.vr = 0
self.dvr = random:float(0, math.pi/4)
self.v = random:float(0.5, 0.7)
end
function Star:update(dt)
self:update_game_object(dt)
self.x = self.x + self.v*math.cos(-math.pi/4)
self.y = self.y + self.v*math.sin(-math.pi/4)
self.vr = self.vr + self.dvr*dt
end
function Star:draw()
star:draw(self.x, self.y, self.vr, self.sx, self.sy, 0, 0, bg[1])
end
SpawnEffect = Object:extend()
SpawnEffect:implement(GameObject)
function SpawnEffect:init(args)

20
todo
View File

@ -1,16 +1,12 @@
5. Stat and description details to each unit when hovering over it in the party section
6. Classes line on each unit's shop card, as well as on party section
Achievements
Hover class highlight
DPS list
Remove enemies killed mode
Make bosses have only wave mode
General balance
11. Steam integration: achievements, etc
12. Hovering over a party member should show which set they belong to and vice-versa
13. Show a unit DPS list like Underlord's to the right side of the screen
15. GO button is grayed out and thus doesn't say it's meant to be clicked on
16. 28/20 enemies or 4/3 wave confuses players and makes them think the level goals are bugged
17. Music for first 9-15 levels should be calm rather than upbeat
Engine improvements for after SNKRX release
Node refactor: described partly somewhere in the devlog
Rewrite SNKRX: using this game as a target for the node refactor will yield good results for both the refactor and future SNKRX updates, if any
on_hit:
on_collision_enter/exit are automatically called and automatically call on_hit/on_leave for each object
This enables the definition of on_hit on each object and the question of where the logic should stay is solved/dodged
@ -18,7 +14,3 @@ Engine improvements for after SNKRX release
Defining on_hit on the Wall creation call for that specific Wall, and thus that specific Wall will have this behavior while other walls won't
https://i.imgur.com/asPdpnQ.png ?
Not sure if I should go all the way with event systems like this or only have it work for specific cases, need to think about it more
release tool:
Build a command line tool that creates projects and builds them up for release automatically on Windows, Web and Steam
All the steps for this are listed on the readme and everything about it that can be automated should
Ideally the user should run one command for creating a project and one for building it for each platform