Village Saviors - Mobile Action/Strategy Game

Last month I set a goal: to get my first mobile game on the app store(s) within 30 days. The deadline for this goal was 5 days ago — and I didn’t complete my objective.

Making games is hard. I feel like I have a good-enough understanding of the technical process (or at least, enough confidence to eventually find a solution online). The challenge now is — if I had to narrow it down to one thing — finishing.

I also have confidence in my idea. But after missing my deadline, my motivation has taken a “hit”. On a positive note, I think the direction I’m going in now for my game is much better than it was before. I’ve been researching a lot of game as well and the version I have is more suited for mobile gaming.

Making Money

For several years I’ve had a good income from marketing. But over time, I started to dislike the industry less and that affected my work. The income was still coming in… but I think without the enthusiasm, I wasn’t as effective as I was when I started.

Because of this, and in addition to my clients having “short-term” goals (e.g., launch a product and make money as fast as possible — without thinking about the brand, customers, etc.), the clients I did have… are also struggling.

Long story short, I would like my game to make money. I would love to have a product of my own. This would motivate me to reignite my marketing skills. I know this is the dream for many and I have to remind myself — constantly — to be realistic.

Therefore, I don’t think I’ll “win the lottery” with my first game. Going back to what I said earlier, the goal is to launch a game. From there, I can do many things. Such as:

  • Get user feedback
  • Build an audience of people who like my games
  • Gain experience
  • Be in a better position to pitch my work to investors/publishers/devs
  • Grow a team (eventually?)

About The Game

That’s a long, but important, intro to my game. I think writing it out helps anyone reading this understand the process is more than coding. It’s about planning, meeting objectives, and staying focused.

As for the game, the current title (that will very likely change) is Village Saviors. The goal is use your resources to take out the enemy village. I’m a little embarrassed to share the following screenshot, because there was so much more involved than what you can see.

But, here it goes.

Let me do my best to summarize the idea:

  • Send troops from your village to the enemy village by tapping the screen
  • Units travel vertically and attack anything in their way
  • The enemy randomly spawns units as well — giving the player a choice to block a unit or attack the village
  • Spells, traps, and other buffs are also planned

I’ve recently moved from a timer (survivors game style) to level-based gameplay. I think this makes more sense for mobile as well as the “logic” of saving villages.

When a level is completed, the player is presented upgrades to choose from in a rogue-like style.

The coding for this upgrade system is working very well. For example, here I’ve chosen the “archer” and it’s now an available unit for me to spawn.

Currently, each map will have x number of villages. All villages must be defeated before the player can move onto the next map. There will be a progression system outside of maps that allow you to upgrade and/or add new units your available selection.

Graphics are a time suck (I did them myself). I’ve been using placeholders for now to get the dimensions right. I also had to develop a workflow for creating and adding my art into the game. I would love to hire someone, but I don’t have the budget to do so and I want to actually get the game finished and tested first.

Well…

That’s it for now. Thanks for reading!

12 Likes

Thank you so much for sharing! I’m excited to see the progress you make on it in your next update!

2 Likes

It’s the weekend and for me, that means less time on the computer and more time with family and friends. Yesterday, my wife and I went to a sports complex and did some working out and swimming before going to dinner. I also started a new book, Children of Ruin, by Adrian Tchaikovsky - Wikipedia. He’s one of my favorite writers and I try to read all of his books the year they’re released. I would recommend started with Children of Time (novel) - Wikipedia.

Minor Updates

I worked on the game lobby (menu) collection. This will be where the player can upgrade the things it will use ingame. I would like to add more here, but the first app-store ready version will only have the upgrades seen on the third tab.

I used ChatGPT for optimizing my code. I had something working, but I felt like it was sloppy. I copied/pasted my code into ChatGPT and it understood my objective and provided a cleaner version of code.

I’m using scaling to open/close tabs. I found that using enable/disable wasn’t working how I wanted it to because you can still interact with a node that’s disabled. You also have to reenable a node before doing things like animating it again.

