You would've changed the world, my friend. |
Our amazing programming team hooked up the scripting language, LUA, into City of Heroes in Issue 24. I wanted to take a moment to explain how it was used to not only make missions cooler but that the knowledge of scripting can help you make your stories much stronger than they previously were.
Before we start, I decided I'm going to try to make a thing out of posting the "developer party" that hooked everything together.
Programming
Rob Anderberg - Lead Programmer, proposed the initial LUA integration.
Jason Lee - Programmer, helped implement and support LUA integration
Tim Sweeney - Designer/Programmer, helped implement and support LUA integration
Party formed! How long can I use Final Fantasy as an analogy? |
The beauty of LUA is that the language acts as a sort of negotiator between hard-coded C+ programming and regular design work. An example of this is the code that was made to change the skies in City of Heroes during invasions. That was "hard-coded", meaning that if we wanted to ever do that anywhere else, a programmer would need to do it each time. If we wanted to do it on the fly, a programmer would need to create a tool, test it themselves, hand it to a designer, the designer tests it, and if we need more with it, the programmer would have to do more work.
LUA, however, helps solve that. All a programmer has to do with LUA is write in what is called "script hooks" for the designer to use. They use hook up a command in LUA that accesses the system that is hard-coded, test it out, then they're done. A designer can now use that function in whatever way they wish in LUA in whatever combination with anything else. In the old way, the programmer would have to write the tool to do things like activate on objective complete, etc. In LUA, it's up to the designer to plug it into whatever they want, giving them a much bigger toolkit to work with; you can tie changing the skies to ANYTHING you want. Here's an example of how changing skies looked in LUA:
Script.SkyFileFade(Script.SkyFileGetByName("SKY_FILE_ORIGINAL"), Script.SkyFileGetByName("SKY_FILE_CHANGE"), 1.0)
How does this all work to help the story? In Issue 24, I wrote a story detailing the formation of a new group - the New Praetorians. We had done group formations in the pass, possibly too much, but I wanted to give a new spin on the idea. I wanted the player to feel like they were actually fighting side by side with these new characters, both during the recruitment stage and afterwards. However, our ally system in City of Heroes wasn't terribly expansive, and the AI tended to do weird things.
Enemies nearby? I think I'll RUN STRAIGHT INTO THEM! AGGRO TRAIN! |
The idea came to my while I was playing Diablo 3. One of the things I really liked was how their companions worked. You learned more about their story while you adventured with them and they never permanently died. There were some issues, of course, with things like repeating dialog, etc. These were all things that I could improve upon. The biggest goal I wanted to hit was the following:
1) Set up a LUA script that would prevent an ally from permanently dying.
2) Make the LUA script generic, so that it could be attached to ANY entity in the future, even ones who weren't your allies.
3) If all of this works, attach dialog for the allies to say throughout the mission to reveal their story as you went along and avoid pausing the action.
I went to work on the LUA script. I knew we had all the tools to do this from my already limited experience with the system. Jason Lee and Tim Sweeney helped me with several logical issues. After several iterations, the script was complete. I'll start with the issues it had first, then move on to the successes, because I'm Catholic and I think about all my flaws first before my successes.
I had to create a unique costume type for any entity used. Most characters in City of Heroes vanished about ten or twenty seconds after their death, and they also rag-dolled. I had to make a one-off version of the entity's costumes that would stop them from vanishing and make their death animation be a "take a knee" animation. If we had more time, I would've requested the ability to dynamically edit an entity's death animation and their time to vanish period, but that would've been a MAJOR undertaking given how deep that code is.
The other issue was that allies who couldn't die would be very over-powered. I added a power onto the unique entity that was already being made to reduce their power. This could have easily been done with existing tools in LUA. We had a function that could add and remove powers to entities. If the tech from the last paragraph was feasible, I would have added this power through the LUA script.
THE THINGS I WOULD HAVE DONE! |
Now that I'm done bashing my own work, on to the good part of it and how the script worked! The designer attached the script to an encounter in a mission and specified which actor it would work on. The designer then could detail what the entity should say when they die, what they say when they revive, and how long it takes for them to revive. Also, when the entity revived, they would run straight for wherever the player was. This required changing the escort script on them so they would never lose the player and also work towards following them. I have to emphasize this was all done through iteration. The first time the script was made, I just had the death/revive dialog exposed. As I played it, I realized it would be good to have the timer exposed for other designers. As well, I noticed my own behavior in testing was to wait around for the ally to be revived, which wasn't fun; adding in the "go to player" aspect allowed me to fire and forget and keep going with the ally playing catch up.
In the end, the script was used on the New Praetorians in the majority of the missions they were featured in Issue 24. In my personal, unbiased opinion, I think it worked well. As you went through the missions, the New Praetorians would make comments on objectives you completed and after a certain number of enemies were killed. You would learn more about how they were evolving and moving along, too. This was the first iteration on the idea, so there were plenty of things that could be improved upon. However, I was really happy with how it turned out. This wasn't just a simple script I made - it was now an entire system for new allies in the future, and it was awesome.
So! What were the lessons I learned from this?
1) LUA is amazing.
2) Give yourself plenty of time to iterate.
3) Enjoy your success, but know that you can never make anything that is perfect. There's always room for improvement. (even now I'm thinking of several things I would've liked to improve upon)
4) Even though nothing is perfect, you've got to get what you're working on to a state you think is good and MOVE ON. The rest of your work will suffer if you stay on one thing, and this is what leads to the dreaded CRUNCH.
LUA is great, hands down. I was able to do all this without bugging the programmers too much. This allowed them to work on the bigger projects that had to be done for the sake of the game, while still allowing design to make interesting missions. I would highly promote LUA usage in many studios, simply because of the sheer power it gives designers. However, as the saying goes, with great power comes great responsibility. LUA must be used in a responsible way and only when needed. It could be made to do amazing things, or great evil.
In my dream future, this system (and dialogue popup windows) were implemented in Mission Architect, and we could have expansive companion characters as a result.
ReplyDeleteI would have done SO MUCH with that. It's difficult to have on-the-fly character development when every NPC only gets two or three short opportunities to say a brief line, or their responses are hidden away in the Clues window the many new players ignored...
*pours a 40 out for MA*