Unity入门理论+实践篇之Luna

创建世界的主角

父子物体

首先创建一个cube物体

可以观察到其在2D视角下的坐标为(0,0)

此时将cube物体拖拽到ldle_0下,如图所示,并将其坐标值改为(2,2)

此时再将ldle_0物体的坐标改为(1,1)并观察ldle_0和cube的坐标

此时可以观察到,ldle_0和cube整体位置发生改动,但cube的坐标却没有变,那是因为,cube作为ldle_0的子物体,其坐标值是相对于ldle_0来说,即以ldle_0为原点

此时再将cube拖拽出ldle_0

此时可以看到cube的坐标值变为(3,3),那是因为cube不再作为ldle_0的子物体,其坐标轴以世界坐标轴为准

1. 现在使物体Luna进行初步的移动

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        Vector2 position = transform.position;
        position.x = position.x + 0.1f;
        transform.position = position;
    }
}

Unity中输入系统与C#浮点型变量(Input)

输入系统即监听用户输入的指令,如下图所示为Input所有输入监听的对象

现在获取水平方向的输入监听

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        Debug.Log(horizontal);
        Vector2 position = transform.position;
        position.x = position.x + 0.1f;
        transform.position = position;
    }
}

由于Input.GetAxis返回的类型为float(浮点数类型)所以也要创建相应类型的变量去接收它的值,现在开始监听水平方向的输入指令,为了监听输入指令,于是就用了Debug.Log()函数(用于在控制面板上输出)

Input.GetAxis("Horizontal")输入范围为-1~1

现在要先通过输入来操纵物体Luna的移动,只需下面代码就可以实现

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Debug.Log(horizontal);
        Vector2 position = transform.position;
        position.x = position.x + 0.1f * horizontal;
        position.y = position.y + 0.1f * vertical;
        transform.position = position;
    }
}

但是速度过快,所以们需要加入Time.daltaTime使物体Luna以每秒0.1米的速度去移动

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Debug.Log(horizontal);
        Vector2 position = transform.position;
        position.x = position.x + 0.1f * horizontal * Time.deltaTime;
        position.y = position.y + 0.1f * vertical * Time.deltaTime;
        transform.position = position;
    }
}

其中 horizonal * Time.daltaTime为1m/s

渲染顺序与层级设置

如上图所示,将物体BattleBackground02拖入场景时,Luna被覆盖住了,这是因为在2D视角下虽然所有物体的(x,y)为(0,0),但在unity3D渲染中还存在深度这一概念,即在3D视角下,摄像机的z轴值为-10,但BattleBackground02的z轴值为-1,除了这两个物体以外的其他物体的z轴值为0,在视觉上来看,BattleBackground02在其他物体前面,所以后被渲染,这样才出现了Luna被覆盖的情况,为了在2D视角下解决这一问题,方法一是将BattleBackground02的z轴设置为0,但调整深度(z轴)有违于2D游戏的设置理念,所以此时就引出了下面概念,层级

层级(Layer)

可以在2D游戏物体中的SpriteRenderer和地形TilemapRenderer组件中的Order in layer属性中去设置层级的大小,值越大,越后渲染,值越小,越先渲染,值大的游戏物体会覆盖值小的游戏物体。

可以在如上图看到,SpriteRenderer中的Order in layer值都为0,所以渲染顺序是随机,所以在不同电脑下所看到的实际展现效果不同

在实际制作中,层级总会随着人物的变动而变换,如果一个个去调会很麻烦,因此,就需要进行相关的设置如下图

其中default代表根据深度(z轴)来进行渲染 先将其改为Custom Axis(自定义)并将y轴的权重设置为1,z轴为0

中心点与轴心点

如上图所示,Luna已经在马车的轮子上面,但是Luna并没有被覆盖,而是要再往上平移一点才行,因此就要进行细节的设计

如图所示,从视觉上来看物体Luna此时应该被覆盖,但是却没有,因为此时的渲染层是按物体的中心点来渲染的

如图所示House的y轴比Luna高,所以未被覆盖,所以此时就要引出轴心点概念

轴心点(Pivot):

轴心点是可以自定义的特殊点,充当精灵的"锚点"精灵以此为支点进行旋转,坐标点位置则是指轴

心点的位置。

现在只需将horse的轴心点改为如下所示

Luna的层级改为如下所示

就可以实现覆盖效果

注意:

虽然修改了轴心点,但一定要将Order in layer数值改为相同大小,不然虽然改变了轴心位置,但渲染顺序不一致还是覆盖不了(Luna会被后渲染)

Unity中的预制体(Prefab)

预制体(Prefab)是Unity中的一种特殊资源。预制体就是一个或者一系列组件的集合体,可以使用预制体实例化克降体,后续可对克隆体属性进行统一修改。

创建预制体

预制体作为母体,他做的所有更改都会同步到其克隆体(子体)中

断开与预制体(母体)的连接,使其成为单独个体

制定世界的因果定律

Unity中的物理系统

刚体组件和碰撞器组件

刚体组件(Rigidbody):

使游戏物体能获得重力,接受外界的受力和扭力功能的组件,可以通过脚本或是物理引擎为游戏对象添加刚体组件。

现在为物体Luna添加一个刚体组件SS

如上图所示Rigidbody中的gravity scale属性值为1,在实际运行中会使Luna沿着y轴一直掉出地图,为了改变此状态只需将gravity scale属性值改为0即可

碰撞器组件

为了使游戏效果更真实,即当Luna碰到NPC时会被挡住过不去,因此就可以采用到碰撞器,现在为Luna和NPC装上碰撞器组件

但此时发现在Luna碰到NPC时,自身会发生沿着z轴的旋转,为了解决这一问题就需要用到下列操作固定物体在z轴上的角度

发生碰撞检测的条件

双方必须都有碰撞器,且一方有刚体,最好是运动的一方。因为Unity为了性能的优化,刚体长时间不发生碰撞检测会休眠。

解决震动问题

在实际运行中,可以发现,当两个碰撞器发生碰撞时,Luna会出现震动效果,这是因为在使用Rigid body2D和Boxcollider 2D这两个组件时,物体会根据unity中已经设置好的固有逻辑(脚本)去对物体进行移动, 而在实际中物体会先根据用户输入的指令进行移动,然后在由系统脚本进行碰撞检测,若是发生碰撞,系统逻辑会将物体移出,这将造成了用户输入指令和体统逻辑判定的一个滞后问题,从而发生震动