Therefore, scaling seems like a better solution. Since nodes at a scale of 0 are gone from the screen and unclickable.

The code looks like this

	if action_id == hash("touch") and action.pressed then
		audio.play("click") -- my module for gated sounds
		local nodes = {self.show_shop, self.show_campaign, self.show_upgrades}
		local scales = {vmath.vector3(), vmath.vector3(), vmath.vector3()}

		if gui.pick_node(self.shop, action.x, action.y) then
			self.selected = 1
			scales[1] = vmath.vector3(1,1,1)
		elseif gui.pick_node(self.campaign, action.x, action.y) then
			self.selected = 2
			scales[2] = vmath.vector3(1,1,1)
		elseif gui.pick_node(self.upgrades, action.x, action.y) then
			scales[3] = vmath.vector3(1,1,1)
			if self.selected ~= 3 then
				self.selected = 3
				show_upgrades()
			end
		elseif gui.pick_node(self.play, action.x, action.y) then
			msg.post("main:/handler", "show_campaign")
		end

		for i, node in ipairs(nodes) do
			gui.set_scale(node, scales[i])
		end
	end
end 

ChatGPT suggested to scale everything to 0 at the start, update the clicked nodes to full scale (1,1,1) and then update them all at the end.

Here’s a video of how it looks

Minor Updates 2

I also cleaned up the gameplay UI. I was using GOs in the previous version, but I realized that with a Fixed Fit zoom, I can place the GUI exactly where I want it.

I also set up visuals for the cooldowns and made it so each selectable item has its own cooldown. I used modules and messages to make everything sync together.

One thing I’m thinking about now is the type of game loop I want. I could either have the level complete once the enemy village is defeated — or — have the game continue to the next phase.

For example, is there only 1 village per level or a number of villages per level (e.g., you need to defeat 30 villages before completing the level)?

Also, in the second version XP and level-up rewards would be provided after each sub-level completion. But in the first version, I could provide XP per enemy unit killed. To help me figure this out, I will make the game loop as finished as possible and see what feels best.

7 Likes

Continued work on the lobby area. I have the menus working how I want them to, for now. I decided to go for a… Candy Crush style of leveling / gameplay. Massive amounts of levels that take a few to several minutes to complete seems like a good approach.

But this has me thinking about the gameplay. Prior to today, I had more of a strategic style. You would place units to counter what the enemy village spawns while planning ahead for which units will reach the village, kind of like chess.

But for a faster style of gameplay, that has more of a mass appeal, it would need to be more arcade like. I always wanted to go with an arcade / casino style, but I now feel like the current version is lacking that.

This gameplay loop will be super important since it’s the thing the player will repeat 1,000s of times. It has to feel fun!

2 Likes

Great work so far! Regarding enabling/disabling nodes, what I do in my code is that I have a boolean flag for each object like obj.is_enabled and I set it each time I send the actual enable/disable message to the object. Then I check in the input listener if the object is enabled using this flag and only then invoke it’s function.

1 Like

I updated the lobby area to reflect the new leveling system.

The game will track how many levels the player has completed and revert to the most-recently available level by default.

I also worked on healthbars.

They’re currently only added to the player units for now, but it will be easy to update anything else in the game. It took some time to figure out, but I got them working how I want them to.

You can see the red bar under the player unit in the video. It is a GUI node that will scale accordingly and stick to the unit.

Here’s how I got them working.

footman.script

function init(self)
	...
	msg.post("/gui#healthbar", "add_unit", { health = self.health } )
end

function final(self)
	self.health = 0
	healthbar.update(self.url, self.pos, 0) -- one last update to delete the gui node & update module
end

function update(self, dt)
	self.pos = go.get_position()
	healthbar.update(self.url, self.pos, self.health)
end

healthbars.gui_script

local offset = vmath.vector3(64, 64, 0)
local widths = { unit = 128 }

