Unity2d速通

1 概述

关于Unity正文前,先大致整理一些预备内容和框架。

  • Unity是一个平台/引擎,有很多库文件,开发者需要做两件事:利用Unity自带的图形化界面(Inspector面板)挂载调整;利用自己写的C#(Script脚本)调用库文件实现逻辑。

  • 一个Unity工程有多个场景(Scene),每个场景下有多个对象(GameObject,也称物件),一个对象就是一个容器(Container),容器中有很多组件(Component),一个组件有很多属性(Attribute)。

  • 通常一个Unity2d项目的对象包括:基础(Camera、EventSystem、Light等)、环境(Background、Midground、Foreground等)、游戏(Player、Enemy等)、用户界面(Canvas、UI Elements等)。

  • Unity的xyz坐标系是左手系。对于在3D场景中z坐标更小(朝外)但图层更低的对象,在2D中会被z坐标更大(朝里)的对象遮住。

  • 做好的复用对象模版保存在预制体(Prefab)中。整体Assets文件夹通常包括Art、Animation、Prefab、Scene、Script等。

  • 如果需要共享工程,只需要打包Assets、Packages、ProjectSettings三个文件夹。

2 C#

好了,让我们学习代码基础。

2.1 让变量显示在Inspector面板

两种方式:SerializeField(序列化字段)或Public(公有)。

    [SerializeField] private float walkSpeed = 1;
    public float jumpForce = 45f;

区别在于SerializeField仍保持封装不被外部脚本修改(推荐),Public的变量则是完全公开修改的。

通常为了易读性,这样组织变量定义:

    [Header("Ground Check Settings:")]
    [SerializeField] private Transform groundCheckPoint; //point at which ground check happens
    [SerializeField] private float groundCheckY = 0.2f; //how far down from ground chekc point is Grounded() checked
    [SerializeField] private float groundCheckX = 0.5f; //how far horizontally from ground chekc point to the edge of the player is
    [SerializeField] private LayerMask whatIsGround; //sets the ground layer
    [Space(5)]

可以在定义时赋值或不赋值。

2.2 生命周期函数

  • Awake():初始化,最先调用

  • OnEnable():对象被启用时调用

  • Start():在Awake()和OnEnable()之后调用

  • FixedUpdate():按固定间隔,多帧调用

  • Update():每帧调用

  • LateUpdate():在Update()之后,每帧调用

  • OnDisable():对象被禁用时调用

  • OnDestroy():对象被销毁时调用

单例模式(Singleton)的类在同一时刻只存在一个实例(比如玩家角色就是单例),并且提供全局访问点,是典型的Awake()函数实现:

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
        }
        DontDestroyOnLoad(gameObject);
    }

Start()通常用于获取组件和属性,为了避免后面每一帧Update()频繁调用,例如:

    void Start()
    {
        pState = GetComponent<PlayerStateList>();

        rb = GetComponent<Rigidbody2D>();
        sr = GetComponent<SpriteRenderer>();

        anim = GetComponent<Animator>();

        gravity = rb.gravityScale;

        Health = maxHealth;
        Mana = mana;
        manaStorage.fillAmount = Mana;
    }

Update()处理每一帧动画,以下是一个完整的2D角色单帧处理流程:

    void Update()
    {
        if (pState.cutscene) return;

        GetInputs();
        UpdateJumpVariables();

        if (pState.dashing) return;
        RestoreTimeScale();
        FlashWhileInvincible();
        Move();
        Heal();
        CastSpell();
        if (pState.healing) return;
        Flip();
        Jump();
        StartDash();
        Attack();
    }

FixedUpdate()用于处理多帧动作:

    private void FixedUpdate()
    {
        if (pState.dashing || pState.healing || pState.cutscene) return;
        Recoil();
    }

2.3 获取Input输入

这些可以在Input Manager调整,代码如:

    void GetInputs()
    {
        xAxis = Input.GetAxisRaw("Horizontal");
        yAxis = Input.GetAxisRaw("Vertical");
        attack = Input.GetButtonDown("Attack");
    }

2.4 地面检测

这个功能通常需要完成两个步骤:将地面设置为专用图层,然后给对象添加地面检查点。

图层设置可以在Inspector中完成(指派Ground图层为whatIsGround),地面检查点作为子对象附加到父对象(groundCheck宽度小于父对象的Box Collider)。

