text game
-
- Prole
- Posts: 1
- Joined: Wed Nov 29, 2023 3:50 pm
text game
Hello everyone, I'm relatively new to Love2D [I already know the basics of programming logic, understand control structures, functions, etc.]. But what I want to say is that I'm somewhat new to game development. I've learned about the three main functions [load, update, draw]. Does anyone have any tips on how to create a simple text-based adventure game? I was planning to create a demo with 5 scenes [just for testing]. The first scene would be the start screen, the second would display the environment, the third scene would be a choice scene with two options - if you choose option 1, you win; if you choose option 2, you lose. Scene 4 is the ending for option 1, and scene 5 is the ending for scene 2. It's for my sister, and I know I don't necessarily need Love2D to make the game since I could run a game like this through the console, but that would be a bit dull. Does anyone have any tips or even a foundation? I would greatly appreciate it.
Re: text game
Hi, as with all things in programming there are lots of different ways to do this. Below I've put one very quick and easy way to do what you're looking for in love2d.
The gist is that you have a table with all of the text for different scenes in (scenes) and you keep track of the current scene in a variable (currentScene). Every frame the love.draw function will draw to screen the current scene's text.
In love.keypressed (where it's checking for input) you check what scene you're currently in and depending on what key has been pressed advance to the appropriate scene.
I hope this makes sense and is useful. It's almost certainly not the best way to do it but it's hopefully easy enough to follow and adapt for your purposes.
To make it look nicer you cna toy with changing the background colour (love.graphics.setBackgroundColor(red, green, blue)), the text colour (love.graphics.setColor(red, green, blue)) or looking into the different font options.
The gist is that you have a table with all of the text for different scenes in (scenes) and you keep track of the current scene in a variable (currentScene). Every frame the love.draw function will draw to screen the current scene's text.
In love.keypressed (where it's checking for input) you check what scene you're currently in and depending on what key has been pressed advance to the appropriate scene.
I hope this makes sense and is useful. It's almost certainly not the best way to do it but it's hopefully easy enough to follow and adapt for your purposes.
To make it look nicer you cna toy with changing the background colour (love.graphics.setBackgroundColor(red, green, blue)), the text colour (love.graphics.setColor(red, green, blue)) or looking into the different font options.
Code: Select all
-- Load, called at game start
function love.load()
screenWidth = love.graphics.getWidth() -- get the screen width for use later
currentScene = 1 -- our current scene
scenes = {} -- a table that will hold all of the scene text
-- enter the text for each scene as a string, \n will insert a line break
scenes[1] = "Do you wan to win?\n\nPress 1 for yes and 2 for no"
scenes[2] = "Well done, you won, you must be really great at things to have managed to have done so well in this game, what a superstar, I can't say enough good things about you but hopefully that's enough to check the text is wrapping correctly\n\nPress any key to restart"
scenes[3] = "You lose\n\nPress any key to restart"
end
-- Update, called every frame, we won't actually use this for this game
function love.update(dt)
end
-- We'll use love.keypressed which is called whenever a key is pressed
function love.keypressed(key)
-- First check what scene we're in. Make sure this is an if .. elseif .. elseif .. chain or we might accidently jump two scenes in one press
-- scene 1
if currentScene == 1 then
if key == "1" then -- if we pressed 1 go to scene 2
currentScene = 2
end
if key == "2" then -- if we pressed 2 go to scene 3
currentScene = 3
end
-- scene 2
elseif currentScene == 2 then
currentScene = 1 -- we pressed anything, go to scene 1 (restart)
-- scene 3
elseif currentScene == 3 then
currentScene = 1 -- we pressed anything, go to scene 1 (restart)
end
end
-- Draw, called every frame after update
function love.draw()
love.graphics.printf(scenes[currentScene], 5, 5, screenWidth - 10) -- print the current scene text at 5, 5 (wrapping to the screen width)
end
Re: text game
You can find lot of nice libraries for state / scene management here:
https://github.com/love2d-community/awesome-love2d
Hump gamestate class is easy to work with and works well.
https://hump.readthedocs.io/en/latest/gamestate.html
You can also find other useful helper libraries in the awesome repo such as input handlers etc. You don't have to invent your own (G)UI either, check my https://github.com/nekromoff/layouter or other linked libraries in the repo.
https://github.com/love2d-community/awesome-love2d
Hump gamestate class is easy to work with and works well.
https://hump.readthedocs.io/en/latest/gamestate.html
You can also find other useful helper libraries in the awesome repo such as input handlers etc. You don't have to invent your own (G)UI either, check my https://github.com/nekromoff/layouter or other linked libraries in the repo.
My boat driving game demo: https://dusoft.itch.io/captain-bradley- ... itius-demo
Re: text game
If I were tasked with creating a text-based game, I would work on it in steps:
1) First get the UI all in place, even if it's a simple font, showing a sample text paragraph ("Lorem ipsum dolor sit amet..."), and make sure that I can break text in lines so I can format paragraphs for things like story, decisions etc.
So that concludes the display of text.
2) Work on the system that reads the player input, like reading a number key for the user to indicate what choice they prefer.
So that concludes the player input.
3) Work on the system that reads a scene file and performs it for the player. From this point on, the engine code would probably stay the same, you would then mostly work on the scene files that define the narrative of your text-based game, including all of the branching (the scenes that fulfill choices like "go left" or "go right" when there's a fork on the road).
Deciding on how you write scenes and what format they're stored in, and how this data is structured internally by the engine, is probably what will take the most time to design.
The most complex of software is still described by a list of instructions or bytecode. Taking inspiration from that, if you use an architecture based on a list of instructions you can definitely describe sophisticated scenes successfully. For instance, among instructions to "show text", "ask question" and such, you would support "jump" instructions to be used when the story needs to branch, so your engine can 'jump' to the (data) point that continues the story based on the decision that the player took, and if a branched route needs to merge back to the main route, then at the end of the branched route you will 'jump' back to the relevant point of the main route.
But to make sure that I can navigate to any point in the story, go back and forth etc to debug and get a feel for the narrative, I would very likely create a simple narrative editor, something like the RPGMaker Event Editor, a tool that lets me do all that and in the end export a "narrative file", an asset that the engine would read and perform.
1) First get the UI all in place, even if it's a simple font, showing a sample text paragraph ("Lorem ipsum dolor sit amet..."), and make sure that I can break text in lines so I can format paragraphs for things like story, decisions etc.
So that concludes the display of text.
2) Work on the system that reads the player input, like reading a number key for the user to indicate what choice they prefer.
So that concludes the player input.
3) Work on the system that reads a scene file and performs it for the player. From this point on, the engine code would probably stay the same, you would then mostly work on the scene files that define the narrative of your text-based game, including all of the branching (the scenes that fulfill choices like "go left" or "go right" when there's a fork on the road).
Deciding on how you write scenes and what format they're stored in, and how this data is structured internally by the engine, is probably what will take the most time to design.
The most complex of software is still described by a list of instructions or bytecode. Taking inspiration from that, if you use an architecture based on a list of instructions you can definitely describe sophisticated scenes successfully. For instance, among instructions to "show text", "ask question" and such, you would support "jump" instructions to be used when the story needs to branch, so your engine can 'jump' to the (data) point that continues the story based on the decision that the player took, and if a branched route needs to merge back to the main route, then at the end of the branched route you will 'jump' back to the relevant point of the main route.
But to make sure that I can navigate to any point in the story, go back and forth etc to debug and get a feel for the narrative, I would very likely create a simple narrative editor, something like the RPGMaker Event Editor, a tool that lets me do all that and in the end export a "narrative file", an asset that the engine would read and perform.
Re: text game
I had to try implementing part of what I said, just to get it off my system.
You need a conf.lua file in your directory with this content, to enable the raw console to pop up when you start Löve:
Then your main.lua code would be the demo below, which is a simple virtual machine to run the instructions that perform your text adventure:
Edit: after some more thinking, how I'd improve on this is:
You need a conf.lua file in your directory with this content, to enable the raw console to pop up when you start Löve:
Code: Select all
function love.conf(t)
t.console = true
t.window = false
end
Code: Select all
-- ==============================================
-- Small text adventure virtual-machine example
--
-- By Rafael Navega (2023)
-- License: Public Domain
-- ==============================================
io.stdout:setvbuf('no')
-- A global table where common information is stored.
local context = {
speaker = '',
text = '',
lastChoice = '',
narrativeIndex = 0,
}
local jumpComparisons = {
equals = function(a, b)
return a == b
end,
greater_than = function(a, b)
return a > b
end,
is_empty = function(a)
return (a and tostring(a) == '' or a == nil)
end,
is_different = function(a, b)
if type(b) == 'table' then
-- Compare 'a' with every ARRAY element of 'b'.
for index = 1, #b do
if a == b[index] then
return false
end
end
return true
else
return a ~= b
end
end
-- (...)
}
local allOperations = {
SET_SPEAKER = function(parameters)
context.speaker = (parameters[2] or '')
return true
end,
SET_TEXT = function(parameters)
context.text = (parameters[2] or '')
return true
end,
SHOW_TEXT = function(parameters)
if type(context.text) == 'string' then
if type(context.speaker) == 'string' and #context.speaker > 0 then
print(context.speaker:upper()..':')
end
print(context.text)
end
-- Pause until the user hits Enter. Store the answer if this text had a question.
local answer = io.read()
if parameters.isQuestion then
context.lastChoice = (answer:match('%g+') or '')
end
print()
return true
end,
JUMP_IF = function(parameters)
if parameters.comparison then
local comparisonFunc = jumpComparisons[parameters.comparison]
if comparisonFunc then
if comparisonFunc(context.lastChoice, parameters.reference) then
-- Advance the narrative (data) index by the steps in the instruction.
context.narrativeIndex = context.narrativeIndex + (parameters.steps or 0)
end
else
error('Unknown JUMP_IF comparison: ' .. tostring(parameters.comparison))
end
else
error('No JUMP_IF comparison set.')
end
return true
end,
END_STORY = function(parameters)
print('--- THE END ---')
return false
end
}
local myNarrativeData = {
{'SET_SPEAKER', '(???)'},
{'SET_TEXT', 'Welcome to your biggest nightmare.'},
{'SHOW_TEXT'},
{'SET_TEXT', 'Are you sure you want to continue?\n[1] Yes.\n[2] No.'},
{'SHOW_TEXT', isQuestion = true},
-- If the lastChoice in the context table is empty or not one of the possible answers,
-- try asking again to that SHOW_TEXT. If not, continue on.
-- Note that for negative steps, -1 must be added to account for the JUMP_IF itself.
{'JUMP_IF', comparison = 'is_different', reference = {'1', '2'}, steps = -2},
-- If the lastChoice in context is equal to "1" ("Yes"), jump over the next 3 instructions
-- to avoid the early story finish.
{'JUMP_IF', comparison = 'equals', reference = '1', steps = 3},
{'SET_TEXT', 'Hah! Coward!'}, -- 1 step.
{'SHOW_TEXT'}, -- 2 steps.
{'END_STORY'}, -- 3 steps.
-- By this point the user chose "1" ("Yes"), so continue with the story.
{'SET_TEXT', 'Very well then, welcome. Let me introduce myself.'},
{'SHOW_TEXT'},
{'SET_TEXT', 'My name is Jereziah.'},
{'SHOW_TEXT'},
{'SET_SPEAKER', 'Jereziah'},
{'SET_TEXT', 'In this adventure, we will bla bla bla...'},
{'SHOW_TEXT'},
-- (...)
{'END_STORY'},
}
local function runNarrative(narrativeData)
context.narrativeIndex = 1
while true do
local instruction = narrativeData[context.narrativeIndex]
if instruction then
local operationName = instruction[1]
local operation = allOperations[operationName]
if operation then
local success = operation(instruction)
if not success then
return
end
else
error(string.format('Unknown operation on index %d: "%s"', index, operationName))
end
else
break
end
context.narrativeIndex = context.narrativeIndex + 1
end
end
runNarrative(myNarrativeData)
- Have the possible choices in a question be additional optional parameters to the SHOW_TEXT operation, then it'd format and print the choices automatically, so you wouldn't have to worry about formatting them manually with the SET_TEXT operation.
- Counting the steps for a JUMP_IF is a bore and a chance for human errors. It's much better to add a new operation type, like {"JUMP_MARKER", name="BeforeQuestion36"}, whose implementation is to record its own data index to the "jumpMarkers" sub-table of the context table. So when you need to jump to a place in the data, instead of an actual number of steps, you'd just go {"JUMP_IF", (...), marker="BeforeQuestion36"}. The JUMP_IF implementation would then set the current index to the index of that named marker, if it exists. The problem is if you want to jump ahead, when future JUMP_MARKERs haven't yet been reached. I guess because of that, you'd need to do a pre-processing of the narrative data list, scanning it for all markers and adding them to the markers table. Only after that would normal execution begin.
- When writing narrative data by hand, to spare on typing, instead of having the operation names being strings I'd make some variables so I wouldn't have to write them in quotes. That is, instead of {"SET_TEXT", ...}, I'd do:
local SET_TEXT = "SET_TEXT" and then the tables can be {SET_TEXT, ...} with no need for quotes on the name.
Who is online
Users browsing this forum: No registered users and 10 guests