找回密码
 立即注册
查看: 1729|回复: 20

Unity游戏开发有哪些让你拍案叫绝的技巧?

[复制链接]
发表于 2020-12-31 15:58 | 显示全部楼层 |阅读模式
Unity游戏开发有哪些让你拍案叫绝的技巧?
发表于 2020-12-31 16:04 | 显示全部楼层
这些技巧都是来自推特的 #UnityTips (感兴趣的可以自行搜索,可以找到更多的技巧)
*未测试Unity4.X版本的使用情况
1.Unity死机未保存场景:当你在Unity中编辑场景,突然死机时,可以在项目文件目录中找到Temp文件夹,双击文件夹,找到_Backupscenes文件夹,把后缀为.backup的文件后缀改为.unity,然后拖进Unity的Project界面里面,这样就可以还原死机前场景最后情况。
2.Inspector界面的数值栏自带加减乘除功能:从Unity5.1开始,可以在Inspector界面中的数值栏进行计算,比如:你可以在Transform里面的Position中X栏中加减乘除。
3.随意移动模型预览视窗:在Project界面里面选中模型,在Inspector界面上会显示模型的预览视图,右击视图的上的双横条,模型预览视图就会跳出来,此时就可以像Scene,Game等视窗一样任意移动,放在你想放的地方,如果想让他变回去,只要关掉它即可。
4.搜寻当前场景中哪些物件运用了指定脚本:在Hierarchy界面中的搜索栏中输入你想要搜索的脚本全名,即可找到哪些物件用了这个脚本,此方法也可以用来搜索哪些物体上有哪些物件,比如:Rigidbody等。
5.在界面上查看private变量:在Inspector界面右击Inspector的标签栏,选择Debug。
6.只让游戏运行一帧:先按下暂停键,再按运行键。
7.通过Debug.Log获取执行此语句物件:在脚本的Debug.Log语句中加入gameObject,即Debug.Log("Test", gameObject); 脚本运行时点击Console界面中的输出语句,就能在Hierarchy界面中看到哪个物件执行了这个脚本。
8.展开所有子节点:按住Alt,用鼠标点击包含子节点物件前的三角形,在Hierarchy界面和Project界面都可以使用。
9.编辑时镜头跟随选中物件:在Scene界面中选中物件按F,镜头就会对准选中物件,并使其在Scene窗口中居中,如果在拖动物件时,要让镜头跟随物件移动,选中物体后按Shift+F即可。
10.在Scene界面中方便移动编辑镜头:在Scene界面中鼠标右键 + A/S/D/W/Q/E来移动镜头, 如果想使Game视窗的镜头跟Scene一样,可以在Unity菜单栏中选择GameObject-->AlignWithView。
11.在Project界面搜索商店内容:先在Project界面中的搜索栏中输入你想要搜索内容的关键字,然后在搜索栏下边的搜索范围选择中选择AssetStore。
发表于 2020-12-31 16:06 | 显示全部楼层
分享一个比较好玩的内容吧。
大家都知道Unity有一个职位叫做Evangelist,翻译成中文就是布道师。他们的任务是介绍Unity最新的功能,分享Unity的知识。那么做一些演讲自然就必不可少了。
包括其他Team的同事,如果遇到了Unite这样的大会,也要做一些演讲。
那么,准备一份Power Point,同时准备一个live demo就是家常便饭的事情了。但是Power Point和Unity之间来回切换,总觉得不太爽,而且大家都使用Power Point似乎又有点无趣。
所以,国外的Evangelist Andy Touch以及Field Engineer Valentin Simonov他们就做了一个Unity Editor的拓展,即在Unity中播放“Power Point”——其实并不是真的播放PPT——而是利用了Unity的UI系统构建了一套类似Power Point,适合做演讲的工程。(这两位也参加了今年的Unite Beijing)。
下面简单演示一下:

Presentation Project (Unity)  
https://www.zhihu.com/video/1033496982748364800
怎么用呢?其实很简单。一套“PPT”就是有很多Scene组成的。
    Download the project.Open Presentation Window from Window > Presentation menu.Locate the sample Slide Deck in Presentation > Unity folder. It is called Unity Sample Presentation.Select it and click Load This Slide Deck button in the Inspector.Press [> B] button in the top right corner of the Presentation Window.Use Left and Right arrows on your keyboard or [<<] and [>>] buttons in the top right corner of the Presentation Window to switch slides.
