Design assistance with user input

2003-03-27
2003-06-01
  • William Seppeler

    Thanks to Lee, my last delemma has be solved, but now I have a whole new issue.  This time, I don't even know how to go about accomplishing the task....well....sorta....I have an idea.

    I'm writing the command interpreter part of my game loop now.
    I have a data structure for each Object in the game.  KrSprite is a subcomponent of that Object class.  I'd like to be able to read user input such that when I mouse click on a sprite, I can get the object.

    I see there is a Kyra function to determine the collision of an X,Y postion.  I will use the mouse corrdinates as the collision point.  The Kyra function will return the Sprite under that X,Y position.  Great, now I have the means to read a "selection"

    The delemma I have is when building my "command", I need the Object that was selected, not just the Sprite.  So how would I go from having the address of my Sprite to getting the address of the Object that contains that Sprite?

    One thought is I'd use the UserData field of the Sprite class to store that address of it's parent structure (the Object class).  This seems like a good solution, but before I go about implementing it, is it the right thing to do?  Come to think of it, it sounds like a good idea, but I don't want to be accused of writing bastardized code.  Is this what the UserData field of the Sprite class was intended for?  Lee, I think you're on stage again :)

     
    • William Seppeler

      Oh.....this was a tricky one...but...I did figure it out!

      This thread and my previous thread probably should have been posted to the Help group, so I appologise for the misplacement of this post.

      My delemma was to figure out two things.  1) How to access my Object class from the Sprite contained within an Object and 2) How to read user input (ie: mouse click).  Number 2 should have been easy, but figuring out how to use GlDynArray with my solution to 1 was what gave me the most problems.

      As a recap, I'll give an overview what what was trying to be accomplished.  I'd like to feel my design is pretty generic, so this may be of use to others...or maybe not:

      Each Sprite in my game has more attributes than just the sprite itself.  A Sprite is just the graphical representation of my Unit (or Building or Particle or ?).  In general, the Sprite is just the graphics to an Object.  It also contains the position and the methods to make it move (ie: defined actions), but it doesn't have information for such things as health, strength and so on.  That's where my Object class came into play.

      Now, I could have made the design decision to put my "extra" Sprite attributes in a generic data structure and have that data structure accessed through the Sprite->GetUserData() function, but I made the decision to have the Sprite be a component of my generic data structure (ie: Object) instead.  Either way might have worked, but it was recommended to create that Object class instead.  The Sprite database is essentially the KrImageTree, but I needed a way to track my Objects too, so I created a GlCircleList arrary to store all Objects in the game.  Again, this may not have been necessary because all Object attributes "could" be accessed through Sprite->GetUserData, but my "top level" list design made it easy to implement a routine to traverse that list once per game tick and update the state of each Object.

      So there is my design.  Pretty generic.  I have a "top level" list of all Objects in the game (ie: remember, Objects can also be Buildings and walls or anything else.  It doesn't even need to have an associated Sprite.  Game stats could just be another Object for example).  An Object has an associated CommandStack and in most cases, a visual Sprite.  Once per game tick, the Object list is traversed and a generic update() routine is called for each Object.  The CommandStack is generic, but the Commands are specific to each Object Type.  The Commands update the internal data structure of each Object.  In most cases, the Commands are what feed actions to the Sprite on the screen.

      That's the general overview of my data structures....actually...that's almost the complete picture.  My delemma came into play when I wanted to read user input.  User input interacts directly with Sprites, but I needed a way to have input effect my Object.  In particular, I needed to interpret the input and push a Command on the stack of the Object which contained the Sprite.  I needed a way to call the Sprite's parent Data Structure.  I definitely had the right idea to use the UserData field of the Sprite class for this operation, but getting it to work was the problem.  So after the complete introduction, here's the syntax I used to make it all work.

      ---<code fragment start>---
      // Unit is a subclass of Object
      Unit* unit = new Unit();;
      KrSprite* sprite = new KrSprite( unitRes );

      // Can access sprite from object
      unit->SetSprite( sprite );

      // Can access object from sprite
      sprite->SetUserData( unit );

      // Sprite belongs to the ImageTree
      engine->Tree()->AddNode( 0, sprite );

      // Object belongs to the ObjectList
      GlCircleList< Object* > objectList;
      objectList.PushBack( unit );

      // A mouse event occured, so interpret it's input
      int x = event.button.x;
      int y = event.button.y;
      int window = engine->GetWindowFromPoint( x,y );
      GlDnyArray< KrImage* > array;
      engine->Tree()->HitTest( x, y, 0, &array, &window );
      if ( ! array.Empty() ) {

        // Normally, you'ld walk the array, but I'll just grab the first
        // This was the key component I had trouble with!
        Object* selectedObject=(Object*)(array[0]->GetUserData());

        // CommandStack is a GlSList generic to all Object Types
        Command command=new Command( ????? );
        selectedObject->commandStack.PushFront( &command );

      }

      // Now, when a TIMER_EVENT goes off, update all objects
      // remember, Update() is generic to all Objects.
      GlCircleListIterator< Object* > it( objectList );
      for( it.Begin(); !itDone(); it.Next() ) {
        it.Current()->Update();
      }

      ---<code fragment end>---

      The heart of most of the game logic will reside in the Update() routine which is specific to each Object Type.  The Update() routine is what takes care of Collision checks, Interpreting Commands on it's CommandStack, Assigning actions to it's Sprite and controlling it's Sprite via DoStep().

      This post was just for the purpose of showing how I was able to interpret user inputh and how I was able to access another data structure from a sprite.  I gave far more detail than was necessary, but I also wanted to give the general overview of how I setup data structures within my game.  I'm not a seasoned programmer, so my methods may be all wrong.  I'm just providing my example for anyone pondering how to setup a generic data structure for tracking objects in a game.  Keep in mind that my concept of a game object is something that get updated once per game tick.  That object need not be something visible on the screen and hence may not have an associated sprite.

       
    • Gerrit Meyer

      Gerrit Meyer - 2003-06-01

      Hmmm, I dunno if I miss something, but I recommend designing your objects derived from KrSprite. You may add your objects directly to the engine (for they actually are KrImNodes then), just having a GlSList with pointers to them (to simplify iteration when updating)

      If you hit test your mouse position and therefore get a pointer to an object, you'll have direct access to it.

      Something like this:

      class MyObjects : public KrSprite
      {
      public:
        MyObjects(KrSpriteResource *r) : KrSprite(r) {} ;
        virtual ~MyObjects() ;
       
        virtual bool Update() =0;
        //your custom methods applying to all objects here...
       
      protected:
        //your custom data structures here
      }

      Note this is a quick scratch and perhaps won't compile, so just check KrSprite definitions!
      Just to give you an idea...
      Derive objects (tanks, houses, aliens, something stupid, anything you need) from that class (Update() differs) and everything is fine.

      WARNING: Traps and mines!
      Be sure to NOT delete such an object from engine()->Tree() WITHOUT deleting it's pointer in your external GlSList! Horrible crashes!
      I recommend setting up a global function DeleteNode(KrImNode *) to delete nodes. There you may check if a pointer to that node exists (whereever you may hold pointers to nodes) and delete it or invalidate it before deleting that node by Tree()->DeleteNode! Care must be taken to not delete such objects via Tree()->DeleteNode if they were not already added by Tree()->AddNode. Check with node->Engine() if they were added.

      Care must be taken with construction and destruction of your classes. Remember: Childs are deleted first! If you hold pointers to childs in your class, DONT DELETE THEM IN ANY WAY in destructors. Always remember: pointers to nodes are just pointers, the node itself belongs to the engine! (You may, however, check if they really belong to the engine by node->Engine() though. If not, you have to delete'em on your own)

      You must not create and add childs in constructors. Use virtual void AddedtoTree() instead. (See krsprite.cpp).
      Remeber to call KrSprite::AddedtoTree() when utilizing this.
      (A tank easily may have a turret, which is controlled by the tank. Adding  a tank to Tree() will create a turret automatically. Very smooth :-) )

      AND: Never hold pointers to nodes not related to your object (from engine's point of view, e.g. siblings, parents or something). If they were destructed, your object will not notice and horribly crash accessing them!
      A global instance of a manager class might be useful to such needs.

      I did that way and I'm quite happy with it.

       

Log in to post a comment.

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:





No, thanks