Medieval themed 2D strategy game

Progress! I ended up using collision detection that enables/disables based on a button in the GUI. It is far from perfect code, but it is working as intended.

1 Like

A small update for now because I’m short on time today.

I cleaned up the code for movement and started to implement combat. The combat will be somewhat automated; based on the detections of opposing units, combat will initiate and continue until either unit dies.

I’ve taken a look at an asset called DefArmy and I think it could be a great way to implement my idea. I’m looking forward to testing it out!

For the movement, I am enabling/disabling a collision object after clicking “move” on the panel. I think I will eventually create a module for this instead of having a script on every object a unit can move to, but I’m sticking to the basics for now.

local PANEL = hash("panel")

function init(self)
	msg.post("#destination_collision", "disable")
	msg.post("#selection_sprite", "disable")
end

function final(self)
	msg.post(".", "release_input_focus")
end

function enable_selection()
	msg.post("#destination_collision", "enable")
	msg.post("#selection_sprite", "enable")
	msg.post(".", "acquire_input_focus")
	msg.post("#main", "release_input_focus")
end

function disable_selection()
	msg.post("#destination_collision", "disable")
	msg.post("#selection_sprite", "disable")
	msg.post(".", "release_input_focus")
	msg.post("#main", "acquire_input_focus")
end

function on_message(self, message_id, message, sender)
	if message_id == hash("enable_selection") then
		enable_selection()
		if message.group == PANEL then
			local move_pos = go.get_position(message.id) - vmath.vector3(0, 50, -1)
			msg.post("#movement", "move here", { destination = move_pos } )
		end
	elseif message_id == hash("disable_selection") then
		disable_selection()
	end
end

One small error I’m seeing is that the game world cursor has slight delay (i.e., less than a second, but still noticeable) when following my mouse cursor. This could be a problem for when a player wants to implement actions ASAP. I only noticed it when I turned on the physics debugger.

Spent some time thinking about how I will handle combat. I want a clean and simple mechanic that’s also strategic. The game that inspired my game has a mechanic similar to the one I’m using, but it only uses ranged combat. That’s why I’ve been working on how melee combat will work.

I’m happy with the current mechanic. It may need some adjustments, but it will work like this.

Each hex is considered a region. If a player enters a region that has an enemy, combat begins. Think of it like cells and viruses — two opposing cells within each other’s vicinity will be in conflict.

Minor progress, but progress. I think I’ve managed to do a decent amount using the simple game mechanics that I understand, such as collisions and animations. I think a lot can be done using the basics!

In addition to getting married yesterday, it’s been a productive week.

2 Likes

Congratulations on the wedding!!

1 Like

Woah! Congratulations!

2 Likes

Congratulations! To the wedding. And the bonus productivity!

1 Like

Today I’m working on the enemy AI. I currently have the hex set up as a trigger that tracks players and enemies; if both a player and enemy exist in the region, it tells the enemy to move toward the player.

So far this is a great way to learn how messaging and triggers work. I’m also experimenting with tables. I think it would be best to have the units added/removed from a table based on when they enter/exit the region.

It’s still a work in progress. I’ll eventually figure it out. Here’s how the AI works as of now.

1 Like

Looking good! It is great to follow your progress and see you pushing through, learning and overcoming obstacles! Keep it up!

3 Likes

Grats and this looks great!

2 Likes

Gradually making progress. I recently became more comfortable with using values when passing messages. This is allowing me to make sure the commands I give to the units are specifically for the unit I select, instead of every unit on the map.

This is the code I’ve written using a table to make sure I’m only clicking on one unit at a time. I realized that if I selected the same unit more than once, the program thought I was always selecting a different or new unit. Now with the table, I’m making sure I’m only selecting one unit at a time.

if message_id == cursor.PRESSED then
    if message.id == PLAYER then
			local id = PLAYER
			if #players < 1 then
				table.insert(players, 1, { id = id } )
			else
				local count = #players
				for i = 0, count do
					players[i]=nil
				end
				table.insert(players, 1, { id = id } )
			end	

I’ve also been working on the AI for combat more as well as thinking about the art style I want to use for the game. I want to aim for a more realistic style, similar to Dark Souls… but not as dark. The graphical design will be similar to Darkest Dungeon or Salt and Sanctuary. Later on, I’ll become more familiar with the spine editor. I’ll likely hire an artist for help as well.

Also considering isometric. I like the style of Forge of Empires.

1 Like

Today I worked on spawning units. I’ve set up a different reactions based on the id of what the cursor is pressed on; units, buildings, enemy, etc.

Small steps, but making progress. I’ve set up some simple damage interaction as well. It’s starting to almost look like a game.

1 Like

Just wanted to do a quick update. Making progress and haven’t made any big changes. I’ve mostly reworked what I’ve already done. As I implement new code, I find myself going back and fixing what’s been done.

For example, I’ve set up the cursor to filter clicks based on groups. This way I can have different behavior based on the group that’s clicked.