为了解决这个问题,我们可以修改代码,使其直接控制游戏物体的刚体,使物体进行移动

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaController : MonoBehaviour
{
    private Rigidbody2D rigidbody;
    // Start is called before the first frame update
    void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Debug.Log(horizontal);
        Vector2 position = transform.position;
        position.x = position.x + 3 * horizontal * Time.deltaTime;
        position.y = position.y + 3 * vertical * Time.deltaTime;
        //transform.position = position;
        rigidbody.MovePosition(position);
    }
}

实体化游戏世界地形并可与游戏人物交互

如下图可以发现,Luna是可以横穿整个地图,但在实际开发中Luna碰到山体是是应该停止的,所以就需要用到碰撞器

现在在Maps下创建一个空物体并添加多面体碰撞器(Polygon Collider 2D),并及逆行相应的编辑

创世的逻辑思维

访问级别与访问修饰符

public :公开的、公共的;

private: 私有的、只能在当前类的内部访问;

protected:受保护的、只能在当前类的内部以及子类中访问;

internal: 只能在当前项目中访问,在项目外无法访问。当时在本项目中 public 与 internal的访问权限是一样的。

protected internal:protected + internal 只能在本项目的该类及该类的子类中访问。

能够修饰类的访问修饰符只有两个:public 、internal;

在修饰类时如果没有修饰符,则默认为 internal。

class Program

{

//默认为 internal,仅能在本项目中进行访问

}

2、internal vs protected

在同一项目中,internal的访问权限比protected的访问权限要大;但是当不在同一个项目中时,protected可以在项目外的子类中被访问,而internal只能在它的本项目中进行访问。

3、访问权限不一致

子类的访问权限不能高于父类的访问权限,不然会暴露父类的成员。

该文引用 https://blog.csdn.net/qq_61709335/article/details/129767609

编写生命值

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaController : MonoBehaviour
{
    private Rigidbody2D rigidbody;
    public float movespeed = 0;
    public int maxHealth = 5;//最大生命值
    private int currentHealth;//Luna的当前最大生命值
    // Start is called before the first frame update
    void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
        currentHealth = maxHealth;
    }

    // Update is called once per frame
    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Debug.Log(horizontal);
        Vector2 position = transform.position;
        position.x = position.x + movespeed * horizontal * Time.deltaTime;
        position.y = position.y + movespeed * vertical * Time.deltaTime;
        /*transform.position = position;*/
        rigidbody.MovePosition(position);
    }
    public void ChangeHealth(int amount)
    {
        currentHealth = Mathf.Clamp(currentHealth + amount,0,maxHealth);
        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

函数、函数签名、返回值与重载

1、构成重载的条件:参数类型不同或者参数个数不同(不严谨的),与返回值无关。

2、函数重载:同名的两个函数的签名(参数的类型、顺序、个数)不能完全一致,否则就会异常。当两个函数同名但签名不一样时,两个都可以正常使用,即函数重载。函数重载是正确的也是常用的。和参数名无关。函数名拼写一样只是大小写不一样时,属于函数不同名,C#大小写敏感。

3、两个函数是否可以重名,跟函数的返回值类型无关。只要函数的签名,即参数的类型、顺序、个数不一样就行。只要参数的类型、顺序、个数不一致才能函数重名,函数返回值类型一致与否无关。

变量赋值的顺序

根剧代码的逐行编译规则,我们可知,以上面所示代码为例,对于maxhealth这一变量而言,若在start函数里对其进行复制,再在监视面板里对其赋值,其最终的覆盖顺序为1. public int maxHealth = 5 2. start 3. 监视面板,最终呈现出的结果为监视面板里所设置的值

触发器与发生触发检测的条件

  1. 至少一方需要有刚体(Rigidbody)或Kinematic刚体:如果两个物体都要参与触发事件,通常至少其中一个物体需要附有Rigidbody或Rigidbody2D(对于2D游戏)组件。如果使用的是静态物体(如地形),作为触发的一方则不必附加刚体。但是,如果双方都是静止的Kinematic刚体,则无法触发事件。

  2. 设置Is Trigger属性:在Inspector面板中,需要将至少一个碰撞器的"Is Trigger"选项勾选上,将其标记为触发器。这样,当这个标记为触发器的碰撞器与其他碰撞器重叠时,会触发特定的事件,而不是进行物理碰撞。

  3. 编写事件处理代码 :为了响应触发器事件,你需要在脚本中编写相应的事件处理函数。对于3D对象,常用的方法是OnTriggerEnter, OnTriggerStay, 和 OnTriggerExit;对于2D对象,则是OnTriggerEnter2D, OnTriggerStay2D, 和 OnTriggerExit2D。这些方法会在进入触发区域、停留于触发区域、以及离开触发区域时被调用。

  4. 添加 collider 并确保重叠:确保参与触发的物体都有合适的碰撞器(Collider)或触发器(Collider)组件,并且它们的形状和尺寸能够正确覆盖你希望触发事件发生的区域。

制作血瓶并实现回血功能

创建脚本之间的联系

为了实现Luna实现回血功能的效果,现在创建一个物体Potion并插入相应的C#脚本,在其中装上触发检测

现在通过脚本观测触发器碰到Luna的那个组件

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Potion : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        Debug.Log(collision);
    }
}

现在实现只要碰到血瓶血条+1的效果

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Potion : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        /* Debug.Log(collision);*/
        LunaController luna = collision.GetComponent<LunaController>();
        luna.ChangeHealth(1);

    }
}

现在实现Luna碰撞血瓶后,血瓶消失

属性的声明定义与使用

在前面的脚本编写过程中,在脚本potion中是可以直接访问脚本LunaController的各个属性值(因为是public),但是为了防止人为开挂行为的出现(比如说把maxHealth的值调到无上限),所以就需要把各个属性进行封装

封装(Encapsulation)

封装是将数据和行为包装在一起形成类的过程,将数据隐藏在类的内部,只能通过类的公共方法来访问和修改。C# 中,我们可以使用访问修饰符来实现封装,如 private、protected、internal 和 public 等。

私有成员只能在类的内部访问,受保护的成员可以在派生类中访问,内部成员可以在同一个程序集中访问,公共成员可以被所有代码访问。

通过封装,可以避免数据被误用或修改,增强了程序的安全性和可维护性。

示例如下:

调用封装的

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Potion : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        /* Debug.Log(collision);*/
        LunaController luna = collision.GetComponent<LunaController>();
        if(luna.Health < luna.MaxHealth)
        {
            luna.ChangeHealth(1);
        }
        Destroy(gameObject);
    }
}

赋予任务个人的意识与行为

添加动画制作组件并创建动画控制器

动画控制器(Animator Controller)

根据当前游戏对象的状态控制播放对应的游戏动画。也可以叫做动画状态机。

动画(Animation)

