A simple point-and-click adventure in the making

THE PROJECT

I want to create a point-and-click adventure in the style of Zak McKracken. Not in an artistic way but with the same game mechanics. Well, maybe a little in the same artistic way.

At this point I don’t have any idea about a story or a character or a situation. I don’t have a sketch or something like that. There is only the wish to create this kind of game.

This is my first computer game ever and that means I couldn’t add any feature I could think of. Instead I add only those features which development I could handle. That doesn’t mean it will be a boring game though it has only a few features. It will just tell a good story and I think that this is much more important than big explosions, guys with guns that kill everything with a heartbeat or the next MMORPG … MOMRPG … MROMGP … I don’t know … one of those letter combinations.

Let’s begin.

THE GAME MECHANICS

A point-and-click adventure is nothing more than a collection of scenes. The intro is a scene, a cutscene is a scene, a playable scene is a scene, the outro is a scene. Whenever something happens on the screen, it happens in a scene.

Hey, that rhymes.

Because of Defolds system of collections and game objects, a scene will be a collection and vice versa. Every hotspot in a scene will be a game object with a unique id and a collision component. By using a collection for a scene, I can test every scene I want just by setting it as the bootstrap collection in the project settings. I don’t have to play all scenes just to get to the one I want.

Within the scene there are hotspots. An item or a NPC the (human) player can interact with is a hotspot. When the pointer hovers over a hotspot, the (human) player can see a description of it. This way the (human) player knows there is “something”. Once the (human) player clicks on the hotspot, one or more actions will be executed, e.g. move the player to a given position, say something, pick up an item. The actions will be executed one after another. First the player walks to the given position, THEN he says something, THEN he picks up the object.

For the actions I would like to use an action list. This is just a stack. The last action to be executed will be at the bottom and the first action will be at the top. Or in another words: last in, first out. Something like this:

walk to given position (top of stack)
say something
pick up object (bottom of stack)

A controller script handles only the first (top) action. There will be a variable called action_done, which is False. As long as the current action doesn’t set action_done to True (e.g. the player hasn’t arrived at the given position), the action will be executed in the next frame of the game loop. As soon as the current action is done (e.g. the player has arrived), action_done will be set to True. The controller script now removes the top action from the stack and sets action_done back to False. At the next frame of the game loop the new top action will be “say something”. And everything starts all over again.

Because I don’t know how to do pathfinding, the player can only walk sideways. To add a little more variety, the line of walk will be on a different height when it is necessary. A high line of walk indicates … well … a high location. It’s a long way down. A low line of walk on the other hand is good for a landscape scene and emphasizes the vastness of the nature. Take a look at the following examples:

devdiary1

devdiary2

I have watched many longplays of point-and-click adventures. The player always walked sideways in about 80% of all scenes the games consist of. If lateral walking is good enough for crabs, then it’s good enough for my game.

There will be no dialog system where a player can choose the next answer. Like pathfinding I don’t know how to handle this at this stage. Instead every dialog between the player and a NPC will be just a little cutscene where the (human) player sits reverently in front of his monitor and witnesses the greatest conversation since the Almighty spoke to Moses.

There should be an inventory system. I am fascinated by the idea that the player can carry around a maximum number of items at a time, let’s say … sixteen. That doesn’t mean there will be only sixteen items in the whole game. There will be a lot more but it’s a kind of strategic placement of items within the game so that the player will not carry around more than sixteen items at a time.

One important feature are game flags. Whenever the (human) player finishes a necessary action, the corresponding game flag will be set to True. With the flags I can create dependencies. The (human) player can only do action X if he has done action Y before. Both actions don’t have to be happen in the same scene. Action X can happen in scene 14 and action Y can happen in scene 27. All the engine has to do in scene 27 is this: check if action X has been done, if not then let the player say “I can’t do that”, but if action X has been done, allow the player to pick up the item or something else.

Game flags could also be very helpful on saving the game. All I have to do is just save the list of game flags. If the (human) player resumes the game, the engine sets the current scene to the last visited scene and by processing the game flags, the scene will be prepared accordingly.

For now I would like to end this post and work on a structure for the collections, the game objects, the components. I hope that this long text isn’t as boring as watching rain drops falling into the ocean. Although I like rain. And the ocean. I was born at the ocean. Nevermind, that’s another story.

P.S.: English is not my native language. If I used a wrong word or a wrong phrase, please let me know. I know this is a forum about a game engine and not an english class but … hey … that doesn’t mean we have to be sloppy about the way we talk and write.

9 Likes

Nice write-up! And best of luck on your point’n’click adventures!

1 Like

Looking forward to seeing this evolving.

For sequence of actions I suggest you look into coroutines.

https://www.lua.org/pil/9.1.html

1 Like

Thanks for the suggestion.

I know there are coroutines and I know they are a better solution for the sequence of actions but right now I don’t have any idea how to include them. I’m a beginner. That’s why I’ll stick with the action list. But I’m sure I’ll dare to use coroutines later.

Thanks anyway. :slight_smile:

THE FILE STRUCTURE

Let’s continue with the game.

First of all I would define two “global” game objects: the pointer and the player.

