Extendable inventory and world objects

Started by
18 comments, last by hplus0603 13 years, 9 months ago
The main thing about an Apple is that it's an object of class Apple -- which in turn is an object of class Fruit, with the name "Apple" and the model "apple.x" and the inventory bitmap "apple.png" and the action script "onActivate(self, player): player.health += 10; self.consume()"

All object classes should/could be loaded on game start time, or level load time. Instances of objects then carry only the information that's specific to the instance. Your design has two kinds of instances: instance in inventory (2D), and instance in world (3D). When an object moves from one to the other, you should create the new instance object, and forward the object type (in this case, the pointer-to-Apple) to the new instance.

enum Bool { True, False, FileNotFound };
Advertisement
No, apple is not a class. If I did so then every of 5000 object types had own class. But what if we'll group it? Apple, cherry, meat, medicine and other 100 health-energy items have the same class.

Yes, you told right that inventory instance has some transformation to world 3D instance but I talk about hard interactions:

1. Apple, cherry, meat (changing parameters) that have class ParamChangeInventotyItem that have action of parameter change on use, after turn to 3D they have Gettable3DObject class that has only action to get from ground and turn to ParamChangeInventotyItem again.

2. Clothes have some another action on use (so we have class ClothesInventoryItem), but turns to Gettable3DObject.

3. Gun has another action in inventory (class GunInventoryItem), but it also turns to Gettable3DObject.

4. The same is with the sword (class SwordInventoryItem) -> Gettable3DObject.

Did you noticed that every different inventory class has the similar behaviour in 3D space. Than talk about next interactions:

5. Table (has class PutableInventoryItem), it turns to Table3DObject, because in 3D world it has some actions, like put food on it, eat something.

6. Chair (class PutableInventoryItem), turns to another Chair3DObject, because we can sit and stand only.

7. Bench (class PutableInventoryItem), turns to another Bench3DObject

And so on...
VATCHENKO.COM
Quote:5. Table (has class PutableInventoryItem), it turns to Table3DObject, because in 3D world it has some actions, like put food on it, eat something.

Those are user (inter)actions. They have little to nothing to do with 3D.

