master
a327ex 2021-03-24 02:57:26 -03:00
parent bafd765ab3
commit 59a62a2ece
29 changed files with 224 additions and 117 deletions

216
arena.lua
View File

@ -12,6 +12,7 @@ function Arena:on_enter(from, level, units)
self.hfx:add('condition2', 1)
self.level = level or 1
self.units = units
self.logo = true
self.floor = Group()
self.main = Group():set_as_physics_world(32, 0, 0, {'player', 'enemy', 'projectile', 'enemy_projectile'})
@ -59,114 +60,128 @@ function Arena:on_enter(from, level, units)
for i, unit in ipairs(units) do
if i == 1 then
self.player = Player{group = self.main, x = gw/2, y = gh/2, leader = true, character = unit.character, level = unit.level}
self.player = Player{group = self.main, x = gw/2, y = gh/2 + 16, leader = true, character = unit.character, level = unit.level}
else
self.player:add_follower(Player{group = self.main, character = unit.character, level = unit.level})
end
end
-- Set win condition and enemy spawns
self.win_condition = random:table{'time', 'enemy_kill', 'wave'}
if self.win_condition == 'wave' then
self.level_to_max_waves = {
1, 2, random:int(2, 3),
3, 3, 3, random:int(3, 4),
4, 4, 4, 4, random:int(4, 5),
5, 5, 5, 5, 5, random:int(5, 6),
6, 7, 8, 9, 9, 10, 12
}
self.max_waves = self.level_to_max_waves[self.level]
self.wave = 0
self.start_time = 3
self.t:after(1, function()
self.t:every(1, function()
if self.start_time > 1 then alert1:play{volume = 0.5} end
self.start_time = self.start_time - 1
self.hfx:use('condition1', 0.25, 200, 10)
end, 3, function()
alert1:play{pitch = 1.2, volume = 0.5}
camera:shake(4, 0.25)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 end, function()
self.wave = self.wave + 1
if self.wave > self.max_waves then return end
self.hfx:use('condition1', 0.25, 200, 10)
self.hfx:pull('condition2', 0.0625)
self.t:after(0.5, function()
local spawn_type = random:table{'left', 'middle', 'right'}
local spawn_points = {left = {x = self.x1 + 32, y = gh/2}, middle = {x = gw/2, y = gh/2}, right = {x = self.x2 - 32, y = gh/2}}
self:spawn_n_enemies(spawn_points[spawn_type], nil, 8 + (self.wave-1)*2)
end)
end, self.max_waves+1)
end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.wave > self.max_waves end, function() self.can_quit = true end)
end)
if self.level == 1000 then
self.level_1000_text = Text2{group = self.ui, x = gw/2, y = gh/2, lines = {{text = '[fg, wavy_mid]SNKRX', font = fat_font, alignment = 'center'}}}
-- self.level_1000_text2 = Text2{group = self.ui, x = gw/2, y = gh/2 + 64, lines = {{text = '[fg, wavy_mid]SNKRX', font = pixul_font, alignment = 'center'}}}
-- Wall{group = self.main, vertices = math.to_rectangle_vertices(gw/2 - 0.45*self.level_1000_text.w, gh/2 - 0.3*self.level_1000_text.h, gw/2 + 0.45*self.level_1000_text.w, gh/2 - 3), snkrx = true, color = bg[-1]}
elseif self.win_condition == 'enemy_kill' then
self.level_to_enemies_to_kill = {
8, 12, random:int(14, 16),
16, 16, 18, random:int(18, 20),
20, 20, 20, 20, random:int(20, 22),
22, 22, 22, 22, 22, random:int(22, 24),
24, 26, 28, 30, 30, 32, 40
}
self.enemies_killed = 0
self.enemies_to_kill = self.level_to_enemies_to_kill[self.level]
self.enemy_spawn_delay = 8
self.enemies_spawned = 0
self.start_time = 3
self.t:after(1, function()
self.t:every(1, function()
if self.start_time > 1 then alert1:play{volume = 0.5} end
self.start_time = self.start_time - 1
self.hfx:use('condition1', 0.25, 200, 10)
end, 3, function()
alert1:play{pitch = 1.2, volume = 0.5}
camera:shake(4, 0.25)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
self:spawn_distributed_enemies()
self.t:every(2, function()
if love.timer.getTime() - self.last_spawn_enemy_time >= self.enemy_spawn_delay and #self.main:get_objects_by_class(self.enemies) < self.enemies_to_kill and not self.transitioning then
self:spawn_distributed_enemies()
end
end, nil, nil, 'spawn_enemies')
end)
end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.enemies_killed >= self.enemies_to_kill end, function() self.can_quit = true end)
elseif self.win_condition == 'time' then
self.level_to_time_left = {
20, 20, random:int(20, 25),
25, 25, 25, random:int(25, 30),
30, 30, 30, 30, random:int(30, 35),
35, 35, 35, 35, 35, random:int(35, 40),
40, 45, 50, 55, 55, 60, 80
}
self.time_left = self.level_to_time_left[self.level]
self.start_time = 3
self.t:after(1, function()
self.t:every(1, function()
if self.start_time > 1 then alert1:play{volume = 0.5} end
self.start_time = self.start_time - 1
self.hfx:use('condition1', 0.25, 200, 10)
end, 3, function()
alert1:play{pitch = 1.2, volume = 0.5}
camera:shake(4, 0.25)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
else
-- Set win condition and enemy spawns
self.win_condition = random:table{'time', 'enemy_kill', 'wave'}
if self.level == 18 and self.trailer then self.win_condition = 'wave' end
if self.win_condition == 'wave' then
self.level_to_max_waves = {
1, 2, random:int(2, 3),
3, 3, 3, random:int(3, 4),
4, 4, 4, 4, random:int(4, 5),
5, 5, 5, 5, 5, random:int(5, 6),
6, 7, 8, 9, 9, 10, 12
}
self.max_waves = self.level_to_max_waves[self.level]
self.wave = 0
self.start_time = 3
self.t:after(1, function()
self.t:every(1, function()
self.time_left = self.time_left - 1
if self.start_time > 1 then alert1:play{volume = 0.5} end
self.start_time = self.start_time - 1
self.hfx:use('condition1', 0.25, 200, 10)
self.hfx:pull('condition2', 0.0625)
end, self.time_left)
self.t:every_immediate(2, function()
if #self.main:get_objects_by_classes(self.enemies) <= 0 or love.timer.getTime() - self.last_spawn_enemy_time >= 8 and not self.transitioning then
self:spawn_distributed_enemies()
end
end, self.time_left/2)
end, 3, function()
alert1:play{pitch = 1.2, volume = 0.5}
camera:shake(4, 0.25)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 end, function()
self.wave = self.wave + 1
if self.wave > self.max_waves then return end
self.hfx:use('condition1', 0.25, 200, 10)
self.hfx:pull('condition2', 0.0625)
self.t:after(0.5, function()
local spawn_type = random:table{'left', 'middle', 'right'}
local spawn_points = {left = {x = self.x1 + 32, y = gh/2}, middle = {x = gw/2, y = gh/2}, right = {x = self.x2 - 32, y = gh/2}}
self:spawn_n_enemies(spawn_points[spawn_type], nil, 8 + (self.wave-1)*2)
end)
end, self.max_waves+1)
end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.wave > self.max_waves end, function() self.can_quit = true end)
end)
end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.time_left <= 0 end, function() self.can_quit = true end)
elseif self.win_condition == 'enemy_kill' then
self.level_to_enemies_to_kill = {
8, 12, random:int(14, 16),
16, 16, 18, random:int(18, 20),
20, 20, 20, 20, random:int(20, 22),
22, 22, 22, 22, 22, random:int(22, 24),
24, 26, 28, 30, 30, 32, 40
}
self.enemies_killed = 0
self.enemies_to_kill = self.level_to_enemies_to_kill[self.level]
self.enemy_spawn_delay = 8
self.enemies_spawned = 0
self.start_time = 3
self.t:after(1, function()
self.t:every(1, function()
if self.start_time > 1 then alert1:play{volume = 0.5} end
self.start_time = self.start_time - 1
self.hfx:use('condition1', 0.25, 200, 10)
end, 3, function()
alert1:play{pitch = 1.2, volume = 0.5}
camera:shake(4, 0.25)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
self:spawn_distributed_enemies()
self.t:every(2, function()
if love.timer.getTime() - self.last_spawn_enemy_time >= self.enemy_spawn_delay and #self.main:get_objects_by_class(self.enemies) < self.enemies_to_kill and not self.transitioning then
self:spawn_distributed_enemies()
end
end, nil, nil, 'spawn_enemies')
end)
end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.enemies_killed >= self.enemies_to_kill end, function() self.can_quit = true end)
elseif self.win_condition == 'time' then
self.level_to_time_left = {
20, 20, random:int(20, 25),
25, 25, 25, random:int(25, 30),
30, 30, 30, 30, random:int(30, 35),
35, 35, 35, 35, 35, random:int(35, 40),
40, 45, 50, 55, 55, 60, 80
}
self.time_left = self.level_to_time_left[self.level]
self.start_time = 3
self.t:after(1, function()
self.t:every(1, function()
if self.start_time > 1 then alert1:play{volume = 0.5} end
self.start_time = self.start_time - 1
self.hfx:use('condition1', 0.25, 200, 10)
end, 3, function()
alert1:play{pitch = 1.2, volume = 0.5}
camera:shake(4, 0.25)
SpawnEffect{group = self.effects, x = gw/2, y = gh/2 - 48}
self.t:every(1, function()
self.time_left = self.time_left - 1
self.hfx:use('condition1', 0.25, 200, 10)
self.hfx:pull('condition2', 0.0625)
end, self.time_left)
self.t:every_immediate(2, function()
if #self.main:get_objects_by_classes(self.enemies) <= 0 or love.timer.getTime() - self.last_spawn_enemy_time >= 8 and not self.transitioning then
self:spawn_distributed_enemies()
end
end, self.time_left/2)
end)
end)
self.t:every(function() return #self.main:get_objects_by_classes(self.enemies) <= 0 and self.time_left <= 0 end, function() self.can_quit = true end)
end
if self.level == 18 and self.trailer then
Text2{group = self.ui, x = gw/2, y = gh/2 - 24, lines = {{text = '[fg, wavy]SNKRX', font = fat_font, alignment = 'center'}}}
Text2{group = self.ui, x = gw/2, y = gh/2, sx = 0.5, sy = 0.5, lines = {{text = '[fg, wavy_mid]try the demo & wishlist!', font = fat_font, alignment = 'center'}}}
Text2{group = self.ui, x = gw/2, y = gh/2 + 24, sx = 0.5, sy = 0.5, lines = {{text = '[light_bg, wavy_mid]music: kubbi - ember', font = fat_font, alignment = 'center'}}}
end
end
if self.level == 1 then
@ -298,7 +313,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)
if input.k.pressed then
local enemies = self.main:get_objects_by_classes(self.enemies)
@ -383,6 +398,7 @@ function Arena:draw()
self.main:draw()
self.post_main:draw()
self.effects:draw()
if self.level == 18 and self.trailer then graphics.rectangle(gw/2, gh/2, 2*gw, 2*gh, nil, nil, modal_transparent) end
self.ui:draw()
camera:attach()

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

