Angelwolf Posted June 29, 2019 Share Posted June 29, 2019 Hi all, I'm looking to implement dialogue options into my current game, but I'm wondering what the best way to do this would be? I'm not great with Lua as some of you older posters may remember, however I often manage to work my way though things using methods that make sense to me but are not necessarily viable or efficient. So far, I have been using a system with 'flags' whereby depending on the answer the player gives (in the way of pressing F1, F2, F3 etc), a flag is set as true or false which then branches the conversation depending on the answers given. Presently, however, I have hit a snag where my code does not allow me to branch past the second question I need to re-use keys in order to progress the discussion. For example: Question 1: How are you feeling today? (answer) F1: I'm great! (answer) F2: I'm okay (answer) F3: I feel sad Supposing the player presses F3... Question 2: Why do you feel sad? (answer) F1: The weather is bad (answer) F2: My cat ran away (answer) F3: I don't like my salary The problem here is that because the player pressed F3 in question 1, the next question given is question 2 (which is correct) but it immediately answers as F3 again. Can anyone shed some advice on this? Quote My Games: Nightmare Prism:::WolfTale:::Dungeon Creeper:::Despair of Ordinary Men:::Piano Simulator 2016:::House (W.I.P) Link to comment Share on other sites More sharing options...
Thirsty Panther Posted June 29, 2019 Share Posted June 29, 2019 Hard to tell without seeing your code but I'm guessing you are not resetting your variable for the key press for the first question. ie Question one: Answer = "F3" Question two: Answer = still equals "F3" from question one. Hope this helps. Good to have you back. 1 Quote Link to comment Share on other sites More sharing options...
Angelwolf Posted June 29, 2019 Author Share Posted June 29, 2019 3 hours ago, Thirsty Panther said: Hard to tell without seeing your code but I'm guessing you are not resetting your variable for the key press for the first question. ie Question one: Answer = "F3" Question two: Answer = still equals "F3" from question one. Hope this helps. Good to have you back. This is exactly right, and what I'm having trouble working out how to fix (because suppose I change the response key to F10, the issue goes away because the initial requirements are still met, but with a different 'fire' button). I'll see if I can get my code online very soon so you can at least see what I mean and also marvel at its lack of elegance. Thanks for the welcoming reply; It's been a busy year or two. Now married with a beautiful daughter! Quote My Games: Nightmare Prism:::WolfTale:::Dungeon Creeper:::Despair of Ordinary Men:::Piano Simulator 2016:::House (W.I.P) Link to comment Share on other sites More sharing options...
Angelwolf Posted June 29, 2019 Author Share Posted June 29, 2019 Script.enabled=true--bool "Enabled" Script.npcname=NPC--string "NPC Name" Script.hudFont = nil -- REMOVE IF BROKEN Script.indialogue=false--bool Script.rude=false--bool Script.askname=false--bool Script.asklocation=false--bool Script.introdone=false--bool Script.intro = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - Intro.tex") --texture for the dialogue box (initial intro) Script.introdonerude = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - introrude.tex") --texture for the dialogue box (if they player was rude, show this intro next time they interact) Script.introdone = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - introdone.tex") --texture for the dialogue box (intro once the player has already spoken to the NPC once before) Script.askedname = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - name.tex") --texture for the dialogue box (NPC tells player their name) Script.askedlocation = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - location.tex") --texture for the dialogue box (NPC tells player where they are) diagboxcolor = Vec4(1,1,1,0.8) diagboxLength = context:GetWidth() --set dialogue box length to the same length as the game window diagboxHeight = context:GetHeight()/3 --set dialogue box height to one third of the height of the game window xback = 0 --set the back (left side) (x) of the dialoge box to the left of the game window yback = context:GetHeight() -diagboxHeight --set the back (bottom) (y) of the dialogue box to the bottom of the game window function Script:Start() self.hudFont = Font:Load("Fonts/hudfont.ttf",12) --REMOVE IF BROKEN, IT IS FOR THE HUD self.introdone = false self.rude = false self.askname = false self.asklocation = false end function Script:Use() if self.indialogue == false then self.indialogue = true else if self.indialogue == true then self.indialogue = false end end --self:Toggle() end function Script:Toggle()--in end function Script:PostRender(context) context:SetBlendMode(Blend.Alpha) -- THE BELOW IS FOR THE INITIAL INTRODUCTION DIALOGUE if self.indialogue == true and self.introdone == false and self.rude == false and self.askname == false and self.asklocation == false then context:SetColor(diagboxcolor) --colour parameters for hudbox, and for alpha context:DrawImage(self.intro,xback,yback,diagboxLength,diagboxHeight) --draw the dialogue box in the screen (within the Script:PostRender(context) function playerCanMove = false if self.indialogue == true and self.introdone == false and self.rude == false and self.askname == false and self.asklocation == false and window:KeyDown(Key.F1) then self.indialogue = true self.introdone = true self.rude = false self.askname = true self.asklocation = false else if self.indialogue == true and self.introdone == false and self.rude == false and self.askname == false and self.asklocation == false and window:KeyDown(Key.F2) then self.indialogue = true self.introdone = true self.rude = false self.askname = false self.asklocation = true else if self.indialogue == true and self.introdone == false and self.rude == false and self.askname == false and self.asklocation == false and window:KeyDown(Key.F3) then self.indialogue = false self.introdone = true self.rude = true self.askname = false self.asklocation = false end end end else if -- THE BELOW IS FOR IF THE PLAYER PRESSES F3 (rude) IN THE INITIAL INTRODUCTION DIALOGUE self.indialogue == true and self.introdone == true and self.rude == true and self.askname == false and self.asklocation == false then context:SetColor(diagboxcolor) --colour parameters for hudbox, and for alpha context:DrawImage(self.introdonerude,xback,yback,diagboxLength,diagboxHeight) --draw the dialogue box in the screen (within the Script:PostRender(context) function playerCanMove = false if self.indialogue == true and self.introdone == true and self.rude == true and self.askname == false and self.asklocation == false and window:KeyDown(Key.F1) then self.indialogue = true self.introdone = true self.rude = false self.askname = true self.asklocation = false else if self.indialogue == true and self.introdone == true and self.rude == true and self.askname == false and self.asklocation == false and window:KeyDown(Key.F2) then self.indialogue = true self.introdone = true self.rude = false self.askname = false self.asklocation = true else if self.indialogue == true and self.introdone == true and self.rude == true and self.askname == false and self.asklocation == false and window:KeyDown(Key.F3) then self.indialogue = false self.introdone = true self.rude = true self.askname = false self.asklocation = false end end end else if --THE BELOW IS FOR IF THE PLAYER PRESSES F1 (name) IN THE INITIAL INTRODUCTION DIALOGUE self.indialogue == true and self.introdone == true and self.rude == false and self.askname == true and self.asklocation == false then context:SetColor(diagboxcolor) --colour parameters for hudbox, and for alpha context:DrawImage(self.askedname,xback,yback,diagboxLength,diagboxHeight) --draw the dialogue box in the screen (within the Script:PostRender(context) function playerCanMove = false if self.indialogue == true and self.introdone == true and self.rude == false and self.askname == true and self.asklocation == false and window:KeyDown(Key.F1) then self.indialogue = true self.introdone = true self.rude = false self.askname = false self.asklocation = true else if self.indialogue == true and self.introdone == true and self.rude == false and self.askname == true and self.asklocation == false and window:KeyDown(Key.F2) then self.indialogue = false self.introdone = true self.rude = false self.askname = false self.asklocation = false else end end end end end --end of else ifs if self.indialogue == false then playerCanMove = true end end Here's my trainwreck of code. Hope it helps to understand what I'm going for. I'm sure there must be a better and more simple way to do this, and I'm willing to re-write this entire thing if need be. Quote My Games: Nightmare Prism:::WolfTale:::Dungeon Creeper:::Despair of Ordinary Men:::Piano Simulator 2016:::House (W.I.P) Link to comment Share on other sites More sharing options...
Rick Posted June 29, 2019 Share Posted June 29, 2019 Just off the top of my head, having anything bug draw logic in PostRender() would be not ideal. Have your logic in UpdateWorld() and use PostRender() to just draw 2d images or text of the results. This means don't have window:KeyDown() in PostRender(). The second thing is using window:KeyDown(). The code is fairly messy to read but remember that KeyDown() will register true each frame if it's held down and it's hard for a person to press a key and have KeyDown() see it for only 1 frame. Try using KeyHit() instead maybe because that only registers the key press for 1 frame no matter if they hold it down. So that might help solve your issue of the question getting answered the same when they press a key. As far as structure goes, this is clearly, I think you know this, a mess of code which isn't very flexible and hard to debug. There is so much code duplication as well which will lead to hard to find bugs as soon as you start changing anything. You should probably go back to the drawing board with how to approach the requirements. Anytime you find yourself using just a **** load of boolean flags it's a good indication there is a better way. A bunch of boolean flags generally means you're trying to control state. So something to possibly look into is a state machine. The practical idea of state machines is that it allows you to change states and each state can be nicely isolated. In the case of a Lua library I made for LE this means each state has functions. Enter/Exit/Update(). When you change from 1 state to another the state you were in calls the Exit() function you defined for that state, then calls the Enter() function for the new state you changed to, then continuly calls Update() for the new state until you change state in which the same thing happens. Exit the old state, enter the new state, then Update() on the new state over and over again where you are looking for the user input or something to change to another state. To look into this go to the Workshop from the LE editor and search on state machine and you'll see my FSM. Download it. In the description I have links to videos explaining how it works. Something else to look at is the structure of your data. In this case it seems like you have questions that can lead to other questions and each question can have 3 answers. So think about how you could structure your data for that. This would be a table that holds question and answers and nested tables under as well to any level you want. 2 Quote Link to comment Share on other sites More sharing options...
Angelwolf Posted June 29, 2019 Author Share Posted June 29, 2019 6 hours ago, Rick said: Very useful stuff I don't want to quote in a massive post Thanks Rick. I did see your LUA library (I understand this is a paid item - no problem). If I were to utilise this, would you be happy to walk me through any troubles I have if I cannot work it out after watching the videos? I think having isolated states would be hugely beneficial and would be the perfect solution for my problem. Something I need to consider is that my game will have a heavy emphasis over dialogue and choices. Will your library be suitable for dialogue on a large scale? Presently I'm using images for dialogue and choices because I find it easier to work with. I could move to text (which might prove better) but I wasn't sure about word wrapping and adjustment for if the player runs at different resolutions. If you would be happy to include a specific dialogue module to your library, or even make one for me personally, I wouldn't object to pay you (or someone) for such a commission, depending on a quote. Quote My Games: Nightmare Prism:::WolfTale:::Dungeon Creeper:::Despair of Ordinary Men:::Piano Simulator 2016:::House (W.I.P) Link to comment Share on other sites More sharing options...
Rick Posted June 29, 2019 Share Posted June 29, 2019 My FSM is free. I would be willing to help yes. You for sure want to go with text vs images but yes that does mean you'll want some kind of generic code (library) to deal with all the issues that come with that (resolutions, line breaks etc). This can get sticky because it's all about font size and in LE you have to load fonts to get different sizes which is a pain. I have a UI library (it's sort of complicated now) where I have a label control where you size the rectangle where the text should fit and on load of the game it loops through a bunch of font sizes and loads them and tries to figure out what size fits best inside the rectangle and the rectangle itself is resolution independent (the size of it is relative to the screen resolution so it always looks the same on any resolution). You'll have the same issue with your images though because the size of them aren't resolution independent and you've had to account for that if you want that way too. I can release my UI library. It's pretty powerful as it handles all this stuff for you but that comes at a cost of complexity and I don't have any tutorials on it. I have some time tomorrow so I'll upload it then and make a youtube video on it. 2 Quote Link to comment Share on other sites More sharing options...
Angelwolf Posted June 30, 2019 Author Share Posted June 30, 2019 Thank you for your assistance with this. Our baby daughter had a good sleep which gave me a good night of sleep for a change. I've gone back to the drawing board with this and it appears to work, and is much easier to read. I'm sure there is still a better way, but this works - so thank you for your help. Any comments are very welcomed. import "Scripts/Functions/ReleaseTableObjects.lua" Script.enabled=true--bool "Enabled" Script.npcname=NPC--string "NPC Name" Script.indialogue=false--bool Script.intro = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - Intro.tex") --texture for the dialogue box (initial intro) Script.introdonerude = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - introrude.tex") --texture for the dialogue box (if they player was rude, show this intro next time they interact) Script.introdone = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - introdone.tex") --texture for the dialogue box (intro once the player has already spoken to the NPC once before) Script.askedname = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - name.tex") --texture for the dialogue box (NPC tells player their name) Script.askedlocation = Texture:Load("Dialogue/Downtown/Paradise Apartments/paradise apartments security guard - location.tex") --texture for the dialogue box (NPC tells player where they are) Script.dialogue = Texture:Load("Dialogue/Downtown/Paradise Apartments/dialoguetest.tex") diagboxcolor = Vec4(1,1,1,0.8) --colour parameters for hudbox, and for alpha diagboxLength = context:GetWidth() --set dialogue box length to the same length as the game window diagboxHeight = context:GetHeight()/3 --set dialogue box height to one third of the height of the game window xback = 0 --set the back (left side) (x) of the dialoge box to the left of the game window yback = context:GetHeight() -diagboxHeight --set the back (bottom) (y) of the dialogue box to the bottom of the game window function Script:Start() self.dialogue = self.intro --Set this as the default dialogue text end function Script:Use() if self.indialogue == false then --Check to see if the player is available for dialogue self.indialogue = true --Register that the player is in a dialogue playerCanMove = false --Stop player form moving when they're in a dialogue end end function Script:UpdateWorld()--in if self.indialogue == true and self.dialogue == self.intro then --Give player the initial introduction if window:KeyHit(Key.F1) then --Player asked name self.dialogue = self.askedname --Change graphic to askedname else if window:KeyHit(Key.F2) then --Player asked location self.dialogue = self.askedlocation --change graphic to askedlocation else if window:KeyHit(Key.F3) then --Player told NPC to buzz off self.dialogue = self.introdonerude --Change graphic to a rude introduction next time (player and NPC have met before) self.indialogue = false --Remove the player form dialogue end end end end if self.indialogue == true and self.dialogue == self.askedname then --NPC answered with their name if window:KeyHit(Key.F1) then --Player asked location self.dialogue = self.askedlocation --Change graphic to askedlocation else if window:KeyHit(Key.F2) then --Player said goodbye self.dialogue = self.introdone --Change graphic to a nice introduction next time (player and NPC have met once before) self.indialogue = false --Remove the player from dialogue end end end if self.indialogue == true and self.dialogue == self.askedlocation then --NPC answered with location if window:KeyHit(Key.F1) then --Player asked name self.dialogue = self.askedname --Change graphic to askedname else if window:KeyHit(Key.F2) then --Player said goodbye self.dialogue = self.introdone --Change graphic to a nice introduction next time (player and NPC have met once before) self.indialogue = false --Remove the player from dialogue end end end if self.indialogue == true and self.dialogue == self.introdone then --Player and NPC have met before if window:KeyHit(Key.F1) then --Player asked NPC to remind them of their name self.dialogue = self.askedname --Change graphic to askedname else if window:KeyHit(Key.F2) then --Player asked NPC to remind them of their location self.dialogue = self.askedlocation --Change graphic to askedlocation else if window:KeyHit(Key.F3) then --Player said goodbye self.indialogue = false --Remove the player from dialogue end end end end if self.indialogue == true and self.dialogue == self.introdonerude then --Player and NPC have met before and player told NPC to buzz off if window:KeyHit(Key.F1) then --Player apologised and asked for name self.dialogue = self.askedname --Change graphic to askedname else if window:KeyHit(Key.F2) then --Player apologised and asked for location self.dialogue = self.askedlocation --Change graphic to askedlocation else if window:KeyHit(Key.F3) then --Player told NPC to go away self.indialogue = false --Remove the player from dialogue self.dialogue = self.introdonerude --Remember that Player has been rude to NPC end end end end if self.indialogue == false then playerCanMove = true end end function Script:PostRender(context) context:SetBlendMode(Blend.Alpha) if self.indialogue then context:SetColor(diagboxcolor) context:DrawImage(self.dialogue,xback,yback,diagboxLength,diagboxHeight) --draw the dialogue box in the screen (within the Script:PostRender(context) function end end function Script:Disable()--in self.enabled=false end function Script:Enable()--in self.enabled=true end function Script:UpdatePhysics() end function Script:Release() ReleaseTableObjects(self.sound) if self.loopsource then self.loopsource:Release() self.loopsource=nil end self.sound=nil end Quote My Games: Nightmare Prism:::WolfTale:::Dungeon Creeper:::Despair of Ordinary Men:::Piano Simulator 2016:::House (W.I.P) Link to comment Share on other sites More sharing options...
Rick Posted June 30, 2019 Share Posted June 30, 2019 I will say good job at moving the core logic part out of PostRender Quote Link to comment Share on other sites More sharing options...
aiaf Posted June 30, 2019 Share Posted June 30, 2019 Load your dialogs from lua files(a table) and have them rendered.You need something generic for all kinds of dialogs and depths. We have something like this in the forth project using leadwerks gui. The choices table tells the next sub dialogs, and each dialog line has a function handler that gets called when option was selected. TerminalDialog = { ["11"] = { speaker = "Monolith", text = "Terminal active. Awaiting commands: ", navButtonType = "End Dialog", choices = {"Print command", "Print command input", "Select operation", "Input command data", "Clear command", "Execute command"}, func = UpdateTerminalDialogData}, ["1111"] = { speaker = "Monolith", text = "Monolith command:\n\n N " .. mNorthInput .. "\n S " .. mSouthInput .. "\n E " .. mEastInput .. "\n W " .. mWestInput .. "\n U " .. mUpInput .. "\n D " .. mDownInput, navButtonType = "End Dialog", choices = {}, func = nil}, ["2111"] = { speaker = "Monolith", text = "Current databanks content:\n\n N " .. northInput .. "\n S " .. southInput .. "\n E " .. eastInput .. "\n W " .. westInput .. "\n U " .. upInput .. "\n D " .. downInput, navButtonType = "End Dialog", choices = {}, func = nil}, ["3111"] = { speaker = "Monolith", text = "Pick type of operation performed when pushing data:", navButtonType = "End Dialog", choices = {"Concatenation", "Addition", "Substraction", "Multiplication"}, func = nil}, ["123111"] = { speaker = "Monolith", text = "Concatenation active", navButtonType = "End Dialog", choices = {}, func = function() selectedOperation = 0 end}, ["223111"] = { speaker = "Monolith", text = "Addition active", navButtonType = "End Dialog", choices = {}, func = function() selectedOperation = 1 end}, ["323111"] = { speaker = "Monolith", text = "Substraction active", navButtonType = "End Dialog", choices = {}, func = function() selectedOperation = 2 end}, ["423111"] = { speaker = "Monolith", text = "Multiplication active", navButtonType = "End Dialog", choices = {}, func = function() selectedOperation = 3 end}, ["4111"] = { speaker = "Monolith", text = "All data pushed", navButtonType = "End Dialog", choices = {}, func = PushTerminalData}, ["5111"] = { speaker = "Monolith", text = "Databanks cleared", navButtonType = "End Dialog", choices = {}, func = ClearTerminalDatabanks}, ["6111"] = { speaker = "Monolith", text = "Execute command", navButtonType = "End Dialog", choices = {}, func = ExecuteCommand} } Its 2 lua classes Dialog and DialogManager.I can send you the files if you want to have a look. Just trying to give you some ideas. 1 Quote I made this with Leadwerks/UAK: Structura | Stacky Desktop Edition Website: Binary Station Link to comment Share on other sites More sharing options...
Angelwolf Posted July 1, 2019 Author Share Posted July 1, 2019 20 hours ago, aiaf said: TerminalDialog = { ["11"] = { speaker = "Monolith", text = "Terminal active. Awaiting commands: ", navButtonType = "End Dialog", choices = {"Print command", "Print command input", "Select operation", "Input command data", "Clear command", "Execute command"}, func = UpdateTerminalDialogData}, ["1111"] = { speaker = "Monolith", text = "Monolith command:\n\n N " .. mNorthInput .. "\n S " .. mSouthInput .. "\n E " .. mEastInput .. "\n W " .. mWestInput .. "\n U " .. mUpInput .. "\n D " .. mDownInput, navButtonType = "End Dialog", choices = {}, func = nil}, ["2111"] = { speaker = "Monolith", text = "Current databanks content:\n\n N " .. northInput .. "\n S " .. southInput .. "\n E " .. eastInput .. "\n W " .. westInput .. "\n U " .. upInput .. "\n D " .. downInput, navButtonType = "End Dialog", choices = {}, func = nil}, ["3111"] = { speaker = "Monolith", text = "Pick type of operation performed when pushing data:", navButtonType = "End Dialog", choices = {"Concatenation", "Addition", "Substraction", "Multiplication"}, func = nil}, ["123111"] = { speaker = "Monolith", text = "Concatenation active", navButtonType = "End Dialog", choices = {}, func = function() selectedOperation = 0 end}, ["223111"] = { speaker = "Monolith", text = "Addition active", navButtonType = "End Dialog", choices = {}, func = function() selectedOperation = 1 end}, ["323111"] = { speaker = "Monolith", text = "Substraction active", navButtonType = "End Dialog", choices = {}, func = function() selectedOperation = 2 end}, ["423111"] = { speaker = "Monolith", text = "Multiplication active", navButtonType = "End Dialog", choices = {}, func = function() selectedOperation = 3 end}, ["4111"] = { speaker = "Monolith", text = "All data pushed", navButtonType = "End Dialog", choices = {}, func = PushTerminalData}, ["5111"] = { speaker = "Monolith", text = "Databanks cleared", navButtonType = "End Dialog", choices = {}, func = ClearTerminalDatabanks}, ["6111"] = { speaker = "Monolith", text = "Execute command", navButtonType = "End Dialog", choices = {}, func = ExecuteCommand} } This looks quite interesting, thank you. How, exactly, does it work? Quote My Games: Nightmare Prism:::WolfTale:::Dungeon Creeper:::Despair of Ordinary Men:::Piano Simulator 2016:::House (W.I.P) Link to comment Share on other sites More sharing options...
aiaf Posted July 2, 2019 Share Posted July 2, 2019 2111 means: option index 2 depth 1 parent 11 323111 means: option index 3 depth 2 parent 3111 This way the key is unique and i have a variable depth so i can access things in the above table. At begin we are at ["11"] i just draw the corresponding choices table: "Print command", "Print command input", "Select operation", "Input command data", "Clear command", "Execute command" Player click Select operation (that is index "3111"), draw the choices if they exits and execute the corresponding func. dialogManager.goto("3111") Its generic, works for any depth, single inconvenient is the key can get pretty long for higher depth. I could hide that from the user, by using a file with tab as depth specifier that after it generates the file as above. Ill send you the code when i come back to my computer. Quote I made this with Leadwerks/UAK: Structura | Stacky Desktop Edition Website: Binary Station Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.