Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Designing Random Encounters for my Vue RPG
Raymond Camden
Raymond Camden

Posted on • Originally published atraymondcamden.com on

     

Designing Random Encounters for my Vue RPG

So I'm still piddling away at my RPG in Vue.js. To be clear, I'm never going to finish it. But you can read about it here (Testing Vue.js Application Files That Aren't Components) and here (Making Monsters with JavaScript). Over the past few months I've been slowly reading an incredible book for RPG lovers,The CRPG Book. It's a review of over 400 games over the past 40 years of computer role-playing.

While reading the book, I'm discovering some cool features that older games had and that I missed while growing up. (Or possibly just don't remember.) A few games have mentioned using encounters with simple "Choose Your Own Adventure" logic. Basically, you are presented with something and given a choice of options. No combat, just simple choices.

The more I thought about this the more I thought it could be an interesting part of my game. If you imagine that there's a random chance of combat as you walk around (part of the core gameplay I want to have), then there could be a smaller chance of a simple encounter. I'd imagine these happening maybe once or twice per gameplay so somewhat rare, but they would be a break from the typical combat encounter.

I designed my system with the following features:

  • Encounters are in an array, randomly chosen.
  • Each encounter has hard coded text and is static.
  • Each enouncter has hard coded options.
  • However, each option has random results.
  • Results can impact player stats, for example, draining health or giving your gold.
  • Finally, each encounter has an optional "prereq". This is a 'safety' check to make things a bit easier for results. So if a result drains 10 gold, I don't want to have to worry about negative balances. Ditto for death. While I'm fine with an encounter harming you, I didn't want it to kill you. I can say this point is one I'm reconsidering and may roll back. For gold, I could simply let it take all your gold and leave you at 0, and for harmful encounters, it may be kinda fun if some could actually kill you.

My data structure than looks like so:

  • prereq: If passed, a string that is evaluated against player data, like "hp>10". If false, this encounter can't happen.
  • text: The text of the encounter.
  • options: An array of options where:
    • text: The text of the option
    • results: An array of results based on this option where one is randomly selected. Each result has:
    • text: The text describing the result.
    • effect: An effect, if any, on the player, also a string that is evaluated, like gold+=10.

Here's an example:

