2019-08-19 17:12:57

I've been looking around the internet (as well as around my own brain) for quite some time now in an effort to figure out a solution for the problem I'm facing. In short, I'm trying to figure out an efficient way to keep track of a game's objectives/goals in a story-based game. In a game with a map that the player can freely explore, for example, let's say that the game in its simplest form consists of the player completing objectives like traveling to various locations and talking to various npc's. I'm trying to figure out a way to keep track of what the player's current goal is and whether or not they've completed it. How should the game check to see if visiting a particular location should trigger a cut scene and advance the story or not. (without crowding the code relating to locations with a bunch of conditions checking for completion of game objectives) IN other words, I'm looking for some kind of design pattern. I've considered a few different solutions, but I figured it wouldn't hurt to ask here. Hopefully the question I have is clear enough.

Trying to free my mind before the end of the world.

2019-08-19 18:00:27

I suggest looking into the observer pattern, maybe it will help somehow.
For example, the square may send a notification when you step on it and it is marked as an important place to reach, or the player may send a notification when a key goal like taking the ultimate weapon and whoever is listening  will receive the message and  something may happen like a levelup, the enemy maneger commanding all enemies to regroop and prepare for you blasting them with the super weapon, etc.

2019-08-19 20:41:45 (edited by Saman 2019-08-19 20:43:52)

As 2 already pointed out, you may use the Observer Pattern to notify and trigger the action without having the portion of the particular code look dirty. You can Take a look at this page to get the initial idea more clearly.

2019-08-19 21:21:44

When you get down to it, you're talking conditionals. The best way I've simplified those, related to story and objectives, is with bitwise operators. If you have more than 64 bits of information, more than one var might be needed, but the ability to shove both integer values and boolean values into a single primitive goes a long way.
... For me. Your mileage may vary. I also tend to use "stage" and "plot" variables when I can expect some degree of linearity, fwiw.

看過來!
"If you want utopia but reality gives you Lovecraft, you don't give up, you carve your utopia out of the corpses of dead gods."
MaxAngor wrote:
    George... Don't do that.

2019-08-19 23:39:25

CAE_Jones wrote:

When you get down to it, you're talking conditionals. The best way I've simplified those, related to story and objectives, is with bitwise operators. If you have more than 64 bits of information, more than one var might be needed, but the ability to shove both integer values and boolean values into a single primitive goes a long way.
... For me. Your mileage may vary. I also tend to use "stage" and "plot" variables when I can expect some degree of linearity, fwiw.

And this will help him how? Nowadays, computer memory is plentyfull so I don't understand why'd do that? Can you vack your post with some code?

2019-09-04 17:05:46

So call me stupid, call me an idiot, but I don't understand.
In post 3 there is a link to an observer pattern explanation. Really good for what it is, but I fail to see how useful it would be overall.
Suppose that we have a map with the player needing to reach certain coordinates. After they do so, they get a message stating that their objective changed and they need to head back. The observer pattern suggests to create a notify function for every observed object. In python, this could look like this:

class tile:
 def __init__(self, min_x, max_x, min_y, max_y, min_z, max_z, type):
  self.min_x, self.min_y, self.min_z, self.max_x, self.max_y, self.max_z, self.type = min_x, min_y, min_z, max_x, max_y, max_z, type
  self.observer_list = []
 def notify(self, x, y, z):
  for i in self.observer_list: i.notify("walked", x, y, z)

That's all well and good, but it becomes an issue with multiple maps when you add loading and saving into account. You'd need something that can export all of your items, observers, and map states. This seems like a lot of work. Is that typical?
I also didn't mention the nastiness of you triggering a notification every time you walk, but I am not sure how much of an impact on performance, if any, it's ultimately going to have.

2019-09-06 19:32:36 (edited by Kyleman123 2019-09-06 19:37:29)

to quote the article posted above:

I hear this a lot, often from programmers who don’t actually know the details of the pattern. They have a default assumption that anything that smells like a “design pattern” must involve piles of classes and indirection and other creative ways of squandering CPU cycles.