function update(_, dt)
	local units = healthbar.units
	local node
	local health
	for k, _ in pairs(units) do
		local unit = units[k]
		node = unit.node
		health = unit.health
		gui.set_position(node, unit.pos - offset)
		if health <= 0 then -- node deleted & module updated when unit has 0 health
			gui.delete_node(node)
			healthbar.units[k] = nil
		end
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("add_unit") then
		local health = message.health
		local node = gui.clone(gui.get_node("healthbar"))
		local node_size = health / health * self.widths.unit
		gui.set_size(node, vmath.vector3(node_size, 5, 0))
		healthbar.units[sender] = { health = health, max_health = health, node = node }
	end
end

healthbar.lua

local M = {}

M.units = {}

function M.update(url, pos, health)
	for k, v in pairs(M.units) do
		if url == k then -- keeps pos & health updated for the gui node
			M.units[k].pos = pos
			M.units[k].health = health
		end
	end
end

function M.reset() -- resets all when level ends, quits game, etc.
	for k,_ in pairs(M.units) do
		M.units[k] = nil
	end
end

return M

Could it be better? I’m sure. But for now it’s working and it will allow me to focus more on the combat part of the game. I had an idea this morning to make the combat feel more arcade like and fast-paced. Instead of having the units stop and attack, they’ll collide and do damage instantly. It will kind of be like a space shooter game, but with units that each have some basic attack mechanics.

Update

To satisfy my curiously, I asked ChatGPT to see if it could make my module more efficient. This is what it gave me.

local Unit = {}

function Unit:new(url)
    local obj = {}
    obj.url = url
    obj.pos = {x = 0, y = 0}
    obj.health = 100
    setmetatable(obj, self)
    self.__index = self
    return obj
end

function Unit:update(pos, health)
    self.pos = pos
    self.health = health
end

function Unit:reset()
    self.pos = {x = 0, y = 0}
    self.health = 100
end

local M = {}

M.units = {}

function M.update(url, pos, health)
    if M.units[url] then
        M.units[url]:update(pos, health)
    else
        M.units[url] = Unit:new(url)
        M.units[url]:update(pos, health)
    end
end

function M.reset()
    for k,v in pairs(M.units) do
        v:reset()
    end
end

return M

Since I’m unfamiliar with OOP, I likely won’t use it. It doesn’t seem more efficient anyway. As the reason it gave was:

In this version of the code, each unit is represented by a Lua object that has its own methods for updating and resetting. This makes the code more modular and easier to understand.

It did give another suggestion, which seems better.

local M = {}

M.units = {}

function M.update(url, pos, health)
    local unit = M.units[url]
    if unit then
        unit.pos = pos
        unit.health = health
    else
        M.units[url] = {pos = pos, health = health}
    end
end

function M.reset()
    for k,_ in pairs(M.units) do
        M.units[k] = nil
    end
end

return M

The reason:

In this version of the code, the update function looks up the unit by its URL using the M.units table as a hash table. If the unit exists, it updates its position and health. If the unit doesn’t exist, it creates a new entry in the M.units table.

4 Likes

Kind of stuck at the moment with figuring how to make the game more… fun. I have some ideas, but nothing that gets me super excited.

Maybe I’ll start to gradually implement my ideas and see how it feels?

2 Likes

Went in a very different direction with the gameplay. I decided to remove the concept of lanes and instead make it more of a mashup between survivors, idle, and tower defense games. Still more work to do of course, but I did enough work to showcase the basic idea.

The placeable objects are now drag-and-droppable. I’m thinking of having an energy meter as well.

Once I get this concept down, I can work on adding all types of cool units, spells, etc.

Next: work on how I want the combat to work. A simple yet possibly-fun version would be to have the enemy units self destruct on impact, as if they were projectiles. This would make the game more arcade like, which is how I wanted it.

Note: there’s a bug in the video. The green unit is supposed to relocated toward the center after the “tower” dies. It normally works, but this time it didn’t.

