Unity 2D 游戏开发解决方案大全

Posted dying 搁浅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity 2D 游戏开发解决方案大全相关的知识,希望对你有一定的参考价值。

Unity 2D 游戏开发解决方案大全

一些官方腔

这篇文章会是一个大纲模式,致力于,为刚入坑的小白,对于一些常见的 Unity 2D 开发问题给出解决方案(啊,尤其是我)

一些方案可能并非最优解,但确实可以解决问题,如果你有更好的方案,欢迎讨论留言

哈哈,我不装了,其实就是想把我日常开发遇到的问题和最终的解决方案总结一下,以免以后忘记,少走些弯路,顺便可以让新手们缩短点找资料时间(大概)

ok 以下是大纲全部内容,可能会持续更新,当然所谓大纲,我不会给出解决方案的详细实现,这里给出的是一个方向,一种思路,详细实现的话,看以后自己摆不摆吧,随缘更新,步骤记不牢就更新。

Unity 2D Q and A

1. 如何做自己的动画和人物?

既然是 2D ,那 3D 建模那套,我们就可以跳过了,所以,人物场景,道具,UI,等等当然是靠画,你可以自己画,或者找人画。

用什么画? 推荐用 PS,属于老牌,adobe 大厂软件,选它是没啥大错的,当然你想用 SAI ,或者像素风的 aesprite 都可以,顺手就行。

怎么做动画? 其实动画的原理是啥?其实就是多张图片连续播放,小时候的那种翻页小人书,而我们也经常听到逐帧动画,所以下功夫逐帧画就完事了。而对于人物,我说两句,一个省事的方法,用骨骼绑定做骨骼动画,这样人物动作的动画效率会高很多,推一个我看的入门视频 Unity最新2D功能系列入门教程1.Skeleton 骨骼绑定

2. 自动寻路实现敌人找玩家

关于自动寻路,你可以用 A* Pathfinding, 项目地址: https://arongranberg.com/astar/,这样你就可以让你的敌人自动靠近你的主角了。

3. 行走攻击

建议,做玩家的时候,上半身和下半身分开导入,在 unity 里拼接,这样你 下半身走路 的动画就可以独立于 上半身攻击 的动画独立制作了,如此就可以分开播放,进而实现行走攻击。这个方法也可以拓展到任何你想分离行动的场景中。

Unity 框架QFramework v1.0 使用指南 工具篇:05. ResKit 资源管理&开发解决方案 | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏

Res Kit 简介

Res Kit,是资源管理&快速开发解决方案

特性如下:

  • 可以使用一个 API 从 dataPath、Resources、StreammingAssetPath、PersistentDataPath、网络等地方加载资源。
  • 基于引用计数,简化资源加载和卸载。
  • 拥抱游戏开发流程中的不同阶段
    • 开发阶段不用打 AB 直接从 dataPath 加载。
    • 测试阶段支持只需打一次 AB 即可。
  • 可选择生成资源名常量代码,减少拼写错误。
  • 异步加载队列支持
  • 对于 AssetBundle 资源,可以只通过资源名而不是 AssetBundle 名 + 资源名 加载资源,简化 API 使用。

Res Kit 快速入门

我们知道,在一般情况下,有两种方式可以让我们实现动态加载资源:

  • Resources
  • AssetBundle

在 Res Kit 中,推荐使用 AssetBundle 的方式进行加载,因为 Res Kit 所封装的 AssetBundle 方式,比 Resources 的方式更好用。

除了 Res Kit 中的 AsseBundle 方式更易用外,AssetBundle 本身相比 Resources 有更多的优点,比如更小的包体,支持热更等。

废话不多说,我们看下 Res Kit 的基本使用。

Res Kit 在开发阶段,分为两步。

  • 标记资源
  • 写代码

在开始之前,我们要确保,当前的 Res Kit 环境为模拟模式。

按下快捷键 ctrl + e 或者 ctrl + shift + r ,我们可以看到如下面板:

确保模拟模式勾选之后,我们就可以进入使用流程了。

1. 资源标记

在 Asset 目录下,只需对需要标记的文件或文件夹右键->@ResKit- AssetBundle Mark,如下所示:

标记完了,

标记成功后,我们可以看到如下结果:

  1. 该资源标记的选项为勾选状态

  1. 该资源的 AssetLabel 中的名字如下

这样就标记成功了。

这里注意,一次标记就是一个 AssetBundle,如果想要让 AssetBundle 包含多个资源,可以将多个资源放到一个文件夹中,然后标记文件夹。

2.资源加载

接下来我们直接写资源加载的代码即可,代码如下,具体的代码含义,看注释即可。。

using UnityEngine;

