The third entry in the Learning Kyra series. The series to date:
- Learning Kyra
- Learning More Kyra
Last time I was able to create a non-interactive moving body with a head attached. It simply walked from the top to the bottom of the window. Fairly simple, just like the graphics created for it.
In this entry, I will describe how I tackled the following tasks:
- Adding multiple characters to move around at once
- Adding interactivity
- Adding collision detection
I began with trying to add multiple characters to the program since it seemed like the simplest task. I did run into some problems, but in the end I managed to accomplish what I wanted.
To start, I needed to create a new character. I decided to make a clone of myself, goatee and all, as I described in the first part of this series.
I figure that as a clone he didn’t need his own body, so I can just reuse the existing one. Kyra allows you to change the colors on the fly, so I could always tint his appearance later. For now, he also gets a blue shirt with brown pants.
Kyra creates sprites based on resources. I already had two resources: gbHeadRes and gbBodyRes, both of which can make the sprites gbHead and gbBody. I simply used similar code to create the first clone: I grabbed the new resource gbCloneHeadRes and created gbCloneHead from it.
I then created a new sprite from gbBodyRes called gbCloneBody. For all intents and purposes, it is a copy of the already existing gbBody, but Kyra needs to know about each sprite. I then added the gbCloneHead as a child of the clone body. I set the position of the clone so that it would start a little higher than the regular character. This way, it will look like he is chasing him.
It actually turned out decent. I then tried to add multiple clones. I thought they would be great in a triangle formation, chasing after the poor original. It was at this point that I started having problems. I thought that since the head would be the same I could probably just add it as a child of all of the new bodies, but Kyra didn’t like that since it would seg fault when I run it. I just created multiple heads to go with the multiple bodies. I then found I had an off-by-one error. I wanted to create a triangle of characters, but I forgot to actually count how many I would need. I needed six, but I only created five, but then I was trying to set six, and so I kept getting seg faults until I figured this one out. /me smacks self in head for that one.
Still, I was victorious in, as you can see:
Objective accomplished! Now onto the next.
Interactivity is the stuff that games are made of. Up until now, I’ve only had an animated graphics demo. Once I get it to be interactive, I’m that much closer to making simple game demos. w00t!!
Kyra did not provide facilities to handle input, so I just followed the demo’s lead and used Simple Directmedia Layer to do so. Since I didn’t want to create multiple images just to have the body move left, right, and up, it would be fairly simplistic. I wanted to make it so the user can press the arrow keys left and right, and the main character would move accordingly while running down the screen.
It was actually fairly simple to implement, and I managed to get it working quite quickly. I simply added a variable for the movement speed and then if SDL detects that the left or right arrow is pressed, I would set the movement speed accordingly. For example:
// These will control the movement based on input
int moveX = 0;
int moveSpeed = 6;
...
case SDL_KEYDOWN:
{
// NEW CODE
if (event.key.keysym.sym == SDLK_LEFT) {
moveX = -moveSpeed;
}
else if (event.key.keysym.sym == SDLK_RIGHT) {
moveX = moveSpeed;
}
//END OF NEW CODE
else {
done = true;
}
}
break;
// MORE NEW CODE
case SDL_KEYUP:
{
moveX = 0;
}
break;
//END OF NEW CODE
I then modified it so that the SetPos for the gbBody would always modify the X coordinate by moveX. Then, just to add some useless complexity, I made it so that two of the clones would also move as you moved. The two middle clones will separate and move together as you move from left to right. It was here that I experienced the issue of z-ordering. Kyra will draw the sprites in the order that you added them to the engine, so while the top row would rightly be behind the second row, the second row would walk over the head of the first clone! To correct this issue, I simply added the clones from front to back.
But what happens when I want to make a sprite run in multiple directions? For example, the Kyra demo shows the Bug Eyed Monster program where the creatures can walk around in a circle, which means that a creature that might be closer to the viewer must be drawn on top of the other creatures, but that same creature can move to a point where another creature will become closer. The demo obviously handles it correctly, so dynamically changing the z-order is clearly possible. I’ll save learning this task for another day.
For now, another objective can be stricken off my todo list. It may not be very complex, but it’s a start.
Time to tackle collision avoidance. At the moment, the clones in the middle row can walk through each other, which is not something I want them to do. I found that the shooter demo makes use of collision detection, so I tried to follow what it did. Unfortunately it wasn’t very clear to me how it was handling it:
KrSprite* newMonster = AddMonsters();
// Flush the changes to this point and then look for
// collisions between the bullets and everything else.
engine->Tree()->Walk();
GlDynArray< KrImage* > hit;
// Can we keep the monster we just added -- or did it collide?
if ( newMonster && engine->Tree()->CheckSiblingCollision( newMonster, &hit, mainWindow ) )
{
engine->Tree()->DeleteNode( newMonster );
newMonster = 0;
}
else if ( newMonster )
{
// This add will not be collision detected.
StatusAdd( newMonster );
}
So it creates a creature, but somehow it is able to determine if there is a collision even if it didn’t set its position? I plan on asking about that on the Kyra forums. (NOTE: I have since discovered that AddMonsters() actually sets the position, so nevermind) For now, I was able to determine that I needed to use the Walk() function to set the tree to check for collisions. I also needed to compare what I had to what was in the list of collided sprites returned by CheckSiblingCollision(). I spent a bit of time here because I was unfortunately trying to grab the wrong information.
I was trying to do this:
if (gbCloneBody[2] == (KrSprite*)(hit.ItemPointer(i))
but I was supposed to do this:
if (gbCloneBody[2] == (KrSprite*)(hit.Item(i))
It turns out that Item() already returns the pointer I needed, so I was getting a pointer to the pointer, which is why I couldn’t detect the collision. Once I figured out this part, I was able to add code to move the clones away from each other if they intersect. They now bounce off of each other, which is closer to what I want.
So hours later, the third task is solved. Mission accomplished!
Recap: Today, I learned how to create multiple characters and display them on the screen simultaneously. I learned how to reuse existing sprites to make new composite sprites. I learned how to add interactivity to my project while also adding some crude collision detection. What does it all mean? It means that I can not only have the clones avoid walking into each other, but I can also check if they intersect other objects, such as powerups or projectiles or anything I can throw at them. I’m excited about the possibilities, and my own demos are fairly crude.
Next time:
- I want to learn about dynamic z-ordering.
- I want to create tiles and backgrounds.
- I want to make a simple game using what I’ve learned, probably something like Pac-man or Lock-n-Chase.
For now, I’ve accomplished enough to go to the Chicago Indie Game Developer meeting on Sunday with pride. B-)
You can download the updated version of the code:
KyraTest-r15.tar.gz
KyraTest-r15.zip
Kyra Source in .tar.gz format
Kyra Source in .zip format
NOTE: You will need Kyra v2.0.7 to use this code. Also, the comments in the code weren’t all updated to reflect the fact that I’ve changed it. It is licensed under the GPL or LGPL as specified.