那么,这样除了逼格突出之外还有什么好处呢?那就是可以很方便的将Live Demo和“PPT”结合起来。
比如Unite Euro 2017上Valentin Simonov的演讲,PPT和Live Demo浑然天成,还可以很方便的直接调试Profiler。

Unite Demo
https://www.zhihu.com/video/1032759513010532352
不过Valentin在玩flappy bird的时候显得很方,不知道是不是因为狗子的注视。
当然,最后送上工程链接:
UnityTechnologies/Presentation

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2020-12-31 16:15 | 显示全部楼层
也说不上拍案叫绝,简单列几个实用的功能。
1、反向播放动画
当我们只有一个方向的动画,比如只有前进动画,我们又需要做一个后退的功能,可以在播放动画的时候设置Speed属性为-1,然后就会反向播放前进动画。
2、多Inspector窗口比较
在Unity开发的时候,有时需要调试对比两个类似的对象的数据,可以点击Inspector窗口右上角Lock按钮,然后新增标签页添加一个Inspector窗口,选中另一个游戏对象,就可以相互比较。
3、多场景编辑
在Hiberarchy窗口下面可以打开场景,可以把多场景的物体合并到一个场景上,这个功能可以在一定程度上做到场景切分编辑。
4、Unity自带编辑器
一直以来,Unity最强大的应该是它的Editor,我们可以把编辑器扩展成一个我们想要的任意配置工具,如技能编辑器、人物属性编辑器等,也可以使用ScriptableObjects管理游戏配置数据,把这些数据序列化存储。
发表于 2020-12-31 16:16 | 显示全部楼层
用Unity自带的动画控制器Mechanim制作可视化状态机AI。

上码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class LogicalStateMachineBehaviour  : StateMachineBehaviour
{
        

    // PRAGMA MARK - StateMachineBehaviour Lifecycle
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        this.animator = animator;
        this.stateInfo = stateInfo;
        // You can reference other monobehaviours here
        if (this.animator != null)
        {
            this.creature = animator.gameObject.GetComponent<Creature>();
        }
        
        this.OnStateEntered();
        this._active = true;
    }

    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        this.animator = animator;
        this.stateInfo = stateInfo;
        
        this._active = false;
        this.OnStateExited();
    }

    public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        switch (animator.updateMode)
        {
            case AnimatorUpdateMode.AnimatePhysics:
                this.deltaTime = Time.fixedDeltaTime;
                break;
            case AnimatorUpdateMode.Normal:
                this.deltaTime = Time.deltaTime;
                break;
            case AnimatorUpdateMode.UnscaledTime:
                this.deltaTime = Time.unscaledDeltaTime;
                break;
            default:
                this.deltaTime = 0f;
                break;

        }
        this.stateInfo = stateInfo;
        this.OnStateUpdated();
    }


    // PRAGMA MARK - Internal
    private bool _active = false;

    protected Animator animator { get; private set; }
    protected float deltaTime { get; private set; }

    // TODO: You should store reference to the script that controls what this agent could do
    //       along with any other important information.
   
    protected Creature creature { get; private set; }

    protected AnimatorStateInfo stateInfo;

    void OnDisable()
    {
        if (this._active)
        {
            this.OnStateExited();
        }
    }

    // Implement the following functions to write actual state behaviors
    protected virtual void OnStateEntered() { }
    protected virtual void OnStateExited() { }
    protected virtual void OnStateUpdated() { }
}这是一个中间脚本,你可以为每个状态创建一个StateMachineBehaviour脚本继承自它,override掉OnStateEntered、OnStateExited以及OnStateUpdated,就可以撰写每个状态具体的enter、update、以及exit的表现,而状态转移的逻辑则会放在Mechanim自身中,通过特定参数的条件判断来实现。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// An Example Patrol behaviour of Mechanim State Machine AI.
/// At this state, the agent will get an random destination and try to move towards it.
/// This script only updates necessary parameters for the mechanim state transition to work.
/// The end condition of the state should be determined from the Mechanim State Machine editor.
/// </summary>
public class PatrolBehaviour : LogicalStateMachineBehaviour
{
        public float StateDurationMin = 3.0f;
        public float StateDurationMax = 6.0f;
        public float newDestRadius = 10.0f;
        public Vector3 destination { get; protected set; }


        protected override void OnStateEntered()
        {
                this.animator.SetFloat("StateDuration", Random.Range(StateDurationMin, StateDurationMax));
                GetRandomDestination();
        }