The pointer object is the crosshair that helps the player to find hotspots. It doesn’t have to be a crosshair. It could be a simple circle, too. Or a unicorn. It doesn’t matter. Anyway.

The pointer object consists of the pointer sprite, a collision component and a text label. The label will be used to show a short description of the hotspot if the pointer is over one. The collision component will be used for the collision with a hotspot. Every hotspot should have a unique id like ++hs_xx_shortnameofhotspot++ (the xx is for the scene number). That uniqueness is important because I would create a file that contains every hotspot id of the game with the corresponding short description. I know that this could be done with a table like this:

local hotspotDatas = {
	[hash("/hs_xx_hotspot")] = { label = "Some text" },
	[hash("/hs_xx_anotherhotspot")] = { label = "Some other text" },
	...
}

I’m still unsure if I should place the table above inside the pointer script or inside a Lua module that I include into the pointer script. I don’t think it does make a difference but you can correct me if you like.

Either way, the pointer script will notice the collision with a hotspot and set the text label according to the hotspot data table. So far I have the following code:

-- pointer.script

local hotspotDatas = {
	[hash("/hs_xx_hotspot")] = { label = "Book" },
	[hash("/hs_xx_anotherhotspot")] = { label = "Key" },
	[hash("/hs_xx_thirdhotspot")] = { label = "Exit" }
}

function init(self)
	msg.post(".", "aquire_input_focus")
	self.mpos_vec = vmath.Vector3(0, 0, 0) -- reset mouse position values
	self.collision_id = nil -- for the id of the hotspot
end

function update(self, dt)
	go.set_position(self.mpos_vec) -- position the pointer object
end

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") then
		if message.enter then
			self.collision_id = message.other_id
			local hotspotData = hotspotDatas[self.collision_id]
			if hotspotData then
				label.set_text("/pointer#label", hotspotData.label)
			end
		else
			self.collision_id = nil
			label.set_text("/pointer#label", "")
		end
	end
end

With the code above, the pointer should display the correct hotspot description, regardless of which scene we are currently in. That’s why the pointer object is a “global” object. I can place it in every scene and it should always do the same in every scene.

The following function now handles the click on a hotspot.

-- still inside pointer.script

function on_input(self, action_id, action)
	-- set pointer object position
	self.mpos_vec.x = action.x
	self.mpos_vec.y = action.y
	-- has the player clicked?
	if action_id == hash("touch") then
		if action.pressed then
			if self.collision_id ~= nil then
				-- send hotspot id to controller script
				-- TODO: use global variable for current scene controller script
				-- instead of /controller#scn_controller
				msg.post("/controller#scn_controller", self.collision_id)
			else
				-- send message 'justwalk' to controller script
				-- TODO: use global variable for current scene controller script
				-- instead of /controller#scn_controller
				msg.post("/controller#scn_controller", "justwalk")
			end
		elseif action.released then
			self.collision_id = nil
		end
	end
end

As you can see, the input function sends a message with the hotspot id to the scene controller script. Every scene (collection) should have a controller script. This script just executes the actions for the given hotspot (e.g. let the player walk to a given position, say something, pick up an item, and so on).

Maybe you ask yourself now: why does this strange guy send a “justwalk” message to the controller script if the player hasn’t clicked on a hotspot but anywhere else in the scene? Because that’s what a player in a point-and-click adventure does if the (human) player clicks anywhere but not on a hotspot. I thought it would be easier to just send a unique message to the controller script instead of using some obscure if-else statement inside the scene controller script.

The scene controller script should have the following code:

-- scn_ctrl.script

function on_message(self, message_id, message, sender)
	if message_id == hash("/hs_xx_hotspot") then
		print("clicked on the book ...")
	elseif message_id == hash("/hs_xx_anotherhotspot") then
		print("clicked on the key ...")
	elseif message_id == hash("/hs_xx_thirdhotspot") then
		print("clicked on exit ...")
	elseif message_id == hash("justwalk") then
		print("move player to click position ...")
	end
end

The print statements will later be replaced by one or more actions that will be executed after the click on the hotspot. But for now, it just prints some text.

I think that’s all there is for the pointer. Or did I miss something?

The other “global” object is the player object. It will be in almost every scene. It will consists of a sprite component and nothing else. Because this is a point-and-click adventure there is no need for a collision with an item or a NPC. There will be of course a player script that handles the different animations or the moving of the object. But I think that’s all.

At this stage I would use the following file structure:

/assets -- all images, sounds, music
/atlases
	general.atlas -- pointer sprite, player sprites, ...
	scn_XX.atlas -- for scene XX
/scenes
	scn_xx.collection -- the scene itself
/scripts
	pointer.script -- for pointer.go
	player.script -- for player.go
	game.script -- for game.collection
	scn_XX_ctrl.script -- controller script for scene XX
/pointer.go
/player.go
/game.collection -- bootstrap collection

The game collection should contain the collection proxy for every scene so that the engine can load only the current scene.

I don’t know if all of this would really works. The code above inside the pointer script and the controller script does work.

But that’s all for now. Watch out for the next post.

Thanks for your attention.

P.S.: If you have any questions or suggestions for improvement, let me know.

2 Likes