2 Likes

Lua does not have OOP; it has syntax which can look like it.
For example in the code below each function definition for m.a is identical to the other, and each function usage is identical to the other. A function can be defined in one way and used in the other, or used in both ways. The advantage of using a colon is not having to repeat the table name.

m = {}
function m.a(self,nn)
  self.n = nn
end
m:a(789)
print(m.n)
function m:a(nn)
  self.n = nn
end
m.a(m,456)
print(m.n)
1 Like

Yeah, calling it OOP was my mistake and it shows how little I know about its relation to Lua. It seems like anything I want to do now is possible without using OOP-related syntax. And as far as I know, the syntax is more cosmetic than anything else.

I think I’ve got the mechanic for the game. Or at the very least, a mechanic I feel comfortable moving forward with.

Yesterday I saw an Early Access game from an Instagram ad, installed it on my phone, and noticed that it’s just a single level with a high score. There was no menu, settings, etc. It was a good reminder that it’s not a bad idea to start small. By testing something minimal, you can get an idea if the market will be responsive or not to your game.

It is standard practise, in Lua, to call it OOP when using that style. Cosmetics could mean clean code or obfuscation.

I haven’t made much progress during the past few days, but the progress I have made was meaningful. I think I’ve decided on a game mechanic that is both fun and scalable. It’s a lot different from what I had before and even more different than what I started with.

I’ve also been educating myself and talking with people about how publishers work. My overall goal now is to launch a prototype and get some initial numbers. If the initial numbers look good, I can start pitching my game. If not, I can move onto something else or get feedback and improve my prototype.

One person I spoke to expressed a concern about the engine I’m using. He said because it’s not an industry standard, it may be prove to be difficult later on. I’m not too concerned about this, because I’m more familiar with Defold than anything else and I have yet to come across a situation where I was not unable to do what I wanted.

2 Likes

That one person is not making an intelligent comment. Decision makers who can only do what they perceive everyone else does, make no contribution. I think you are saying that right now for what you are doing Defold is the right choice for you. Using more than one game engine is the thing to do I guess, as time allows in any case.

1 Like

Yeah, I’ve heard some publishers say similar things. These publishers know exactly what to do when they get a Unity game in their lap, but as soon as they encounter a game made with another engine it becomes a huge headache because they can’t simply apply their standard set of checklists, tools and integrations (analytics, ads etc).

It’s pretty silly since Defold supports basically all industry standard analytics and ad network integrations. The effort to adapt a Defold game to the requirements of a publisher is really not huge at all.

3 Likes

The person who told me this has experience with mobile games and working with publishers. I understood his point of view, but learning a new engine would cost precious time. Besides, I’m very happy with Defold and the community here.

Goal: Finish Prototype

I’ve revamped the movement and combat AGAIN… and I still think it needs tweaking. But I’m satisfied with how it’s working now. I’m trying to make it as simple as possible.

So, the unit can now move in any direction while locked to a grid. When the unit is selected (tapped), the user can select which action to take. I set this up using raycasts. I’m having a lot more ideas with traps, projectiles, etc., but for now I’m working on basic melee attacks.

I’ve downloaded and played dozens of mobile games for inspiration. The game I continue to go back to is Candy Crush. While the genre is very different, the fact that the game has been popular for over a decade(?) and continues to be a top grossing game on the app stores says a lot to me. I think one of the many reasons why the game is popular is its simple, interaction, and engaging gameplay mechanic. If I could model a similar mechanic, I’d be very happy.

**Updated with a better video.

2 Likes

Putting this on hold for now. I learned a lot and want to continue, but other things are taking priority.

My biggest lessons from this and previous projects is: finish in a day. Even if it’s just a simple prototype, getting things done in a day is the goal. For example, I had an idea for a non-game related product and after finishing it in a day, I got several people giving me positive feedback.

Maybe I’ll work on more game jams in the future.

2 Likes

Join the defold game jam in may!

1 Like