Unity中的动画资源,可以由动画控制器,代码去进行播放,也是一种组件。

现在为Luna赋予个人意识和动作

创建一个Animator组件

为了更好地管理动画制作组件,要创建一个Animators文件保存Animator Controller,以及为了更好地管理每个人物的动作,创建一个Animations文件,并在Animators文件下创建Animator Controller文件,文件名Luna

注:此时Luna有预制体,应对其预制体进行添加

动画状态机与状态机的过渡条件

现在进行动画的制作

打开动画制作窗口

打开动画状态机面板

将已有的图片拖入动画制作窗口,并设置保存位置,如此便可一创作出动画,并为每个动作加上关联

通过如上图所示,创建完人物动作以后,众所周知,在实际中每个人物的状态切换,都会有一个准备动作,如走------跑,但在2D游戏制作中是不需要这样的,我们需要的是跟控制机器一样地窗台切换,所以就需要调整过渡条件

通过上图可知,Unity3D对我们物体动画动作之间的切换进行了融合,现在我们需要去掉融合

但是此时发现我们的动画并没有播放完,只需要将Exit time值改为1即可

混合书地使用方式和意义

在游戏设计中,一个人物走路地方向有八个方位,这样创建动作之间地过渡条件会变得很多,形成蛛网,为了建设动画设计窗口的复杂程度,就引出了混合树的概念

混合树(Blend Tree)

用于允许通过按不同程度组合所有动画的各个部分来平滑混合多个动画各个运动参与形成最终效果的量使用混合参数进行控制,该参数只是与动画器控制器关联的数值动画参数之一。要使混合运动有意义,混合的运动必须具有相似性质和时间。混合树是动画器控制器中的特殊状态类型,

下图是已经别写好的部分以及相关图像运动