{prereq:'gold>0 && hp>0',text:'You meet a beggar who asks for help. He looks desperate.',options:[{text:'Give a gold coin to him.',results:[{text:'The beggar thanks you!',effect:'gold--'},{text:'The beggar thanks you, winks, and dissappears.',effect:'gold += 300'},// it was a god or whatever in disguise{text:'The beggar smirks and punches you!',effect:'hp--'}]},{text:'Walk away.',results:[{text:'The beggar spits at you!',effect:''},// no effect{text:'The beggar growls and punshes you!',effect:'hp--'}]},]},
Enter fullscreen modeExit fullscreen mode

The JavaScript utility has two main methods. The first returns a random encounter that's filtered by prereqs. A player object is passed in (I'm not using TypeScript so what I really mean is a "simple object representation" of the player). The next method takes a player object, an encounter, and a selected option. It figures out the random result and applies the effect. Here's the entire utility.

import{misc}from'./misc'constdata=[{prereq:'gold>0 && hp>0',text:'You meet a beggar who asks for help. He looks desperate.',options:[{text:'Give a gold coin to him.',results:[{text:'The beggar thanks you!',effect:'gold--'},{text:'The beggar thanks you, winks, and dissappears.',effect:'gold += 300'},// it was a god or whatever in disguise{text:'The beggar smirks and punches you!',effect:'hp--'}]},{text:'Walk away.',results:[{text:'The beggar spits at you!',effect:''},// no effect{text:'The beggar growls and punshes you!',effect:'hp--'}]},]},{prereq:'hp>0',text:'You hear a growl from behind you.',options:[{text:'Put on a brave face.',results:[{text:'You seem to have scared off whatever was stalking you.',effect:'exp+=100'}]},{text:'Run away',results:[{text:'You run until your out of breath.',effect:''},// no effect{text:'You run, but trip and sprain your ankle!',effect:'hp--'}]},]}]exportconstencounterMaker={// given a player ob, find an encounter they can doselect(player){letpossibleEncounters=data.filter(d=>{if(!d.prereq)returntrue;letprereq=fixEvalString(d.prereq);returneval(prereq);});if(possibleEncounters.length===0)returnnull;returnpossibleEncounters[misc.getRandomIntInclusive(0,possibleEncounters.length-1)];},resolve(player,encounter,choice){if(choice>=encounter.options.length)choice=0;letselected=encounter.options[choice];letresult=selected.results[misc.getRandomIntInclusive(0,selected.results.length-1)];console.log('result for'+choice,result);if(result.effect!=''){console.log(player);eval(fixEvalString(result.effect));console.log(player);}returnplayer;}}// utility function to fix eval string to include playerfunctionfixEvalString(str){str=str.replace(/gold/g,'player.gold');str=str.replace(/hp/g,'player.hp');str=str.replace(/exp/g,'player.exp');returnstr;}
Enter fullscreen modeExit fullscreen mode

The two methods I described above are defined asselect andresolve. Notice that I wrote a function,fixEvalString, that can be used by my prereqs and effects to modify the player. This feels like bad code. I mean, eval is bad in general. Given that I know the "shape" of my player data I could switch to another way of doing this, but I'll worry about that when I finish the game, which is, you know, never.

I did build a utility to help test this, and here's what it looks like:

/*Ray, run with: node -r esm test.js*/import{encounterMaker}from'../src/utils/encounterMaker'console.log('basic player');console.log(encounterMaker.select({gold:10,hp:10}));console.log('poor player');console.log(encounterMaker.select({gold:0,hp:10}));console.log('dead player');console.log(encounterMaker.select({gold:10,hp:0}));console.log('---------------------------------');console.log('basic player resolve');letplayer={gold:10,hp:10,exp:200};letenc=encounterMaker.select(player);console.log('chosen enc',enc);player=encounterMaker.resolve(player,enc,0);console.log('Player at end',player);player=encounterMaker.resolve(player,enc,1);console.log('Player at end2',player);
Enter fullscreen modeExit fullscreen mode

As you can see, I've got a fewselect calls and a fewresolve ones. The output looks like so:

basicplayer{prereq:'hp>0',text:'You hear a growl from behind you.',options:[{text:'Put on a brave face.',results:[Array]},{text:'Run away',results:[Array]}]}poorplayer{prereq:'hp>0',text:'You hear a growl from behind you.',options:[{text:'Put on a brave face.',results:[Array]},{text:'Run away',results:[Array]}]}deadplayernull---------------------------------basicplayerresolvechosenenc{prereq:'gold>0 && hp>0',text:'You meet a beggar who asks for help. He looks desperate.',options:[{text:'Give a gold coin to him.',results:[Array]},{text:'Walk away.',results:[Array]}]}resultfor0{text:'The beggar thanks you!',effect:'gold--'}{gold:10,hp:10,exp:200}{gold:9,hp:10,exp:200}Playeratend{gold:9,hp:10,exp:200}resultfor1{text:'The beggar spits at you!',effect:''}Playeratend2{gold:9,hp:10,exp:200}
Enter fullscreen modeExit fullscreen mode

You can find the complete repo athttps://github.com/cfjedimaster/vue-demos/tree/master/grpg. I think next I'm going to take a stab and creating a map. I've been hashing around some ideas for a few weeks now and I think I'm ready to put pen to paper so to speak.

Photo byTommy Tang onUnsplash

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Raymond Camden is a experienced developer advocate and evangelist. His work focuses on APIs, the web platform, and generative AI. He is the author of multiple books on development and has been activel
  • Location
    Louisiana
  • Work
    API Evangelist at Foxit
  • Joined

More fromRaymond Camden

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp