在我们一头扎入模式之前,我想先讲一些对软件架构和其在游戏上的应用的理解, 也许能帮你更好的理解这本书的其余部分。 至少,在你被卷入一场关于软件架构有多么糟糕(或多么优秀)的辩论时, 这可以给你一些武器支援。
What is software architecture?
If you read this book from beginning to end, you won't find the calculus behind the linear algebra or game physics behind 3D graphics. This book will not tell you how to trim your ai tree with Βsearch, nor will it tell you how to simulate reverb in your room in audio.
Instead, this book tells you something between the programs. This book is not so much about how to write code. Rather, it's about how the code is organized. Every program has a certain organization, even "plug all the code into the main () function", so I think it's interesting to talk about what makes a good organization. How do we differentiate between good architecture and bad architecture?
I have been thinking about this question for five years. Of course, like you, I have intuition about good design. We have suffered too much because of poor code quality, and now my biggest wish is to share these good code design paradigms to ease the pain of engineers.
承认这一点,我们中大多数都该对它们中的一些负责。
Only a few lucky people have the opposite experience and have the opportunity to work on a well-designed code base. That CodeBase looks like a luxury hotel, and the concierge is ready to meet your whim. What is the difference between the two?
What is a good software architecture?
For me, good design means that when demand changes, it's as if the whole program is built for change. I just need a few options function call can perfectly solve the task without changing the logic of the code calm surface at the same time
It sounds beautiful, but it's not completely operable. "Just write your code, and such a change will not affect its calm surface." Yes
Let me break a little. The first key part is that architecture is associated with change . Someone has always modified the code base. If no one touches the code--either because the code is too perfect or because he is too bad, no one will tarnish his own text editor. Then its design is irrelevant. The evaluation architecture is designed to evaluate how it is coping with change. If there is no change, it is an athlete who will never leave the starting line.
How do you deal with change?
When you change the code to add new features, to fix bugs, or whatever you need to use the editor, you need to understand what the current code is doing. Of course, you don't need to understand the whole program, but you need to put all the relevant stuff into your brain.
有点诡异,这字面上是一个OCR过程。
We tend to ignore this step, but usually the most time-consuming part of programming. If you think that loading some data from disk paging to ram is slow, think about loading this data into your brain through the nerves?
Once you have all the right contexts in mind, you think for a while and then find a solution. There can be a lot of back and forth here, but usually relatively simple. Once you understand the problem and the code that needs to be changed, the actual coding work is sometimes trivial.
You use fat fingers on the keyboard for a while, until the screen shows the correct color of light, and then even if it is done, right? Not yet! Before you write a test for it and send it to the code review, you often have some cleanup work to do.
You've added some code to your game, but you don't want the next person to be transferred to the pit you left behind. Unless the changes are small, you'll need some reorganization to make your new code seamlessly integrated. If you do the right thing, the next person who sees the code can't even say which code is new.
In summary, the programming process is like this:
How did the decoupling help?
Although it is not obvious, I think many software architectures are about the learning phase. Loading code into neurons is too slow, and finding strategies to reduce the amount of load is worth doing. This book has a whole chapter about decoupling patterns, and many design patterns are about the same topic.
You can define "decoupling" in a variety of ways, but I think if you have two pieces of code that are coupled, that means you can't just understand one of them. If you decouple them, you can understand one piece on your own. That's fine, because only one piece is relevant to your problem, and you just need to load it into your monkey's head without having to load another piece.
For me, here's the key goal of the software architecture: Minimize the knowledge you need to get into the brain before you can handle it.
Of course, it can also be seen from a later stage. Another definition of decoupling is that there is no need to modify additional code when a piece of code changes. We definitely need to modify something, but the smaller the coupling, the less the change will ripple.
What is the price?
Sounds great, doesn't it? Decouple anything, and then you can code like the wind. Modify only one or two specific methods for each change, just as you dance on the water surface of the codebase, leaving only a reflection.
This is why people are excited about abstraction, modularity, design patterns, and software architectures. Working in a well-architected program is a great experience and everyone wants to work more efficiently. A good architecture can make a huge difference in productivity. It's hard to exaggerate its powerful influence.
But, like anything in life, there is no free lunch. A good design also requires sweat and rules. Every time you make a change or implement a feature, you need to integrate it elegantly into other parts of the program. You need to spend a lot of effort to manage the code, in the development process to face thousands of changes still maintain its management structure.
You need to think about the part of the code that needs decoupling and abstraction. You also need to consider the extent to which the part needs to be considered for future changes and designed to be extensible.
Often people are passionate about this, and they assume that developers will just have to introduce his codebase. Because this code base has become powerful and extensible.
First, the trickiest part, whenever you add a layer of abstraction or support for extensions, you're assuming you need flexibility later on. But adding code and complexity to the game, it all takes time to develop, debug, and maintain.
If you guessed right, and then contacted the code, then the effort is not a conscientious. But predicting the future is hard, and if modularity doesn't help at the end, it hurts. After all, you have to deal with more code.
When people pay too much attention to this, it's your code that gets out of control. Because interfaces and abstractions are everywhere. Plug-in systems, abstract base classes, virtual methods, and a variety of extension methods.
It takes endless time to go back to all the scaffolding and find the code that really works. When you need to make a change, of course, there may be an interface that can help, but can only be found to wish you good luck. In theory, decoupling means that you need to know less code before you extend the code, but the abstraction layer itself fills your mental staging disk.
Code libraries like this let people oppose software architectures, especially design patterns. It's easy to immerse yourself in the code and ignore the point where you want to publish the game. Countless developers are listening to an alert that strengthens scalability and spends years making "engines" without figuring out what the engine is for.
Performance and Speed
Software architectures and abstractions can sometimes be criticized, especially in game development: It hurts the performance of the game. Many patterns that make code more flexible rely on virtual schedules, interfaces, pointers, messages, and other mechanisms, all of which consume runtime costs.
一个有趣的反面例子是在C++中的模板。模板编程有时可以给你抽象接口而无需运行时开销。这是灵活性的两极。但你写代码调用类中的具体方法时,你在写作时修改类——你硬编码了调用的是哪个类。但通过虚方法或接口时,直到运行时你才知道调用的类。这更加灵活但增加了运行时开销。模板编程是在两者这件。你在编译时初始化模板,决定调用哪些类。
There is one more reason. Many software architectures are designed to make programs more flexible. It takes less effort to change it. There are fewer assumptions about the program when coding. You can use interfaces to allow your code to interact with any class that implements it, not just the classes that are now written. Today. You can use observers and messages to communicate the two parts of the game, and later it can easily be extended to three or four parts to communicate with one another.
But performance is related to assumptions. Practice optimization is based on defined limits. Will there ever be more than 256 kinds of enemies? OK, we can encode the ID into one byte. Only one method is called in a specific type? OK, we can do static dispatch or inline. All entities are of the same class? Great, we can do a continuous array to store them.
But that doesn't mean that flexibility is bad! It allows us to quickly improve our games and the speed of development is an absolutely important factor in gaining interesting development experience. No one, even will Wright, can build a balanced game on paper. This requires iteration and experimentation.
The quicker you try to see how it works, the more you can try something, the more likely you are to find something valuable. Even if you find the right mechanism, you need time to adjust. A tiny imbalance can ruin the fun of the whole game.
There is no simple solution here. Make your program more flexible and you can make prototypes faster with a little bit of performance loss. Similarly, optimizing the code makes it less flexible.
In my personal experience, it's much easier to make fun games faster than to make fast games fun. One way to compromise is to keep the code flexible until the design is fixed, and then extract the abstraction layer to improve performance.
The advantage of bad code
This comes to the next point: different code styles vary. Most of this book is about keeping clean and controllable code, so I insist on writing the code in the right way, but the bad code has some advantages.
Writing a well-architected code requires careful thought, which can be a cost to the time. It takes a lot of effort to maintain a good architecture throughout the project's lifecycle. You need to handle the codebase as carefully as the camper does: always keep it better than when you first started.
It's good when you're going to spend a long time on a project. But, as I mentioned earlier, game design requires a lot of experimentation and exploration. Especially in the early days, it's common to write code that you know you want to throw away.
If you just want to try some of the game's ideas are not correct, good design means you can see and get feedback on the screen before it takes a long time. If it turns out that the idea is wrong, then when you delete the code, the time spent making the code more elegant is completely wasted.
Prototypes--A simple piece of code that is barely pieced together and can only answer design questions--is a perfectly reasonable programming habit. Although when you write a one-time code, you have to be sure you can throw it away. I've seen so many times that bad managers are playing this trick:
老板:“嗨,我们有些想试试的点子。只要原型,不需要做的很好。你能多快搞定?”那么几天后我能给你一个临时的代码文件。”老板:“太好了。”
A few days later
老板:“嘿,原型很棒,你能花上几个小时清理一下然后变为成品吗?”
You have to make it clear that the code that can be discarded, even if it seems to work, cannot be maintained and must be rewritten. If it is possible to maintain this code, you have to be defensive and write it well.
一个保证你的原型代码不会变成真正用的代码的技巧是使用一种和游戏不同的语言。这样,你在实际应用于游戏中之前必须重写。
Maintain balance
Some factors are in mutual restriction:
- In order to maintain readability throughout the project's life cycle, we need a good architecture.
- We need better run-time performance.
We need to make today's features more achievable.
Interestingly, these are all speed: the speed of our long-term development, the speed of the game running, and the speed of our short-term development.
These goals are at least partially antagonistic. A good architecture improves productivity over the long term, and it means that maintaining each change requires more effort to keep the code neat.
The code of the grass is rarely the fastest at run time. Conversely, improving performance requires a lot of programming time. Once done, it pollutes the code base: highly optimized code is inflexible and difficult to change.
There is always the pressure of today's work to be completed today. But if the feature is implemented as quickly as possible, the codebase will be filled with black magic, loopholes and confusion, hindering future output.
There is no simple solution, only trade-offs. From the email I received, it hurts a lot of people, especially those who just want to play a game. It seems to be in intimidation, "there is no right answer, only a different taste of the mistake." ”
But, to me, it's so exciting! Look at any area where people are engaged and you can always find some conflicting restrictions. In any case, if there is a simple answer, everyone will do that. The area that can be mastered in a week is very boring. You've never heard of anyone talking about digging a hole.
游戏你会;我没有深究这个类比。 我知道的是,可能有挖坑热爱着,挖坑规范,以及一整套亚文化。 我算什么人,能在此大放厥词?
Simple
Recently, I felt that if there was anything that would simplify these limits, it would be simple. In my current code, I try to write the simplest and most straightforward solution. After reading this code, you fully understand what it is doing and can't think of other ways to do it.
My goal is to get the data structure and algorithm right (roughly like this), and then start there. I found that if I could make things simple, there would be less code, which would mean less code in the mind when the changes were made.
It usually runs very fast, because there is no overhead, and there is no code to execute. (Although this is not the case most of the time.) You can add a lot of loops and recursion to a small piece of code. )
However, note that I did not say that the simple code needed less time to write. You would think that because you eventually get less code, but a good solution is not to inject water into the code, but to steam the code.
Blaise Pascal有句著名的信件结尾,“我没时间写的更短。”另一句名言来自Antoine de Saint-Exupery:“完美是可达到的,不是没有东西可以添加的时候,而是没有东西可以删除的时候。”言归正传,我发现每次我重写本章,它就更短。有些章节比他们刚完成时短了20%。
We seldom encounter the problem of elegant expression. Instead, it is the case of every station. You want X to do y in the case of Z, to do w in case a, and so forth.
The least expensive solution is to write a piece of code for each paragraph. If you look at novice programmers, they often do this: they write logical loops for each problem at hand.
But this is not elegant, that style of code encountered a little bit of an unexpectedly input will crash. When we think of elegant code, we think of the generic one: only a small amount of logic can cover a wide variety of situations.
Finding such a method is a bit like pattern recognition or solving puzzles. Efforts are needed to identify the hidden laws of scattered use cases. You'll feel better when you're done.
We're almost done.
Almost everyone will skip the introductory chapters, so congratulations on seeing this. I don't have much to repay your patience, but I have some advice for you, and I hope it works for you:
- Abstract decoupling makes it easier to extend code faster, but don't do it unless you're sure you need flexibility.
- Consider and design for performance throughout your development cycle, but if possible defer the underlying, basic fact optimizations that will lock your code.
Believe me, the two months before the release is not the time for you to start thinking "games Run only 1FPS".
Quickly explore the design space of your game, but don't run too fast and leave mess behind. After all, you gotta come back and deal with them.
If you're going to abandon this code, don't try to write it perfectly. The rock star messed up the hotel room because they knew someone was going to clean it up tomorrow.
But most importantly, if you want to make something enjoyable, enjoy the process of doing it.
Architecture, performance and gaming