The simplest way is to define a base class:
struct Action {  virtual string getName();  virtual int id();  virtual string getDescription();  virtual string perform();}struct InteractiveObject { // base class for objects user can interact with  list<Action *> getActions(User *); // get list of actions for given user};


This approach is known as command pattern. On creation, each instance populates its actions, or creates them on demand in response to getActions. In Java, Swing uses this with Action class to define UIs. Most other UI frameworks offer similar. QT, being almost a clone of Java standard library uses almost identical semantics.

Renderer will be completely separate, the only connection will be some ray picking algorithm, which will determine which piece of geometry the user clicked.

Then, the ID of this geometry is sent to server. Server resolves the ID to object, calls getActions, and serializes that back to client.

Client now displays the UI (popup or similar) with list of action names.

When user chooses an action, it sends its ID or similar to server, and server calls ->perform() on that action.

But there really shouldn't be any, or very little relation between 3D and logic. Or, logic should still be separate, but perhaps connect itself to collision detection. I don't think there is any realistic need for logic to know any kind of details regarding meshes or textures.

At least not at this stage, or for simple to moderately complex interactions.
Quote:Original post by Anton Vatchenko
No, apple is not a class. If I did so then every of 5000 object types had own class. But what if we'll group it?


If you look at my actual suggestion, I used "class" to mean "object type" more than "C++ type." The differentiation between "Apple" and "Orange" is done using properties, not code. (And, perhaps, scripting -- again, see my example).

Scripting is really powerful when you want to describe thousands of slightly different objects. You might even want to generate the scripts from an Excel sheet or database.

Btw: This thread has very little to do with networking -- if it weren't so long, I'd move it to general programming.
enum Bool { True, False, FileNotFound };
Antheus, the problem is not in renderer, object picking, and not in getting several actions for 3D object. We're talking about life cycle of an object - from inventory to 3D and back. Some objects are represented by several classes in inventory and same 3D classes, some have same inventory classes and several 3D classes. Engine is not scriptable. 100% C++ and text/binary config files.

And still a problem is to implement unique images, sounds, texts in inventory and their processing. Because every preconfigured object can have a link to object type where you can getName() that gets m_name field or getType that gets m_type field, but unique objects are not stored in engine. They are in database.
VATCHENKO.COM
Well, what you're talking about here is the result of crossing the list of verbs with the list of nouns.

The trick here is spotting that you're trying to express a MANY<->MANY relationship between nouns and verbs.

One of the simple ways of doing this is to imagine a grid. It has verbs across the top, and nouns down the side. The intersection of the rows and columns are what happens when you try and do verb V to noun N.

At each intersection, you want to be able to set what happens. So you have "EAT/BOOK" which says "it's not nutritious", "EAT/APPLE" which increases a stat. "READ/APPLE" doesn't do anything and "READ/BOOK" tells you what the book is about.

So what you have here is a "sparse grid" of actions. Where there's no interaction, you can just omit it. Your verbing system detects there's nothing there and says "don't be silly". Otherwise it calls the verb function.


So, the next question is how do you populate your rows?

The easiest way to do this is to have "mixin" classes. These are small aspects of objects, "Eatable", "Readable" etc.

The mixins just populate the rows of the object with function pointers.

This makes it really easy to do things like create new classes of objects; you can do it in one of two ways. Firstly you can list the mixins which make it up, but also you can use another object as a prototype.

So you can pick an INSTANCE of an object and use it as the root of a new class. You can say that "Orange001 is like Apple887, except that it has attribute[colour]=orange".

You can also clone the objects and add a mixin. Oranges often have a small label on them saying where they come from, so you can say "Orange001 is Readable, text='grown in Florida' " and now it can be read.

This is particularly useful for highly dynamic environments when you don't know how many of the things to make.

Serialising the object to a DB or other store is a little more tricky but with some thought upfront, not so bad. Firstly it's a set of name=value pairs. And secondly it's just a set of verb=functionname pairs. Store both and you can put the object in and out of a datastore.
No-no-no. I'm not talking about mixing of actions and objects. I don't make them eatable, readable etc. I group them to executeable. Every item is executable and dropable. Problems are not in in those actions.

First problem is to process the use of unique objects and non-unique in good way.
VATCHENKO.COM
I already suggested how to do this. You create an object instance that represents the common properties of an object "type." This would describe what is an "Apple" and what is an "Orange." You then create a separate object which contains the unique properties, such as 3D position or enchantment state (if objects can be enchanted). That object references the type object for all common properties.
enum Bool { True, False, FileNotFound };
Please describe how to do with database interactions and unique objects, because as I understand we store in inventory table something like:

user, type, subtype, count:
1, 2, 1, 5
1, 7, 3, 2
1, 3, 2, 5
2, 1, 5, 7

and then:
// Some typedef that adds to global type mapADD_TYPE(2, GeneralGun);ADD_TYPE(7, ParamChanger);ADD_TYPE(3, Image);ADD_TYPE(1, Sound);void Player::processRow(int type, int subtype, int count){    addToInventory(getWorld()->typeList[type]->create(subtype), count);}Item* GeneralGun::create(int subtype){    return m_subTypeList->get(subtype)->create();}Item* Image::create(int subtype){    return new ImageItem(query_result("select * from images where id=" + subtype));}...
VATCHENKO.COM
You still haven't described what's what. You need more than one table.

The "class" of an object, or the "prototype" of an object in certain systems, lives in the "object class" table.

The "instance" of an object, which lives in the world, lives in an "instances" table.

The "instances" table contains information such as position, and contains a reference to the appropriate class in the "class" table.

The "class" table contains all the information that never changes about an object type.

You mirror rows in these tables into objects in your game.

When an object changes state -- from "in world" to "in inventory" for example -- then you change objects (delete the in-world object, increment the count for the object class in question in the inventory of the player) and also change the database to match (update the row that counts number-of-instances-of-this-type-for-this-player, and delete the row that says instance-of-object-type-in-world).
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement