Game architecture with ScriptableObjects | Open Projects Devlog

Unity
19 Dec 202012:05

Summary

TLDR本期开发日志介绍了Unity首个“开放项目”游戏架构的设计,使用了ScriptableObjects来创建灵活、模块化和可扩展的游戏架构。通过避免硬编码和Singleton模式的问题,项目利用ScriptableObjects作为数据容器和事件通信的中间点,实现了跨场景的全局访问和一致性。同时,项目鼓励社区贡献和学习,提供了GitHub链接和相关资源,以促进项目的持续发展和功能的丰富。

Takeaways

  • 📝 这是Unity首个“开放项目”的开发日志,名为'Chop Chop',是一个经典动作冒险游戏,允许社区成员自由协作并贡献于整个游戏开发过程。
  • 🔗 项目链接和相关信息可以在视频描述中找到,鼓励开发者根据专长贡献代码或学习新知识。
  • 💻 游戏开发使用Unity 2019.4 LTS版本以确保稳定性,但为了与最新Unity版本兼容,项目已升级至Unity 2020.2版本。
  • 🎯 开放项目的目标是展示有价值的开发模式和技术,分享有用的工具,并创建一个灵活、模块化、可扩展的游戏架构。
  • 🔗 硬编码连接在项目初期可能方便,但随着项目扩大,会导致可扩展性问题和难以追踪的错误。
  • 🔄 单例模式是一种常用解决方案,它使用一个全局可访问的类实例,适用于全局管理器,但大型项目中会引入依赖问题。
  • 📦 ScriptableObjects是数据容器,类似于材料或3D模型,它们不依赖于播放模式,数据全局可访问且与场景无关。
  • 🗣️ ScriptableObjects用于存储对话行、声音预设和库存项目等数据,易于设计师修改,并且所有脚本都可以访问。
  • 🎮 ScriptableObjects还可以作为Unity功能的扩展层,以重用通用功能和连接系统,例如InputReader用于集中处理玩家输入。
  • 📡 使用ScriptableObjects作为通信的中间点,创建了灵活的事件系统,避免了场景内对象之间的依赖。
  • 🔊 通过ScriptableObjects实现的音频系统允许在不同场景中播放声音,AudioManager脚本根据请求激活AudioSources。
  • 🔄 加载系统使用ScriptableObjects实现场景切换,通过LocationExit脚本触发事件,而LocationLoader管理器负责加载所有场景。
  • 🛠️ 项目正在开发自定义检查器和编辑器窗口,以更舒适地创建和管理ScriptableObjects,避免手动插入场景名称的错误。
  • 🌟 鼓励社区成员参与开发,下载项目进行实验,提出新特性,或在自己的游戏中重用部分代码。