        protected override void OnStateUpdated()
        {
                // Update Parameters
                this.animator.SetFloat("StateDuration", this.animator.GetFloat("StateDuration") - this.deltaTime);
                this.animator.SetFloat("DistanceAway", Vector3.Distance(destination, creature.transform.position));
            
                Debug.DrawLine(creature.transform.position, destination, Color.yellow);
                creature.MoveTowards(destination, Time.fixedDeltaTime); // Replace with your movement function
        }

        protected override void OnStateExited()
        {
                this.animator.SetFloat("StateDuration", 0f);
        }

        void GetRandomDestination()
        {
                destination = creature.transform.position + Random.onUnitSphere * newDestRadius;
        }
}我这里引用的Monobehaviour是Creature脚本,并使用MoveTowards方法来移动,你可以把它换为自己的。




然后可以在动画控制器中单独新开一个layer(也可以视情况使用原有的动画layer),编辑状态,把脚本挂到对应的状态上去,就可以用动画状态机的逻辑表现来控制AI。注意要把Transition duration设为0。
在这个状态机里,Creature会进入Patrol状态时生成一个倒计时StateDuration和一个目标点Destination,在倒计时过期或目标点距离过近时重新进入一次Patrol状态,从而重新生成游荡目标。
通过创建更多的状态表现,并控制状态转移的参数与条件,你可以轻松地使用极少的脚本不使用外来插件的前提下定制自己的状态机表现,但是难以进行针对性的多线程性能优化,需要自己权衡利弊。
参见:
https://medium.com/the-unity-developers-handbook/dont-re-invent-finite-state-machines-how-to-repurpose-unity-s-animator-7c6c421e5785State Machine Behaviours - Unity

利用自定义Attribute轻松定制Property参数Inspector的GUI表现。



1、用MinMaxSlider调整上下界范围参数
示例:
[MinMaxSlider(0f, 1.0f)]
public Vector2 range = new Vector2(0.25f, 0.6f);

代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class MinMaxSliderAttribute : PropertyAttribute
{

    public readonly float max;
    public readonly float min;
    //public readonly bool hasInt;

    public MinMaxSliderAttribute(float min, float max)
    {
        this.min = min;
        this.max = max;
        //this.hasInt = hasInt;
    }
}


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(MinMaxSliderAttribute))]
class MinMaxSliderDrawer : PropertyDrawer
{
    //private const float FIELD_WIDTH = 40f;
    private const float LABEL_WIDTH = 35f;
    private const float FIELD_SPACE = 5f;
    private const string LABEL_MIN = "Min:";
    private const string LABEL_MAX = "Max:";

    private int _numberOfLines = 2;
    private Rect _rectPosition;
   
    float _previousLabelWidth;
    //float _previousFieldWidth;
   
    MinMaxSliderAttribute minMaxSliderAttribute
    {
        get { return ((MinMaxSliderAttribute)attribute); }
    }
   
   
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        _rectPosition = position;

        if (property.propertyType != SerializedPropertyType.Vector2)
        {
            EditorGUI.LabelField(position, label, "MinMaxSlider only works with Vector2");
            return;
        }
        
        //EditorGUI.BeginProperty(position, label, property);

        Vector2 range = property.vector2Value;
        float min = range.x;
        float max = range.y;

        // Draw MinMax Slider
        EditorGUI.BeginChangeCheck();
        Rect rect = new Rect(position.x, position.y, position.width, GetHeight());
        EditorGUI.MinMaxSlider(rect, label, ref min, ref max, minMaxSliderAttribute.min, minMaxSliderAttribute.max);
        if (EditorGUI.EndChangeCheck())
        {
            range.x = min;
            range.y = max;
            property.vector2Value = range;
        }


        // Draw Float/Int Field
        
        _previousLabelWidth = EditorGUIUtility.labelWidth;
        //_previousFieldWidth = EditorGUIUtility.fieldWidth;
        EditorGUIUtility.labelWidth = LABEL_WIDTH;
        //EditorGUIUtility.fieldWidth = FIELD_WIDTH;
        
        EditorGUI.BeginChangeCheck();

