[b]Entity Hierarchy[/b]
Before I get into the Lua, I wanted to mention a pretty significant change that we have made to the Entity/Component hierarchy. Before, an "Entity" simply inherited from a "gyro::Rect." If somebody wanted to apply a spritesheet/texture to an Entity, he would have to attach a "Sprite" component. This sprite component contained its own renderable "gyro::Sprite" which also inherits from "gyro::Rect." This caused two different transforms to be required per textured entity (and the Sprite transform had to be updated to reflect the state of the entity's transform). This was ugly as shit from a Lua perspective and was slightly wasteful of resources.
The "Entity" class now inherits from "gyro::Sprite," so that any entity can go from a simple rect to textured by merely setting a texture (rather than having to attach a component). Animation may now be applied to this textured entity through attaching an "Animation" component (which works exactly as the old Sprite component did minus the texture attributes).
[b]Behaviors[/b]
Before we dive directly into our Lua implementation, I must first explain the concept of a "Behavior." A Behavior is a type of "Component" containing a series of "triggers" that the engine invokes when a certain event occurs.
class Behavior: public Component { private: char _typeName[100]; static Component::TYPE _type; int _triggerBits; static std::mapcreator; protected: Behavior(EditorEntity &parent, const char *const typeName); static void RegisterCreator(const char *const typeName, Behavior* (*const func)(EditorEntity&)); public: static Behavior *Create(const char *const typeName, EditorEntity &parent); const char *getTypeName() const; //List of all triggers: virtual void Update(); virtual void OnLoad(); //virtual void OnCreate(); //virtual void OnDestroy(); //Item triggers: //virtual void OnUse(); //Collidable triggers: //virtual void OnCollide(Collidable &collidable); //Conversation triggers: //virtual void OnSpeak(); virtual bool canAddTo(EditorEntity &entity); virtual bool Load(std::ifstream &stream); virtual bool Save(std::ofstream &stream); virtual bool AddToLevel(); virtual bool RemoveFromLevel(); };
From here, you can see these "triggers" implemented as virtual functions. There are only two currently implemented (Update() and OnLoad()), but we have plenty of ideas for many, many more (and they are easy to add as we go). Any "Behavior" (inheriting from this base class) can now implement a series of trigger functions or methods that the engine invokes. The triggers for each Behavior are specified as a series of bit flags stored within
int _triggerBits;
For example if the first bit is true, then the engine knows to call the Behavior's "OnLoad()" trigger when the Behavior is loaded. The second bit corresponds to "Update()", and so on.
The entire concept of a "Behavior" is a powerful mechanism that allows us to attach level/situation-specific code or attributes to an entity at runtime. We can attach an AI script to make an enemy walk around in a circle as a Behavior. We can attach a Behavior to a friendly NPC that causes him to give the player a special item when he is spoken to. We can attach a script to a character that causes a cutscene to be triggered when we walk by. The applications are limitless here, and this is how all "game-specific" (versus Engine) logic will be implemented. This is also extremely efficient, because the engine itself is handling the polling (and our "Behavior" code is only invoked after the trigger occurs.)
[b]Lua Behaviors[/b]
A "LuaBehavior" is simply a C++ class inheriting from "Behavior" (which inherits from Component, so it's attachable to an Entity). The class contains a Lua script filename and a "LuaThread" for the script to execute within.
class LuaBehavior: public Behavior { private: static bool _statConst; static bool StaticConstructor(); static char _typeName[]; LuaThread luaThread; protected: LuaBehavior(EditorEntity &entity); static Behavior *CreateBehavior(EditorEntity &parent); public: bool Load(std::ifstream &stream); bool Save(std::ofstream &stream); void Update(); void OnLoad(); //void OnCreate(); //void OnDestroy(); };
The implementation of this class is really extremely simple. Each trigger implementation simply makes a call to a function defined in the specified Lua script:
void LuaBehavior::Update() { luaThread.CallFunction("Update"); } void LuaBehavior::OnLoad() { luaThread.CallFunction("OnLoad"); }
As a direct result, we are now able to implement attachable "Behavior" components in Lua. :D
[b]C++ Behaviors[/b]
I wanted to mention an extremely powerful and intentional design implication here. A "Behavior" is a C++ class. A "LuaBehavior" is simply a C++ class inheriting from "Behavior" whose triggers all invoke Lua script functions. As a direct result, any Lua script (or "LuaBehavior") can [i]easily[/i] be implemented as a "Behavior" in C++ and be compiled natively with the engine.
Figure 1: LuaBehavior/C++Behavior inheritance tree (beautiful art by Falco Girgis)
This will allow us to dramatically optimize Behaviors requiring any type of performance AS WELL AS move commonly used Lua scripts (such as walking AI) into the engine for yet another performance boost. Keep in mind that our Lua scripts have to run in real-time on a Dreamcast, PSP, and iPhone. While it won't be a problem 90% of the time, if we ever bottleneck a CPU, this problem will be easily rectified. :D
[b]Attaching a Lua Script to an Entity[/b]
Before we get into actually writing your Lua scripts/Behaviors, I wanted to briefly discuss how to Load a script and attach it to an entity. Just like every other component, Behaviors (when Marcel hurries the FUCK up) can be attached to an Entity through the level editor. When you save, this Entity/Component data is written to the "entity.txt" file associated with each area.
Until Marcel gets his shit in order, we are required to manually edit this entity.txt file. Fortunately, this isn't so bad. Unfortunately, we still should never have to do this manually. The following will add an invisible entity with "script2.lua" attached as a Behavior to the current area. Append this to any entity.txt, and you'll have your script executing in the engine!
MYLUATEST <- Entity Name 55 <- X coordinate 55 <- Y coordinate 50 <- Z coordinate 32 <- Width 32 <- Height 1.0 <- ScaleX 1.0 <- ScaleY 0.0 <- Orientation 0 <- Renderable 1 <- # of Components 10 <- Component Type (10 is BEHAVIOR) LuaBehavior <- Behavior Name 87 <- Bit fields for implemented triggers (these aren't used just yet, the engine just assumes all triggers are being implemented level/Cave2/area/Cave2/script2.lua <- Name of script file to execute for LuaBehavior
Here we can see the entity.txt file in the top left. This file tells the engine (top right) to attach the Lua script "script2.lua" to an empty entity. Engine output is at the bottom right.
Before you attempt to load your current area, you must ensure that any triggers the behavior is using are implemented in your script. For example, this is would be a template Lua script:
-- Global Scope function OnLoad() -- Any entity initialization and shit happens here end function Update() -- Any game logic happens here end
Any code written at the global scope will be executed immediately upon the loading of the script. However, it is not a good idea to do any initialization at this point. You may be trying to access/grab references to entities that are lower down in the entity.txt file (and haven't been loaded yet). Instead, this kind of initialization code should be implemented within OnLoad() (which the engine calls once the ENTIRE area has finished loading).
The Update trigger is called for each Behavior every frame. Until we implement more useful triggers, this is where your actual logic will be implemented. As I said before, we will be implementing more and more different triggers (OnTalk, OnExamine, OnThrow, OnPickup, OnUse, etc.) as we go. Polling all logic every frame within Update() is pretty damn inefficient, but it's acceptable while we're tweaking/refining our Lua implementation. :D
[b]LibGyro Access[/b]
Now lets discuss some of the stuff you have access to... which is absolutely everything. First of all, the libGyro backbone, including all video, audio, input, and system functions are accessible through Lua. This gives a Lua scripter an immense amount of power. if there is some kind of special logic or Entity that you would like to implement (completely independent of the Engine), you may do it completely in Lua. Lets take a look at an example:
function OnLoad() fuckmite = gyro.Sprite:new(); fuckmite.transform:setPosition(32, 32); fuckmite.transform:setOrientation(33.456); fuckmite.transform:setZ(100.0); fuckmite:setAlpha(.5, 1, .5, 1); fuckmite.transform:setSize(200,200); tex = System.gyroVideo:TexturePNG("item/star.PNG", 32, 32, 32, 32); fuckmite:setTexture(tex); end function Render() System.gyroVideo:RenderSprite(fuckmite); end function Update() Render(); fuckmite.transform:setOrientation(fuckmite.transform:getOrientation()+0.02); fuckmite.transform:Move(velocity); if fuckmite.transform:getPosition().x >= 640 then fuckmite.transform:setPosition(0, fuckmite.transform:getPosition().y); end if fuckmite.transform:getPosition().x < 0 then fuckmite.transform:setPosition(640, fuckmite.transform:getPosition().y); end if fuckmite.transform:getPosition().y >= 480 then fuckmite.transform:setPosition(fuckmite.transform:getPosition().x, 0); end end
This relatively simple Lua script creates a 200x200 Sprite (with a Star texture) that bounces around the screen with pong physics. The engine is completely ignorant of the star's existence, and it is solely managed within Lua.
Here you can see a giant ass star bouncing around completely controlled by Lua. I attached a SECOND script to a second entity (same script), except the texture on the second is a Gamecube. Now both the Gamecube and Star are bouncing around the window with pong physics.
Any libGyro class must be prefixed with "gyro." as it is contained within the "gyro" namespace. Any engine class can be accessed without a prefix, because Lua exists within the namespace "elysian."
The libGyro video subsystem may be accessed through "System" which is a class in the engine containing static pointers to each libGyro subsystem. Audio will be accessible in the same manner (when I get around to implementing it).
[b]Entity/Component System Access[/b]
Through Lua, you can access any entity/component that has been loaded into the area (from the entity.txt (which will be generated by Marcel's toolkit)). You can do this with the "Entity::getByName(const char*)" function. This function returns a reference to the requested entity.
dude = Entity:getByName("Dude");
You can also dynamically create your own Entities that exist only within your scripts (they are automatically added to the engine for you), and attach/manipulate their respective components.
dude = Entity:new(); dude:setName("Dude");
Either of these two methods will work for entity manipulation. The first method (grabbing a reference to an existing entity) is the preferred method, so that you can use Marcel's toolkit to add/edit entities and their respective components (rather than having to do everything in code), but given the fact that the toolkit cannot lay entities just yet, it will probably be much easier on you to use the second method (rather than having to fuck with entity.txt).
So anyway, once you have access to an Entity, you can do whatever the fuck you like with it.
function OnLoad() doucheWad = Entity:new(); doucheWad:setName("DoucheWad"); doucheWad.transform:setPosition(200,200); doucheWad.transform:setSize(150, 150); doucheWad.transform:setScale(1.5,1.5); doucheWad.transform:setOrientation(4); --doucheWad:setColor(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1); doucheWad:setAlpha(0.5, 1, 0.5, 1); doucheWad:setTexture(System.gyroVideo:TexturePNG("item/xbox.PNG", 32, 32, 32, 32)); animComp = doucheWad:AddAnimation(); collidComp = doucheWad:AddCollidable(); warpComp = doucheWad:AddWarp(); itemComp = doucheWad:AddItem(); charComp = doucheWad:AddCharacter(); friendlyComp = doucheWad:AddFriendly(); enemyComp = doucheWad:AddEnemy(); convoComp = doucheWad:AddConversation(); end
Here we have given our empty, boring-ass entity a texture, animation, collidable, warp, character, friendly, enemy, item, and conversation component--that's every component even in the engine so far. ;) From here you can add dialog, define your own animations, and do absolutely anything in Lua.
Here we see a gigantic-ass, semi transparent entity textured as an XBox with a billion different components attached. You can do anything with this bad boy in Lua.
[b]Engine Subsystem Access[/b]
As you should know by now, the functionality in the engine is broken into a series of subsystems. Each subsystem is also in charge of managing a particular component or portion of the engine. The WarpSystem manages each Warp, the CharacterSystem manages each Character, the TerrainSystem manages the map, etc.
Lua also has access to each of this subsystems. They can be accessed through the "LuaSingleton" class (which contains static pointers to each respective subsystem).
Lets check out a simple Lua script that does random cool shit utilizing a couple subsystems:
player1 = LuaSingleton:getPartySystem():getPlayablePartyMember(0); pos = player1.transform:getPosition(); LuaSingleton:getTerrainSystem():setTileLayer(1, pos.x/32, (pos.y/32)+1, math.random(0,9));
As you can see, we are grabbing a reference to the party leader. Then we are using the leader's position to set the terrain under his feet to a random tile value (between 0 and 9). The resulting script causes the terrain to change beneath the player's feet as he walks around.
The resulting super-fucked level as I destroy the terrain by walking over it. :)
[b]Outro[/b]
I'm sure that all of you are wondering "where the fuck do I start with all this?" At this very moment, Tyler is creating a definitive Lua reference/tutorial for all of you. I would like to encourage EVERYBODY to give Lua a try. Its super simple, really fun, and will allow us to begin implementing all of our ideas for the game. Also, you can provide feedback when you find something broken or have a great new idea that should be implemented.
Lua is currently tested and running flawlessly on OSX and Windows. I'm sure it runs fine (even though it's untested) on Linux. This week I will be working with MDK and presenting to you this update running on the Dreamcast, PSP, iPhone, and iPad. I would also very much like to get audio implemented within libGyro, so that we can start hearing cypher's tracks in-game WITH Pritam's art... I think we will immediately start seeing/hearing the ES experience come to life from there. Once these few little details are finished, everybody should be good to go with Lua development. I will then be helping Marcel full-time get his toolkit to the point of badassery that it needs to be at.
Also, we have a pretty large (and important!) meeting with our boss at Digital Radiance regarding our progress on Elysian Shadows this Friday. We're super excited to demonstrate the things that Lua can do (running on every platform). Expect pictures of the ordeal as well. :D
Thanks for your continued efforts. We are getting closer than ever to reaching our goals. BACK TO WORK, GENTLEMEN!