通过使人物运动

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaController : MonoBehaviour
{
    private Rigidbody2D rigidbody;
    public float movespeed = 30;
    private int maxHealth = 5;//最大生命值
    private int currentHealth;//Luna的当前最大生命值
    private Vector2 lookDirection;
    public int Health { get { return currentHealth; } }//返回currentHealth
    public int MaxHealth { get { return maxHealth; } }
    private Animator animator;
    // Start is called before the first frame update
    void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
        currentHealth = maxHealth;
        animator = GetComponentInChildren<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //监听玩家输入
        float horizontal = Input.GetAxis("Horizontal");//水平
        float vertical = Input.GetAxis("Vertical");//垂直
        Vector2 move = new Vector2(horizontal, vertical);
        animator.SetFloat("MoveValues", 0);
        /*Debug.Log(horizontal);*/
        //当前玩家输入某个轴向不为0
        if(!Mathf.Approximately(move.x,0)||!Mathf.Approximately(move.y,0))
        {//判断参数近似相等
            lookDirection.Set(move.x, move.y);
            lookDirection.Normalize();//单位化
            animator.SetFloat("MoveValues", 1);
        }
        //动画控制
        animator.SetFloat("LookX", lookDirection.x);
        animator.SetFloat("LookY", lookDirection.y);
        //物体控制 
        Vector2 position = transform.position;
        position.x = position.x + movespeed * horizontal * Time.deltaTime;
        position.y = position.y + movespeed * vertical * Time.deltaTime;
        /*transform.position = position;*/
        rigidbody.MovePosition(position);
    }
    public void ChangeHealth(int amount)
    {
        currentHealth = Mathf.Clamp(currentHealth + amount,0,maxHealth);
        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

从实际上来看,虽然实现了跑步效果,但发现人物移动速度有时候快有时候慢,所以就需要把物理相关的函数放在FixedUpdate()里

神之眼

Unity中的摄像机

导入包使摄像机跟随人物移动

下载好以后创建如下图所示相机类型

下载完后,Main Camera中会被自动插入相关组件如下图所示

两种不同的摄象模式(perspective,orthographic)

perspective(一般在3D游戏中使用)

orthographic(一般在2D游戏中使用)

为了使相机跟随Luna进行移动,只需如下步骤即可

但是此时发现当Luna快走到边界时,会出现如下情况

为了解决这种情况,需要给摄像机设置一个边界值

如下图所示,为MapW设置一个碰撞器,但一定要将其改为触发器,否则,人物会被弹出地图之外

现在就可以解决上面问题

使用代码生成游戏物体并销毁

实现在Luna吃掉物体后出现星光效果

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Potion : MonoBehaviour
{
    public GameObject effectGo;
    private void OnTriggerEnter2D(Collider2D collision)
    {
        /* Debug.Log(collision);*/
        LunaController luna = collision.GetComponent<LunaController>();
        if(luna.Health < luna.MaxHealth)
        {
            luna.ChangeHealth(1);
            Destroy(gameObject);
            Instantiate(effectGo, transform.position, Quaternion.identity);
        }
    }
}

但是现在发现,在Luna吃掉血瓶后出现的星光效果没有消失,现在要使其在播放完一定时间后消失

创建一个effect脚本使特效消失

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class effect : MonoBehaviour
{
    public float destoryTime;//设置销毁时间
    // Start is called before the first frame update
    void Start()
    {
        Destroy(gameObject, destoryTime);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

攀爬系统的实现

在动画状态机面板创建Boolean类型的变量Climb,并未动画Climb赋值,如下图所示

因为walk与idle,run动画衔接没有间隔,所以Climb不需要连接run,idle动画,只需连接walk即可

在如上图所示的藤蔓处创建攀爬效果再创建一个碰撞体并将其设置为触发器即可,如下图所示

现在就需要编写代码使Luna在此位置具有攀爬动作

在LunaContriller脚本下编写Climb函数对动画状态机窗口的Climb进行赋值

cs 复制代码
 public void Climb(bool start)
    {
        animator.SetBool("Climb", start);
    }

现在在Climb_area脚本下编写代码进行赋值

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Climb_area : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
       /* if(collision.name == "Luna")
        {

        }
        if(collision.tag == "Luna")
        {

        }*/
        if (collision.CompareTag("Luna"))
        {
            collision.GetComponent<LunaController>().Climb(true);
        }
    }
}

但此时发现Luna在进行攀爬动作时具有延迟动作,所以需要修改状态机窗口

神明与世界交互的接口

现在对用户界面的ui进行设计,在进行UI设计时,画布Canvas是必要创建的物体

先创建一个Luna的头像

制作Luna的血条和蓝条,创建Panel_Main文件存放头像,并且将画布属性调成屏幕属性调成屏幕自适应模式(Scale With Screen Size)

在做外如上图所示操作以后,需要进行一个血条填充操作,在血条位置上插入image

从上图可知道血条框是一个不规则图形,所有为了达到预想的呈现效果,进行如下图所示操作,在空物体中创建一个遮罩组件(mask)和image组件

现在需要实现血条的填充效果,只需要改变其缩放就行,但其轴心点需要放在最左侧

同理创造蓝条

给游戏世界带来战争

首先运用单例设计模式将游戏脚本进行管理

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Potion : MonoBehaviour
{
    public GameObject effectGo;
    private void OnTriggerEnter2D(Collider2D collision)
    {
        /* Debug.Log(collision);*/
        /*LunaController luna = collision.GetComponent<LunaController>();
        if(luna.Health < luna.MaxHealth)
        {
            luna.ChangeHealth(1);
            Destroy(gameObject);
            Instantiate(effectGo, transform.position, Quaternion.identity);
        }*/

        if (Gamemanager.Instance.Health < Gamemanager.Instance.MaxHealth)
        {
            Gamemanager.Instance.ChangeHealth(1);
            Destroy(gameObject);
            Instantiate(effectGo, transform.position, Quaternion.identity);
        }
    }
}
cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaController : MonoBehaviour
{
    private Rigidbody2D rigidbody;
    public float movespeed = 1;
    
    private Vector2 lookDirection;
    private Vector2 move;
    
    private Animator animator;
    private float moveScale; //移动状态,停,走,跑
    // Start is called before the first frame update
    void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
        
        animator = GetComponentInChildren<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //监听玩家输入
        float horizontal = Input.GetAxis("Horizontal");//水平
        float vertical = Input.GetAxis("Vertical");//垂直
        move = new Vector2(horizontal, vertical);
        animator.SetFloat("MoveValues", 0);
        /*Debug.Log(horizontal);*/
        //当前玩家输入某个轴向不为0
        if(!Mathf.Approximately(move.x,0)||!Mathf.Approximately(move.y,0))
        {//判断参数近似相等
            lookDirection.Set(move.x, move.y);
            lookDirection.Normalize();//单位化
            /*animator.SetFloat("MoveValues", 1);*/
        }
        //动画控制
        moveScale = move.magnitude;
        if(move.magnitude>0)
        {
            if(Input.GetKey(KeyCode.LeftShift))
            {
                moveScale = 1;
            }
            else
            {
                moveScale = 2;
            }
        }
        animator.SetFloat("MoveValues", moveScale);
        animator.SetFloat("LookX", lookDirection.x);
        animator.SetFloat("LookY", lookDirection.y);
       
    }
    private void FixedUpdate()
    {
        //物体控制 
        Vector2 position = transform.position;
        /*position.x = position.x + movespeed * horizontal * Time.deltaTime;
        position.y = position.y + movespeed * vertical * Time.deltaTime;*/
        /*transform.position = position;*/
        position = position + movespeed * move * Time.fixedDeltaTime;
        rigidbody.MovePosition(position);
    }
   
    public void Climb(bool start)
    {
        animator.SetBool("Climb", start);
    }
}
cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Gamemanager : MonoBehaviour
{
    public static Gamemanager Instance;
    private int maxHealth = 5;//最大生命值
    private int currentHealth;//Luna的当前最大生命值
    public int Health { get { return currentHealth; } }//返回currentHealth
    public int MaxHealth { get { return maxHealth; } }
    public void Awake()
    {
        Instance = this;
        currentHealth = 4;
    }
    public void ChangeHealth(int amount)
    {
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

现在将战斗场景拖拽到摄像机前(注意z轴)

现在将战斗状态下的Luna和master拖拽到战斗场景,并调整Flip使怪物正对着Luna(沿x轴翻转180度)

现在进行跳跃系统的实现

在动画状态机窗口创建Jump,其类型为Boolean,在进行如下图所示设置

创建Jump_area管理跳跃区域

并在其中文件安装box collider组件,将其调到相应位置,并设置跳跃点A,B

如图所示,下面场景的都被碰撞器组件所包围,Luna物体具有刚体组件,在进行跳跃时会发生碰撞检测,为了避免这种情况,就可以使Luna的Rigidbody组件暂时休眠(simulated属性打勾不休眠,不打勾休眠),这样就不会发生碰撞检测

如图所示

创建Jump_area脚本控制跳跃

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class Jump_area : MonoBehaviour
{
    public Transform jumpA;
    public Transform jumpB;
    // Start is called before the first frame update
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Luna")){
            LunaController luna = collision.transform.GetComponent<LunaController>();
            luna.Jump(true);
            //
            float disA = Vector3.Distance(luna.transform.position, jumpA.position);
            float disB = Vector3.Distance(luna.transform.position, jumpB.position);
            Transform targetTrans;
            /*if (disA > disB)
            {
                targetTrans = jumpA;
            }
            else
            {
                targetTrans = jumpB;
            }*/
            targetTrans = disA > disB ? jumpA : jumpB;
            luna.transform.DOMove(targetTrans.position, 0.5f).OnComplete(() => endJump(luna));
        }
    }
    public void endJump(LunaController luna)
    {
        luna.Jump(false);
    }
}
现在需要使跳跃效果更加真实

在此场景下,Luna组件由父物体和子物体一起控制,但在进行碰撞检测时,脚本控制父物体和子物体移动会显得矛盾,因此只需要做到父物体在移动(子物体会跟随着一起移动),为了使运动效果更加逼真,只需调整子物体在有、轴上的位移即可,如下面所示

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class Jump_area : MonoBehaviour
{
    public Transform jumpA;
    public Transform jumpB;
    // Start is called before the first frame update
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Luna")){
            LunaController luna = collision.transform.GetComponent<LunaController>();
            luna.Jump(true);
            //
            float disA = Vector3.Distance(luna.transform.position, jumpA.position);
            float disB = Vector3.Distance(luna.transform.position, jumpB.position);
            Transform targetTrans;
            /*if (disA > disB)
            {
                targetTrans = jumpA;
            }
            else
            {
                targetTrans = jumpB;
            }*/
            targetTrans = disA > disB ? jumpA : jumpB;
            //在移动完后执行endJump语句
            luna.transform.DOMove(targetTrans.position, 0.5f).OnComplete(() => endJump(luna));
            //获取子物体
            Transform lunaLocalTrans = luna.transform.GetChild(0);//表示第0个元素
            Sequence sequence = DOTween.Sequence();//表示动画的演示顺序
            sequence.Append(lunaLocalTrans.DOLocalMoveY(1.5f, 0.25f));
            sequence.Append(lunaLocalTrans.DOLocalMoveY(0.61f, 0.25f));
            sequence.Play();
        }
    }
    public void endJump(LunaController luna)
    {
        luna.Jump(false);
    }
}