namespace QFramework.Example

    public class ResKitExample : MonoBehaviour
    
        // 每个脚本都需要
        private ResLoader mResLoader = ResLoader.Allocate();

        private void Start()
        
            // 项目启动只调用一次即可
            ResKit.Init();
            
            // 通过资源名 + 类型搜索并加载资源(更方便)
            var prefab = mResLoader.LoadSync<GameObject>("AssetObj");
            var gameObj = Instantiate(prefab);
            gameObj.name = "这是使用通过 AssetName 加载的对象";

            // 通过 AssetBundleName 和 资源名搜索并加载资源(更精确)
            prefab = mResLoader.LoadSync<GameObject>("assetobj_prefab", "AssetObj");
            gameObj = Instantiate(prefab);
            gameObj.name = "这是使用通过 AssetName  和 AssetBundle 加载的对象";
        

        private void OnDestroy()
        
            // 释放所有本脚本加载过的资源
            // 释放只是释放资源的引用
            // 当资源的引用数量为 0 时,会进行真正的资源卸载操作
            mResLoader.Recycle2Cache();
            mResLoader = null;
        
    

将此脚本挂到任意 GameObject 上,运行后,结果如下:

资源加载成功。

模拟模式与非模拟模式

AssetBundle 的不便之处

在使用 Res Kit 之前,相信大家多多少少接触过 AssetBundle。 有的童鞋可能是在项目中用过 AssetBundle,有的童鞋可能只是简单学习过 AssetBundle。总之,AssetBundle 在不通过 Res Kit 使用之前,总结下来就两个字:麻烦。

AssetBundle 麻烦在哪里呢?

首先 AssetBundle,需要打包才能在运行时加载资源。而打包需要我们写编辑器扩展脚本,在编辑器扩展脚本中还要处理平台和路径相关的逻辑。

在运行时,还需要根据平台和路径去加载对应的 AssetBundle。

这些操作想想就比较头痛。

既然 AssetBundle 这么麻烦,我们为什么还要用 AssetBundle 呢?

因为 AssetBundle 可以给项目带来更好的性能,而且 AssetBundle 支持热更新。

有了这两个优势,AssetBundle 就成了很多项目的必然选择。

而 Res Kit 中,为了解决频繁打包的问题,引入了一个概念:模拟模式(Simulation Mode)。

模拟模式(Simulation Mode)

什么是模拟模式?

顾名思义,就是模拟加载 AssetBundle 的模式,这里只是模拟,并没有真正去加载 AssetBundle,而是去加载 Application.dataPath 目录下的资源,也就是 Assets 目录下的资源。

这样做有什么好处呢?

好处就是每当有资源修改的时候,就不用再打 AB 包了,就可以在运行时加载到修改后的资源。

如果是非模拟模式下,每当有资源修改时,就需要再打一次 AB 包,才能加载到修改后的资源。

所以一个模拟模式,解决了频繁打 AB 包的问题,从而在开发阶段提高我们的开发效率。

那么在使用 Res Kit 的时候,模拟模式对应的阶段是开发阶段,那么非模拟模式对应的是什么阶段呢?

答案就是真机阶段。

开发阶段、真机阶段

开发阶段、真机阶段并不是 Unity 提供的概念,而是笔者在迭代 Res Kit 中提出的两个概念。

这两个概念很容易理解:

  • 开发阶段:开发逻辑的阶段,需要编写大量的逻辑,大部分情况下都在 Unity Editor 环境下开发。
  • 真机阶段:需要在真机上运行的阶段,这个阶段主要是做大量的测试或者真正发布了。

相信有点规模的项目都会分阶段出来的,比如开发阶段、测试阶段、生产阶段等等,大家理解起来应该不难。

接下来简单分析一下开发阶段、真机阶段的特点。

开发阶段
在开发阶段,开发者需要写大量的逻辑,而且资源的目录还没有稳定,一般在开发过程中会有很大的变化。
如果每次资源的修改都需要打 AB 包的话,会非常影响开发进度。

真机阶段
真机阶段,一般就是一个版本的逻辑都写完了,只需要做一些测试和 debug 工作。在这个阶段,资源目录都稳定了,不需要做很大的调整。

在真机阶段,每次打 App 包之前,只需要 Build 一次 AB 即可。

当然,在 Unity Editor 环境中,可以取消勾选模拟模式,这样在 Unity Editor 环境下可以加载真正的 AssetBundle 包。

在上一篇文章所说的,拥抱各个开发阶段指的就是为开发阶段、和真机阶段做了考虑。

此篇的内容就这些。

小结

  • 开发阶段:
    • 模拟模式
  • 真机阶段:
    • 每次打 App 包之前,打一次 AB 包。
    • 可以在 Unity Editor 环境下,取消勾选模拟模式,这时在运行时加载的资源则是真正的 AssetBundle 资源