        min = EditorGUI.FloatField(
            new Rect(GetFieldX(0), GetFieldY(1), GetFieldWidth(), GetHeight()),
            new GUIContent(LABEL_MIN), min);
        max = EditorGUI.FloatField(
            new Rect(GetFieldX(1), GetFieldY(1), GetFieldWidth(), GetHeight()),
            new GUIContent(LABEL_MAX), max);
        if (EditorGUI.EndChangeCheck())
        {
            range.x = Mathf.Clamp(min, minMaxSliderAttribute.min, minMaxSliderAttribute.max);
            range.y = Mathf.Clamp(max, minMaxSliderAttribute.min, minMaxSliderAttribute.max);
            property.vector2Value = range;
            
        }

        
        EditorGUIUtility.labelWidth = _previousLabelWidth;
        //EditorGUIUtility.fieldWidth = _previousFieldWidth;
        //EditorGUI.EndProperty();

    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
        return _numberOfLines*(EditorGUI.GetPropertyHeight(property, label) + EditorGUIUtility.standardVerticalSpacing);
    }

    #region GUI_SIZE
   
    float GetFieldWidth()
    {
        return (_rectPosition.width - _previousLabelWidth) / 2f - FIELD_SPACE;
    }
   
    float GetHeight()
    {
        return _rectPosition.height /(float)_numberOfLines - EditorGUIUtility.standardVerticalSpacing;
    }

    float GetFieldX(int id)
    {
        return _rectPosition.x + _previousLabelWidth + (GetFieldWidth() + FIELD_SPACE) * id;
    }
   
    float GetFieldY(int id)
    {
        return _rectPosition.y + GetHeight() * id;
    }
   
    #endregion


}(写的比较粗糙见谅)
这里用的是Vector2来作为上下界范围的数据类型(因为是存了两个浮点数),你可以根据实际需要修改,也可以自己定义一个struct。
适用场景主要是一些随机范围的选取,相当于把两个参数并为一个参数,并规定浮动范围,对Inspector的可视化有很大帮助。
参见:
https://gist.github.com/LotteMakesStuff/0de9be35044bab97cbe79b9ced695585

2、用布尔值bool/枚举类型enum决定某些参数在Inpector的显示情况。


篇幅问题代码丢在Github上了,参见以下链接中的ConditionalHide和EnumHide:
xylitogum/X-Utils

EnumHide范例






切换这里的MovementType会决定Inspector里是否显示对应类型下的特殊参数。
参考资料:
Hiding or Disabling inspector properties using PropertyDrawers within Unity 5 - Brecht Lecluyse

其他值得一提的Unity功能
    ScriptableObject类,用作数据存放器将设置数据存在文件中,便于数据与逻辑的分离解耦,以及在不同Data或Config之间随时切换。

    UnityEvent,类似Delegate,在Inspector中可视化的Event类,参见UGUI Button和Slider等事件编辑器,可以在编辑器中选择引用的外部物体、函数和对应参数,对减少代码量有极大帮助。在你需要的时机代码中Invoke即可。

    AnimationCurve,可视化曲线编辑器,编辑取值0-1之间的贝塞尔曲线。本来是用作Unity动画编辑器的一部分,但你可以作为脚本参数类型,做出自己的参数放大器、自定义数学函数或其他动画效果等。用0-1之间的值作为x轴输入,调用Evaluate方法即可得到对应的y轴输出。Gradient同理。

    ComputeShader,想要把通用运算放在GPU上?用它就对了。缺点是兼容性差和学习成本偏高,坑很深。StreamingAssets文件夹有人提到过了,在这个文件夹下的文件不会被打包进去而是独立存在的文件,方便外部引用和修改,也可以实现一些能够看到存档文件的保存功能。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2020-12-31 16:26 | 显示全部楼层
说一个独立开发不常见的技巧,但是做游戏的配置非常方便。做好了可以省很多时间
我们开发游戏的时候经常喜欢挂脚本放在物体上,像这样。





但是如果到了后期,你要实现很多功能的时候,一个设计不好就会放很多脚本上去,像这样





如果你开始做第二个外观不同的模型的时候,但是功能相同的时候就要全部再挂一遍,数据复制过来一次。你可以说我可以直接复制预制体然后改变里面的网格就好了,但是这样经常会失去引用,比如你移动挂载了模型的某个挂点,这样一替换挂点就消失了。
一些有经验的程序员会选择动态挂载脚本(其实项目开发中基本都是动态挂载脚本到物体上)





然后数据要么通过读表,要么是和美术商定一个规则,动态赋值数据读取给对应脚本。
说真的,这样处理不好维护,而且需要策划配置表来控制数据,等到实际看到效果中间的流程太冗长了。
有没有办法能直接保存Unity放在面板上的数据呢?





有一个叫做ScriptableObject的类,这个类可以序列化存储数据,用的时候可以通过实例化来得到数据。
比如我们做一个这样的类,简单记录高度和宽度数据。用来存储数据





创建一个mono的类用来挂载在物体上显示数据





创建一个Editor脚本来做一些简单的显示控制





外界就看到简单的一个数据,双击它