Q & A

  • 什么是'Open Project',它有什么特点?

    -‘Open Project’是一种开放源代码的游戏项目,允许创作者社区自由地协作并积极参与整个游戏开发过程。参与者可以根据自己的专长做出贡献,或者学习新知识。

  • Unity 'Open Project'的第一个项目叫什么名字,它属于哪种游戏类型?

    -Unity 'Open Project'的第一个项目名为'Chop Chop',它属于经典的动作冒险游戏类型。

  • 为什么Unity 2019.4 LTS版本被选为开发'Chop Chop'的主要版本?

    -Unity 2019.4 LTS版本被选为开发'Chop Chop'的主要版本,因为它能提供最佳的开发稳定性。

  • ScriptableObject和MonoBehaviour有什么区别?

    -ScriptableObject是数据容器,它们像材质或3D模型一样是资源,不依赖于应用程序的播放状态,可以存储在播放模式之外的值。而MonoBehaviour通常被称为脚本,是附加到GameObject上的组件,它们的值在退出播放模式时会被重置。

  • 为什么在游戏开发中使用硬编码连接可能会导致问题?

    -硬编码连接在项目规模扩大时可能导致严重的可扩展性问题,并引入难以追踪的意外错误。

  • Singleton模式是什么,它在Unity中通常用于解决什么问题?

    -Singleton模式是一种编程模式,它使用一个类的唯一、全局可访问的实例,始终可用。这在创建全局管理器以持有全局可访问的变量和函数、实现跨多个场景的持久状态以及快速实现方面非常有用。

  • ScriptableObject在'Chop Chop'项目中扮演了什么角色?

    -在'Chop Chop'项目中,ScriptableObject被用作数据容器,用于存储对话行、声音预设和库存项等。它们还可以作为创建Unity功能层的强大工具,以公开和重用公共功能并桥接系统。

  • 如何使用ScriptableObject创建灵活的事件系统?

    -通过使用ScriptableObject作为通信的中间点,可以创建灵活的事件系统。这些ScriptableObject被称为'channels',它们像广播电台一样,可以广播事件并监听它们,从而在两个或多个通信部分之间建立连接。

  • 为什么使用ScriptableObject作为事件通信的中间点可以提高模块化和项目可维护性?

    -使用ScriptableObject作为事件通信的中间点可以提高模块化和项目可维护性,因为事件的发送者和接收者不需要事先知道对方的存在,它们可以在隔离的层中独立存在,代表单一的入口点。

  • 如何使用ScriptableObject来改进游戏的音频系统?

    -通过创建一个名为'AudioCueEventChannelSO'的ScriptableObject类,可以将UnityAction包含在其中,请求三个参数:两个ScriptableObject引用和一个Vector3。这使得音频系统模块化,并且可以在不同的场景中播放声音。

  • 如何使用ScriptableObject来实现游戏的加载系统?

    -通过使用名为'LoadEventChannelSO'的ScriptableObject类,它包含一个接受GameSceneSO数组和布尔值的UnityAction。这允许从任何地方、任何时间请求场景变换。

  • 为什么在所有channel脚本中在引发事件之前检查空值很重要?

    -在所有channel脚本中在引发事件之前检查空值很重要,因为如果没有活动订阅者,会引发空引用异常。检查空值可以确保在没有监听器的情况下在控制台中显示通知,避免程序崩溃。

  • 如何通过自定义检查器来改善ScriptableObject的工作流程?

    -通过创建自定义检查器,可以以更舒适的方式编写ScriptableObject,有时还可以创建自定义编辑器窗口,将它们聚集在一个地方,减少设置不同资产中存储的值所需的繁琐来回操作。

  • 如何避免在ScriptableObject中手动插入场景名称时出现的错误?

    -通过使用自定义检查器,例如GameSceneSOEditor脚本中的例子,可以从真实场景引用的列表中选择场景名称,避免手动插入,这样可以减少错误并防止在更新场景时破坏ScriptableObject中使用的引用。

Outlines

00:00

😀 Unity'Open Project'游戏架构设计

本视频日志介绍了Unity首个开源项目'Chop Chop'的游戏架构设计。'Open Projects'允许创作者社区自由协作,贡献于整个游戏开发过程。项目使用Unity 2019.4 LTS确保开发稳定性,并升级至Unity 2020.2以展示最新Unity版本。项目目标是展示有价值的开发模式和技术,分享实用工具,创建灵活、模块化、可扩展的游戏架构。介绍了Singleton模式的局限性,并提出使用ScriptableObjects作为解决方案,它们是数据容器,可在Play Mode之外存储全局访问和场景独立的数据。

05:02

😉 ScriptableObjects的高级应用

ScriptableObjects不仅作为数据容器,还用于创建Unity特性之上的层,以公开和重用共同功能,连接系统。例如,InputReader ScriptableObject作为中央事件中继器,监听玩家输入并触发UnityActions。这种解决方案提高了模块化和项目的可维护性。进一步使用ScriptableObjects创建灵活的事件系统,通过'channels'作为通信中介,避免了场景内对象之间的直接依赖。项目中实现了多种类型的channel ScriptableObjects,用于不同用途,提高了事件的通用性和可重用性。

10:04

🎉 组织和优化ScriptableObjects工作流程

随着项目功能的日益丰富,保持组织和易于访问变得至关重要。团队计划创建自定义检查器和编辑器窗口,以更舒适的方式编写ScriptableObjects,减少设置不同资产中存储的值所需的来回操作。例如,GameSceneSOEditor脚本通过列表选择场景名称,避免了手动插入和潜在的错误。视频鼓励观众了解ScriptableObjects的优势,并参与Open Project的开发,下载项目进行实验,提出新功能,或在自己的游戏中重用其部分。项目将持续更新,观众可以通过GitHub仓库的路线图、日常变更和双周直播流来跟进项目进展。

Mindmap

Keywords

💡ScriptableObjects

ScriptableObjects是Unity中的一种特殊类型的对象,它们不是组件,而是数据容器,可以在Play模式之外存储数据。在视频中,ScriptableObjects用于设计游戏架构,实现数据的全局访问和场景独立性,从而提高游戏的灵活性、模块化和可扩展性。例如,它们被用来存储对话行、声音预设和库存项目等。

💡Open Projects

Open Projects指的是开源的游戏项目,允许创造者社区自由地协作和积极地贡献于整个游戏开发过程。视频提到的第一个项目名为'Chop Chop',是一个经典的动作冒险游戏类型。Open Projects的目标是展示有价值的开发模式和技术,分享有用的工具。

💡Unity 2019.4 LTS

Unity 2019.4 LTS是Unity的一个长期支持版本,它保证了开发过程中的最佳稳定性。视频中提到,虽然游戏最初使用Unity 2019.4 LTS开发,但为了使内容在最新版本的Unity中可用,项目被升级到Unity 2020.2版本。

💡Singleton模式

Singleton模式是一种编程模式,它使用一个类的唯一、全局可访问的实例,始终可用。这种模式在小型项目中很有用,但在大型项目中可能会导致严重的可扩展性问题和难以追踪的错误。视频中提到,尽管Singleton模式在某些情况下有用,但在'Chop Chop'项目中,开发者选择了一种不同的方法,即依赖于ScriptableObjects。

💡数据容器

数据容器是用于存储数据的对象,ScriptableObjects就是Unity中的一种数据容器。它们可以存储如对话行、声音预设等数据,并且这些数据可以在Play模式之外访问和修改。在视频中,数据容器的概念被用来展示如何通过ScriptableObjects实现数据的全局访问和场景独立性。

💡InputReader

InputReader是视频中提到的一个ScriptableObject,它不仅仅是一个简单的数据容器,而是一个能够监听玩家输入并触发相关UnityActions的对象。它作为一个集中的事件中继器,允许项目中的对象重用输入逻辑,提高了模块化和项目的可维护性。

💡UnityAction

UnityAction是Unity中的一种委托类型,用于定义可以被调用的函数或方法。在视频中,UnityAction被用于ScriptableObjects,以允许对象订阅并响应特定的事件或输入。例如,InputReader的OnMove函数就会触发一个UnityAction,以便所有订阅的对象可以执行它们的回调。

💡事件系统

事件系统是一种设计模式,用于对象之间的松散耦合通信。在视频中,开发者使用ScriptableObjects作为事件的中间点,创建了灵活的事件系统。这些ScriptableObjects被称为“channels”,它们允许不同部分的代码通过事件进行通信,而不需要直接引用彼此。

💡AudioCueEventChannelSO

AudioCueEventChannelSO是视频中提到的一个ScriptableObject类,用于音频系统的模块化。它包含一个UnityAction,请求三个参数:两个ScriptableObject引用和一个Vector3。这些参数用于指定要播放的音频片段、播放设置以及3D空间中的播放位置。

💡GameSceneSO

GameSceneSO是视频中提到的一个ScriptableObject资产,用于存储要加载的场景信息。它被用于加载系统的ScriptableObject通道中,允许从任何地方、任何时间请求场景变化。

💡LocationExit

LocationExit是视频中提到的一个脚本,附加在GameObject上,当玩家触发碰撞器时,它会调用ScriptableObject的RaiseEvent方法来请求场景切换。这是实现场景切换请求的多种可能性之一。

💡自定义检查器

自定义检查器是Unity编辑器中用于自定义ScriptableObjects编辑界面的工具。在视频中,开发者计划创建自定义检查器,以便更舒适地创作ScriptableObjects,并有时创建自定义编辑器窗口,将它们聚集在一个地方,减少设置不同资产中存储的值所需的来回操作。

Highlights

使用ScriptableObjects设计Unity游戏架构,为游戏开发提供灵活性、模块化和可扩展性。

介绍“Open Projects”,即开源游戏项目,允许创作者社区自由协作并积极参与整个游戏开发过程。

首个项目名为“Chop Chop”,采用经典动作冒险游戏类型。

项目使用Unity 2019.4 LTS版本以保证开发稳定性,并升级至Unity 2020.2版本以适应最新Unity版本。

ScriptableObjects与MonoBehaviours的区别,前者作为数据容器,不依赖于游戏的播放状态。

ScriptableObjects作为数据容器,存储对话行、声音预设和库存项等,提高数据的全局访问性和场景独立性。

ScriptableObjects用于创建Unity特性之上的层,以公开和重用公共功能并桥接系统。

介绍InputReader ScriptableObject,作为集中事件中继器,监听玩家输入并触发相关的UnityActions。

使用ScriptableObjects创建灵活的事件系统,避免场景内对象之间的直接依赖。

ScriptableObjects作为通信的中间点,称为“channels”,用于广播和监听事件。

实现“VoidEventChannelSO”,一种通用事件通道,用于传播不带数据的事件。

通过ScriptableObjects模块化游戏音频系统,使用“AudioCueEventChannelSO”进行音频播放请求。

创建不同的事件通道有助于组织和逻辑上划分功能。

介绍基于ScriptableObjects的加载系统,允许从任何地方请求场景更改。

使用“LoadEventChannelSO”进行场景加载请求,包含场景信息和是否显示加载屏幕的布尔值。

设计LocationLoader管理器,将所有场景添加性地加载到名为Initialization的场景上。

在所有通道脚本中检查空引用以避免null引用异常,并在没有监听器时在控制台显示通知。

目标是创建自定义检查器以舒适地创作ScriptableObjects,并有时创建自定义编辑器窗口以集中管理它们。

通过GameSceneSOEditor脚本示例,避免手动插入场景名称,减少错误并自动更新场景引用。

鼓励观众参与Open Project的开发,下载项目进行实验,提出新功能,并在自己的游戏中重用部分代码。

Transcripts

play00:00

In this second devlog, we're going  to share how we used ScriptableObjects  

play00:04

to design the game architecture of the first Unity "Open Project."

play00:10

"Open Projects" are small, open-source games where the creator community

play00:14

is free to collaborate and contribute actively  to the entire game development journey.  

play00:19

You can make a contribution to them according  to your expertise, or learn something new.

play00:24

For this first project, titled “Chop Chop,” we went with a classic game genre: the action-adventure.

play00:30

You can find the link to the GitHub project and all the other relevant links

play00:33

to learn more and get involved in the video description below.

play00:37

We are making the game using Unity 2019.4 LTS,

play00:41

which will guarantee us the best stability during development.

play00:44

But since we wanted to make the contents of this devlog series available to you in the latest Unity version,

play00:49

we upgraded the project to work on Unity 2020.2,

play00:52

the latest version available when recording this devlog.

play00:55

You can find this 2020.2 version of the project on a Git branch of the original repository

play01:01

called devlogs/2-scriptable-objects.

play01:04

We conceived Open Projects with the goal of exposing valuable development patterns and techniques,

play01:09

and to share useful tools.

play01:11

One of our goals is to create a game architecture that is flexible, modular, and extensible.

play01:17

When developing a game, it’s very common to have multiple GameObjects or Scenes

play01:22

that need to share information or states with each other.

play01:25

It’s handy and easy to create hard-coded  connections between these entities, like,

play01:29

referencing objects by string or running a search  in the scene for a specific type of component.  

play01:34

But, as soon as the project grows,

play01:36

rigid connections can lead to serious scalability problems

play01:39

and introduce unexpected errors that can be hard to track down.

play01:43

A popular solution is the Singleton pattern.

play01:46

This programming pattern uses a single, globally-accessible instance of a class, available at all times.

play01:53

This is useful to make global managers that hold variables  and functions that are globally accessible,

play01:58

achieve a persistent state across multiple scenes, and are fast to implement.

play02:03

With a smaller project, this approach can be useful.

play02:06

The problems come with larger projects.

play02:09

When we’re referencing the instance of a Singleton class from another script,

play02:13

we’re creating a dependency between these two classes.

play02:16

Let’s assume we want to test the functionality provided by a certain Prefab,

play02:20

and we drag and drop it into an empty scene.

play02:22

If the Prefab contains code that calls and runs something inside a Singleton class,

play02:27

we also need this Singleton GameObject in the new scene.

play02:30

And, if the Singleton Class has other dependencies,

play02:33

we’re basically recreating the full game logic to test an isolated function.

play02:38

One system can’t exist without the other.

play02:41

There are other issues with Singletons that  we won’t go into right now.

play02:45

We encourage you to independently look into the Singleton  pattern to understand the pros and cons of it.

play02:51

For our case, we went with quite a different approach,

play02:54

which relies on ScriptableObjects - used in a specific way.

play02:58

Before we dive into the specifics, let’s see what ScriptableObjects are

play03:03

and how they differ from classic MonoBehaviours.

play03:06

MonoBehaviours, commonly referred to as just “scripts”,

play03:09

are components that are attached to GameObjects.

play03:11

