Connecting two independent classes

Started by
14 comments, last by SolarChronus 10 years, 8 months ago

@AllEightUp

I love that solution and know of quite a few places I can already apply it and some future areas as well. So I'm trying to apply it to solve this current dilemma.

However, I still have the problem of supplying the players inventory. The player isn't really a game object or movable object in the game. The game itself is a series of locations and when the user changes locations all that's done is a change of the area and activation of any in-game events that haven't been triggered.

The player class is really just a data container of their attributes, their inventory, their status, etc. With a few methods for modifying that data, TakeDamage() for example.

So when a new location is loaded a list of events (not to be confused with "Events") are invoked. I derive differing events from a common interface which allows me to stack differing events together. I.E A message box event and then force a combat event.

So when the user goes to a location which has a ShopEvent, that ShopEvent is invoked and populates a new shop with items. Then I do what you suggested above with that form of callback, passing in my UI (my UI class is a static class), passing in the shop inventory, and... I don't have the player inventory. So I was able to solve not having an instance of the shop inventory, but I'm lost as to how to incorporate the player.

Looking back through my code, I initialize the player, hook up a few Events so the UI can change as the players values are modified(health, etc), and that's it. Unsure of how to get an instance of the player inventory into the ShopEvent.

I could modify the base interface that all my game events are derived from, but other events would need to implement that version of Invoke, even if they're never going to use it.

Advertisement

Without more insight into your codebase, I can only make general suggestions. The first and most obvious idea is if anything up the call tree to the load level knows about the player, if so can that object or the player inventory simply be passed down the chain? If that is not viable or desirable, say it passes through several classes that have no logical reason to know anything about the player inventory or it would require modifying 10 levels of function calls or something like that, I would use a different solution. Overall, you need to consider that this is a symptom of a common problem. You have a piece of data defined way over in some other area of code which you need access to in order to make the desired functionality, but from a logical division of classes, there is no direct connection or reason for many of the intervening systems to know about one or the other items which are being tied together. This is generally called cross cutting and can be a serious pain in the ass.

Now, why the details. Static and global solutions are not great, but sometimes with cross cutting issues they are better than the other options. With a "level load" function, I most definitely don't want to pass in a specific object like player inventory just to solve this one off issue. If you keep going down that path as the solution, you end up passing 50 parameters to the function and at any given time only 0 or 1 of those parameters is required. A static or global is a short circuit for the parameter explosion but it can be as bad or worse, especially if you don't restrict access to the items and they start getting used in other places. It's 50/50 with the single benefit that you can implement it in 5 minutes and move onto making the game again, it is dirty though, so up to you.

With that out of the way, I usually have at some level a "World" instance which represents everything in the world, player(s), camera(s), etc. That object passed into the load level function makes sense and from that you can get the player information. If you have any concept such as this, it may be the solution to just pass it down the call chain if possible. If, on the other hand, it is still not viable to pass down the call chain a single question remains. Is there any possibility that your could would EVER have two world objects? If not, a singleton may be the solution. I know, knee jerk reaction, singletons are EVIL! Yes and no. Overuse of singletons and the phoenix pattern are evil. A well controlled and limited use of singletons still solves a lot of issues and if you use only accessors and functions, it can be controllable and clean. I know I'll probably get talked at evilly for this suggestion, but it is a tool like any other, smashing your thumb with a hammer is not the hammers fault, yer the dumby that swung the thing. :)

in CAVEMAN, there's an object types database, and a data structure called a "stuff list" (an inventory list). players and "stores" both use stuff lists for inventory. for transactions, there's a "trade module" which takes pointers to two stuff lists, such as a player and a store stuff list. its all done with just one data structure type (the stuff list) which makes it a good candidate for OO encapsulation.

its sounds like your store and player inventories are different types of objects, and you are therefore unsure if your "trade module" method should be part of a player inventory or a store inventory object - or should be a separate object entirely.

doing it the oo way, you'd probably want an "inventory list" object with methods like add item, remove item, select item, display list, etc. the player and each store would have an instance of an "inventory list".