为了使跳跃的速度由慢变快再变慢,只需如下图所示

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class Jump_area : MonoBehaviour
{
    public Transform jumpA;
    public Transform jumpB;
    // Start is called before the first frame update
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Luna")){
            LunaController luna = collision.transform.GetComponent<LunaController>();
            luna.Jump(true);
            //
            float disA = Vector3.Distance(luna.transform.position, jumpA.position);
            float disB = Vector3.Distance(luna.transform.position, jumpB.position);
            Transform targetTrans;
            /*if (disA > disB)
            {
                targetTrans = jumpA;
            }
            else
            {
                targetTrans = jumpB;
            }*/
            targetTrans = disA > disB ? jumpA : jumpB;
            //在移动完后执行endJump语句,表示速度以线性的方式呈现
            luna.transform.DOMove(targetTrans.position, 0.5f).SetEase(Ease.Linear).OnComplete(() => endJump(luna));
            //获取子物体
            Transform lunaLocalTrans = luna.transform.GetChild(0);//表示第0个元素
            Sequence sequence = DOTween.Sequence();//表示动画的演示顺序
            sequence.Append(lunaLocalTrans.DOLocalMoveY(1.5f, 0.25f).SetEase(Ease.InOutSine));//表示速度以正弦函数的方式呈现
            sequence.Append(lunaLocalTrans.DOLocalMoveY(0.61f, 0.25f).SetEase(Ease.InOutSine));
            sequence.Play();
        }
    }
    public void endJump(LunaController luna)
    {
        luna.Jump(false);
    }
}

敌人脚本的成员变量与组件的获取

现在将怪物拖拽到场景中,并创建EnemyController脚本控制其运动(巡逻)

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    public bool vertical;
    public float speed = 5;
    private Rigidbody2D rigidbody2d;
    //控制方向
    private int direction = 1;
    //控制方向改变时间间隔
    public float changeTime = 5;
    //计时器
    public float Timer;
    //动画控制器
    private Animator animator;

    // Start is called before the first frame update
    void Start()
    {
        rigidbody2d = GetComponent<Rigidbody2D>();
        Timer = changeTime;
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        Timer -= Time.fixedDeltaTime;
        if (Timer < 0)
        {
            Timer = 5;
            direction = -1 * direction;
        }
    }
    private void FixedUpdate()
    {
        Vector3 pos = rigidbody2d.position;
        if (vertical)//垂直方向移动
        {
            animator.SetFloat("LookY", direction * 1);
            animator.SetFloat("LookX", 0);
            pos.y = pos.y + speed * direction * Time.fixedDeltaTime;
        }
        else//水平轴向移动
        {
            animator.SetFloat("LookY", 0);
            animator.SetFloat("LookX", direction * 1);
            pos.x = pos.x + speed * direction * Time.fixedDeltaTime;
        }
        rigidbody2d.MovePosition(pos);
    }
}

触发战斗与战斗动画控制器

创建如下图所示,战斗场景

现在创建BattleController脚本来控制战斗脚本

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BattleController : MonoBehaviour
{
    public Animator LunaBattleAnimator;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void LunaAttack()
    {
        StartCoroutine(PerformAttackLogic());
    }
    IEnumerator PerformAttackLogic()
    {
        UI_Manager.Instance.ShowOrHideBattlePanel(false);
        LunaBattleAnimator.SetBool("MoveValues", true);
        LunaBattleAnimator.SetFloat("MoveValues", -1);
        yield return null;
    }
}

再对UImanager创建相关函数控制其的显现(ShowOrHideBattlePanel)

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Manager : MonoBehaviour
{
    public static UI_Manager Instance;
    public Image hpmask_image;
    public Image mpmask_image;
    public float originalSize;//血条原始宽度
    public GameObject BattlePanelGo;
    void Awake()
    {
        Instance = this;
        originalSize = hpmask_image.rectTransform.rect.width;
        SetHPValue(0.5f);
    }

    // Update is called once per frame
    public void SetHPValue(float fillPercent)
    {
        hpmask_image.rectTransform.SetSizeWithCurrentAnchors(
            RectTransform.Axis.Horizontal, fillPercent * originalSize);
    }
    public void SetMPValue(float fillPercent)
    {
        mpmask_image.rectTransform.SetSizeWithCurrentAnchors(
            RectTransform.Axis.Horizontal, fillPercent * originalSize);
    }
    public void ShowOrHideBattlePanel(bool show)
    {
        BattlePanelGo.SetActive(show);
    }
}

对Button_Attack按钮进行赋值

以下是效果展现

