views:

897

answers:

5

How can I have that functionality in my game through which the players can change their hairstyle, look, style of clothes, etc., and so whenever they wear a different item of clothing their avatar is updated with it.

Should I:

  • Have my designer create all possible combinations of armor, hairstyles, and faces as sprites (this could be a lot of work).

  • When the player chooses what they should look like during their introduction to the game, my code would automatically create this sprite, and all possible combinations of headgear/armor with that sprite. Then each time they select some different armor, the sprite for that armor/look combination is loaded.

  • Is it possible to have a character's sprite divided into components, like face, shirt, jeans, shoes, and have the pixel dimensions of each of these. Then whenever the player changes his helmet, for example, we use the pixel dimensions to put the helmet image in place of where its face image would normally be. (I'm using Java to build this game)

  • Is this not possible in 2D and I should use 3D for this?

  • Any other method?

Please advise.

+1  A: 

3D will not be necessary for this, but the painter algorithm that is common in the 3D world might IMHO save you some work:

The painter algorithm works by drawing the most distant objects first, then overdrawing with objects closer to the camera. In your case, it would boild down to generating the buffer for your sprite, drawing it onto the buffer, finding the next dependant sprite-part (i.e. armour or whatnot), drawing that, finding the next dependant sprite-part (i.e. a special sign that's on the armour), and so on. When there are no more dependant parts, you paint the full generated sprite on to the display the user sees.

The combinated parts should have an alpha channel (RGBA instead of RGB) so that you will only combine parts that have an alpha value set to a value of your choice. If you cannot do that for whatever reason, just stick with one RGB combination that you will treat as transparent.

Using 3D might make combining the parts easier for you, and you'd not even have to use an offscreen buffer or write the pixel combinating code. The flip-side is that you need to learn a little 3D if you don't know it already. :-)

Edit to answer comment:

The combination part would work somewhat like this (in C++, Java will be pretty similar - please note that I did not run the code below through a compiler):

// 
// @param dependant_textures is a vector of textures where 
// texture n+1 depends on texture n. 
// @param combimed_tex is the output of all textures combined
void Sprite::combineTextures (vector<Texture> const& dependant_textures, 
                              Texture& combined_tex) {
   vector< Texture >::iterator iter = dependant_textures.begin();
   combined_tex = *iter;

   if (dependant_textures.size() > 1)
     for (iter++; iter != dependant_textures.end(); iter++) {
        Texture& current_tex = *iter;

        // Go through each pixel, painting:
        for (unsigned char pixel_index = 0; 
             pixel_index < current_tex.numPixels(); pixel_index++) {
           // Assuming that Texture had a method to export the raw pixel data
           // as an array of chars - to illustrate, check Alpha value:
           int const BYTESPERPIXEL = 4; // RGBA
           if (!current_tex.getRawData()[pixel_index * BYTESPERPIXEL + 3]) 
              for (int copied_bytes = 0; copied_bytes < 3; copied_bytes++)
              {
                int index = pixel_index * BYTESPERPIXEL + copied_bytes;
                combined_tex.getRawData()[index] = 
                   current_tex.getRawData()[index];
              }               
        }
     }
}

To answer your question for a 3D solution, you would simply draw rectangles with their respective textures (that would have an alpha channel) over each other. You would set the system up to display in an orthogonal mode (for OpenGL: gluOrtho2D()).

mstrobl
Thanks. Can you tell me what would be the method for doing this in 2D, if any, and if its possible to use 3D for only the player sprites and the rest in 2D?
Click Upvote
And for combining 3D and 2D: that certainly is possible, but is more havoc than it's worth. Using a 3D framework in an orthogonal mode is essentially 2D, though.
mstrobl
Thanks a lot :). Is the code that you gave above for combining 2D and 3D (i.e 3D for characters and 2D for everything else), or for doing it all in 2D?Thanks aain!
Click Upvote
It's for doing it all in 2D, but you can use it in 3D as well - that would save you from drawing multiple rectangles/quads.
mstrobl
+1  A: 

I'd go with the procedural generation solution (#2). As long as there isn't a limiting amount of sprites to be generated, such that the generation takes too long. Maybe do the generation when each item is acquired, to lower the load.

perimosocordiae
+2  A: 

One major factor to consider is animation. If a character has armour with shoulder pads, those shoulderpads may need to move with his torso. Likewise, if he's wearing boots, those have to follow the same cycles as hid bare feet would.

Essentially what you need for your designers is a Sprite Sheet that lets your artists see all possible frames of animation for your base character. You then have them create custom hairstyles, boots, armour, etc. based on those sheets. Yes, its a lot of work, but in most cases, the elements will require a minimal amount of redrawing; boots are about the only thing I could see really taking a lot of work to re-create since they change over multiple frames of animation. Be rutheless with your sprites, try to cut down the required number as much as possible.