if message_id == cursor.PRESSED then
	local id = message.id
	local group = message.group

	if #selected > 0 then
		msg.post(selected[1].id, "deselect")
		msg.post("/guis#panel", "reset panels")
	end

	if group ~= nil then
		local count = #selected
		for i = 0, count do
			selected[i]=nil
		end
		table.insert(selected, 1, { id = id, group = group } )
		msg.post(selected[1].id, "select")
		msg.post("/guis#panel", "panel", { id = id, group = group })
	end

I’m using a table to temporarily store what’s clicked; then clearing it each click, so I make sure I’m only clicking one unit, and storing its values if necessary, at a time.

Panels are opened based on the group and the logic picks up from the GUI.

I’m currently implementing a waypoint / destination system for unit movement. I’ve had one working before, but it wasn’t ideal for factories and when multiple waypoints are spawned/despawned over time as the game progresses.

My idea right now is to enable a map-wide trigger that creates a table based on trigger collisions with currently available waypoints. This will allow me to get the most recent destinations based on what’s happening in the game. The trigger will be disabled and table deleted immediately after being used since it its purpose is to only send movement commands to the selected unit.

3 Likes

Took a few days to finally get this right. And while it’s working, I’m sure it could be improved. It is, however, WORKING and that’s what’s important.

I created a map-wide trigger that… triggers when the player requests to move. Because the map may change over time — and new destinations may be created, destroyed, etc., — I needed a way to check the map dynamically.

This map-wide trigger does just that.

It took some time to figure out the logic and groups.

Here’s how it looks.

And, the code behind the scenes:

if message_id == hash("trigger_response") then
		if message.group == hash("catapult") then
			if message.enter then
				print("enter", message.other_id)
				msg.post(message.other_id, "waypoint_on")
			else
			end
		elseif message.group == hash("waypoint") then
			if message.enter then
			else
				print("exit", message.other_id)
				msg.post(message.other_id, "waypoint_off")
			end
		end
	end

Next, I want to implement the economic system. Gold will be used to build units and expand the kingdom and food will be required to move.

2 Likes

Thinking about the mechanics and art behind the game. I want to keep the art simple, so I can focus on the gameplay. Top-down seems like a solid choice. There are a lot of resources available for things like this. Inkarnate, which is software for making maps, (https://inkarnate.com/) is one of them.

For the gameplay, I want food to be a source of energy for movement. This mechanic will change over time as the player progresses through different ages/stages of the game. For example, collecting food will done by foragers during the early stages. Then, as the player progresses, they’ll get upgrades to build farms, butchers, etc. I’m going to research more and try to make the process as accurate as I can.

4 Likes

Making progress, but for some reason my gui is no longer receiving input. I even started the game with everything disabled but the GUI, but nothing. I’ve triple checked the spelling of my nodes, etc.

May be there is something wrong with an update in one of the dependencies I’m using. I’m confused because I think I’ve checked everything.

Need to investigate further…

Are you loading your game through a collection proxy? If this is the case you must also make sure the game object holding your collection proxy has acquired input focus.

No, I’m not using collection proxies yet. I only have main collection. The messages are being sent correctly, but the panel isn’t receiving input.

Check the console for errors. Print the url you are using when calling msg.post() and also add a print(message_id) in the on_message() function of the receiver.

I’m trying that and the messages are working. I’ve even set the panel to true as default when the game starts, but it’s still not receiving input. Maybe it’s being pushed off the top of the stack?

Here’s the flow; first, I send this message from the cursor (cursor.PRESSED)

if group ~= nil then
			local count = #selected

			for i = 0, count do
				selected[i]=nil
			end
			table.insert(selected, 1, { id = id, group = group } )
			msg.post(selected[1].id, "select")
		end

If the go accepts the message, it looks like this:

if message_id == hash("select") then
		sprite.set_constant("#range_sprite", "tint", vmath.vector4(1, 1, 1,.50))
		label.set_text("#hp", self.health .. "/" .. self.max_health)
		label.set_text("#name", self.name)
		label.set_text("#storage", self.storage .. "/" .. self.max_storage)
		msg.post("/guis#panel", "gatherer_panel", { name = self.name } )
	end

The labels and sprites are visual indicators of the go being selected. The message to the panel, opens the panel.

if message_id == hash("gatherer_panel") then
        gui.set_enabled(self.gatherer_panel, true)
		gui.set_position(self.gatherer_panel, vmath.vector3(480, 0, 0))
		gui.set_enabled(self.gatherer_move, true)
		gui.set_text(self.name, message.name)
		msg.post(".", "acquire_input_focus")
		print("focus acquired")

After doing this, I can’t get the panel to receive input. I’m using the following in the gui script to test.

if action_id == TOUCH and action.pressed then
		print("pressing")
	return true
	end
		-- if gui.pick_node(self.gatherer_panel, action.x, action.y) then
	end
end
if action_id == TOUCH and action.pressed then
   	print("pressing")
   return true

I suggest doing “pprint(action_id, action)” to see what input you’re actually getting (if any).
And, to make sure, are you sending the “acquire_input_focus”?
And, you haven’t added a “return true” somewhere in the input stack?