Changes made to their values are reset when you exit Play mode.

play03:15

ScriptableObjects, on the other hand,

play03:17

are assets just like a Material or a 3D model, and not components.

play03:22

They don’t follow the MonoBehaviour lifecycle and don’t depend on the application’s play state.

play03:27

Therefore, we can say that ScriptableObjects are data containers

play03:31

that can hold values that also exist outside of Play Mode.

play03:35

Plus, since they are assets,

play03:37

the data we store inside them is globally accessible and scene-independent.

play03:41

If you want to dive more into the basics for ScriptableObjects,

play03:44

you can find links to other dedicated videos in the description below.

play03:48

Our project indeed uses them as data containers for many features,

play03:52

like to store dialog lines, sound presets, and inventory items.

play03:57

You can find them in the Assets folder under ScriptableObjects.

play04:01

If you select a DialogLine ScriptableObject,

play04:04

you can see how it represents a fragment of a conversation between characters,

play04:08

easy to be modified by a designer thanks to all the Inspector-exposed fields.

play04:12

Like a databank ready to be accessed and available to all scripts.

play04:16

Data storage is not the only use  case for ScriptableObjects, though.  

play04:20

For instance, they can be a powerful tool  to create layers on top of existing Unity features

play04:25

to expose and reuse common functionality and bridge systems.

play04:29

An example of this is our InputReader.

play04:31

This ScriptableObject is not a simple data container

play04:34

but an object capable of listening to the Inputs received from the player,

play04:38

invoking the relevant UnityActions.

play04:41

You could say it’s a centralized event-relayer.

play04:44

For example,it implements a function called OnMove,

play04:48

which listens to the Move input provided by Unity’s Input system.

play04:51

We support both keyboard and gamepad in Chop Chop,

play04:54

and each time Unity reads the value of any of these physical devices,

play04:58

the OnMove function of the ScriptableObject is executed.

play05:02

Then, this function invokes a UnityAction

play05:05

so that all the objects subscribed to it will, in return, execute their callbacks.

play05:10

Receiving input messages and propagating them from the InputReader ScriptableObject

play05:14

allows us to reuse this logic across the project easily.

play05:18

From here, an object like the Player script can reference the InputReader ScriptableObject

play05:23

to tap into its UnityActions and implement a specialized response.

play05:28

This solution also improves modularization and project maintainability.

play05:32

The InputReader doesn’t need to know in advance who uses and accesses it

play05:37

since it lives in an isolated layer and represents a single entry point.

play05:41

This means that there’s only one place to modify if we want to add or remove inputs,

play05:46

and the way different objects consume input is consistent and unified.

play05:50

Almost like a Singleton, but without the cons!

play05:54

Let’s take ScriptableObjects a step further,

play05:56

and see how we used them to create a flexible event system in Chop Chop.

play06:00

The idea is again to take advantage of the  independent nature of ScriptableObjects.  

play06:05

Usually, when we communicate directly  through events and delegates,

play06:09

we reference the class that owns the data structure

play06:11

and subscribe to start listening for messages.

play06:14

This approach creates in-scene dependencies between the objects involved

play06:18

since the event subscribers need to reference the senders to work.

play06:22

We solved this using the ScriptableObjects as an intermediate point of communication.

play06:28

We call these ScriptableObjects “channels” since, like in radio,

play06:32

they represent channels to broadcast events on and listen to them,

play06:35

creating a connection between two or more communicating parts.

play06:39

We have several types of channel ScriptableObjects  in the project, for different uses.

play06:44

In general, the number and types of parameters that travel via the UnityAction contained in them

play06:49

define how much a channel type is generic and reusable.

play06:53

Starting simple, we implemented a very generic type called "VoidEventChannelSO",

play06:58

which can propagate events with no data attached to them.

play07:02

For instance, we use this to provide an event channel to close the game.

play07:06

If any object raises the event on that channel, the game shuts down. 

play07:10

But let’s see some more interesting examples from our game!

play07:14

We modularised the audio system in the game

play07:16

thanks to a ScriptableObject class we called "AudioCueEventChannelSO".

play07:21

The UnityAction contained in it requests three parameters: two ScriptableObject references and a Vector3.

play07:28

The two ScriptableObjects are just data.

play07:31

They reference the AudioClips we want to play and some playback settings.

play07:34

The Vector3 informs the message receiver where we want to play the audio in the 3D space.

play07:41

With this structure in place, we created two of these ScriptableObjects:

play07:45

one where we propagate sound effects requests,  and the other for music requests.

play07:49

Creating different channels helps to organize and keep features logically ordered and subdivided.

play07:55

Our AudioManager script then references both these ScriptableObject channels

play07:59

and subscribes to their UnityActions.

play08:02

Now each script that wants to play  audio can reference these channels too  

play08:06

and perform a request by calling the  ScriptableObject’s RaiseEvent method.  

play08:10

The AudioManager will listen to this request and  activate one or more AudioSources from its pool.  

play08:16

It’s important to note that with this architecture,

play08:19

the AudioManager and the GameObjects that request the sounds can live in different scenes,

play08:23

and in fact, they do.

play08:25

This allows us to play sounds across scenes, even when we load a new one.

play08:31

In the demo assets we provide, look for  a scene called AudioExamples

play08:35

to find different examples of looping, one-shot,  and composite sounds using this system.  

play08:40

Another feature we built on top of a  ScriptableObject channel is the Loading System.  

play08:45

The logic is very similar to the Audio system

play08:48

and allows, by referencing the right ScriptableObject channel,

play08:51

to request a scene change at any time, from anywhere.

play08:54

In this case, the class we use to create  ScriptableObjects is named "LoadEventChannelSO"

play09:00

and contains a UnityAction that accepts an array of GameSceneSO and a boolean value.

play09:06

The GameSceneSO is a simple ScriptableObject asset  that stores information on the scene we want to load.

play09:12

The boolean value tells us if, during the  loading phase, we want to show a loading screen.

play09:18

In our scenes, we can now place a GameObject  with a LocationExit script attached,  

play09:23

that calls the ScriptableObject’s RaiseEvent  method when the player triggers the collider.

play09:28

This trigger zone is just one of many possibilities  we can implement to request a scene switch.

play09:34

As a general note, we designed the LocationLoader manager

play09:37

to load all the scenes additively on top of a scene called Initialization.

play09:41

This separation allows us to use the Initialization scene as a container for scripts

play09:46

with functions useful to all scenes,

play09:49

like the same LocationLoader or AudioManager,

play09:52

and then taking advantage of the flexibility of ScriptableObjects,

play09:55

have these communicate with gameplay scenes.

play09:59

If an event is invoked without active subscribers, we’d get a null reference exception.

play10:04

For this reason, in all our channel scripts we check for null before raising the event.

play10:09

If there aren’t listeners, a notification appears in the console.

play10:14

Thanks to your contributions, the project is  becoming richer in features and content every day.  

play10:19

For the team to stay productive, it’s important  to keep everything organized and easy to access.  

play10:24

Especially with ScriptableObjects used as  data containers that reference each other,  

play10:28

it can be easy for things to get messy.

play10:31

One of our next goals is to create custom Inspectors to author ScriptableObjects in a comfortable way,

play10:36

and sometimes custom Editor windows to gather them in one place.

play10:40

This aims to reduce the annoying back and forth actions necessary to set up the values stored in different assets.

play10:46

You can find an example of a custom  Inspector in the GameSceneSOEditor script.  

play10:51

We made this enhancement to avoid inserting the scene’s names manually.

play10:54

This action is error-prone, and if we update the scenes, we may break references used in the ScriptableObjects.

play11:01

Now it’s possible to choose the scene’s name from a list containing the real scene references.  

play11:08

We hope that after watching this  video,

play11:10

you will get a good idea of the advantages that ScriptableObjects can bring

play11:13

besides being data containers.

play11:15

We’d also like to invite you to participate in  the development of this Open Project,

play11:19

and maybe help us improve these ScriptableObjects workflows.

play11:23

Feel free to download the project and experiment with it,

play11:26

propose new features, and reuse parts of it inside your own games!

play11:30

Keep yourself updated on the first  Open Project by following the roadmap,  

play11:34

the daily changes in the GitHub repository, and the bi-weekly live streams.

play11:39

The project will keep upgrading and changing during development on the main branch,

play11:42

but remember that you can always find this 2020.2 version

play11:46

in a branch on the repository called devlog-2-scriptable-objects.

play11:50

We can’t wait to see your  feedback and contributions!  

play11:53

Thanks for watching!

Rate This

5.0 / 5 (0 votes)

Related Tags
Unity开发游戏架构ScriptableObjects开源项目动作冒险模块化设计事件系统音频管理场景加载社区协作开发模式
Do you need a summary in English?