Leadwerks 3 Update Available
This update brings the addition of lightmapping across curved surfaces with smooth groups. The image below is a set of lightmapped CSG brushes, not mesh lighting. You can read a detailed account of our implementation of this feature here.
The project manager now includes an "Update" button, so you can easily update your project any time we modify files in the template folders. Although we tested with no problems, it is recommended you back up your project before using this feature. The editor will make a copy of any overwritten files, as an added precaution.
You've now got full control over all the features and settings in the Leadwerks editor, through the new Options dialog. This gives you control over various program behaviors and settings. You can toggle grid snapping, control the snap angle, and lots of other stuff.
We have made one change to the script system. Our multiple script design worked reasonably well during the development of Darkness Awaits. The flowgraph interactions were clean, but when it came to AI and player interaction, things got messy. For example, when the player pushes a button we perform a raycast or proximity test to get the entity hit. Then we have to go through all the scripts attached to the entity, looking for a relevant script attached to it:
--Check if GoblinAI component is present
if entity.script.GoblinAI~=nil then
--Call the TakeDamage() function
entity.script.GoblinAI:TakeDamage(10)
end
That works okay in our example, but when we consider other enemies we want to add, suddenly it gets ugly. We have a few choices.
- Add an if statement for every new AI script, checking to see if it is present.
- Separate the health value out into its own script.
- Loop through all attached scripts looking for a TakeDamage() function.
It should be obvious why option #1 is a bad idea. This would make our code highly interdependent. Encapsulation is one of our goals in game scripting, so we can achieve drag and drop functionality (or as close to that as is reasonably achievable without limiting ourselves).
The second option would solve our immediate problem, but this approach means that every single script variable two scripts access has to be a separate script. The thought of this just shuts my creativity down. I already think it's tedious to have to attach an AI and AnimationManager script to an entity, and can't imagine working with even more pieces.
The third option is the most reasonable, but it greatly impedes our coding freedom. It means every time two entities interact, you would have to do something like this:
--Check if GoblinAI component is present
if entity.components~=nil then
for local k,v in entity.components do
if type(v.TakeDamage)=="function" do
--Call the TakeDamage() function
v:TakeDamage(10)
end
end
end
If you actually wanted to return a value, you would just have to get the first value and exit the loop:
local enemyhealth=0
--Check if components table is present
if entity.components~=nil then
--Iterate through all attached components
for local k,v in entity.components do
if type(v.GetHealth)=="function" do
--Call the GetHealth() function
enemyhealth = v:GetHealth()
break
end
end
end
This is a major problem, because it means there is no firm "health" value for an entity. Sure, we could consider it a "HealthManager.health" value but therein lies the issue. Instead of having plug-and-play scripts everyone can share, we have to devise a system of expected script names and variables. This breaks compartmentalization, which is what we were going for in the first place. Both Chris and I realized this approach was fundamentally wrong.
After careful consideration, and based on our experience working on Darkness Awaits, we have restructured the system to work as follows, with a 1:1 entity:script relationship:
--Check if script is present
if entity.script~=nil then
if type(entity.script.TakeDamage)=="function" do
--Call the TakeDamage() function
entity.script.TakeDamage(10)
end
end
You can set an entity's script with the new function:
Entity::SetScript(const std::string& path)
You can also set and get entity script values right from C++:
virtual void SetString(const std::string& name, const std::string& s); virtual void SetObject(const std::string& name, Object* o); virtual void SetFloat(const std::string& name, const float f);
And it's easy to call a script function from your C++ code:
virtual bool CallFunction(const std::string& name, Object* extra=NULL);
The properties dialog is changed slightly, with a consistent script tab that always stays visible. Here you can set the script, and properties will appear below, instead of being spread across a bunch of different tabs:
Maps with entities using only one script (which has been almost everything we see) are unaffected. Objects with multiple scripts need to have their code combined into one, or split across multiple entities. I am very reluctant to make changes to the way our system works. Our API has been very stable since day one of release, and I know it's important for people to have a solid foundation to build on. However, I also knew we made a design mistake, and it was better to correct it sooner rather than later.
Probably the best aspect of the script system in Leadwerks 3 has been the flowgraph connections between scripted objects. That's been a big winner:
As we use the editor more and hear about the community's experience, it allows us to refine our tools more. One of the more subtle but we made is in the properties editor. One of the annoyances I experienced was when setting a property like mass or position when an entire hierarchy of entities was selected. Obviously I didn't want to set the mass for every single bone in a character, but how to tell the editor that? I went through all the properties, and for ones that the user is unlikely to want set, I made the following rule: If the entity has a selected parent anywhere in the hierarchy, it gets ignored when properties are retrieved and applied. (Other things like color will still work uniformily.) Just try it, and you'll see. It speeds up editing dramatically.
In a similar vein, we finally solved the problem of "too many selection widgets", We only display the top-most control widget for each group of selected objects. Again, it's easier for you to just try it and see than for me to explain in detail. If we did our job right, you might not even notice because the editor will just do what you want without any thought.
You've also got control over the color scheme, and can use it to customize the look and feel of the 3D viewports and code editor.
On Windows we fixed an annoyance that triggered menu shortcuts when the user tried to copy and paste in text fields. This tended to happen in the properties editor. We're working to resolve the same issue in the Cocoa UI for Mac.
I'm a big fan of the 3ds max Max Script system, so in the output panel we've added a real-time Lua console:
You can type Lua commands in and interact with Leadwerks in real-time. Just for fun, try pasting in this line of code and see what happens:
for i=0,10 do a = Model:Box(); a:SetColor(math.random(0,1),math.random(0,1),math.random(0,1)); a:SetPosition(i*2,0,0); end
You can't create objects the editor will recognize (yet), but it's fun to play with and should give you an idea of where we're going with the editor.
This is an important update that includes new features and enhancements that improve usability and workflow. Thank you for your feedback. It's great to see all the activity that is taking place as people learn Leadwerks 3.
- 5
19 Comments
Recommended Comments