then you'd have a hunk of controlling code that would display the lists, make the calls to select the item to purchase, do the checks for sufficient funds and encumbrance, and make the calls to remove money from player's inventory, add money to the store's inventory, remove the item from store's inventory, and add the item to the player's inventory.

all this would probably be wrapped up in a single "inventory list" class, with "conduct transaction" as one of its methods. you'd have to come up with a convention as to which inventory list was the "host" (the one whose "conduct transaction" method you call) and which was the "guest" - the list the host is trading with. the guest's list would be passed to the host as a parameter.

or you could put the put the controlling code in its own class and pass it two inventory lists.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Again without knowing the code base, but hearing the problem and having decades of experience...

Why does it need to know about the shop at all?

It seems like you have an event: A character is near an object. That event gets broadcast for the UI.

Then you have an interaction, and quite likely your game already has several hundred interactions, and that "shop at the store" interaction gets run.

The object (which happens to be a store) receives the event that a character is near. If the character has an inventory, make the interaction available for the UI; if the character does not have an inventory or if the shop's inventory is empty, do not make it available.

Naturally your system is generic and has thousands of specialized interaction instances. The player sees the interaction on the UI, and chooses it, which launches that one interaction. When that particular interaction to buy from the shopping object is run, it plays the little animation and then runs Target.ShowShoppingDialog(Actor);

The shopping object (which is the target) generates a list of what we'll call StoreItem objects. Each StoreItem contains a name, a base price, a thumbnail image ID, the number of items available (including an 'infinite' option), a custom data pointer that gets passed back, a function to create the object, and anything else you may need. Exactly how the object comes up with the list is specific to the object. Some store-like objects, such as a shelf, may have objects placed on it and later removed. Other objects (like a rock mine) may have infinite number of the objects. Or it may have a mix, with a limited number of some objects and an infinite number of another object. Either way, you have a list of store items to sell.

Then you pull up the shopping UI, and pass it the customer, the list of items to sell, the destination inventory, and any other things the UI needs. The UI code does all the validation, such as ensuring the player can afford all their items, that they don't buy too many items for their inventory, that they limit the number to what is actually in stock, and so on. When the UI dismisses it has a callback, as most good UI systems do.

Very roughly you get this type of code:


struct StoreItem {
 string Name;  /* Unlocalized name of object to create */
 string BasePrice;
 ThumbnailKey Thumbnail;
 s32 AvailableCount;  /* note: use -1 for 'infinite' available. */
 void* CustomData;  
 Function <void*, GameObject> CreateStoreObject; /* Creates the object using the custom data, returns the new object which the UI code places in the Actor's inventory */
 ...
}

class SomeKindOfStore : GameObject {
  std::vector<StoreItem> GetItemsForSale( Actor actor) { 
    ... /* Make the list */ 
  } 
  class BuyStuffAtStore : Interaction < Actor, SomeKindOfStore > {
    bool Run () {
      ... /* Do starting animations and whatever else */
      float markup = 0.0f;
      float sale = 0.0f;      
      return UI::StartShoppingDialog( actor, target.GetItemsForSale(actor), markup, sale, FinishedCallback);
    }
    void FinishedCallback( Actor actor) {
        ... /* Play some finishing animations */
    }
  }
}


Then you pull up the shopping UI, and pass it the customer, the list of items to sell, the destination inventory, and any other things the UI needs. The UI code does all the validation, such as ensuring the player can afford all their items, that they don't buy too many items for their inventory, that they limit the number to what is actually in stock, and so on. When the UI dismisses it has a callback, as most good UI systems do.

this would be the equivalent of the "transaction module" in CAVEMAN.

looks like in the OP's case, his UI is the logical choice for the "conduct transaction" controlling code.

which really makes sense. don't think of the UI as a screen, think of it as a "transaction conducting machine" that just happens to display some pick menus, message boxes, etc. in the process of conducting a transaction. so it would seem that a "transaction conductor" class is called for. probably a class with no data and just methods.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Those last 4 posts were so insightful I've solved my problem. I had to go back and refactor most of my UI (which allowed me to delete a lot of gunk code) and it works beautifully now.

Thank you for the suggestions and advice! I'm a better programmer for it.

This topic is closed to new replies.

Advertisement