View File

@ -170,9 +170,9 @@ function BuyScreen:set_party_and_sets()
local classes = get_classes(self.units)
for i, class in ipairs(classes) do
local x, y
if #classes <= 8 then x, y = math.index_to_coordinates(i, 2)
if #classes <= 6 then x, y = math.index_to_coordinates(i, 2)
else x, y = math.index_to_coordinates(i, 3) end
table.insert(self.sets, ClassIcon{group = self.main, x = (#classes <= 8 and 319 or 308) + (x-1)*20, y = 45 + (y-1)*56, class = class, units = self.units, parent = self})
table.insert(self.sets, ClassIcon{group = self.main, x = (#classes <= 6 and 319 or 308) + (x-1)*20, y = 45 + (y-1)*56, class = class, units = self.units, parent = self})
end
end
@ -319,12 +319,26 @@ function RerollButton:update(dt)
self:update_game_object(dt)
if self.selected and input.m1.pressed then
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
self.parent:set_cards(random:int(1, 25), true)
self.selected = true
self.spring:pull(0.2, 200, 10)
gold = gold - 2
self.parent.shop_text:set_text{{text = '[wavy_mid, fg]shop [fg]- [fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}}
if gold < 2 then
self.spring:pull(0.2, 200, 10)
self.selected = true
error1:play{pitch = random:float(0.95, 1.05), volume = 0.5}
if not self.info_text then
self.info_text = InfoText{group = main.current.ui}
self.info_text:activate({
{text = '[fg]not enough gold', font = pixul_font, alignment = 'center'},
}, nil, nil, nil, nil, 16, 4, nil, 2)
self.info_text.x, self.info_text.y = gw/2, gh/2 + 10
end
self.t:after(2, function() self.info_text:deactivate(); self.info_text.dead = true; self.info_text = nil end, 'info_text')
else
ui_switch2:play{pitch = random:float(0.95, 1.05), volume = 0.5}
self.parent:set_cards(random:int(1, 25), true)
self.selected = true
self.spring:pull(0.2, 200, 10)
gold = gold - 2
self.parent.shop_text:set_text{{text = '[wavy_mid, fg]shop [fg]- [fg, nudge_down]gold: [yellow, nudge_down]' .. gold, font = pixul_font, alignment = 'center'}}
end
end
end

View File

@ -413,3 +413,24 @@ Mostly done with balance tuning. Now all that's left are some final details:
The demo is finally 100% complete. Now tomorrow I'll spend some time recording gameplay and hopefully finishing the trailer. After that I can start working on the steam page.
If I have it done by tomorrow and Valve takes 5 business days to approve the store page I should have everything ready by the 25th. And then I can release the game 14 days after that, which would be the 8th of April.
I definitely want to release it around that time, before the 15th because then I will have completed the game in less than 60 days which is my limit, although I should probably aim for 40 days going forward.
# Week 5 - 17/03/21 to 24/03/21
This latest week I did everything necessary to get the game into a playable state as well as all the work needed for a Steam page. Most of it was spent making the trailer, but I feel like the more trailers I make
the faster I get at making them. This time it took like 3-4 days out of laziness, but I can easily see it being a 1 day job in the future.
I also tested the demo out with a few people and the results were underwhelming. No one seemed to play it for too much time, which I suspected would happen given that the longer term loop of the game isn't in yet.
More interestingly though, all of the feedback I was given about things that needed to be changed were things that I knew needed to be changed/added for the game's release, which means that I have a pretty good idea of where
the game is from other people's perspective.
The web build has a few bugs that I can't fix like sound effects not playing randomly, and since my strategy was doing a web demo coupled with the page's release, I've decided to change it. Both because of these bugs and the
underwhelming response I feel like releasing a demo at this point will damage the game more than help it. I have ~3 weeks until release date (13th of April) from now, and that should be enough to add enough things into the game
to make it significantly better.
Going forward I think the Steam page reveal demo strategy probably isn't a good idea for these 1 month games. They're small enough already as they are and releasing them in an ever more crude state is probably a waste of time.
The only thing I need to schedule better for next releases is my trailer making timing. If I want to release a game in 1 month I need to have a trailer by day 15 at the latest, which means I need to work on the game for 2 weeks
and then take 1 day to make a trailer.
As for feedback given from the demo:

View File

@ -58,8 +58,8 @@ function Seeker:on_collision_enter(other, contact)
elseif table.any(main.current.enemies, function(v) return other:is(v) end) then
if self.being_pushed and math.length(self:get_velocity()) > 60 then
other:hit(math.floor(self.dmg/2))
self:hit(self.dmg)
other:hit(math.floor(self.dmg/4))
self:hit(math.floor(self.dmg/2))
other:push(random:float(10, 15), other:angle_to_object(self))
HitCircle{group = main.current.effects, x = x, y = y, rs = 6, color = fg[0], duration = 0.1}
for i = 1, 2 do HitParticle{group = main.current.effects, x = x, y = y, color = self.color} end

View File

@ -1,4 +1,4 @@
call "C:\Program Files\7-Zip\7z.exe" a -r %1.zip -w ..\..\ -xr!engine/love -xr!builds -xr!.git -xr!*.moon
rename %1.zip %1.love
call love-js -c -m 268435456 -t %1 %2\engine\love\%1.love ..\..\builds\web
call love-js -c -m 1073741824 -t %1 %2\engine\love\%1.love ..\..\builds\web
del %1.love

View File

@ -1,3 +1,4 @@
web = true
require 'engine'
require 'shared'
require 'arena'
@ -423,7 +424,6 @@ function update(dt)
end
if input.m.pressed then
print(music.volume)
if music.volume == 0.5 then
music.volume = 0
elseif music.volume == 0 then
@ -443,7 +443,7 @@ end
function love.run()
return engine_run({
game_name = 'SNAKRX',
window_width = 480*3,
window_height = 270*3,
window_width = 480*2,
window_height = 270*2,
})
end

View File

@ -469,6 +469,9 @@ function Player:on_collision_enter(other, contact)
if other:is(Wall) then
if self.leader then
if other.snkrx then
main.current.level_1000_text:pull(0.2, 200, 10)
end
self.hfx:use('hit', 0.5, 200, 10, 0.1)
camera:spring_shake(2, math.pi - self.r)
self:bounce(contact:getNormal())

2
run.sh
View File

@ -1,4 +1,4 @@
#!/bin/bash
cd E:/a327ex/SNAKRX # change to the directory of the current project
cd E:/a327ex/SNKRX # change to the directory of the current project
engine/love/love.exe --console .

View File

@ -14,7 +14,7 @@ function shared_init()
purple = ColorRamp(Color'#8e559e', 0.025),
}
for name, color in pairs(colors) do _G[name] = color end
modal_transparent = Color(0.1, 0.1, 0.1, 0.5)
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)
graphics.set_background_color(bg[0])
@ -433,6 +433,7 @@ global_text_tags = {
light_bg = TextTag{draw = function(c, i, text) graphics.set_color(bg[5]) end},
fg = TextTag{draw = function(c, i, text) graphics.set_color(fg[0]) end},
fgm5 = TextTag{draw = function(c, i, text) graphics.set_color(fg[-5]) end},
fgm10 = TextTag{draw = function(c, i, text) graphics.set_color(fg[-10]) end},
wavy = TextTag{update = function(c, dt, i, text) c.oy = 2*math.sin(4*time + i) end},
wavy_mid = TextTag{update = function(c, dt, i, text) c.oy = 0.75*math.sin(3*time + i) end},
wavy_mid2 = TextTag{update = function(c, dt, i, text) c.oy = 0.5*math.sin(3*time + i) end},
@ -474,6 +475,7 @@ Text2:implement(GameObject)
function Text2:init(args)
self:init_game_object(args)
self.text = Text(args.lines, global_text_tags)
self.w, self.h = self.text.w, self.text.h
end
@ -484,7 +486,14 @@ end
function Text2:draw()
self.text:draw(self.x, self.y, self.r, self.sx, self.sy)
self.text:draw(self.x, self.y, self.r, self.spring.x*self.sx, self.spring.x*self.sy)
end
function Text2:pull(...)
self.spring:pull(...)
self.r = random:table{-math.pi/24, math.pi/24}
self.t:tween(0.2, self, {r = 0}, math.linear)
end

44
todo
View File

@ -1,3 +1,40 @@
Trailer:
1
01:31
03:37
05:45
06:44
07:50
14:07
2
01:01
05:21
08:02
10:40
11:55
27:00 wall bump
27:17
3
00:23
12:12
4
02:30
5
01:00
03:36
07:07
6
00:14
03:45
04:29
06:19
09:40
10:27
12:51
13:51
15:12 fast buy
Mini Boss every 3rd level
Show a unit DPS list like Underlord's to the right side of the screen
About 20-30 passive items that can be collected every 3 levels
@ -42,3 +79,10 @@ Bugs
Sage's pull force doesn't increase with unit level
Cleric's healing amount doesn't increase with unit level
Squire and Chronomancer's buffs don't increase with unit level
Engine improvements for after SNKRX release
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
Spurred by Wall needing to have its own on_hit function to do something when the player hits it, without having to change player code for all Walls,
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