Mobile Adventure RPG

Hello (again). I was active here for a while and then took a break. I still find learning how to program games using Lua and Defold to be both exciting and challenging, so I’m taking some ideas I have into a game. The idea is to make a game like the original Zelda with a compelling story and fun gameplay.

Currently the game will have a two game pads; one for movement, and one for actions. I created some tiles and basic code so far. It looks like this.

The next step is to create impassable tiles. I’ve experimented with some of the ways other projects are doing this and I’m unhappy with how the work with the style of game I want to create.

For example, the following code produces a jittering effect when the player is close to two walls. It also doesn’t completely work; I can spam the directions and go into walls.

if message_id == hash("contact_point_response") and message.group == hash("wall") then
	go.set_position(go.get_position() + message.normal * message.distance)
end

I’m thinking of enabling/disabling a movement check collision before the player moves. If the check considers the destination walkable, the player moves. If not, the movement is ignored. I like this approach of preventing an action instead of resetting what’s been done (i.e., the above code).

To make the collision detection work, I created a new game object with a “walkable” collision. I spawn this with a factor from the player input. The game object is triggered to delete upon update. If it collides with a walkable tile, it sends a message to the player to move.

Here’s a video of how it works (with debugging on).

You can see the collision appear when I hold the move button down. I’m doing this to show how the game object blocks movement. When I release the button, the game object deletes itself.

And here is the code.

First, the creation of the go that checks if a destination is walkable.

// player.script
local p = go.get_position()
if state == "idle" then
local component = "/factories#walkable"
		if action_id == hash("w") then
			p.y = p.y + 64
			factory.create(component, p, nil, { dir = hash("north") } )
		elseif action_id == hash("s") then
			p.y = p.y - 64
			factory.create(component, p, nil, { dir = hash("south") } )
		elseif action_id == hash("d") then
			p.x = p.x + 64
			factory.create(component, p, nil, { dir = hash("east") } )
		elseif action_id == hash("a") then
			p.x = p.x - 64
			factory.create(component, p, nil, { dir = hash("west") } )
		end
	end

Next, the walkable game object checks to see if the location is walkable.

// walkable.script

function update(self, dt)
	go.delete()
end

// walkable.script

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") and message.group == hash("walkable") then
		if self.dir == hash("north") then
			msg.post("/player#player", "go_north")
		elseif self.dir == hash("south") then
			msg.post("/player#player", "go_south")
		elseif self.dir == hash("east") then
			msg.post("/player#player", "go_east")
		elseif self.dir == hash("west") then
			msg.post("/player#player", "go_west")
		end
end
end

Finally, the code that actually moves the player. I added a slight timer to give things a little delay.

// player.script

function update(self, dt)
	local p = go.get_position()
	go.set_position(p + self.dir)
	self.dir = vmath.vector3()
end

function on_message(self, message_id, message, sender)

	if message_id == hash("go_north") then
		self.dir.y = self.dir.y + 64
		state = "north"
		timer.delay(.1, false, function(self, id)
			state = "idle"
		end)
	elseif message_id == hash("go_south") then
		self.dir.y = self.dir.y - 64
		state = "south"
		timer.delay(.1, false, function(self, id)
			state = "idle"
		end)
	elseif message_id == hash("go_east") then
		self.dir.x = self.dir.x + 64
		state = "east"
		timer.delay(.1, false, function(self, id)
			state = "idle"
		end)
	elseif message_id == hash("go_west") then
		self.dir.x = self.dir.x - 64
		state = "west"
		timer.delay(.1, false, function(self, id)
			state = "idle"
		end)
	end

end

The code could be cleaner, but that will come later. I’m glad to have gotten this mechanic working. I plan to use a similar mechanic for attacking and interacting with the world (e.g., entering a door, opening a chest, talking to an NPC, etc.)

6 Likes

Because the Legend of Zelda is a classic, there are a lot of resources available online.

Such as:

Definitely looking forward to learning from these and implementing the mechanics in Defold.

6 Likes

Still unhappy with the movement. The prior method had some issues where I could press two directions and the player would quickly move in one direction and then another, which could result in them clipping inside of a wall.

I went back to the tutorials (which is what I usually do when stuck) and implementing the code from the walking astronaut. I added states so only 4 directions are possible (NESW).

I think I’ll look into raytracing next for blocking paths.

1 Like

Ended up going back to collisions. Code could be cleaner, but I like how it works now vs. before.

go.property("speed", 150)
go.property("pos", vmath.vector3())

function init(self)
	msg.post(".", "acquire_input_focus")
	self.dir = vmath.vector3()
	self.moving = false
	self.block_n = false
	self.block_e = false
	self.block_s = false
	self.block_w = false
end

function update(self, dt)
	
	if vmath.length_sqr(self.dir) > 1 then
		self.dir = vmath.normalize(self.dir)
	end
	
	local p = go.get_position()
	go.set_position(p + self.dir * self.speed * dt)
	self.dir = vmath.vector3()

	if p == p then
		self.moving = false
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") then
		if sender ==  msg.url("/player#collision_n") then
			if message.enter then
				self.block_n = true
			else
				self.block_n = false
			end
		elseif sender ==  msg.url("/player#collision_s") then
			if message.enter then
				self.block_s = true
			else
				self.block_s = false
			end
		elseif sender ==  msg.url("/player#collision_e") then
			if message.enter then
				self.block_e = true
			else
				self.block_e = false
			end
		elseif sender ==  msg.url("/player#collision_w") then
			if message.enter then
				self.block_w = true
			else
				self.block_w = false
			end
		end
	end
end

function on_input(self, action_id, action)
	if not self.moving then
		if action_id == hash("w") then
			if not self.block_n then
				self.dir.y = 1
				self.moving = true
			else
				return end
		elseif action_id == hash("s") then
			if not self.block_s then
				self.dir.y = -1
				self.moving = true
			else
				return end
		elseif action_id == hash("d") then
			if not self.block_e then
				self.dir.x = 1
				self.moving = true
			else
				return end
		elseif action_id == hash("a") then
			if not self.block_w then
				self.dir.x = -1
				self.moving = true
			else
				return end
		end
	end
end

Any input on improvements would be appreciated.

The four directions I’m checking for trigger collisions look like this:

1 Like

Cleaned up code and added attacks. The player cannot attack while moving and the attack has a delay using socket.gettime in order to prevent attack spam. Also got the collisions working better.

Next: enemies and actual combat.

I’ll be using Legend of Zelda on the NES as a template and adding my own ideas along the way.

3 Likes

Got the directions working and now I’m working on the animations. I also got a deflect system to work. I found another article that breaks down the mechanics of Zelda and that’s been helpful for setting up my game.

I’m making the sprites myself. They’ll be 16x16. I’m thinking of having different sprites for in/out of town.

2 Likes