The Observer pattern gets a particularly bad rap here because it’s been known to hang around with some shady characters named “events”, “messages”, and even “data binding”. Some of those systems can be slow (often deliberately, and for good reason). They involve things like queuing or doing dynamic allocation for each notification.

This is why I think documenting patterns is important. When we get fuzzy about terminology, we lose the ability to communicate clearly and succinctly. You say, “Observer”, and someone hears “Events” or “Messaging” because either no one bothered to write down the difference or they didn’t happen to read it.

That’s what I’m trying to do with this book. To cover my bases, I’ve got a chapter on events and messages too: Event Queue.
But, now that you’ve seen how the pattern is actually implemented, you know that isn’t the case. Sending a notification is simply walking a list and calling some virtual methods. Granted, it’s a bit slower than a statically dispatched call, but that cost is negligible in all but the most performance-critical code.

I find this pattern fits best outside of hot code paths anyway, so you can usually afford the dynamic dispatch. Aside from that, there’s virtually no overhead. We aren’t allocating objects for messages. There’s no queueing. It’s just an indirection over a synchronous method call.

plus you can't say its to slow if you've never implemented it and tested it out. try it and report back.

I don’t believe in fighting unnecessarily.  But if something is worth fighting for, then its always a fight worth winning.
check me out on Twitter and on GitHub

2019-09-08 01:14:33 (edited by Munawar 2019-09-08 01:21:08)

You can simplify this problem by using a finite state machine. This is usually achieved using enums.

Let's take your coordinates example. Logic like this will work just fine:

if state == waitingForDesiredCoords and coords == desiredCoords then:
  advance the state machine // This will advance the state and also play the next objective if your game requires this.

No need to complicate this with bitwise operators like another poster suggested--it will be really easy to lose track of things by employing that tactic and will also make your code much more difficult to read. In the end, readable code is generally desired over code that looks cool.

Here is a state example from TDV:

        public enum Stage
        {
            takingOff,
            missileHit,
            aboveIsland,
            trainingGrounds,
            airbase,
            discovery,
            powerPlant,
            chopperFight,
            juliusRadioIntercept,
            radarTowers,
            juliusBattle,
            gameEnd
        }

There is a function, playNextMissionObjective, which increments the enum state by 1 since enums are internally stored as integers, so playing the next mission objective is simply:

playFile("filename" + (int)stage + ".wav")

So state holds the stage of the objective list the player is currently looking to achieve. For example, enum.takingOff means the player is looking to complete the taking off objective.

2019-09-11 17:20:38

Lol, I won't let this die until I understand this.
@8: I have done quite a bit of reading, but as far as I know, Python does not have enums unless they're like arrays.
In your enum you have things like takingOff, missileHit, but what stores your information? I.e, you gave this example in your post:
if state == waitingForDesiredCoords and coords == desiredCoords then:
What would store the DesiredCoords? The class that holds the enum? Another class? Does it depend on the structure of the code? I'm sorry, I just want to fully understand this.

2019-09-11 19:42:38 (edited by Munawar 2019-09-11 19:45:03)

Hi,
That's just an example conditional and yes it is entirely game-dependent. In reality your code will look something like this if you were to implement the example above. Let's assume that you want the user to go next to coordinates(50, 40). Once they get there, they should pick up the object at those coordinates.

if (state == States.desiredCoords && x == 50 && y == 40) then: {
// Do something here like advance the state. Assume the next goal is to pick up the object at 50 40:
state = States.pickUpSomething
}

So, to your question of storage, x and y are just integer variables, and state is of type States.
Here is the guide on Python enums: https://docs.python.org/3/library/enum.html

2019-09-11 20:30:10

I hope i am understanding it right, but the way i see it is:
Why not have an array, and loop through the array and for example say:
if my_x==200 and my_y==200 and list[0]=="locked":
list[0]=="unlocked"
#do any thing else with the list and values in the function

best regards
never give up on what ever you are doing.

2019-09-11 20:30:24

@9, look here, python do have enums: https://docs.python.org/3/library/enum.html

If you like what I do, Feel free to check me out on GitHub, or follow me on Twitter