How to write more flexible game code
Summary
TLDR本视频提供了编写更清晰代码的实用技巧,强调了应用层次结构的重要性,以及如何通过遵循这一结构来保持代码的灵活性和易于变更。视频中讨论了依赖关系的正确流向、关注点分离、数据与逻辑的结合、使用组件化和依赖注入来提高代码的可重用性和可维护性。此外,还介绍了设计模式的价值,特别是状态机和观察者模式,并鼓励开发者逐步学习和实践,以避免过度理论化。最后,视频强调了迭代改进代码风格的重要性,并提倡在实践中学习和成长。
Takeaways
- 📈 保持代码灵活性和易于变更的关键在于尊重应用层级结构,确保依赖关系仅向下流动。
- 🔄 避免在代码中假设结构,以保持对象的独立性和游戏的灵活性。
- 🤖 保持关注点分离,例如,玩家对象不应直接操作动画的帧或位图数据。
- 🔍 将数据和操作数据的逻辑保持在一起,而不是分离它们。
- 🧱 使用组件来封装功能,而不是创建大型单一对象或过度使用继承。
- 📌 依赖注入可以提高代码的可重用性和灵活性,通过在运行时提供对象引用。
- 🔄 学习设计模式可以避免重复发明轮子,特别是状态机和观察者模式。
- 🔄 迭代改进代码风格,不要一开始就试图掌握所有设计模式。
- 🚀 游戏开发中,即使代码质量一般,能够发布的游戏也比完美但未发布的游戏要好。
- 📚 在遇到痛点时再进行研究和学习,实践中学到的经验比理论更有价值。
- 🔄 通过小项目实践,了解最终产品的样子和学到的经验,为将来做得更好打下基础。
Q & A
什么是应用层次结构?
-应用层次结构是指每个应用无论简单或复杂,都有一个层次结构,这个结构需要被尊重。依赖关系应该只向下流动,而不是向上或横向,这样可以保持代码的解耦和灵活性。
为什么我们应该避免在对象树中假设结构?
-假设结构会导致代码不够灵活,因为一旦改变或破坏了依赖关系,可能会影响到其他部分的代码。例如,如果玩家对象假设总是有一个名为物品的同级对象,那么在所有地方都需要这个物品对象,这限制了游戏的灵活性。
分离关注点是什么意思?
-分离关注点是指保持代码的不同部分专注于它们自己的职责。例如,玩家对象应该只关心高层次的操作,而不是动画的具体实现细节,这样可以在不破坏玩家代码的情况下,后期更改动画的实现方式。
为什么建议将数据和操作数据的逻辑保持在一起?
-这样做可以提高代码的可重用性和灵活性。例如,玩家对象应该告诉动画容器改变动画,而不是直接访问和修改动画容器中的数据。这样可以确保动画相关的代码逻辑集中,便于管理和调试。
组件化有什么好处?
-组件化可以将复杂的对象分解为更小、自给自足的代码块,这样可以提高代码的组织性和可重用性。例如,游戏中需要健康条的多个对象可以使用同一个健康组件,而不是在每个对象上管理单独的变量和方法。
依赖注入是什么?
-依赖注入是一种在运行时而不是编译时提供对象引用的技术。这可以使代码更加可重用和灵活,因为它允许在不硬编码具体对象的情况下,动态地将对象引用传递给需要它们的部分。
为什么学习设计模式很重要?
-设计模式是解决软件开发中常见问题的成熟解决方案。学习设计模式可以避免重复发明轮子,提高开发效率,并帮助开发者更好地理解和解决复杂问题。
状态机和观察者模式为什么很重要?
-状态机和观察者模式是两种非常重要的设计模式。状态机可以帮助管理对象的状态变化,而观察者模式可以使得对象之间的通信更加灵活。它们在游戏开发和软件工程中有着广泛的应用。
如何提高代码质量?
-提高代码质量可以通过迭代改进代码风格、学习设计模式、使用组件化和依赖注入等技术。同时,实践中遇到问题时进行研究和改进,以及在小项目中尝试不同的方法,都是提高代码质量的有效途径。
为什么说发布一个中等质量的游戏比完美代码的游戏更好?
-因为游戏开发的目标是创造出可以被玩家体验的产品。一个即使代码质量不是最优,但能够发布并被玩家接受的游戏,比一个从未完成的完美代码游戏更有价值。实践中的经验和反馈对于长期提高开发技能至关重要。
Outlines
📚 代码架构与应用层次
本段落讨论了如何保持代码的灵活性和易于变更。首先强调了应用层次的重要性,指出依赖关系应该仅在层次结构中向下流动,避免向上或横向依赖。通过一个小游戏的层次结构示例,说明了保持代码解耦的重要性。接着,讨论了分离关注点的概念,即对象应该专注于其职责范围内的操作,避免涉及超出其范围的实现细节。此外,提出了将数据和操作数据的逻辑保持在一起的原则,以及使用组件来组织代码的建议。最后,介绍了依赖注入的概念,这是一种在运行时提供对象引用的技术,有助于提高代码的可重用性和灵活性。
🔍 设计模式与代码迭代
这一段落继续深入探讨了设计模式的重要性,特别是状态机和观察者模式,它们对于提高代码的灵活性和可维护性至关重要。作者建议开发者学习这些模式,以避免重复造轮子。同时,提到了中介者模式作为另一个有用的设计模式。作者还强调了实践的重要性,建议开发者在遇到具体问题时再进行研究和学习,而不是一开始就试图掌握所有设计模式。最后,作者提醒开发者,一个能够发布的平庸代码的游戏比一个从未发布的完美代码的游戏要好,鼓励开发者在实践中学习和改进。
Mindmap
Keywords
💡应用层次结构
💡解耦
💡组件
💡依赖注入
💡设计模式
💡迭代改进
💡状态机
💡观察者模式
💡分离关注点
💡代码重用
💡干代码原则
Highlights
讨论了编写更清晰代码的技巧
分享了一些结构化游戏的个人技术
讨论如何保持代码的灵活性和易于改变
应用层次结构的重要性
依赖关系应该只向下流动,而不是向上或横向
每个对象都应该能够在隔离状态下工作
举例说明了玩家对象的灵活性
避免假设结构和关注点分离
保持事物分离意味着可以后期更改实现而不破坏代码
数据和操作数据的逻辑应该保持在一起
使用组件来划分代码,而不是创建大型单一对象
依赖注入的概念和应用
设计模式的重要性和学习优先级
状态机和观察者模式的重要性
迭代改进代码风格,不要一开始就追求完美
实践中学习,遇到问题时再研究解决方案
游戏开发和软件开发是长期学习的过程
Transcripts
we've looked at some tips for writing
cleaner code and I've even shown some of
my own techniques for structuring games
so now let's take a proper slightly
higher level view of our application and
discuss how we can keep our code
flexible and more receptable to change
this is not intended as an authoritative
or exhaustive list by any means but
rather a collection of small actionable
tips that you can use to immediately
improve your code base so let's first
discuss the foundational idea behind
several of the tips we'll discuss today
the application hierarchy every
application no matter how simple or how
complex has some sort of a hierarchy and
this hierarchy needs to be respected
this means that dependencies should only
flow down the hierarchy never up or
laterally additionally we generally want
to go down only one level for any
dependencies this keeps our code
properly decoupled and makes it easy to
use various objects around the
application wherever we need them since
each object is able to work in isolation
and has no dependencies on the state or
structure of the application Beyond its
realm of focus as an example of this
principle let's consider the simple
hierarchy of a small game if our player
is completely isolated and only
concerned with what it contains then we
can put our player wherever we want
other levels bonus game modes where the
game structure is completely different
or even as an interactive way of
selecting levels from a menu but if the
player always assumes that it will have
a sibling in the tree named items that
immediately breaks this flexibility as
we now have to have an items object in
the same level of the player in all
locations whether or not we need it not
only that but we've also locked
ourselves into the implementation of our
sibling and parent dependencies how much
flexibility does our game really have if
we always assume that there is an item
sibling with coin objects as direct
children and each one has a function
called play sound effect this has
nothing to do with the player
specifically but if we change or break
this dependency our player is now broken
as well so that establishes why we don't
want to assume structure above or around
our object in the tree but why is going
too far down a problem well really for
the same reason Assumption of structure
and separation of concerns if our player
needs to change animations it can tell
the animation container to do so but it
shouldn't try to manipulate the
individual frames of a specific
animation and it definitely shouldn't
try to alter the bitmap data itself as
that is Way Beyond its scope keeping
things separated means we can change our
implementation later without breaking
the player code maybe we originally used
individual images to stitch into an
animation and later switch over to a
Sprite sheet the player which should
really only be concerned with the
overall high level operation of our
character really doesn't need to know or
care how the animation is played only
that it has a way to request animation
changes which really leads me right into
my next Point generally speaking we want
to keep data and the logic operating on
those data together rather than asking
an object for its data so we can operate
on them separately there are exceptions
to this rule of course such as maybe
with some very generic utility functions
but I'm going to keep it more specific
to the player for instance so revisiting
the previous example about the player's
relationship to the animation container
the player should tell the animation
container the change animations but not
manual annually access the data within
the container and make any required
changes as that moves the logic of
changing animations outside of the
animation oriented code and into an
object that really shouldn't concern
itself with how this transition occurs
creating a function on the animation
container the player can call keeps our
animation related code where it belongs
and ensures we can reason about the
state of the application at any time as
there are no external effects to be
worried about for example if an
animation isn't playing properly we know
that the issue must be contained within
the animation container not sitting in a
random function somewhere else in our
application this also keeps our code
reusable and flexible since all the
functionality we may need is contained
in our animation object enemies NPCs and
the player can all use one consistent
interface to manage animations this rule
may seem a bit obvious but these issues
can easily sneak their way into the
application when you really just need to
make one small change this one time
the previous rules really lead right
into this next one rather than creating
large monolithic objects to get all the
functionality you need or going
overboard with inheritance use
components to compartmentalize smaller
self-sufficient bits of code and compose
complex objects out of those components
for instance there's probably a lot of
objects in your game that may have a
health bar the player various enemies
breakable crates and so on rather than
having to manage separate variables and
methods on each object to manage Health
you could create a health component or
even an entire stats component that
houses and standards the data and
functions you need now every item in
your game that needs a health bar can
use this component components can
massively improve the organization and
reusability of your code and also remove
dead code by not having a lot of
inheritance you don't need now let's
talk dependency injection which is the
technique of supplying references to
objects at runtime rather than compile
time it's a great way to make your code
more reusable and flexible because sure
it's easy to say that there's a
hierarchy to follow and that certain
design patterns can help you maintain
that hierarchy but there are times when
you do still need a reference to
something that is outside of your direct
descendants State machines come to mind
for instance since they're generally
owned by an object higher in the
hierarchy than them but they need a
reference to that parent object so that
they can control it if we hard code this
reference we'll have to duplicate our
state machine for every object which not
only is a pain in the butt but it also
breaks the principles of keeping our
code dry with dependency injection we
can instead say that our state will
control an object of Type X such as a
rigid body and write our code around
that type but without any specific
references to the objects that will hold
this state at runtime a parent object
can pass a reference of itself to the
state and that state will now control
that specific object as if it knew about
it all along that's dependency injection
in a nutshell hopefully you can see just
how useful it can be in a lot of
different situations including the
previously mentioned technique about
compartmentalizing functionality in two
components with components and
dependency injection we can write
generic code to control movement Health
Ai and more that can flexibly operate on
a variety of different objects as needed
so now let's go even higher level and
talk about design patterns and look I
get it design patterns aren't the
sexiest thing in the world to study but
why bother Reinventing the wheel and
probably doing a worse job of it when a
lot of common issues software developers
run into have already been solved it's
worth it to take some time to at least
learn the major ones but I'm not just
going to leave it at telling you to go
study software architecture although
that's not the worst idea in the world
if you've got Ambitions for systems
driven games so here's what I would
prioritize first off State machines I've
talked about State machines multiple
times already and I could probably
squeeze out at least one more video on
them if I felt compelled to so let that
be a signal of just how strongly I feel
you should be familiar with this pattern
whether you prefer to use enumerators or
more object-oriented code learn State
machines even the simplest of games can
often benefit from them next up I would
learn about the Observer pattern State
machines may be my number one pick but
the Observer pattern is a very close
second that has huge implications for
making your code more flexible with
Native support in most engines and
programming languages which means you
don't even have to put in a lot of time
or effort into implementing it this is
arguably the biggest bang for your buck
when studying design patterns after
those two patterns it kind of starts
getting into a realm of what do you
really need to study to learn about but
the mediator pattern could be a good way
to go after the Observer pattern I've
not covered this one yet but it is
coming so I'll just leave it at this if
the Observer pattern is too one way for
you this is the pattern you want to
study and as I just mentioned once
you're comfortable with a couple of
these patterns you can start diving into
specific issues you're running into or
even just doing a full read of a book
like game programming patterns or maybe
a more generic software engineering book
and seeing what sticks out to you and
that's largely it for today but I do
want to leave you with one more
important tip it's okay to be iterative
with improving your code style don't try
to learn every design pattern right away
and spend weeks at the start of your
next project trying to make your code as
perfect as possible before you even
fully know what the requirements are or
even have the smallest vertical slice
possible too much Theory and not enough
practice can just burn you out out or
even lead to not really understanding
the why and how because you've not put
it into practice plus let's not forget
that a game with mediocre code that
ships is better than a perfectly coded
game that never does it is okay to work
until you hit a pain point and then
research how to fix it and it can even
be worth doing it wrong on a small
project seeing what the final product
looked like and what your Lessons
Learned were and then researching how to
do it better in the future since you'll
have a better idea of the issues you're
going to run into remember both game
development and software development as
a whole are entire career paths it takes
time to learn how to tackle these
problems
[Music]
Weitere verwandte Videos ansehen
Software Engineering: Crash Course Computer Science #16
Add a ViewModel with @EnvironmentObject in SwiftUI | Todo List #3
How I Structure New Projects In Golang
Honest FUTURE of Software Engineering | Is It A Good Career Option in 2024 🥲? AI, DEVIN is HERE
Natural Language Processing: Crash Course Computer Science #36
how I studied for 12 hours a day for over a year
5.0 / 5 (0 votes)