代码可以这样实现:

    public bool Grounded()
    {
        if (Physics2D.Raycast(groundCheckPoint.position, Vector2.down, groundCheckY, whatIsGround) 
            || Physics2D.Raycast(groundCheckPoint.position + new Vector3(groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround) 
            || Physics2D.Raycast(groundCheckPoint.position + new Vector3(-groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

这里Raycast(射线检测)指的是从起点沿着指定的方向发射一条射线,检测是否与任何物体发生碰撞。Vector2和Vector3分别代表二维向量与三维向量,Vector2.down是正下方,groundCheckX和groundCheckY是检测范围长度。

2.5 左右翻转

这可以通过一个状态bool变量实现。

    public bool lookingRight;

然后编写代码:

    void Flip()
    {
        if (xAxis < 0)
        {
            transform.localScale = new Vector2(-Mathf.Abs(transform.localScale.x), transform.localScale.y);
            pState.lookingRight = false;
        }
        else if (xAxis > 0)
        {
            transform.localScale = new Vector2(Mathf.Abs(transform.localScale.x), transform.localScale.y);
            pState.lookingRight = true;
        }
    }

Transform是少数不需要手动GetComponent()的组件,其三个属性分别是position、rotation、localscale。

另外移动的代码如下:

    private void Move()
    {
        if (pState.healing) rb.velocity = new Vector2(0, 0);
        rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y);
        anim.SetBool("Walking", rb.velocity.x != 0 && Grounded());
    }

2.6 帧间时间deltatime

为了避免帧率对时间造成影响,采用deltatime(每帧的时间流逝量)如:

    void Attack()
    {
        timeSinceAttack += Time.deltaTime;
        if (attack && timeSinceAttack >= timeBetweenAttack)
        {
            timeSinceAttack = 0;
            anim.SetTrigger("Attacking");

            if (yAxis == 0 || yAxis < 0 && Grounded())
            {
                Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX, recoilXSpeed);
                Instantiate(slashEffect, SideAttackTransform);
            }
            else if (yAxis > 0)
            {
                Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, recoilYSpeed);
                SlashEffectAtAngle(slashEffect, 80, UpAttackTransform);
            }
            else if (yAxis < 0 && !Grounded())
            {
                Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, recoilYSpeed);
                SlashEffectAtAngle(slashEffect, -90, DownAttackTransform);
            }
        }

这里也用到了自定义的计时器,用于控制时间间隔。如果涉及Time.timeScale,那么Time.unscaledDeltaTime则是不受缩放影响的真实时间,Time.deltaTime会受到更改。

2.7 可视化调试OnDrawGizmos

这种方法只在编辑模式和游戏模式下的场景视图中起作用,不会在实际游戏运行时的屏幕上显示。

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
        Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
        Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
    }

2.8 协程Coroutine

协程用于处理需要一定时间完成的动作。以冲刺为例:

    IEnumerator Dash()
    {
        canDash = false;
        pState.dashing = true;
        anim.SetTrigger("Dashing");
        rb.gravityScale = 0;
        int _dir = pState.lookingRight ? 1 : -1;
        rb.velocity = new Vector2(_dir * dashSpeed, 0);
        if (Grounded()) Instantiate(dashEffect, transform);
        yield return new WaitForSeconds(dashTime);
        rb.gravityScale = gravity;
        pState.dashing = false;
        yield return new WaitForSeconds(dashCooldown);
        canDash = true;
    }

3 面板

3.1 图像

PPU(Pixels Per Unit)越大,导入图片越小,画面越清晰。

Max Size是导入的图片在缩放下不超过的最大尺寸,如果过小会使得画面模糊。

Filter Mode是过滤模式,通常多线性的纹理效果优于点线性。

Wrap Mode是环绕模式,Repeat会在超出范围时重复显示,Clamp是钳制。

3.2 刚体

Rigidbody处理物体的物理运动,包括重力、速度、力。

注意如果子对象不是刚体,那么父对象的Collider是自己和子对象的Collider之和。此时子对象可以触发父对象的OnTriggerEnter2D()。

3.3 SortingLayer

Unity有两种自带的图层:Layer和Sorting Layer。前者如检测碰撞用于图层逻辑(逻辑分层),后者用于显示渲染覆盖(排序分层)。

Sorting Layer在制作环境、背景时非常有用,可以制作景深、雾气、特殊的前后遮挡效果等等。

如果UI物件和场景物体相互遮盖,可以在Layer中隐藏。

3.4 URP Lighting

2D Light在新版Unity中被放进URP(Universal Render Pipeline)Package中。

为了添加Lighting,需要把所有被Light的对象材质换成URP材质。

选择需要的Sorting Layer添加2D Light,适当调整颜色和强度以匹配环境。

4 动画

4.1 K帧

K帧即关键帧动画,通过在关键帧之间插值实现平滑过渡。

Unity的K帧动画在Animation窗口,朴素的方法是把预制的关键帧Sprite添加进去保存,会自动在Animator窗口生成状态机,然后可以在代码中调用如:

        anim.SetBool("Walking", rb.velocity.x != 0 && Grounded());

这样可以控制Animator的标记变量实现条件状态转移。

4.2 骨骼动画

朴素的K帧消耗大量时间,这时可以用Unity的skinning editor处理分层的单张图片(推荐psb/psd,也可以是png;Unity可以自动切割图片,注意背景透明)。

通常先绑定(Rigging)骨骼(如要细致动画可在头发或衣服上添加分叉的骨骼),并在Visualization分配骨骼排序。然后使用Auto Geometry生成权重,应用后在需要动画的对象添加Sprite Skin组件创建骨骼。

如果需要为同一个物件准备多个Sprite,那么都对其作绑定,后续动画采用Sprite Resolver或Sprite Swap等组件设置同一个物件不同Sprite的可见性。

武器装备之类应当和角色骨骼层级相同,不应低于角色。

4.3 IK反向动力学

IK Manager可以实现子骨骼连续带动父骨骼转动的效果。

这通常是把手、脚等边缘骨骼挂载到IK Manager的List中,然后创建Target,并对翻转、约束作调整。

4.4 粒子系统ParticleSystem

粒子系统可以制作动画特效。

首先需要粒子的Material,然后在层次面板创建粒子系统,在Inspector中配置。

posted @   rzk_零月  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示