但我们很快发现Luna并没有返回,现在需要进行如下设置

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class BattleController : MonoBehaviour
{
    public Animator LunaBattleAnimator;
    public Transform LunaTrans;//获取Luna_Battle和Master的Transform组件
    public Transform MonsterTrans;
    private Vector3 monsterInitPos;//获取Luna_Battle、怪物初始位置
    private Vector3 LunaInitPos;
    public SpriteRenderer MonsterSr;//获取怪物的SpriteRenderer组件
    // Start is called before the first frame update
    void Start()
    {
        monsterInitPos = MonsterTrans.localPosition;//赋值为相对于父物体的位置
        LunaInitPos = LunaTrans.localPosition;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void LunaAttack()
    {
        StartCoroutine(PerformAttackLogic());
    }
    IEnumerator PerformAttackLogic()
    {
        UI_Manager.Instance.ShowOrHideBattlePanel(false);
        LunaBattleAnimator.SetBool("MoveState", true);
        LunaBattleAnimator.SetFloat("MoveValue", -1);
        //运用0.5秒时间运动到monster前面一米
        LunaTrans.DOLocalMove(monsterInitPos + new Vector3(1, 0, 0), 0.5f).OnComplete
            (
                () =>
                {
                    LunaBattleAnimator.SetBool("MoveState", false);
                    LunaBattleAnimator.SetFloat("MoveValue", 0);
                    LunaBattleAnimator.CrossFade("Attack", 0);//直接播放Attack动画,层级为0
                    MonsterSr.DOFade(0.3f, 0.2f).OnComplete(
                        () =>
                        {
                            JudgeMonsterHP(20);//透明度恢复正常
                        });
                    //是怪物在被攻击到后出现隐身效果,动画播放时长为0.2秒,透明度参数(0.3f)范围为(0-1.0f),表示透明程度,若不用此函数,其范围为0-255
                }
            );
        yield return new WaitForSeconds(1.667f);
        MonsterTrans.DOLocalMove(monsterInitPos, 0.3f);
        LunaBattleAnimator.SetBool("MoveState", true);
        LunaBattleAnimator.SetFloat("MoveValue", 1);
        LunaTrans.DOLocalMove(LunaInitPos, 0.5f).OnComplete(()=> { LunaBattleAnimator.SetBool("MoveState", false); });//使luna变成Idle状态
        yield return new WaitForSeconds(0.8f);
        StartCoroutine(MonsterAttack());

现在仿照上面做出Monster的攻击行为

cs 复制代码
IEnumerator MonsterAttack()
    {
        UI_Manager.Instance.ShowOrHideBattlePanel(false);
        UI_Manager.Instance.ShowOrHideBattlePanel(false);
        MonsterTrans.DOLocalMove(LunaInitPos - new Vector3(1, 0, 0), 0.5f).OnComplete
            (
                () =>
                {
                    LunaBattleAnimator.CrossFade("Hit", 0);//直接播放Hit动画,层级为0
                    LunaSr.DOFade(0.3f, 0.2f).OnComplete(
                        () =>
                        {
                            JudgeLunaHP(20);//透明度恢复正常
                        });
                    //是Luna在被攻击到后出现隐身效果,动画播放时长为0.2秒,透明度参数(0.3f)范围为(0-1.0f),表示透明程度,若不用此函数,其范围为0-255
                }
            );
        yield return new WaitForSeconds(1.333f);
        LunaBattleAnimator.SetBool("MoveState", true);
        LunaBattleAnimator.SetFloat("MoveValue", 1);
        MonsterTrans.DOLocalMove(monsterInitPos, 0.5f).OnComplete(() => { LunaBattleAnimator.SetBool("MoveState", false); });//使luna变成Idle状态
        yield return new WaitForSeconds(0.5f);
        UI_Manager.Instance.ShowOrHideBattlePanel(true);
    }

现在开始制作Luna的防御行为

cs 复制代码
 public void LunaDefend()
    {
        StartCoroutine(PerformDefendLogic());
    }
    IEnumerator PerformDefendLogic()
    {
        UI_Manager.Instance.ShowOrHideBattlePanel(false);
        LunaBattleAnimator.SetBool("Defend", true);
        MonsterTrans.DOLocalMove(LunaInitPos - new Vector3(1.5f, 0, 0), 0.5f);
        yield return new WaitForSeconds(0.5f);
        LunaBattleAnimator.SetFloat("MoveValue", -1);
        //运用0.5秒时间运动到monster前面一米
        MonsterTrans.DOLocalMove(LunaInitPos, 0.2f).OnComplete
            (
                () =>
                {
                    MonsterTrans.DOLocalMove(LunaInitPos - new Vector3(1.5f, 0, 0), 0.5f);
                    LunaTrans.DOLocalMove(LunaInitPos + new Vector3(1f, 0, 0), 0.2f).OnComplete(() =>
                    {
                        LunaTrans.DOLocalMove(LunaInitPos, 0.2f);
                    });
                }
            );
        yield return new WaitForSeconds(0.4f);
        MonsterTrans.DOLocalMove(monsterInitPos, 0.3f).OnComplete(()=> {
            UI_Manager.Instance.ShowOrHideBattlePanel(true);
            LunaBattleAnimator.SetBool("Defend", false);
        });
    }

蓝耗判定与蓝量血量的增加减少方法

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Gamemanager : MonoBehaviour
{
    public static Gamemanager Instance;
    public int LunaHP;//最大生命值
    public int LunacurrentHP;//Luna的当前最大生命值
    public int LunaMP;//最大蓝量
    public int LunaCurrentMP;//当前蓝量
    /*public int Health { get { return LunacurrentHP; } }//返回LunacurrentHP*/
    public int MaxHP { get { return LunaHP; } }
    public GameObject BattleGo;//战斗场景,游戏物体
    public void Awake()
    {
        Instance = this;
    }
    /*public void ChangeHealth(int amount)
    {
        LunacurrentHP = Mathf.Clamp(LunacurrentHP + amount, 0, LunaHP);
        Debug.Log(LunacurrentHP + "/" + LunaHP);
    }*/
    public void EnterOrExitBallte(bool enter = true)
    {
        BattleGo.SetActive(enter);
    }
    //回血
    public void AddOrDecreaseHP(int value)
    {
        LunacurrentHP += value;
        if (LunacurrentHP >= LunaHP)
        {
            LunacurrentHP = LunaHP;
        }
        if (LunaCurrentMP <= 0)
        {
            LunaCurrentMP = 0;
        }
        UI_Manager.Instance.SetHPValue((float)LunacurrentHP / LunaHP);
    }
    //回蓝
    public void AddOrDecreaseMP(int value)
    {
        LunaCurrentMP += value;
        if (LunaCurrentMP >= LunaHP)
        {
            LunaCurrentMP = LunaHP;
        }
        if (LunaCurrentMP <= 0)
        {
            LunaCurrentMP = 0;
        }
        UI_Manager.Instance.SetMPValue((float)LunaCurrentMP / LunaMP);
    }
    //是否能使用技能
    public bool CanUseMP(int value)
    {
        return LunaCurrentMP >= value;
    }
}

对话系统的UI与对话信息结构体

创建NPCDialog脚本控制Luna与NPC的对话(DialogInfo)

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NpcDialog : MonoBehaviour
{
    private List<DialogInfo[]> dialogInfoList;
    private int contentIndex;//控制每一段的每一条的索引
    public Animator animator;
    // Start is called before the first frame update
    void Start()
    {
        dialogInfoList = new List<DialogInfo[]>()
        {
            new DialogInfo[]{
                new DialogInfo() {name="Luna",content="(,,・∀・)ノ゛hello,我是LuNa,你可以用上下左右控制我移动,空格键与NPC进行对话,战斗中需要简单点击按钮执行相应行为" }
            },
            new DialogInfo[]{
                new DialogInfo() {name="Nala",content="好久不见了,小猫咪(*ΦωΦ*),Luna~" },
                new DialogInfo() {name="Luna",content="好久不见,Nala,你还是那么有活力,哈哈" },
                new DialogInfo() {name="Nala",content="还好吧~" },
                new DialogInfo() {name="Nala",content="我的狗一直在叫,但是我这会忙不过来,你能帮我安抚一下它吗?" },
                new DialogInfo() {name="Luna",content="啊?" },
                new DialogInfo() {name="Nala",content="(,,´•ω•)ノ(´っω•`。)摸摸他就行,摸摸说呦西呦西,真是个好孩子呐" },
                new DialogInfo() {name="Nala",content="别看他叫的这么凶,其实他就是想引起别人的注意" },
                new DialogInfo() {name="Luna",content="可是。。。。" },
                new DialogInfo() {name="Luna",content="我是猫女郎啊" },
                new DialogInfo() {name="Nala",content="安心啦,不会咬你哒,去吧去吧~" },
            },
            new DialogInfo[]{
                new DialogInfo() {name="Nala",content="他还在叫呢" }
            },
            //3
            new DialogInfo[]{
                new DialogInfo() {name="Nala",content="感谢你呐,Luna,你还是那么可靠!" },
                new DialogInfo() {name="Nala",content="我想请你帮个忙好吗" },
                new DialogInfo() {name="Nala",content="说起来这事怪我。。。" },
                new DialogInfo() {name="Nala",content="今天我睡过头了,出门比较匆忙" },
                new DialogInfo() {name="Nala",content="然后装蜡烛的袋子口子没封好!o(╥﹏╥)o" },
                new DialogInfo() {name="Nala",content="结果就。。。蜡烛基本丢完了" },
                new DialogInfo() {name="Luna",content="你还是老样子,哈哈。。" },
                new DialogInfo() {name="Nala",content="所以,所以喽,你帮帮忙,帮我把蜡烛找回来" },
                new DialogInfo() {name="Nala",content="如果你能帮我找回全部的5根蜡烛,我就送你一把神器" },
                new DialogInfo() {name="Luna",content="神器?(¯﹃¯)" },
                new DialogInfo() {name="Nala",content="是的,我感觉很适合你,加油呐~" },
            },
            new DialogInfo[]{
                new DialogInfo() {name="Nala",content="你还没帮我收集到所有的蜡烛,宝~" },
            },
            //5
            new DialogInfo[]{
                new DialogInfo() {name="Nala",content="可靠啊!竟然一个不差的全收集回来了" },
                new DialogInfo() {name="Luna",content="你知道多累吗?" },
                new DialogInfo() {name="Luna",content="你到处跑,真的很难收集" },
                new DialogInfo() {name="Nala",content="辛苦啦辛苦啦" },
                new DialogInfo() {name="Nala",content="这是给你的奖励" },
                new DialogInfo() {name="Nala",content="蓝纹火锤,传说中的神器" },
                new DialogInfo() {name="Nala",content="应该挺适合你的" },
                new DialogInfo() {name="Luna",content="~~获得蓝纹火锤~~(遇到怪物可触发战斗)" },
                new DialogInfo() {name="Luna",content="哇,谢谢你!Thanks♪(・ω・)ノ" },
                new DialogInfo() {name="Nala",content="嘿嘿(*^▽^*),咱们的关系不用客气" },
                new DialogInfo() {name="Nala",content="正好,最近山里出现了一堆怪物,你也算为民除害,帮忙清理5只怪物" },
                new DialogInfo() {name="Luna",content="啊?" },
                new DialogInfo() {name="Luna",content="这才是你的真实目的吧?!" },
                new DialogInfo() {name="Nala",content="拜托拜托啦,否则真的很不方便我卖东西" },
                new DialogInfo() {name="Luna",content="无语中。。。" },
                new DialogInfo() {name="Nala",content="求求你了,啵啵~" },
                new DialogInfo() {name="Luna",content="哎,行吧,谁让你大呢~" },
                new DialogInfo() {name="Nala",content="嘻嘻,那辛苦宝子啦" }
            },
            new DialogInfo[]{
                new DialogInfo() {name="Nala",content="宝,你还没清理干净呢,这样我不方便嘛~" },
            },
            new DialogInfo[]{
                new DialogInfo() {name="Nala",content="真棒,luna,周围的居民都会十分感谢你的,有机会来我家喝一杯吧~" },
                new DialogInfo() {name="Luna",content="我觉得可行,哈哈~" }
            },
            new DialogInfo[]{
                new DialogInfo() {name="Nala",content="改天再见喽~" },
            }
        };
        Gamemanager.Instance.dialogInfoIndex = 0;
        contentIndex = 1;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void DisplayDialog()
    {
        if (Gamemanager.Instance.dialogInfoIndex > 7)
        {
            return;
        }
        if (contentIndex >= dialogInfoList[Gamemanager.Instance.dialogInfoIndex].Length)
        {
            //当前对话结束,可以控制Luna
            contentIndex = 0;
            UI_Manager.Instance.ShowDialog();
            Gamemanager.Instance.canControlLuna = true;
        }
        else
        {
            DialogInfo dialogInfo = dialogInfoList[Gamemanager.Instance.dialogInfoIndex][contentIndex];
            UI_Manager.Instance.ShowDialog(dialogInfo.content, dialogInfo.name);
            contentIndex++;
            animator.SetTrigger("Talk");
        }
    }
}
public struct DialogInfo
{
    public string name;
    public string content;
}

创建Talk函数控制Luna在与Nala对话时的动作(在LunaController下进行设置)

cs 复制代码
public void Talk()
    {
        //设置触发器检测的半径
        Collider2D collider = Physics2D.OverlapCircle(rigidbody.position,
            0.5f, LayerMask.GetMask("NPC"));
        if (collider != null)
        {
            if (collider.name == "Nala")
            {
                Gamemanager.Instance.canControlLuna = false;
                collider.GetComponent<NpcDialog>().DisplayDialog();
            }
            else if (collider.name == "Dog"
                && !Gamemanager.Instance.hasPetTheDog &&
                Gamemanager.Instance.dialogInfoIndex == 2)
            {
                PetTheDog();
                Gamemanager.Instance.canControlLuna = false;
                collider.GetComponent<Dog>().BeHappy();
            }
        }
    }

其中Physics2D.OverlapCircle控制检测半径,LayerMask.GetMask("NPC")表示只有在触碰到层级为NPC的游戏物体才会进行触发

层级设置如下

并且在LunaController中的update函数中编写如下脚本

cs 复制代码
void Update()
    {
        //监听玩家输入
        float horizontal = Input.GetAxis("Horizontal");//水平
        float vertical = Input.GetAxis("Vertical");//垂直
        move = new Vector2(horizontal, vertical);
        animator.SetFloat("MoveValues", 0);
        /*Debug.Log(horizontal);*/
        //当前玩家输入某个轴向不为0
        if(!Mathf.Approximately(move.x,0)||!Mathf.Approximately(move.y,0))
        {//判断参数近似相等
            lookDirection.Set(move.x, move.y);
            lookDirection.Normalize();//单位化
            /*animator.SetFloat("MoveValues", 1);*/
        }
        //动画控制
        moveScale = move.magnitude;
        if(move.magnitude>0)
        {
            if(Input.GetKey(KeyCode.LeftShift))
            {
                moveScale = 1;
                movespeed = initspeed/2;
            }
            else
            {
                moveScale = 2;
                movespeed = initspeed;

            }
        }
        animator.SetFloat("MoveValues", moveScale);
        animator.SetFloat("LookX", lookDirection.x);
        animator.SetFloat("LookY", lookDirection.y);
        //按下空格可以进行交互
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Talk();
        }

    }

实现对话之间的跳转

cs 复制代码
public void DisplayDialog()
    {
        if (Gamemanager.Instance.dialogInfoIndex > 7)
        {
            return;
        }
        if (contentIndex >= dialogInfoList[Gamemanager.Instance.dialogInfoIndex].Length)
        {
            if (Gamemanager.Instance.dialogInfoIndex == 2 &&
                !Gamemanager.Instance.hasPetTheDog)
            {

            }
            else if (Gamemanager.Instance.dialogInfoIndex == 4 &&
                Gamemanager.Instance.candleNum < 5)
            {

            }
            else if (Gamemanager.Instance.dialogInfoIndex == 6 &&
                Gamemanager.Instance.killNum < 5)
            {

            }
            else
            {
                Gamemanager.Instance.dialogInfoIndex++;
            }
            //在完成前面的任务以后进行清怪任务
            if (Gamemanager.Instance.dialogInfoIndex == 6)
            {
                Gamemanager.Instance.ShowMonsters();
            }
            contentIndex = 0;
            UI_Manager.Instance.ShowDialog();
            Gamemanager.Instance.canControlLuna = true;
        }

        else
        {
            DialogInfo dialogInfo = dialogInfoList[Gamemanager.Instance.dialogInfoIndex][contentIndex];
            UI_Manager.Instance.ShowDialog(dialogInfo.content, dialogInfo.name);
            contentIndex++;
            animator.SetTrigger("Talk");//使只有Luna在触碰到Nala并且有对话时,Nala才会进行Talk动画

        }
    }

任务一:安抚狗狗

在Dog物体上设置Dog脚本

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Dog : MonoBehaviour
{
    private Animator animator;
    public GameObject starEffect;
    public AudioClip petSound;

    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();
    }

    public void BeHappy()
    {
        animator.CrossFade("Comfortable", 0);
        Gamemanager.Instance.hasPetTheDog = true;
        Gamemanager.Instance.SetContentIndex();
        Destroy(starEffect);
        Gamemanager.Instance.PlaySound(petSound);
        Invoke("CanControlLuna", 2.5f);
    }

    private void CanControlLuna()
    {
        Gamemanager.Instance.canControlLuna = true;
    }
}

任务二:收集蜡烛以及清楚怪物任务

在Candle脚本中进行如下设置

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Candle : MonoBehaviour
{
    public GameObject effectGo;
    private void OnTriggerEnter2D(Collider2D collision)
    {
        Gamemanager.Instance.candleNum++;
        /* Debug.Log(collision);*/
        Gamemanager.Instance.AddOrDecreaseHP(1);
        Destroy(gameObject);
        Instantiate(effectGo, transform.position, Quaternion.identity);
        /*LunaController luna = collision.GetComponent<LunaController>();
        Gamemanager.Instance.ChangeHealth(1);*/
        if (Gamemanager.Instance.candleNum >= 5)
        {
            Gamemanager.Instance.SetContentIndex();
        }
        Destroy(gameObject);
        
    }
}

对函数进行如下改动

Gamemanager:

cs 复制代码
public void EnterOrExitBallte(bool enter = true,int addkillNum = 0)
    {
        UI_Manager.Instance.ShowOrHideBattlePanel(enter);
        BattleGo.SetActive(enter);
        enterBattle = enter;
        if (!enter)//非战斗状态
        {
            killNum += addkillNum;
            if (addkillNum > 0)
            {
                DestoryMonster();
            }
            MonsterCurrentHP = 50;
            if (LunaCurrentMP <= 0)
            {
                LunacurrentHP = 100;
                LunaCurrentMP = 0;
                battleMonsterGo.transform.position += new Vector3(0, 2, 0);
            }
        }
    }

BattleController

cs 复制代码
private void JudgeMonsterHP(int value)
    {
        if (Gamemanager.Instance.AddOrDecreaseMonsterHP(value) <= 0){
            Gamemanager.Instance.EnterOrExitBallte(false , 1);
        }
        else
        {
            MonsterSr.DOFade(1, 0.4f);
        }
    }

现在插入背景音乐

Gamemanager

cs 复制代码
public AudioSource audioSource;//音乐组件
public AudioClip normalClip;
public AudioClip battleClip;
public void PlayMusic(AudioClip audioClip)
    {
        if (audioSource.clip != audioClip)
        {
            audioSource.clip = audioClip;
            audioSource.Play();
        }
    }

    public void PlaySound(AudioClip audioClip)
    {
        if (audioClip)
        {
            audioSource.PlayOneShot(audioClip);
        }
    }
cs 复制代码
public void EnterOrExitBallte(bool enter = true,int addkillNum = 0)
    {
        UI_Manager.Instance.ShowOrHideBattlePanel(enter);
        BattleGo.SetActive(enter);
        enterBattle = enter;
        if (!enter)//非战斗状态
        {
            killNum += addkillNum;
            if (addkillNum > 0)
            {
                DestoryMonster();
            }
            MonsterCurrentHP = 50;
            PlayMusic(normalClip);
            if (LunaCurrentMP <= 0)
            {
                LunacurrentHP = 100;
                LunaCurrentMP = 0;
                battleMonsterGo.transform.position += new Vector3(0, 2, 0);
            }
        }
        else//战斗状态
        {
            PlayMusic(battleClip); 
        }
    }
相关推荐
charon877819 分钟前
UE ARPG | 虚幻引擎战斗系统
游戏引擎
小春熙子1 小时前
Unity图形学之Shader结构
unity·游戏引擎·技术美术
Sitarrrr4 小时前
【Unity】ScriptableObject的应用和3D物体跟随鼠标移动:鼠标放置物体在场景中
3d·unity
极梦网络无忧4 小时前
Unity中IK动画与布偶死亡动画切换的实现
unity·游戏引擎·lucene
逐·風12 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
_oP_i13 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
代码盗圣17 小时前
GODOT 4 不用scons编译cpp扩展的方法
游戏引擎·godot
Leoysq1 天前
【UGUI】实现点击注册按钮跳转游戏场景
游戏·unity·游戏引擎·ugui
PandaQue1 天前
《潜行者2切尔诺贝利之心》游戏引擎介绍
游戏引擎
_oP_i1 天前
unity中 骨骼、纹理和材质关系
unity·游戏引擎·材质