After you've amassed a library of elements you can start cheating. Recycle the same hair style and adjust its colour either in Photoshop or directly in the game with sliders in your character creator.

The last step, to ensure good performance in-game, would be to flatten all the different elements' sprite sheets into a single sprite sheet that is then split up and stored in sprite buffers.

Soviut
So when he creates the custom hairstyles, boots etc, would they create them as sprite sheets also, or just as stand-alone images?
Click Upvote
They would all be overlapping sprite sheets. This keeps your formats easy to maintain and understand since they could essentially be a set of layers in Photoshop.
Soviut
A: 

Since I was asked in comments to supply a 3D way aswell, here is some, that is an excerpt of some code I wrote quite some time ago. It's OpenGL and C++.

Each sprite would be asked to draw itself. Using the Adapter pattern, I would combine sprites - i.e. there would be sprites that would hold two or more sprites that had a (0,0) relative position and one sprite with a real position having all those "sub-"sprites.

void Sprite::display (void) const
{
  glBindTexture(GL_TEXTURE_2D, tex_id_);
  Display::drawTranspRect(model_->getPosition().x + draw_dimensions_[0] / 2.0f,
      model_->getPosition().y + draw_dimensions_[1] / 2.0f,
      draw_dimensions_[0] / 2.0f, draw_dimensions_[1] / 2.0f);
}

void Display::drawTranspRect (float x, float y, float x_len, float y_len)
{   
  glPushMatrix();

  glEnable(GL_BLEND);   
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glColor4f(1.0, 1.0, 1.0, 1.0);

  glBegin(GL_QUADS);     
    glTexCoord2f(0.0f, 0.0f); glVertex3f(x - x_len, y - y_len, Z);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(x + x_len, y - y_len, Z);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(x + x_len, y + y_len, Z);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(x - x_len, y + y_len, Z);
  glEnd();

  glDisable(GL_BLEND);  
  glPopMatrix();
}

The tex_id_ is an integral value that identifies which texture is used to OpenGL. The relevant parts of the texture manager are these. The texture manager actually emulates an alpha channel by checking to see if the color read is pure white (RGB of (ff,ff,ff)) - the loadFile code operates on 24 bits per pixel BMP files:

TextureManager::texture_id 
TextureManager::createNewTexture (Texture const& tex) {
    texture_id id;
    glGenTextures(1, &id);
    glBindTexture(GL_TEXTURE_2D, id);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);     
    glTexImage2D(GL_TEXTURE_2D, 0, 4, tex.width_, tex.height_, 0, 
     GL_BGRA_EXT, GL_UNSIGNED_BYTE, tex.texture_);

    return id;
}

void TextureManager::loadImage (FILE* f, Texture& dest) const {
  fseek(f, 18, SEEK_SET);
  signed int compression_method;
  unsigned int const HEADER_SIZE = 54;

  fread(&dest.width_, sizeof(unsigned int), 1, f);
  fread(&dest.height_, sizeof(unsigned int), 1, f);
  fseek(f, 28, SEEK_SET);
  fread(&dest.bpp_, sizeof (unsigned short), 1, f);
  fseek(f, 30, SEEK_SET);
  fread(&compression_method, sizeof(unsigned int), 1, f);

  // We add 4 channels, because we will manually set an alpha channel
  // for the color white.
  dest.size_ = dest.width_ * dest.height_ * dest.bpp_/8 * 4;
  dest.texture_ = new unsigned char[dest.size_];
  unsigned char* buffer = new unsigned char[3 * dest.size_ / 4];    

  // Slurp in whole file and replace all white colors with green
  // values and an alpha value of 0:
  fseek(f, HEADER_SIZE, SEEK_SET);   
  fread (buffer, sizeof(unsigned char), 3 * dest.size_ / 4, f); 
  for (unsigned int count = 0; count < dest.width_ * dest.height_; count++) {    
    dest.texture_[0+count*4] = buffer[0+count*3];
    dest.texture_[1+count*4] = buffer[1+count*3];
    dest.texture_[2+count*4] = buffer[2+count*3];
    dest.texture_[3+count*4] = 0xff;

    if (dest.texture_[0+count*4] == 0xff &&
        dest.texture_[1+count*4] == 0xff &&
        dest.texture_[2+count*4] == 0xff) {
      dest.texture_[0+count*4] = 0x00;
      dest.texture_[1+count*4] = 0xff;
      dest.texture_[2+count*4] = 0x00;
      dest.texture_[3+count*4] = 0x00;
      dest.uses_alpha_ = true;
    }       
  }
  delete[] buffer;          
}

This was actually a small Jump'nRun that I developed occasionally in my spare time. It used gluOrtho2D() mode aswell, btw. If you leave means to contact you, I will send you the source if you want.

mstrobl
A: 

Older 2d games such as Diablo and Ultima Online use a sprite compositing technique to do this. You could search for art from those kind of older 2d isometric games to see how they did it.

sean riley
Do you have any links?
Click Upvote