就出现可以编辑数据的界面





然后在Editor脚本中加入导入和保存的数据的功能:





导出就可以看到脚本数据被保存到Resources目录了







以上就是用ScriptableObject这个类保存数据的流程,这个类的有点在于我们保存的数据可以动态的修改,而且是在游戏里面可视化的修改。如果程序和美术的数据用这个做存储,美术直接修改配置我们的程序用的时候就可以直接用这个数据了。而不用去做Json或者其他格式的存储数据来转换读取数据。
而缺陷在于这个类是会依赖你的脚本,如果要做热更新功能,必须脚本变量和存储的变量数据是统一的。


如果你对unity 的Editor功能熟悉,可以做它来做一个很棒的插件脚本,比如这个之前给美术处理角色信息的工具,程序只用给工具加轮子,美术只要用工具配置,就可以直接看到效果



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2020-12-31 16:27 | 显示全部楼层
拍案叫绝倒不至于,不过Unity作为一个引擎来说提供的很多功能确实挺好用,用了之后可以极大的提高效率,能想起几个写几个吧,想起来补充就是了。

一,快捷目录。
Project窗口下,可以把文件夹直接拖到Favorites下作为快捷入口,再也不用找一个文件翻半天了,常用的目录都可以放在这里。


二,线框输出。
Game窗口的右上角有个Gizmos按钮,点击之后可以把你用于Debug画出来的所有线框都画到游戏场景中,其中包含Gizmos类画出来的所有东西,以及Unity本身提供的碰撞体等等这些需要去Scene窗口看的东西,尤其是没有双屏幕的开发者,这个功能太贴心了。

三,自定义Profiler。
利用Profiler.BeginSample()和Profiler.EndSample()函数,可以将任何代码断加入Profiler输出,性能调优的利器,一般来说如果有某个地方你自己对于性能不是很有把握,检测一下即可知道其使用开销,用了之后就停不下来了。

这里列举的都是Unity引擎的一些使用技巧,没有去考虑C#本身的黑科技,比较突出的暂时想起这么多,以后补充。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2020-12-31 16:32 | 显示全部楼层
我来说个简单的:
编辑时场景对象索引:新建一个没有逻辑的脚本,公开一个List<GameObject> 把它挂在某个根节点上,把经常需要选中,深度比较深的对象引用进来,这样就实现了一个简单的Favorites功能。


另外:
你在开发游戏的时候都用过或者看到过哪些欺骗人眼睛的技巧(trick)? - 行云的回答

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2020-12-31 16:40 | 显示全部楼层
重剑无锋,大巧不工。
分享一些Unity项目(特别是移动游戏)的优化技巧,希望能有所帮助。
性能优化,永无止境---CPU篇
性能优化,进无止境---内存篇(上)
性能优化,进无止境---内存篇(下)
扒一扒Profiler中这几个“占坑鬼”
发表于 2020-12-31 16:47 | 显示全部楼层
恩 技法很多的 有些是通用技法哪个引擎都一样,妙归妙 ,也没什么拍案的
还有些in-stuido阴招,那我不能够告诉你啊,当年整出来费不少心血,就指望这个碾压友商来的。

说个没人用,估计游戏圈不太有人知道的。
先看效果

康托耶夫第一次做马赛曲石膏灯光秀玩(农业级别)—在线播放—优酷网,视频高清在线观看
http://v.youku.com/v_show/id_XMzc0MDEyMzY0.html


灯光秀大家都知道的,就是搞一段影片,根据某个建筑造型 ,投影到大楼上面,晚上看起来这个建筑就各种奇怪的扭动变化,这种效果风行过一段时间。


这种东西难的部分在拼接和形变,需要有专门的软件去把多台投影仪拼在一起,然后需要专门的软件吧整图裁开,扭曲变形适配到大楼上面。


这种软件都贼贵,我这边一套coolux 60w多万,成本贵哭。
而且,还不带交互,如果你弄个超级玛丽在大楼上面蹦跶,让下面人用手柄玩起,怎么整啊! 那些死贵的软件根本没有这个啊!

这个时候,两大神器登场,
mac下面的syphon,
Syphon
win下面spout,
Spout
直接把unity里面的游戏视频流传给矫正或者拼合软件,你就可以直接在大楼上面玩超级玛丽 抓娃娃什么的了。



ps.发现离开我上次玩这个都四年了,好像国内还是没多少这方面类似的出来,你看,有些东西就是你不搞就真没别人做了,你说国内玩的是有多单一吧

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-22 06:55 , Processed in 0.187135 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表