如何打 AssetBundle(真机模式)

取消勾选模拟模式情况下,点击打 AB 包 即可。

异步加载

异步加载代码如下:

// 添加到加载队列
mResLoader.Add2Load("TestObj",(succeed,res)=>
    if (succeed) 
    
        res.Asset.As<GameObject>()
						.Instantiate();
    
);

// 执行异步加载
mResLoader.LoadAsync();

与 LoadSync 不同的是,异步加载是分两步的,第一步是添加到加载队列,第二步是执行异步加载。

这样做是为了支持同时异步加载多个资源的。

异步加载

代码如下:

using System.Collections;
using UnityEngine;

namespace QFramework.Example

    public class AsyncLoadExample : MonoBehaviour
    
        IEnumerator Start()
        
            yield return ResKit.InitAsync();

            var resLoader = ResLoader.Allocate();
            
            resLoader.Add2Load<GameObject>("AssetObj 1",(b, res) =>
            
                if (b)
                
                    res.Asset.As<GameObject>().Instantiate();
                
            );

            // AssetBundleName + AssetName
            resLoader.Add2Load<GameObject>("assetobj 2_prefab","AssetObj 2",(b, res) =>
            
                if (b)
                
                    res.Asset.As<GameObject>().Instantiate();
                
            );
            
            resLoader.Add2Load<GameObject>("AssetObj 3",(b, res) =>
            
                if (b)
                
                    res.Asset.As<GameObject>().Instantiate();
                
            );

            resLoader.LoadAsync(() =>
            
                // 加载成功 5 秒后回收
                ActionKit.Delay(5.0f, () =>
                
                    resLoader.Recycle2Cache();

                ).Start(this);
            );
        

    

结果如下:

加载场景

注意:标记场景时要确保,一个场景是一个 AssetBundle。

using UnityEngine;

namespace QFramework.Example

	public class LoadSceneExample : MonoBehaviour
	
		private ResLoader mResLoader = null;

		void Start()
		
			ResKit.Init();

			mResLoader = ResLoader.Allocate();

			// 同步加载
			mResLoader.LoadSceneSync("SceneRes");

			// 异步加载
			mResLoader.LoadSceneAsync("SceneRes");

			// 异步加载
			mResLoader.LoadSceneAsync("SceneRes", onStartLoading: operation =>
			
				// 做一些加载操作
			);
		

		private void OnDestroy()
		
			mResLoader.Recycle2Cache();
			mResLoader = null;
		
	

加载 Resources 中的资源

using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example

	public class LoadResourcesResExample : MonoBehaviour
	
		public RawImage RawImage;
		
		private ResLoader mResLoader = ResLoader.Allocate();
		
		private void Start()
		
			//  加载 Resources 目录里的资源不用调用 ResKit.Init
			
			RawImage.texture = mResLoader.LoadSync<Texture2D>("resources://TestTexture");
		

		private void OnDestroy()
		
			Debug.Log("On Destroy ");
			mResLoader.Recycle2Cache();
			mResLoader = null;
		
	

关联对象管理

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example

    public class ResLoaderRelateUnloadAssetExample : MonoBehaviour
    
        // Use this for initialization
        IEnumerator Start()
        
            var image = transform.Find("Image").GetComponent<Image>();

            ResKit.Init();

            var resLoader = ResLoader.Allocate();
            
            var texture2D = resLoader.LoadSync<Texture2D>("TextureExample1");

            // create Sprite 扩展
            var sprite = Sprite.Create(texture2D, new Rect(0, 0, texture2D.width, texture2D.height), Vector2.one * 0.5f);

            image.sprite = sprite;

            // 添加关联的 Sprite
            resLoader.AddObjectForDestroyWhenRecycle2Cache(sprite);

            yield return new WaitForSeconds(5.0f);
            
            // 当释放时 sprite 也会销毁
            resLoader.Recycle2Cache();
            resLoader = null;
        
    

SpriteAtlas 加载

using System.Collections;
using UnityEngine;
using UnityEngine.U2D;
using UnityEngine.UI;

namespace QFramework

	/// <inheritdoc />
	/// <summary>
	/// 参考:http://www.cnblogs.com/TheChenLin/p/9763710.html
	/// </summary>
	public class TestSpriteAtlas : MonoBehaviour
	
		[SerializeField] private Image mImage;

		// Use this for initialization
		private IEnumerator Start()
		
			var loader = ResLoader.Allocate();

			ResKit.Init();

			var spriteAtlas = loader.LoadSync<SpriteAtlas>("spriteatlas");
			var square = spriteAtlas.GetSprite("shop");
			
			loader.AddObjectForDestroyWhenRecycle2Cache(square);

			mImage.sprite = square;

			yield return new WaitForSeconds(5.0f);

			loader.Recycle2Cache();
			loader = null;
		
	

