If you want to play around with concepts as you learn them, you can skip to the bottom and follow the instructions for "Creating Your Own Sprite". You'll then have a test-bed object to work with as you read the docs.
-- Double dash is used to make comments
-- Below is a VisualRep definition
VisualRep = { { "bullet01.bmp" },
{ "bullet02.bmp" },
{ "bullet03.bmp" },
{ "bullet04.bmp" };
IndexedBy = "image_frame"
}
This VisualRep only has one imageset. That image set contains four
image filenames. You can see those four filenames listed first. Pay
attention to the punctuation. Note that each image is surrounded
in braces, and separated by a comma from the next image. Notice that
the last image in the imageset has a semicolon, separating it from
the next section.
NOTE: In the current source and binary releases, there are four zeros listed after each of the images in the visrep.lua tables. This is vistigial, has no purpose, and has thus been removed. With these older versions you can remove them without harm.
After the imageset, the line with the 'IndexedBy' keyword tells the game engine what object variable holds the index into the animation array. In this case, we've said that the name of the object property variable is "image_frame". Before this sprite is drawn, the game engine will read the value of the script object's "image_frame" property to figure out which image to be drawn. If there is no "image_frame" property, or the property has an invalid value, the first image in the imageset will be drawn. imagesets such as the one above are numerically indexed starting at 1. To get the last image, the "image_frame" property should be set to "4".
You can see an example which works just like this in HZ by looking at the 'Flag' in 'lua\obj_flg.lua' and 'lua\visrep.lua'. All of the game's VisualRep definisions are stored in visrep.lua. Other files merely reference these visualreps. In obj_flg.lua, you can see the code for the simple flag. The new() function is called each time a new flag is created in the game. The doTick() function is called every game frame. You can see that the property used to index this object's imageset is called flagimg.
In the example below, we have a bullet which can be red or white.
VisualRep = {
Red = { {"red_bullet0.bmp"},
{"red_bullet1.bmp"},
{"red_bullet2.bmp"},
{"red_bullet3.bmp"};
IndexedBy = "image_frame"
},
White = { {"white_bullet0.bmp"},
{"white_bullet1.bmp"},
{"white_bullet2.bmp"},
{"white_bullet3.bmp"};
IndexedBy = "image_frame"
},
IndexedBy = "Color"
}
Now, lets dissect this, the 'IndexedBy = "Color"' tells the engine that it
should look for an object variable named 'Color' to figure out which of
the two trees it should descend. If the Color script variable is equal to
the string "Red" then it will use the top list, if it's equal to the
string "White" then it'll use the bottom list. If it's not-present or
neither of those, it will default to the top list (i.e. the red list).
If we wanted to use all named indicies, we could have instead done:
VisualRep = {
Red = { one = {"red_bullet0.bmp"},
two = {"red_bullet1.bmp"},
three = {"red_bullet2.bmp"},
four = {"red_bullet3.bmp"},
IndexedBy = "image_frame"
},
White = { one = {"white_bullet0.bmp"},
two = {"white_bullet1.bmp"},
three = {"white_bullet2.bmp"},
four = {"white_bullet3.bmp"},
IndexedBy = "image_frame"
},
IndexedBy = "Color"
}
Notice that there are no more semicolons (";") in the array lists. This is
because there is no need to separate the unnamed elements from the named
elements. Because names are used, the script object property 'image_frame' should
now contain the string values "one", "two", "three", or "four" to
properly select the associated images.
VisualRep = {
L1 = { one = {"red_bullet0.bmp"},
two = {"red_bullet1.bmp"},
three = {"red_bullet2.bmp"},
four = {"red_bullet3.bmp"},
IndexedBy = "image_frame"
},
L2 = { one = {"white_bullet0.bmp"},
two = {"white_bullet1.bmp"},
three = {"white_bullet2.bmp"},
four = {"white_bullet3.bmp"},
IndexedBy = "image_frame"
},
IndexedBy = "@Layer"
}
When you use @Layer, it is important that you label your layers
appropriately. They must be named with "L" followed by a number. In the
above case, there are two layers 'L1' and 'L2'. The layers will be
rendered with the lowest numbered layer on top. So in the above case, the
red bullet will be on top of the white bullet.
You can see an example like this in 'lua\visrep.lua' if you look at the helicopter visual rep. (VisualRepHeli)
There are three ways each script object is given a chance to run script code:
In between the rendering of each frame, the game gives each sprite a chance to execute some script code to react to the world. It does this by calling each sprite's doTick(tick_diff method. Inside this method, you should place code which handles the object physics, decides what images to show and sets the appropriate object properties, and anything else you want to have the object do automagically. You should return from your doTick() as soon as possible, because if you spend too long in there, you'll slow down the game. Because your VisualRep is looking at script object properties, you can make any properties you want, and make your code set those properties to any values, for any reasons. However, in order to make the game engine fast, there are certain things which must be handled by the C++ part of the game engine. The special C functions available to operate on objects are:
C_obj_delete(objnum);
C_obj_viewFollow(objnum);
vx,vy = C_obj_getVelocity(objnum);
C_obj_setVelocity(objnum,vx,vy);
x,y = C_obj_getPos(objnum);
C_obj_setPos(objnum,x,y);
C_obj_setLayer(objnum,layer_number);
Notice that they all take objnum as their first paramater.
objnum is a special property of all script objects which identify them
to the C++ game engine. Therefore, if you want to make your object's doTick()
method set the object's position to (10,10), inside your object definition
you would write:
doTick = function(self,tick_diff)
C_obj_setPos(self.objnum,10,10);
end
NOTE: The use of these C_obj_* functions is a bit nasty. I am considering changing
them to be methods on the object, in which case you would
merely write: self:C_setPos(10,10). The "C_" is to remind you that these
functions are part of the base game engine functionality.
Also notice that C_obj_getVelocity() and C_obj_getPos() each return two arguments. Convinently, Lua supports multiple return arguments from a function. To move yourself two map pixels to the right of your current position, you would write:
doTick = function(self,tick_diff)
local xpos,ypos;
xpos,ypos = C_obj_getPos(self.objnum);
xpos = xpos + 2;
C_obj_setPos(self.objnum,xpos,ypos);
end
Normally, it's good to make use of the velocity functions. This allows the C++ game
engine to do your range checking math and apply the velocity to your position
automatically. However, there are times where you want to move a sprite to a specific
position on the map. You will see an example like this in the next section.
ge_collision(x,y,whoIhit)
Whenever your sprite has been found to collide with another sprite, you're sprite's ge_collision() method will be called. This is used to handle bullet's hitting objects and doing damage, figuring out when you are flying over another object, or just about anything which involves two objects touching eachother.
The exact point where the collision occured is provided in the x and y paramaters. Sometimes collisions will occur at more than one point, if that's the case, it will be the center of the collisions. In the case of a bullet striking another object, this position is probably where you want to put the explosion.
The object you hit is provided in the whoIhit paramater. This is the Lua script object. You can call any method on that object which you know to be available. You can read or write that object's properties like they are your own. You can store the pointer to this object in a property in your own object for later use. You can even call C api functions by providing whoIhit.objnum as the object to act on instead of your own objnum. Here is an example method which moves any object you collide with five pixels to the right:
ge_collision = function(self,x,y,whoIhit)
local xpos,ypos;
xpos,ypos = C_obj_getPos(whoIhit.objnum);
xpos = xpos + 5;
C_obj_setPos(whoIhit.objnum,xpos,ypos);
end
keyUp(key_code)
keyDown(key_code)
inputEvent(ascii_key)
These methods are used to accept key presses from the user. They are only called when the current sprite is the one being followed by the main view. You can set which object is followed by the main view (and which gets keyboard events) by using the C_obj_viewFollow(objnum) game api function.
keyDown() and keyUp() provide raw access to the keyboard. Using these event methods you can track which keys are currently being pressed, and thus know exactly which keys are being held down. The key information is currently provided in the native keymap format. [[ unfortunatly, this differs from platform to platform. We need to make the game engine code map into a standard keyboard map, and make symbols for that keyboard map available to Lua... ]]
inputEvent(ascii_key) is a convinence function provided to give you post-processed keyboard information. The ascii_key code of the key is provided. Because this is a post-processed key, it also delivers on events such as key-repeat.
For most game-oriented control, you will use keyUp/keyDown and ignore inputEvent completely. If you wanted to receive a user's typing, such as for a chat window, you would use inputEvent. As an example of how these events are delivered, if a user presses and holds the "r" key for two seconds, depending on his key repeat rate, the following events would be called:
keyDown(r_key_code);
inputEvent("r");
-- pause for key-repeat to kick in
inputEvent("r");
inputEvent("r");
inputEvent("r");
inputEvent("r");
keyUp(r_key_code);
mysprite_visualrep = { { "img/redflag0.bmp" },
{ "img/redflag1.bmp" };
IndexedBy = "flagimg" };
Notice that I named it mysprite_visualrep because that's the variable name
your object is putting into it's VisualRep property.
For more information about how to program in the script language Lua, you should consult the online Lua v3.1 documentation