【问题标题】:Customizable player avatar in a 2D Game2D游戏中可自定义的玩家头像
【发布时间】:2008-12-31 08:29:36
【问题描述】:

我怎样才能在我的游戏中拥有这样的功能,玩家可以通过它来改变他们的发型、外观、衣服风格等,所以每当他们穿着不同的衣服时,他们的头像就会随之更新。

我应该:

  • 让我的设计师创建所有可能的盔甲、发型和面孔组合作为精灵(这可能需要大量工作)。

  • 当玩家在游戏介绍过程中选择他们的外观时,我的代码会自动创建这个精灵,以及头饰/盔甲与该精灵的所有可能组合。然后每次他们选择一些不同的盔甲时,都会加载该盔甲/外观组合的精灵。

  • 是否可以将角色的 sprite 分成组件,如脸、衬衫、牛仔裤、鞋子,并具有每个组件的像素尺寸。然后,每当玩家更换头盔时,例如,我们使用像素尺寸将头盔图像放置在其面部图像通常的位置。 (我正在使用 Java 构建这个游戏)

  • 这在 2D 中是不可能的,我应该为此使用 3D 吗?

  • 还有其他方法吗?

请指教。

【问题讨论】:

    标签: java 3d 2d


    【解决方案1】:

    要考虑的一个主要因素是动画。如果角色有带肩垫的盔甲,那么这些肩垫可能需要随着他的躯干移动。同样,如果他穿着靴子,则必须遵循与隐藏赤脚相同的循环。

    基本上,您的设计师需要的是Sprite Sheet,它可以让您的艺术家看到您的基本角色的所有可能的动画帧。然后,您让他们根据这些表创建自定义发型、靴子、盔甲等。是的,它的工作量很大,但在大多数情况下,元素将需要最少的重绘;靴子是我能看到的唯一一件真正需要大量工作才能重新创建的东西,因为它们会在多个动画帧中切换。对你的精灵无情,尽量减少所需的数量。

    在你积累了一个元素库之后,你就可以开始作弊了。回收相同的发型并在 Photoshop 中或直接在游戏中使用角色创建器中的滑块调整其颜色。

    为确保游戏中的良好性能,最后一步是将所有不同元素的精灵表展平为一个单独的精灵表,然后将其拆分并存储在精灵缓冲区中。

    【讨论】:

    • 那么当他创建自定义发型、靴子等时,他们会将它们也创建为精灵表,还是仅创建为独立图像?
    • 它们都是重叠的精灵表。这使您的格式易于维护和理解,因为它们本质上可以是 Photoshop 中的一组图层。
    【解决方案2】:

    3D 将不是必需的,但 3D 世界中常见的画家算法可能会为您节省一些工作:

    painter 算法的工作原理是首先绘制最远的对象,然后用更靠近相机的对象进行过度绘制。在您的情况下,它可以归结为为您的精灵生成缓冲区,将其绘制到缓冲区上,找到下一个依赖精灵部分(即盔甲或其他东西),绘制它,找到下一个依赖精灵部分(即特殊盔甲上的标志)等等。当没有更多的依赖部分时,您将完整生成的精灵绘制到用户看到的显示器上。

    组合的部分应该有一个 alpha 通道(RGBA 而不是 RGB),这样您就可以只组合将 alpha 值设置为您选择的值的部分。如果您出于某种原因无法做到这一点,请坚持使用一种您将视为透明的 RGB 组合。

    使用 3D 可能会使您更轻松地组合部件,您甚至不必使用屏幕外缓冲区或编写像素组合代码。另一方面,如果您还不了解 3D,您需要学习一点 3D。 :-)

    编辑回复评论:

    组合部分的工作方式有点像这样(在 C++ 中,Java 将非常相似 - 请注意,我没有通过编译器运行下面的代码):

    // 
    // @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];
                  }               
            }
         }
    }
    

    要回答您关于 3D 解决方案的问题,您只需绘制具有各自纹理(具有 Alpha 通道)的矩形相互重叠即可。您可以将系统设置为以正交模式显示(对于 OpenGL:gluOrtho2D())。

    【讨论】:

    • 谢谢。你能告诉我在 2D 中执行此操作的方法是什么,如果有的话,以及是否可以仅将 3D 用于玩家精灵而其余的 2D ?
    • 对于 3D 和 2D 的结合:这当然是可能的,但其破坏性大于其价值。不过,在正交模式下使用 3D 框架本质上是 2D。
    • 非常感谢:)。您上面给出的代码是用于组合 2D 和 3D(即 3D 用于字符,2D 用于其他所有内容),还是用于全部以 2D 进行?谢谢!
    • 它用于在 2D 中完成所有操作,但您也可以在 3D 中使用它 - 这样可以避免您绘制多个矩形/四边形。
    【解决方案3】:

    我会选择procedural generation 解决方案(#2)。只要生成的精灵数量没有限制,这样生成时间就会太长。也许在获取每个项目时进行生成,以降低负载。

    【讨论】:

      【解决方案4】:

      因为我在 cmets 中也被要求提供 3D 方式,所以这里有一些,这是我很久以前写的一些代码的摘录。它是 OpenGL 和 C++。

      每个精灵都会被要求绘制自己。使用适配器模式,我将组合精灵 - 即会有两个或多个精灵具有(0,0)相对位置和一个精灵具有所有这些“子”精灵的真实位置。

      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();
      }
      

      tex_id_ 是一个整数值,用于标识用于 OpenGL 的纹理。纹理管理器的相关部分是这些。纹理管理器实际上通过检查读取的颜色是否为纯白色(RGB of (ff,ff,ff))来模拟 Alpha 通道 - loadFile 代码对每像素 24 位的 BMP 文件进行操作:

      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;          
      }
      

      这实际上是我在业余时间偶尔开发的一个小型 Jump'nRun。顺便说一句,它也使用了 gluOrtho2D() 模式。如果您离开意味着与您联系,如果您愿意,我会将来源发送给您。

      【讨论】:

        【解决方案5】:

        较早的 2d 游戏(如暗黑破坏神和 Ultima Online)使用 sprite 合成技术来实现这一点。你可以从那些老式的 2d 等距游戏中搜索艺术,看看他们是如何做到的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-13
          相关资源
          最近更新 更多