加载网络图片

using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example

    public class NetImageExample : MonoBehaviour
    
        ResLoader mResLoader = ResLoader.Allocate();

        // Use this for initialization
        void Start()
        
            var image = transform.Find("Image").GetComponent<Image>();
            
            mResLoader.Add2Load<Texture2D>(
                "http://pic.616pic.com/ys_b_img/00/44/76/IUJ3YQSjx1.jpg".ToNetImageResName(),
                (b, res) =>
                
                    if (b)
                    
                        var texture = res.Asset as Texture2D;

                        var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),
                            Vector2.one * 0.5f);
                        image.sprite = sprite;
                        mResLoader.AddObjectForDestroyWhenRecycle2Cache(sprite);
                    
                );
            
            mResLoader.LoadAsync();
        
        
        private void OnDestroy()
        
            mResLoader.Recycle2Cache();
            mResLoader = null;
        
    

从 PersistentDataPath 加载图片

namespace QFramework.Example

	using System.Collections;
	using UnityEngine.UI;
	using UnityEngine;
	
	public class ImageLoaderExample : MonoBehaviour
	
		private ResLoader mResLoader = null;

		private IEnumerator Start()
		
			ResMgr.Init();
			
			mResLoader = ResLoader.Allocate();

			// local image
			var localImageUrl = "file://" + Application.persistentDataPath + "/Workspaces/lM1wmsLQtfzRQc6fsdEU.jpg";

			mResLoader.Add2Load(localImageUrl.ToLocalImageResName(),
				delegate(bool b, IRes res)
				
					Debug.LogError(b);
					if (b)
					
						var texture2D = res.Asset as Texture2D;
						transform.Find("Image").GetComponent<Image>().sprite = Sprite.Create(texture2D,
							new Rect(0, 0, texture2D.width, texture2D.height), Vector2.one * 0.5f);
					
				);
			
			mResLoader.LoadAsync();
			
			
			yield return new WaitForSeconds(5.0f);
			mResLoader.Recycle2Cache();
			mResLoader = null;
		
	

自定义 Res

ResKit 提供了 自定义 Res ,通过自定义 Res 可以非常方便地自定义 Res 的加载来源,比如 PersistentDataPath、StreamingAssetPath、AssetBundle 等,甚至是内存中的 GameObject 等资产,还可以集成 Addressables 或者其他的资源管理方案,ResKit 内置支持的 AssetBundle、Resources、网络图片加载、PersistentDataPath 图片加载都是通过自定义 Res 的方式扩展而来。

我们看下自定义 Res 的用法,如下:

using UnityEngine;

namespace QFramework

    public class CustomResExample : MonoBehaviour
    
        // 自定义的 Res
        public class MyRes : Res
        
            public MyRes(string name)
            
                mAssetName = name;
            

            // 同步加载(自己实现)
            public override bool LoadSync()
            
                // Asset = 加载的结果给 Asset 赋值 
                State = ResState.Ready;
                return true;
            

            // 异步加载(自己实现)
            public override void LoadAsync()
            
                // Asset = 加载的结果给 Asset 赋值 
                State = ResState.Ready;
            
            

            // 释放资源(自己实现)
            protected override void OnReleaseRes()
            
                // 卸载操作
                // Asset = null
                State = ResState.Waiting;
            
        

        // 自定义的 Res 创建器(包含识别功能)
        public class MyResCreator : IResCreator
        
            // 识别
            public bool Match(ResSearchKeys resSearchKeys)
            
                return resSearchKeys.AssetName.StartsWith("myres://");
            

            // 创建
            public IRes Create(ResSearchKeys resSearchKeys)
            
                return new MyRes(resSearchKeys.AssetName);
            
        

        void Start()
        
            // 添加创建器
            ResFactory.AddResCreator<MyResCreator>();

            var resLoader = ResLoader.Allocate();

            var resSearchKeys = ResSearchKeys.Allocate("myres://hello_world");
            
            var myRes =  resLoader.LoadResSync(resSearchKeys);
            
            resSearchKeys.Recycle2Cache();
            
            Debug.Log<

以上是关于Unity 2D 游戏开发解决方案大全的主要内容,如果未能解决你的问题,请参考以下文章

unity2d游戏入门开发【三】各个面板作用

unity2d游戏开发系列教程:四一个2D游戏所需要的主要功能(游戏框架)

unity3d开发2d游戏中Assetbundle有啥作用

一种Unity2D多分辨率屏幕适配方案

干货:Unity游戏开发图片纹理压缩方案

《Unity3D/2D游戏开发从0到1》正式出版发行