Tuesday, July 23, 2013

Avalot lives!

Yes, he does!
As you may have noticed, the menu bar on the top is also visible. So I can say that everything is shown what is needed for the first scene of the game! (Expect the clock and some status leds to the left of it. That will remain for later as I mentioned before.) Oh, and I got rid of that disgusting pink blanket as well and gave it the pleasant color of flesh! ;)

Let me speak first about Avvy, since it was a bit tricky to make him (and consistently the other characters during the game) appear, and I would have certainly gone mad if fuzzie didn't help me with a lot of explanations and suggestions.
So I added a new function to Graph: drawSprite(), which is - obviously - responsible for drawing the sprites of a character. The data of all characters are stored in an array named 'tr' located in Trip, which have the type of triptype. I made a little revision of this. The original code didn't divide data about a character in any ways, so I did. Everything connected to the display of a sprite (actually, the data what drawSprite() uses) went to a new type named SpriteInfo in grap.h and triptype got a new data member with the name of _info and the type of SpriteInfo. (You can take a look at these types and the array named 'tr' in Trip and Graph.)
The next thing was to implement Graph::drawSprite(). Here came in handy the new type I introduced, because now we only have to pass this data member of a triptype variable to the function and it can work using only these informations. Here it is:
void Graph::drawSprite(const SpriteInfo &sprite, byte picnum, int16 x, int16 y) {

 /* First we make the pixels of the spirte blank. */
 for (byte qay = 0; qay < sprite.yl; qay++) {
  byte *mask = new byte[sprite.xl];

  for (byte qax = 0; qax < sprite.xl; qax++) {
   byte count = qax / 8;
   mask[qax] = ((*sprite.sil[picnum])[qay][count] >> ((7 - qax % 8)) & 1);
   if (mask[qax] == 0)
    *getPixel(x + qax, y + qay) = 0;
  }

  delete[] mask;
 }

 /* Then we draw the picture to the blank places. */
 uint16 i = 0; // Because the original siltype starts at 5!!! See Graph.h for definition.

 for (byte qay = 0; qay < sprite.yl; qay++)
  for (int8 plane = 3; plane >= 0; plane--) // The planes are in the opposite way.
   for (uint16 qax = 0; qax  < sprite.xl; qax += 8) {
    byte pixel = (*sprite.mani[picnum])[i++];
    for (byte bit = 0; bit < 8; bit++) {
     byte pixelBit = (pixel >> bit) & 1;
     *getPixel(x + qax + 7 - bit, y + qay) += (pixelBit << plane);
    } 
   }
}

Use of image masks
I think the comments and mostly the code speak for themselves, but here's a brief summary of it:
The method consists of two parts: before the drawing, we have to blank the pixels of the sprite to black (0). Here, we use the array 'sil' which contains the mask for it. After that we can go through the image itself to draw it plane-by-plane, which is identical to the method I showed you before in my previous posts. You can read about the topic of image masks here in a bit more depth. Note that x and y here are the coordinates of the upper left corner of the rectangle which is an invisible frame around the sprite. (That was what you saw in my previous post in the color of magenta.)

The next thing I implemented was the changing of the disturbing pink color to a smoother 'flesh color'. It wasn't a big deal, I only had to set two palette entries what I did in Graph::flesh_colours(). (Keeping the name of the original function, but moving it from Trip to Graph.)

The third thing was the menu on the top of the screen. I have to tell you, it doesn't operate at all yet, it's only drawn to the screen so we don't have a blank line there. But of course it wasn't as easy as a bunch of writelns or couts. I had to draw the subtitles as pictures to the screen and they had a very nasty way of working. It's all done in Dropdown::chalk():
void Dropdown::chalk(int16 x, int16 y, char t, Common::String z, bool valid) {
 byte ander;
 if (valid)
  ander = 255;
 else
  ander = 170;

 for (byte fv = 0; fv < z.size(); fv++)
  for (byte ff = 0; ff < 8; ff++) {
   byte pixel = ~(_vm->_gyro.little[z[fv]][ff] & ander); // Note that it's the bitwise NOT operator!
   for (byte bit = 0; bit < 8; bit++) {
    byte pixelBit = (pixel >> bit) & 1;
    *_vm->_graph.getPixel(x * 8 + fv * 8 + 7 - bit, y + ff) = pixelBit + (pixelBit << 1) + (pixelBit << 2);
    // We don't have to bother with the planes, since they all have the same value. See the original.
    // Note that it's the bitwise OR operator!
   }
  }

 if (! z.contains(t))
  return;
 else {
  byte fv;
  for (fv = 0; z[fv] != t; fv++); // Search for the character in the string.
 
  // Similar to the cycle before.
  byte pixel = ~ ander;
  for (byte bit = 0; bit < 8; bit++) {
   byte pixelBit = (pixel >> bit) & 1;
   *_vm->_graph.getPixel(x * 8 + fv * 8 + 7 - bit, y + 8) = pixelBit | (pixelBit << 1) | (pixelBit << 2);
  }
 }

 _vm->_lucerna.blitfix();
}
At least here I didn't have to blank out the pixels or bother with planes and these made the whole thing a little bit easier. (In truth understanding the original Pacal code was much harder then implementing it in C++ again.) There is two stances of a menu item: valid (black) and invalid (grayed out). In truth, there's a third one: highlighted, but since the menu doesn't operate yet, I didn't have to bother with the implementation of it. Depending on the current state of the menu item, we get the pixels of the characters with bitwise operations. According to the original code, the planes all have the same value (since in dropdown.pas chalk() does the same for every plane), and since we have 1 byte for every row of a character in 'little', all the characters are 8 pixels wide as well. After that comes the underlining of a character of every text with a very similar algorithm and that's it: we are done. We call chalk() for every menu item after drawing a gray bar on the top of the screen and finally everything looks fine.

Implementing the drop-down menu system will remain for after midterm. My main concerns now are to make Avvy move around in the room with the help of the arrow keys, and if possible, put him into the bed at the beginning of the game, and make the speech bubble system work along with text input, so I'll be able to display the first few animations of the game (when Avalot wakes up to the screaming of her wife). I hope I'll be able to do all of this before midterm. I am giving it all I've got, and we'll soon see the result of it.

4 comments:

  1. Perhaps it is worth to do the vertical scaling, so the image is not so squeezed?

    ReplyDelete
  2. Good work!

    In case you're wondering where Trip got its name, it comes from the heraldic word "trippant" meaning "walking": http://en.wikipedia.org/wiki/Attitude_%28heraldry%29#Passant . I made up much of this stuff independently (I remember working out the AND/XOR algorithm with pencil and paper) so I had to make up a lot of names rather quickly.

    ReplyDelete