2D游戏案例:Ruby‘s Adventure

程序文件打包:

链接:https://pan.baidu.com/s/1wyV_4k45eXhzrrq0_CVfQw 
提取码:olhi

目录

第一步:导入素材

第二步:编写第一个脚本

第三步:绘制游戏场景 

第四步:实现相机跟随

第五步:采集生命道具

第六步:伤害检测

第七步:引入敌人元素

第八步:添加物品动画

第九步:添加敌人动画

第十步:添加玩家动画

第十一步:发射子弹

第十二步:增添敌人特效

第十三步:增添物品特效

第十四步:显示血条

第十五步:增加场景音乐

第十六步:创建NPC

第十七步:创建子弹补给

第十八步:打包安装


正片开始。

第一步:导入素材

(1)目的:获取官方Ruby’s Adventure游戏资源。

(2)方法:点击上方菜单栏:window -> Asset Store -> Search:Ruby -> Import

(3)注意:要把所有资源一次性全部导入。

第二步:编写第一个脚本

(1)目的:测试游戏角色是否能够移动

(2)方法:

        1、在素材文件夹(Art->Spirits->Characters)中拖拽一张Ruby图片到左侧的任务区,默认为角色对象。

        2、在Asset文件夹中新建Scripts脚本文件夹。

        3、在Scripts文件夹中新建C#脚本,命名为PlayerController(程序员可自行更改)。

        4、编写脚本:是角色向右移动。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度
    
    // Start is called before the first frame update
    void Start()
    {
            
    }

    // Update is called once per frame
    void Update()
    {
        transform.Translate(transform.right * speed * Time.deltaTime);//角色向右移动,Time.deltaTime-是计算机渲染一
    }
}

        5、将写好的脚本挂给角色属性栏。点击播放,就能看到Ruby开始移动了。

示例:

6、测试移动脚本-1 通过awsd(或四个方向键)控制角色移动。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度
    
    // Start is called before the first frame update
    void Start()
    {
            
    }

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0

        Vector2 position = transform.position;//定义角色位置向量
        position.x += moveX * speed * Time.deltaTime;
        position.y += moveY * speed * Time.deltaTime;
        transform.position = position;//将位置信息传输给角色
    }
}

第三步:绘制游戏场景 
 

(1)目的:给游戏画面增添地图场景。

(2)方法:

        1、鼠标在左侧任务区点击右键,选择创建 2D Object。

        2、任务栏中出现 Grid文件夹,里面有一个Tilemap文件。Tilemap就是帮助我们实现画图的工具。

        3、点击资源文件夹 Art->Spirits->Environment,找到想要绘制的场景图片。

        4、创建第一张贴图:先在Asset文件夹下创建一个新的文件夹叫Tiles来存放图片信息。点开Tiles文件,右键,点击Tile,创建第一块贴图,起名叫FirstTile。此时下面的Tiles文件夹中就会出一张瓦片贴图。

        5、裁剪图片:将一张图分解成3*3的图片。(1) 找到你要裁剪的图片,观察右侧图片的参数信息,找到Sprite Mode 将single(默认)调成Mutiple。再点击下方的Sprite Editor (精灵编辑)。出现切割界面后点击左上角Slice->Type->Grid by cell Count(按数量切割) 更改参数R (row):3C(clumn):3,切好后点击右上角 Apply 应用完成,关闭界面。切割好一个后,其余的地面图片也要进行切割,所以选择头图片按住shift键再选择尾图片修改Spite Mode为Multiple,点击下方Aply,这样就完成了批量更改精灵模式操作,接下来在进行依次切割就可以了。

裁剪完的示意图:

        6、创建贴图调色板:点击上方菜单栏:Window->2D->Tile Pallet。这时调色板就已经出现了,点击新建,重新命名Game Pallet。保存在Tiles文件夹下,这样一个新的调色板就建好了。然后将想要绘制的图片直接拖入调色板中,(这里需要注意的是,在选择文件夹时我们的瓦片信息都是存放在Tiles文件夹下的,)然后选择图块用调色板上方的笔刷功能就可以进行绘制了。

调色板准备就绪:

        

7、使贴图占满整格:在刚开始绘制的时候,一般都会遇到这种现象,就是,图片的大小与网格大小不匹配,格子与格子之间有空隙。这是就需要我们手动计算调整一下图片尺寸了。点击原图,查看属性右侧上方,Pixels Per Unit :100(一个格子占长宽是100个像素)在看一下右侧下方原图属性:192*192,每一块边长是192个像素,分成9块后,每一块边长就是64,所64<100,格子无法占满。也就是说,只要我们把每个格子100个像素改成每个格子64个像素就能实现图片填满的效果了,最后不要忘了点击aply。

        8、给物体增加碰撞和物理刚体:首先选择一个对象,点击右侧栏下Add Component(增添组件),搜索Box Collider 2D ,再点击 Edit Collider给物体会发生碰撞的位置做一个选区即可。

        9、给角色增加物理刚体:点击角色对象,在右侧信息栏中点击Add Component,搜索Ragidbody 2D, 点击后角色就增加了物理属性,因为我们做的是2D游戏,所以添加了物理缸体后要将重力加速度的勾选去掉,否则角色就会一直下落。

        10、丰富我们的场景:一个物体一个物体的绘制相当麻烦,为了方便我们实现克隆式的批量操作,我们先在Asset文件夹中建立一个新的文件夹Prefab(预制体)。先拖拽出一个物体设置好它的属性后将其拖入Prefab文件夹,然后在将它从Prefab文件中拖出,发现字体都是蓝色,那就说明预制体设置成功,每一个从Prefab文件夹里拖出的复制品都有相同的属性。(友情提示:在设置物体属性时最好是调整好属性之后在进行拖拽进Prefab中去,这真的挺重要的我觉得。。。)

        11、利用图层排序实现物体遮挡效果:Edit->Project Settings->Graphics      再点击Transparency Sort Mode 选择Custom Axis(自定义轴) 将参数改成 X :0   Y:1    Z:0   改好之后点击Ctrl + s保存设置(这个过程必须是非演示时才能生效),回到主界面,点击角色和物体图层,必须保证角色和物体图层为同一层级时才能实现遮挡。

        12、解决角色与物体碰撞发生的抖动与旋转问题。旋转问题非常好解决,首先判断发生旋转的原因,因为角色挂上了物理刚体组件,所以碰撞会产生弹力因为受力不均就会发生旋转,那么解决的办法是,点击角色查看属性,找到刚体属性,再限制绕z轴旋转即可。解决角色抖动,只需要把脚本中的角色位置传输改成刚体即可。

        13、编写角色脚本(引入物体刚体组件):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度

    Rigidbody2D rbody;//刚体组件

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

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0
        //================移动================================================
        Vector2 position = rbody.position;//定义角色位置向量
        position.x += moveX * speed * Time.deltaTime;
        position.y += moveY * speed * Time.deltaTime;
        rbody.MovePosition(position);//将位置信息传输给角色
    }
}

(3)注意:如果任务列表中东西太多,可以新建一个New Empty用来专门存放这些物体。

      

第四步:实现相机跟随

(1)目的:使相机跟随角色移动。

(2)方法:

        1、下载相机组件:(否则的话要自己编写代码)

        2、点击上方菜单栏:Window->Package Manner->all package->cinemachine->install

        3、组件下载完之后,上方菜单栏会出现Cinemachine(本教程使用2.2.9版本),点击后选择cinema 2D创建一个2D相机。

        4、再将Ruby拖拽到相机属性的follow一栏中,就能实现玩家跟随了。视角大小可以在属性栏中修改。

        5、限制角色在地图上的活动范围:点击Tilemap,属性中增加 Tilemap Collider 2D,现在角色无法移动,因为周围都是绿格子(碰撞盒)。然后点击Tile文件夹,将所有不包含水面的图片选择中,将右侧信息栏中的collider type改成None,这样就实现了除水面随便走的情况。

        6、为了节省性能,通常我们会选择碰撞格的合并:在Tilemap里添加组件composite 组件,之后会出现一个刚体属性,将刚体属性的重力加速度归零,在把composite勾选,这样碰撞盒就能合并了,减少性能消耗。如果你的地图会抖动,那么就将xyz轴的限制框选都勾上或者将刚体设置成 Static 静态。

        7、设置相机边界:点击左侧任务栏中的相机对象,观察右侧属性栏下方有一个Add Extension选择CinemachineConfiner,点击添加。然后在左侧任务栏右键Create Empty,命名为CameraConfiner,右侧新增属性Add component,搜索Polygon Collider 2D,拖点编辑范围即可,最后将编辑好的组件,拖入到相机属性的Extensions的选窗里即可(Bounding Shape 2D)。设置完边界后,不出意外,你的角色是看不到的,因为他被相机刚体挤到了外面,这时把CameraConfiner属性栏中的isTrigger勾选上即可。

 相机边界绿线:

第五步:采集生命道具

(1)目的:增加草莓物体,恢复角色血量。        

(2)方法:

        1、在素材中选择草莓,拖拽进左边任务栏中,点击草莓原图,更改草莓属性大小,调到合适位置即可。然后增加碰撞属性(box collider 2D),为了让角色能穿过草莓,在草莓的box collider 属性栏里勾选is trigger,这样角色就能穿过草莓了。

        2、编写拾取草莓脚本:用触发碰撞函数确定角色和草莓相撞。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 草莓被玩家碰撞时检测的相关
/// </summary>
public class Collectible : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    /// <summary>
    /// 碰撞检测相关
    /// </summary>
    void OnTriggerEnter2D(Collider2D other)
    {
        PlayerController pc = other.GetComponent<PlayerController>();
        if(pc != null)
        {
            Debug.Log("玩家碰到了草莓!");
        }
    }
}

        3、玩家脚本的更改:因为在血量不足的情况下才会拾取草莓,所以 Collectible这个类中药访问到玩家的血量。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度

    private int maxHealth = 5;//最大生命值

    private int currentHealth;//当前生命值

    //访问私有变量的两个函数
    public int MyMaxHealth { get { return maxHealth; } }

    public int MyCurrentHealth { get { return currentHealth; } }

    Rigidbody2D rbody;//刚体组件

    // Start is called before the first frame update
    void Start()
    {
        currentHealth = 2;//设置初始血量为2
        rbody = GetComponent<Rigidbody2D>();        
    }

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0
        //================移动================================================
        Vector2 position = rbody.position;//定义角色位置向量
        position.x += moveX * speed * Time.deltaTime;
        position.y += moveY * speed * Time.deltaTime;
        rbody.MovePosition(position);//将位置信息传输给角色
    }
    /// <summary>
    /// 改变玩家的生命值
    /// </summary>
    public void ChangeHealth(int amount)
    {
        Debug.Log(currentHealth + "/" + maxHealth);
        //把玩家的生命值约束在0到最大值之间
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);

        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

        4、更改草莓脚本:当检测到草莓被拾取时,销毁草莓对象。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 草莓被玩家碰撞时检测的相关
/// </summary>
public class Collectible : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    /// <summary>
    /// 碰撞检测相关
    /// </summary>
    void OnTriggerEnter2D(Collider2D other)
    {
        PlayerController pc = other.GetComponent<PlayerController>();
        if(pc != null)
        {
           if(pc.MyCurrentHealth < pc.MyMaxHealth)
            {
                pc.ChangeHealth(1);//血量加一
                Destroy(this.gameObject);//加完血后草莓消失
            }
        }
    }
}

        5、接下来将草莓设为预制体,复制多个。

碰撞检测成功:

第六步:伤害检测

        (1)目的:设置陷阱,是角色减血,增加游戏困难度。

        (2)方法:

                1、找到素材里的陷阱图片,拖拽到任务栏,添加碰撞属性,勾选is Trigger。

                2、给陷阱添加脚本:如果玩家与陷阱发生碰撞,玩家生命值减1.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 伤害陷阱相关
/// </summary>
public class DamageArea : MonoBehaviour
{
    void OnTriggerStay2D(Collider2D other)
    {
        PlayerController pc = other.GetComponent<PlayerController>();
        if (pc != null)
        {
            pc.ChangeHealth(-1);
        }
    }
}

        3、更改玩家脚本:玩家踩到陷阱受到一次伤害,玩家持续踩陷阱,每隔两秒受到一次伤害。2秒成为无敌时间。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度

    private int maxHealth = 5;//最大生命值

    private int currentHealth;//当前生命值

    //访问私有变量的两个函数
    public int MyMaxHealth { get { return maxHealth; } }

    public int MyCurrentHealth { get { return currentHealth; } }

    private float invincibleTime = 2f;//无敌时间两秒

    private float invicibleTimer;//无敌计时器

    private bool isInvincible;//是否处于无敌状态

    Rigidbody2D rbody;//刚体组件

    // Start is called before the first frame update
    void Start()
    {
        currentHealth = 2;//设置初始血量为2
        invicibleTimer = 0;
        rbody = GetComponent<Rigidbody2D>();        
    }

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0
        //================移动================================================
        Vector2 position = rbody.position;//定义角色位置向量
        position.x += moveX * speed * Time.deltaTime;
        position.y += moveY * speed * Time.deltaTime;
        rbody.MovePosition(position);//将位置信息传输给角色
        //==================无敌计时==============================================
        if (isInvincible)
        {
            invicibleTimer -= Time.deltaTime;
            if(invicibleTimer < 0)
            {
                isInvincible = false;//倒计时结束后取消无敌状态
            }
        }
    }
    /// <summary>
    /// 改变玩家的生命值
    /// </summary>
    public void ChangeHealth(int amount)
    {
        if(amount < 0)
        {
            if (isInvincible == true)
                return;
            isInvincible = true;
            invicibleTimer = invincibleTime;
        }

        Debug.Log(currentHealth + "/" + maxHealth);
        //把玩家的生命值约束在0到最大值之间
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);

        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

*注意:做好之后,会发现一个bug,就是当玩家站在陷阱中不动时,血量不会减少。改正:左侧任务栏点击玩家,查看属性在刚体栏中,找到Sleeping Mode,并选择Never Sleep,这样就能解决上述问题了。

        4、陷阱区域的放大缩小:点击陷阱对象,右侧属性栏Draw Mode选择Tiled,点击下方文件夹中的图片将属性Mesh Type改成Full Rect。点击应用即可。缩放的时候选择工具rect tool 拉扯图形边框才能缩放,其他方式不行。

第七步:引入敌人元素

(1)目的:增强游戏趣味性。

(2)方法:

        1、将敌人图片拖入任务栏,命名为Robot,给敌人属性加入Rigidbody 2D(物理刚体) 和box collider 2D(碰撞盒)。

        2、加入机器人移动脚本 : 机器人向上或向右移动脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 敌人控制相关
/// </summary>
public class EnemyController : MonoBehaviour
{
    public float speed = 3;//移动速度

    public bool isVertical;//是否垂直方向移动

    private Vector2 moveDirection;//移动方向

    private Rigidbody2D rbody;//引入刚体变量

    // Start is called before the first frame update
    void Start()
    {
        rbody = GetComponent<Rigidbody2D>();

        moveDirection = isVertical ? Vector2.up : Vector2.right;//如果垂直移动,方向朝上,否则朝右
    }

    // Update is called once per frame
    void Update()
    {
        Vector2 position = rbody.position;
        position.x += moveDirection.x * speed * Time.deltaTime;
        position.y += moveDirection.y * speed * Time.deltaTime;
        rbody.MovePosition(position);
    }
}

*注意:在引用这个脚本后,Robot脚本得属性栏中有一个可勾选的isVertical,勾选则向上,不勾则向右。

        3、控制敌人移动的脚本2:使敌人改变方向来回巡逻

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 敌人控制相关
/// </summary>
public class EnemyController : MonoBehaviour
{
    public float speed = 3;//移动速度

    public float changeDirectionTime = 2f;//改变方向的时间

    private float changeTimer;//改变方向的计时器

    public bool isVertical;//是否垂直方向移动

    private Vector2 moveDirection;//移动方向

    private Rigidbody2D rbody;//引入刚体变量

    // Start is called before the first frame update
    void Start()
    {
        rbody = GetComponent<Rigidbody2D>();

        moveDirection = isVertical ? Vector2.up : Vector2.right;//如果垂直移动,方向朝上,否则朝右

        changeTimer = changeDirectionTime;
    }

    // Update is called once per frame
    void Update()
    {
        changeTimer -= Time.deltaTime;
        if(changeTimer < 0)
        {
            moveDirection *= -1;
            changeTimer = changeDirectionTime;
        }
        Vector2 position = rbody.position;
        position.x += moveDirection.x * speed * Time.deltaTime;
        position.y += moveDirection.y * speed * Time.deltaTime;
        rbody.MovePosition(position);
    }
}

        4、给机器人增加碰撞脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 敌人控制相关
/// </summary>
public class EnemyController : MonoBehaviour
{
    public float speed = 3;//移动速度

    public float changeDirectionTime = 2f;//改变方向的时间

    private float changeTimer;//改变方向的计时器

    public bool isVertical;//是否垂直方向移动

    private Vector2 moveDirection;//移动方向

    private Rigidbody2D rbody;//引入刚体变量

    // Start is called before the first frame update
    void Start()
    {
        rbody = GetComponent<Rigidbody2D>();

        moveDirection = isVertical ? Vector2.up : Vector2.right;//如果垂直移动,方向朝上,否则朝右

        changeTimer = changeDirectionTime;
    }

    // Update is called once per frame
    void Update()
    {
        changeTimer -= Time.deltaTime;
        if(changeTimer < 0)
        {
            moveDirection *= -1;
            changeTimer = changeDirectionTime;
        }
        Vector2 position = rbody.position;
        position.x += moveDirection.x * speed * Time.deltaTime;
        position.y += moveDirection.y * speed * Time.deltaTime;
        rbody.MovePosition(position);
    }
    void OnCollisionEnter2D(Collision2D other)
    {//这里碰撞使用的函数是OnCollisionEnter因为玩家与敌人都是物理刚体且不需要穿过所以这个碰撞方法比较好
        //之前用过的OnTriggerEnter是触发碰撞,玩家碰到物体可触发穿透物体,两种碰撞方法各有其优,请见机选择
        PlayerController pc = other.gameObject.GetComponent<PlayerController>();
        if(pc!=null)
        {
            pc.ChangeHealth(-1);
        }
    }
}

第八步:添加物品动画

(1)目的:使画面更加生动。

(2)方法:

1、选择一个要加动画的物体

2、点击Window->Animation->Animation(或者按快捷键Ctrl+6)

3、制作界面出来后,点击creat->Asset->新建一个文件夹Animator->点击进入再新建一个文件夹Items->点击进入在新建一个文件夹idle

4、建好之后回到制作界面,将帧数调低,Samples:60->4

5、Add Property->Transform->Scale(缩放)

6、调帧

7、点击下方Curves调节曲线使变化更平滑。

8、若调整好的物品是预制体的话,那么直接复制粘贴就可以得到一堆长得一样的会动的物品。

第九步:添加敌人动画

(1)目的:使敌人更具威慑力。

(2)方法:

1、将敌人移动的图片素材上下左一次拉入Robot对象中,然后在Animator中新建Enemy文件夹专门存放敌人动画。然后点击ctrl+6,出现了动画制作界面,因为素材中没有给到向右的动画,所以我们将新建一组动画,然后将向左的动画进行x轴的Flip(翻转),这样就得到了一组向右的动画。

2、如何将向左的动画调制向右:假设你已经将左、上、下的三个动画制作完成(1)、在制作动画界面点击creat new clip,命名robot_right(与其他三组保持一致即可),(2)、将下方素材向左的图片选择并拉入制作动画界面的右半部分,然后调好帧数,点击add property,选择Spirit Renderder

选择Filp X(水平翻转)加入,然后就挨帧打钩就行了。

3、点击robot属性栏,找到Animator,点击Controller,查看动画树

4、将左侧robot拖拽如右下预览窗中即可预览动画结果。

5、点击右键创建混合树(右键->Creat State->From New Blend Tree)

6、单击新建的Blend Tree模块,将右侧的属性名称改成Walk,再双击Walk模块再单击Blend Tree模块,右侧属性栏里,找到Blend Type选项选择 Simple Derictional 2D选项。在属性栏找到List is Empty点击+号,选择motion is filed。然后再点击下方加号加到四个为止。然后在自己创建的Enemy动画文件夹中选择四个动画依次放入刚创建的动画框中。

调节参数。返回,右键walk将他设为默认枝干,并把多余的模块删掉。

7、给混合树两个参数,现将默认参数改名为moveX

点击小菜单栏paramenters,再点击加号,Trigger一个新的触发器,命名为fix用来显示修好的动画

8、点击路径箭头,在右侧属性栏找到BlendTree点击加号,将选项调成fix,上方的has exit time取消勾选,Setting里面的Fixed Duration取消勾选,下方的Transition Duration清零即可。

9、左侧小菜单栏点加号float,新建moveY,在双击walk,点击动画主模块,在右侧属性栏Paramaters中一个选择moveX,另一个选择moveY。

10、在脚本里面设置动画

walk动画调参:

 修改动画路径:

第十步:添加玩家动画

(1)目的:使玩家更喜欢游戏角色。

(2)方法:

1、点击角色Ruby文件,右侧属性栏新增组件Animator,找到Controller栏,选项为Ruby,然后Ruby动画就完成了。(这里得感谢官方大大)

2、编写播放角色动画的脚本: 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度

    private int maxHealth = 5;//最大生命值

    private int currentHealth;//当前生命值

    //访问私有变量的两个函数
    public int MyMaxHealth { get { return maxHealth; } }

    public int MyCurrentHealth { get { return currentHealth; } }

    private float invincibleTime = 2f;//无敌时间两秒

    private float invicibleTimer;//无敌计时器

    private bool isInvincible;//是否处于无敌状态

    //=======玩家朝向====================================
    private Vector2 lookDirection = new Vector2(1, 0);//玩家默认朝向右

    Rigidbody2D rbody;//刚体组件

    Animator anim;

    // Start is called before the first frame update
    void Start()
    {
        currentHealth = 2;//设置初始血量为2
        invicibleTimer = 0;
        rbody = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0

        Vector2 moveVector = new Vector2(moveX, moveY);
        if(moveVector.x != 0 || moveVector.y != 0)
        {
            lookDirection = moveVector;
        }
        anim.SetFloat("Look X", lookDirection.x);
        anim.SetFloat("Look Y", lookDirection.y);
        anim.SetFloat("Speed", moveVector.magnitude);

        //================移动================================================
        Vector2 position = rbody.position;//定义角色位置向量
        position += moveVector * speed * Time.deltaTime; 
        rbody.MovePosition(position);//将位置信息传输给角色
        //==================无敌计时==============================================
        if (isInvincible)
        {
            invicibleTimer -= Time.deltaTime;
            if(invicibleTimer < 0)
            {
                isInvincible = false;//倒计时结束后取消无敌状态
            }
        }
    }
    /// <summary>
    /// 改变玩家的生命值
    /// </summary>
    public void ChangeHealth(int amount)
    {
        if(amount < 0)
        {
            if (isInvincible == true)
                return;
            isInvincible = true;
            invicibleTimer = invincibleTime;
        }

        Debug.Log(currentHealth + "/" + maxHealth);
        //把玩家的生命值约束在0到最大值之间
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);

        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

第十一步:发射子弹

(1)目的:使游戏具有打击感。

(2)方法:

1、拖拽子弹图片拉入任务栏,调整好图片尺寸,增加刚体(rigidbody2D),和碰撞属性(collider2D)。

2、编写子弹脚本(BulletController)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制子弹的移动、碰撞
/// </summary>
public class BulletController : MonoBehaviour
{
    Rigidbody2D rbody;
    // Start is called before the first frame update
    void Awake()
    {
        rbody = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    /// <summary>
    /// 子弹的移动
    /// </summary>
    public void Move(Vector2 moveDirection, float moveForce)
    {
        rbody.AddForce(moveDirection * moveForce);
    }
}

*注意:这里没有用Start()方法,而是用的Awake()方法,是因为Start方法不会在程序运行就执行,所以刚体组件不会立刻就被调用。如用了Awake方法的话,里面的语句是程序一开始就可以执行的。

3、与子弹相关的玩家脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度

    private int maxHealth = 5;//最大生命值

    private int currentHealth;//当前生命值

    //访问私有变量的两个函数
    public int MyMaxHealth { get { return maxHealth; } }

    public int MyCurrentHealth { get { return currentHealth; } }

    private float invincibleTime = 2f;//无敌时间两秒

    private float invicibleTimer;//无敌计时器

    private bool isInvincible;//是否处于无敌状态

    public GameObject bulletPrefab;//子弹

    //=======玩家朝向====================================
    private Vector2 lookDirection = new Vector2(1, 0);//玩家默认朝向右

    Rigidbody2D rbody;//刚体组件

    Animator anim;

    // Start is called before the first frame update
    void Start()
    {
        currentHealth = 2;//设置初始血量为2
        invicibleTimer = 0;
        rbody = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0

        Vector2 moveVector = new Vector2(moveX, moveY);
        if(moveVector.x != 0 || moveVector.y != 0)
        {
            lookDirection = moveVector;
        }
        anim.SetFloat("Look X", lookDirection.x);
        anim.SetFloat("Look Y", lookDirection.y);
        anim.SetFloat("Speed", moveVector.magnitude);

        //================移动================================================
        Vector2 position = rbody.position;//定义角色位置向量
        position += moveVector * speed * Time.deltaTime; 
        rbody.MovePosition(position);//将位置信息传输给角色
        //==================无敌计时==============================================
        if (isInvincible)
        {
            invicibleTimer -= Time.deltaTime;
            if(invicibleTimer < 0)
            {
                isInvincible = false;//倒计时结束后取消无敌状态
            }
        }
        //==========按下 J 键,进行攻击================================
        if(Input.GetKeyDown(KeyCode.J))
        {
            GameObject bullet = Instantiate(bulletPrefab, rbody.position, Quaternion.identity);
            BulletController bc = bullet.GetComponent<BulletController>();
            if(bc != null)
            {
                bc.Move(lookDirection, 300);
            }
        }
    }
    /// <summary>
    /// 改变玩家的生命值
    /// </summary>
    public void ChangeHealth(int amount)
    {
        if(amount < 0)
        {
            if (isInvincible == true)
                return;
            isInvincible = true;
            invicibleTimer = invincibleTime;
        }

        Debug.Log(currentHealth + "/" + maxHealth);
        //把玩家的生命值约束在0到最大值之间
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);

        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

*注意:脚本写好之后,将脚本挂在子弹上,然后将子弹做成预制体,将原来的任务栏中的子弹删除。打开预制体文件夹找到子弹预制体,在点击左侧玩家对象将预制体的拖入到右侧玩家属性栏中player Controller (script)中的bullet prefab里面就行了。

完成之后按下 J 建就能发射子弹了,如果子弹发生旋转请在刚体属性中勾选freeze Z。

在发射子弹的过程中,子弹与玩家会产生刚体与刚体间的碰撞,怎么解除这种碰撞呢,请看下文。

4、解除子弹与玩家间的碰撞

        1、点击玩家,查看属性,点击Layer选择新增,选择第八层创建player(随便选),选择第九层创建bullet。

        2、保存。将玩家和子弹的Layer改成player和bullet

        3、点击上方菜单栏Edit->Project Settings->Pysics 2D,右下方就是碰撞矩阵,将玩家和bullet的勾选取消,它们就不会发生碰撞了,再把bullet和bullet的勾选取消即可。

        4、改写子弹脚本:使子弹碰到物体后消失。

 //碰撞检测
    void OnCollisionEnter2D(Collision2D other)
    {
        Destroy(this.gameObject);
    }

        5、两秒以后消失

 void Awake()
    {
        rbody = GetComponent<Rigidbody2D>();
        Destroy(this.gameObject, 2f);//两秒后消失
    }

        6设置子弹与敌人的碰撞

 //碰撞检测
    void OnCollisionEnter2D(Collision2D other)
    {
        EnemyController ec = other.gameObject.GetComponent<EnemyController>();
        if(ec != null)
        {
            Debug.Log("碰到敌人了");
        }
        Destroy(this.gameObject);
    }

        7、播放敌人修复动画:

  /// <summary>
    /// 敌人修复
    /// </summary>
    public void Fixed()
    {
        rbody.simulated = false;
        anim.SetTrigger("fix");
    }

     机器人已修复:

   8、机器人被修理的脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 敌人控制相关
/// </summary>
public class EnemyController : MonoBehaviour
{
    public float speed = 3;//移动速度

    public float changeDirectionTime = 2f;//改变方向的时间

    private float changeTimer;//改变方向的计时器

    public bool isVertical;//是否垂直方向移动

    private Vector2 moveDirection;//移动方向

    private Rigidbody2D rbody;//引入刚体变量

    private bool isFixed;//是否被修复

    private Animator anim;//获取动画组件

    // Start is called before the first frame update
    void Start()
    {
        rbody = GetComponent<Rigidbody2D>();

        anim = GetComponent<Animator>();//创建动画组件对象

        moveDirection = isVertical ? Vector2.up : Vector2.right;//如果垂直移动,方向朝上,否则朝右

        changeTimer = changeDirectionTime;

        isFixed = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (isFixed) return;//被修理了就停止以下活动
        changeTimer -= Time.deltaTime;
        if(changeTimer < 0)
        {
            moveDirection *= -1;
            changeTimer = changeDirectionTime;
        }
        Vector2 position = rbody.position;
        position.x += moveDirection.x * speed * Time.deltaTime;
        position.y += moveDirection.y * speed * Time.deltaTime;
        rbody.MovePosition(position);
        anim.SetFloat("moveX", moveDirection.x);
        anim.SetFloat("moveY", moveDirection.y);
    }
    /// <summary>
    /// 与玩家的碰撞检测
    /// </summary>
    /// <param name="other"></param>
    void OnCollisionEnter2D(Collision2D other)
    {//这里碰撞使用的函数是OnCollisionEnter因为玩家与敌人都是物理刚体且不需要穿过所以这个碰撞方法比较好
        //之前用过的OnTriggerEnter是触发碰撞,玩家碰到物体可触发穿透物体,两种碰撞方法各有其优,请见机选择
        PlayerController pc = other.gameObject.GetComponent<PlayerController>();
        if(pc!=null)
        {
            pc.ChangeHealth(-1);
        }
    }
    /// <summary>
    /// 敌人修复
    /// </summary>
    public void Fixed()
    {
        isFixed = true;
        rbody.simulated = false;
        anim.SetTrigger("fix");
    }
}

        *注意:写好之后不要忘记在子弹类中调用

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制子弹的移动、碰撞
/// </summary>
public class BulletController : MonoBehaviour
{
    Rigidbody2D rbody;
    // Start is called before the first frame update
    void Awake()
    {
        rbody = GetComponent<Rigidbody2D>();
        Destroy(this.gameObject, 2f);//两秒后消失
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    /// <summary>
    /// 子弹的移动
    /// </summary>
    public void Move(Vector2 moveDirection, float moveForce)
    {
        rbody.AddForce(moveDirection * moveForce);
    }
    //碰撞检测
    void OnCollisionEnter2D(Collision2D other)
    {
        EnemyController ec = other.gameObject.GetComponent<EnemyController>();
        if(ec != null)
        {
            ec.Fixed();//修复敌人
        }
        Destroy(this.gameObject);
    }
}

        9、子弹发射位置微调(本来是在脚底现在调到中央)

 //==========按下 J 键,进行攻击================================
        if(Input.GetKeyDown(KeyCode.J))
        {
            anim.SetTrigger("Launch");//播放攻击动画
            GameObject bullet = Instantiate(bulletPrefab, rbody.position + Vector2.up * 0.5f, Quaternion.identity);
            BulletController bc = bullet.GetComponent<BulletController>();
            if(bc != null)
            {
                bc.Move(lookDirection, 300);
            }
        }

第十二步:增加敌人特效

(1)目的:使游戏画面更真实。

(2)方法:

1、找到特效素材

2、将素材Spirit Mode改成multiple

3、点击Spirit Edit

4、点击slience,再点按数量切割,选择C :4  R : 4,就切好了。

5、任务栏右键Effects->Partical System,重命名brokenEffect

6、特效属性栏中找到贴图T..S..A勾选上,Mode改成Spirit,加入两张特效图片。

7、点击frame,点击小三角,选择随机,参数改成 0-2就可随机播放两张图了

8、在Asset文件夹下新建文件夹Materials,进入右键creat->material新建一个采色球叫effectMaterial。

9、查看属性,Sharder选择Particles 选择 Alpha Blended,随便选张图片(子弹带)

10、回到特效属性,查看Renderer,将material选择刚刚创建的采色球,这样边框就没了

11、再调属性:

        生存周期、生成速度、初始大小、初始旋转、所在space调成world、stop action调成destroy,跟随游戏体共存亡。在点击shape调整形状。*调渐变色:color over lifetime,调上面的针能控制透明度。

       ***注意: 如何去掉特效的橙色边框:在scenes视窗里有个gizmos菜单,打开后有一个selection outline选项,把它前面的勾选去掉就好了

        ***注意:如果你所做成的特效被场景遮挡,不要慌,这是图层测次序问题,找到特效属性栏中的Renderer,找到order of Layer,将此处的数值改成比图层数值大的即可。

12、修补后特效消失脚本(敌人类)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 敌人控制相关
/// </summary>
public class EnemyController : MonoBehaviour
{
    public float speed = 3;//移动速度

    public float changeDirectionTime = 2f;//改变方向的时间

    private float changeTimer;//改变方向的计时器

    public bool isVertical;//是否垂直方向移动

    private Vector2 moveDirection;//移动方向

    public ParticleSystem brokenEffect;//损坏特效

    private Rigidbody2D rbody;//引入刚体变量

    private bool isFixed;//是否被修复

    private Animator anim;//获取动画组件

    // Start is called before the first frame update
    void Start()
    {
        rbody = GetComponent<Rigidbody2D>();

        anim = GetComponent<Animator>();//创建动画组件对象

        moveDirection = isVertical ? Vector2.up : Vector2.right;//如果垂直移动,方向朝上,否则朝右

        changeTimer = changeDirectionTime;

        isFixed = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (isFixed) return;//被修理了就停止以下活动
        changeTimer -= Time.deltaTime;
        if(changeTimer < 0)
        {
            moveDirection *= -1;
            changeTimer = changeDirectionTime;
        }
        Vector2 position = rbody.position;
        position.x += moveDirection.x * speed * Time.deltaTime;
        position.y += moveDirection.y * speed * Time.deltaTime;
        rbody.MovePosition(position);
        anim.SetFloat("moveX", moveDirection.x);
        anim.SetFloat("moveY", moveDirection.y);
    }
    /// <summary>
    /// 与玩家的碰撞检测
    /// </summary>
    /// <param name="other"></param>
    void OnCollisionEnter2D(Collision2D other)
    {//这里碰撞使用的函数是OnCollisionEnter因为玩家与敌人都是物理刚体且不需要穿过所以这个碰撞方法比较好
        //之前用过的OnTriggerEnter是触发碰撞,玩家碰到物体可触发穿透物体,两种碰撞方法各有其优,请见机选择
        PlayerController pc = other.gameObject.GetComponent<PlayerController>();
        if(pc!=null)
        {
            pc.ChangeHealth(-1);
        }
    }
    /// <summary>
    /// 敌人修复
    /// </summary>
    public void Fixed()
    {
        isFixed = true;
        if(brokenEffect.isPlaying == true)
        {
            brokenEffect.Stop();
        }
        rbody.simulated = false;
        anim.SetTrigger("fix");
    }
}

*注意:编写完这个脚本后,要机器人目录下的特效拖入右侧的属性栏的 BrokenEffect 里,否则程序无法正常执行。

第十三步:增添物品特效 

(1)目的(同上)

(2)方法(仿上)

第十三步:血条显示

(1)目的:使玩家直观的感受到角色的状态。

(2)方法:

1、任务列表右键UI->image->自动生成Canvas画布->image重命名HeadFrame->右侧属性栏Source Image选择想要的图片,点击set native size恢复原始大小,按住shift可以进行等比例缩放。在属性栏中点击排布图标,按住alt键选择排版。点击Game窗口可以预览效果。

2、 调整headframe框的缩放大小,点击Canvas点击属性scale with screem size(随着屏幕窗口大小改变)

3、 在HeadImage文件下在新增一个图片文件叫head同样的方法将头像放在头像框上,然后再创建一个HealthBar图片文件,将血条图片加载进来。然后将血条的长短用数值控制。(1)点击血条文件,将image type选择filled,将fill mothod改成Hrizonal,即可。

4、新建脚本血量可视化:(新建UImanager类)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// UI管理相关
/// </summary>
public class UImanager : MonoBehaviour
{
    //单例模式
    public static UImanager instance { get; private set; }

    void Awake()
    {
        instance = this;
    }

    public Image healthBar;
/// <summary>
/// 更新血条
/// </summary>
    public void UpdateHealthBar(int curAmount, int maxAmount)
    {
        healthBar.fillAmount = (float)curAmount / (float)maxAmount;
    }
}

5、角色脚本相关:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度

    private int maxHealth = 5;//最大生命值

    private int currentHealth;//当前生命值

    //访问私有变量的两个函数
    public int MyMaxHealth { get { return maxHealth; } }

    public int MyCurrentHealth { get { return currentHealth; } }

    private float invincibleTime = 2f;//无敌时间两秒

    private float invicibleTimer;//无敌计时器

    private bool isInvincible;//是否处于无敌状态

    public GameObject bulletPrefab;//子弹

    //=======玩家朝向====================================
    private Vector2 lookDirection = new Vector2(1, 0);//玩家默认朝向右

    Rigidbody2D rbody;//刚体组件

    Animator anim;

    // Start is called before the first frame update
    void Start()
    {
        currentHealth = 5;//设置初始血量为2
        invicibleTimer = 0;
        rbody = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0

        Vector2 moveVector = new Vector2(moveX, moveY);
        if(moveVector.x != 0 || moveVector.y != 0)
        {
            lookDirection = moveVector;
        }
        anim.SetFloat("Look X", lookDirection.x);
        anim.SetFloat("Look Y", lookDirection.y);
        anim.SetFloat("Speed", moveVector.magnitude);

        //================移动================================================
        Vector2 position = rbody.position;//定义角色位置向量
        position += moveVector * speed * Time.deltaTime; 
        rbody.MovePosition(position);//将位置信息传输给角色
        //==================无敌计时==============================================
        if (isInvincible)
        {
            invicibleTimer -= Time.deltaTime;
            if(invicibleTimer < 0)
            {
                isInvincible = false;//倒计时结束后取消无敌状态
            }
        }
        //==========按下 J 键,进行攻击================================
        if(Input.GetKeyDown(KeyCode.J))
        {
            anim.SetTrigger("Launch");//播放攻击动画
            GameObject bullet = Instantiate(bulletPrefab, rbody.position + Vector2.up * 0.5f, Quaternion.identity);
            BulletController bc = bullet.GetComponent<BulletController>();
            if(bc != null)
            {
                bc.Move(lookDirection, 300);
            }
        }
    }
    /// <summary>
    /// 改变玩家的生命值
    /// </summary>
    public void ChangeHealth(int amount)
    {
        if(amount < 0)
        {
            if (isInvincible == true)
                return;
            isInvincible = true;
            invicibleTimer = invincibleTime;
        }

        Debug.Log(currentHealth + "/" + maxHealth);
        //把玩家的生命值约束在0到最大值之间
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
        UImanager.instance.UpdateHealthBar(currentHealth, maxHealth);
        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

*注意:写完脚本需要给脚本一个对象,现在左侧任务栏中新建一个空文件取名为UImanager,然后将UImanager脚本挂在上面,在把healthBar的UI拖拽进UImanager属性栏中。

第十五步:增加场景音乐

(1)目的:增强游戏沉浸感。

(2)方法:

1、新建文件夹AudioManager->添加组件AudioSource->AudioClip选择背景音乐设置循环播放

2、新建脚本:AudioManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 播放音乐音效
/// </summary>
public class AudioManager : MonoBehaviour
{
    public static AudioManager instance { get; private set; }

    private AudioSource audioS;
    // Start is called before the first frame update
    void Start()
    {
        instance = this;
        audioS = GetComponent<AudioSource>();
    }
    /// <summary>
    /// 播放特定音效
    /// </summary>
    /// <param name="clip"></param>
    public void AudioPlay(AudioClip clip)
    {
        audioS.PlayOneShot(clip);
    }
}

3、角色加音效

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度

    private int maxHealth = 5;//最大生命值

    private int currentHealth;//当前生命值

    //访问私有变量的两个函数
    public int MyMaxHealth { get { return maxHealth; } }

    public int MyCurrentHealth { get { return currentHealth; } }

    private float invincibleTime = 2f;//无敌时间两秒

    private float invicibleTimer;//无敌计时器

    private bool isInvincible;//是否处于无敌状态

    public GameObject bulletPrefab;//子弹

    //=================玩家的音效=====================
    public AudioClip hitClip;//受伤音效
    public AudioClip lauchClip;//发射音效
    //=======玩家朝向====================================
    private Vector2 lookDirection = new Vector2(1, 0);//玩家默认朝向右

    Rigidbody2D rbody;//刚体组件

    Animator anim;

    // Start is called before the first frame update
    void Start()
    {
        currentHealth = 5;//设置初始血量为2
        invicibleTimer = 0;
        rbody = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0

        Vector2 moveVector = new Vector2(moveX, moveY);
        if(moveVector.x != 0 || moveVector.y != 0)
        {
            lookDirection = moveVector;
        }
        anim.SetFloat("Look X", lookDirection.x);
        anim.SetFloat("Look Y", lookDirection.y);
        anim.SetFloat("Speed", moveVector.magnitude);

        //================移动================================================
        Vector2 position = rbody.position;//定义角色位置向量
        position += moveVector * speed * Time.deltaTime; 
        rbody.MovePosition(position);//将位置信息传输给角色
        //==================无敌计时==============================================
        if (isInvincible)
        {
            invicibleTimer -= Time.deltaTime;
            if(invicibleTimer < 0)
            {
                isInvincible = false;//倒计时结束后取消无敌状态
            }
        }
        //==========按下 J 键,进行攻击================================
        if(Input.GetKeyDown(KeyCode.J))
        {
            anim.SetTrigger("Launch");//播放攻击动画
            AudioManager.instance.AudioPlay(lauchClip);//攻击音效
            GameObject bullet = Instantiate(bulletPrefab, rbody.position + Vector2.up * 0.5f, Quaternion.identity);
            BulletController bc = bullet.GetComponent<BulletController>();
            if(bc != null)
            {
                bc.Move(lookDirection, 300);
            }
        }
    }
    /// <summary>
    /// 改变玩家的生命值
    /// </summary>
    public void ChangeHealth(int amount)
    {
        if(amount < 0)
        {
            if (isInvincible == true)
                return;
            isInvincible = true;
            anim.SetTrigger("Hit");
            AudioManager.instance.AudioPlay(hitClip);//受伤音效
            invicibleTimer = invincibleTime;
        }

        Debug.Log(currentHealth + "/" + maxHealth);
        //把玩家的生命值约束在0到最大值之间
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
        UImanager.instance.UpdateHealthBar(currentHealth, maxHealth);
        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

4、敌人加音效

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 敌人控制相关
/// </summary>
public class EnemyController : MonoBehaviour
{
    public float speed = 3;//移动速度

    public float changeDirectionTime = 2f;//改变方向的时间

    private float changeTimer;//改变方向的计时器

    public bool isVertical;//是否垂直方向移动

    private Vector2 moveDirection;//移动方向

    public ParticleSystem brokenEffect;//损坏特效

    public AudioClip fixedClip;//被修复的音效

    private Rigidbody2D rbody;//引入刚体变量

    private bool isFixed;//是否被修复

    private Animator anim;//获取动画组件

    // Start is called before the first frame update
    void Start()
    {
        rbody = GetComponent<Rigidbody2D>();

        anim = GetComponent<Animator>();//创建动画组件对象

        moveDirection = isVertical ? Vector2.up : Vector2.right;//如果垂直移动,方向朝上,否则朝右

        changeTimer = changeDirectionTime;

        isFixed = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (isFixed) return;//被修理了就停止以下活动
        changeTimer -= Time.deltaTime;
        if(changeTimer < 0)
        {
            moveDirection *= -1;
            changeTimer = changeDirectionTime;
        }
        Vector2 position = rbody.position;
        position.x += moveDirection.x * speed * Time.deltaTime;
        position.y += moveDirection.y * speed * Time.deltaTime;
        rbody.MovePosition(position);
        anim.SetFloat("moveX", moveDirection.x);
        anim.SetFloat("moveY", moveDirection.y);
    }
    /// <summary>
    /// 与玩家的碰撞检测
    /// </summary>
    /// <param name="other"></param>
    void OnCollisionEnter2D(Collision2D other)
    {//这里碰撞使用的函数是OnCollisionEnter因为玩家与敌人都是物理刚体且不需要穿过所以这个碰撞方法比较好
        //之前用过的OnTriggerEnter是触发碰撞,玩家碰到物体可触发穿透物体,两种碰撞方法各有其优,请见机选择
        PlayerController pc = other.gameObject.GetComponent<PlayerController>();
        if(pc!=null)
        {
            pc.ChangeHealth(-1);
        }
    }
    /// <summary>
    /// 敌人修复
    /// </summary>
    public void Fixed()
    {
        isFixed = true;
        if(brokenEffect.isPlaying == true)
        {
            brokenEffect.Stop();
        }
        AudioManager.instance.AudioPlay(fixedClip);
        rbody.simulated = false;
        anim.SetTrigger("fix");
    }
}

5射击物加音效

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制子弹的移动、碰撞
/// </summary>
public class BulletController : MonoBehaviour
{
    Rigidbody2D rbody;

    public AudioClip hitClip;//命中音效
    // Start is called before the first frame update
    void Awake()
    {
        rbody = GetComponent<Rigidbody2D>();
        Destroy(this.gameObject, 2f);//两秒后消失
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    /// <summary>
    /// 子弹的移动
    /// </summary>
    public void Move(Vector2 moveDirection, float moveForce)
    {
        rbody.AddForce(moveDirection * moveForce);
    }
    //碰撞检测
    void OnCollisionEnter2D(Collision2D other)
    {
        EnemyController ec = other.gameObject.GetComponent<EnemyController>();
        if(ec != null)
        {
            ec.Fixed();//修复敌人
        }
        AudioManager.instance.AudioPlay(hitClip);//播放命中音效
        Destroy(this.gameObject);
    }
}

将音效素材拖入对象属性的相应位置即可。


第十六步:创建NPC

(1)目的:略

(2)方法:

1、找到NPC素材,打包成动画

2、添加对话框->新建画布->UI->Image->建立对话框并调整大小,添加文字

3、写代码:

UImanager脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// UI管理相关
/// </summary>
public class UImanager : MonoBehaviour
{
    //单例模式
    public static UImanager instance { get; private set; }

    void Awake()
    {
        instance = this;
    }

    public Image healthBar;
/// <summary>
/// 更新血条
/// </summary>
    public void UpdateHealthBar(int curAmount, int maxAmount)
    {
        healthBar.fillAmount = (float)curAmount / (float)maxAmount;
    }
}

4、NPC对话出现脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// npc交互
/// </summary>
public class NPCmanager : MonoBehaviour
{
    public GameObject tipImage;//提示

    public GameObject dialogImage;
    
    public float showTime = 4;//对话显示时间

    private float showTimer;//计时器

    // Start is called before the first frame update
    void Start()
    {
        tipImage.SetActive(true);
        dialogImage.SetActive(false);//初始隐藏对话框
        showTimer = -1;
    }
    void Update()
    {
        showTimer -= Time.deltaTime;

        if(showTimer < 0)
        { 
            tipImage.SetActive(true);
            dialogImage.SetActive(false);
        }
    }
    //显示对话框
    public void ShowDialog()
    {
        showTimer = showTime;
        tipImage.SetActive(false);
        dialogImage.SetActive(true);
    }

}

第十七步:创建子弹补给

(1)目的:增加子弹数量。增添游戏趣味性。

(2)方法:

1、与草莓道理相近,先在画面中建立子弹包,然后和角色建立关联脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 子弹补给
/// </summary>
public class BulletBag : MonoBehaviour
{
    public int bulletCount = 10;//里面含有的子弹数量 

    void OnTriggerEnter2D(Collider2D other)
    {
        PlayerController pc = other.GetComponent<PlayerController>();
        if(pc!=null)
        {
            if(pc.MyCurBulletCount < pc.MyMaxBulletCount)
            {
                pc.ChangeBulletCount(bulletCount);//增加子弹数
                Destroy(this.gameObject);
            }
        }
    }
}

2、角色脚本更改

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制角色移动、生命、动画等
/// </summary>
public class PlayerController : MonoBehaviour
{
    public float speed = 5f;//移动速度

    private int maxHealth = 5;//最大生命值

    private int currentHealth;//当前生命值

    //访问私有变量的两个函数
    public int MyMaxHealth { get { return maxHealth; } }

    public int MyCurrentHealth { get { return currentHealth; } }

    private float invincibleTime = 2f;//无敌时间两秒

    private float invicibleTimer;//无敌计时器

    private bool isInvincible;//是否处于无敌状态

    public GameObject bulletPrefab;//子弹

    //=================玩家的音效=====================
    public AudioClip hitClip;//受伤音效
    public AudioClip lauchClip;//发射音效
    //=======玩家朝向====================================
    private Vector2 lookDirection = new Vector2(1, 0);//玩家默认朝向右

    //=====玩家子弹数量========================
    [SerializeField]
    private int maxBulletCount = 30;
    private int curBulletCount;//当前子弹数

    public int MyCurBulletCount { get { return curBulletCount; } }

    public int MyMaxBulletCount { get { return maxBulletCount; } }

    Rigidbody2D rbody;//刚体组件

    Animator anim;

    // Start is called before the first frame update
    void Start()
    {
        currentHealth = 5;//设置初始血量为2
        curBulletCount = 2;
        invicibleTimer = 0;
        rbody = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
        UImanager.instance.UpdateBulletCount(curBulletCount, maxBulletCount);

    }

    // Update is called once per frame
    void Update()
    {
        float moveX = Input.GetAxisRaw("Horizontal");//控制水平移动方向 A:-1  D:1  不按: 0
        float moveY = Input.GetAxisRaw("Vertical");//控制垂直移动方向 W:-1  S:1  不按: 0

        Vector2 moveVector = new Vector2(moveX, moveY);
        if(moveVector.x != 0 || moveVector.y != 0)
        {
            lookDirection = moveVector;
        }
        anim.SetFloat("Look X", lookDirection.x);
        anim.SetFloat("Look Y", lookDirection.y);
        anim.SetFloat("Speed", moveVector.magnitude);

        //================移动================================================
        Vector2 position = rbody.position;//定义角色位置向量
        position += moveVector * speed * Time.deltaTime; 
        rbody.MovePosition(position);//将位置信息传输给角色
        //==================无敌计时==============================================
        if (isInvincible)
        {
            invicibleTimer -= Time.deltaTime;
            if(invicibleTimer < 0)
            {
                isInvincible = false;//倒计时结束后取消无敌状态
            }
        }
        //==========按下 J 键,进行攻击================================
        if(Input.GetKeyDown(KeyCode.J) && curBulletCount > 0)
        {
            ChangeBulletCount(-1);
            anim.SetTrigger("Launch");//播放攻击动画
            AudioManager.instance.AudioPlay(lauchClip);//攻击音效
            GameObject bullet = Instantiate(bulletPrefab, rbody.position + Vector2.up * 0.5f, Quaternion.identity);
            BulletController bc = bullet.GetComponent<BulletController>();
            if(bc != null)
            {
                bc.Move(lookDirection, 300);
            }
        }
        //========按下E键进行对话==================================
        if (Input.GetKeyDown(KeyCode.E))
        {
            RaycastHit2D hit = Physics2D.Raycast(rbody.position, lookDirection, 2f, LayerMask.GetMask("NPC"));
            if(hit.collider != null)
            {
                NPCmanager npc = hit.collider.GetComponent<NPCmanager>();
                if(npc != null)
                {
                    npc.ShowDialog();//显示对话框
                }
            }
        }
    }
    /// <summary>
    /// 改变玩家的生命值
    /// </summary>
    public void ChangeHealth(int amount)
    {
        if(amount < 0)
        {
            if (isInvincible == true)
                return;
            isInvincible = true;
            anim.SetTrigger("Hit");
            AudioManager.instance.AudioPlay(hitClip);//受伤音效
            invicibleTimer = invincibleTime;
        }

        Debug.Log(currentHealth + "/" + maxHealth);
        //把玩家的生命值约束在0到最大值之间
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
        UImanager.instance.UpdateHealthBar(currentHealth, maxHealth);
        Debug.Log(currentHealth + "/" + maxHealth);
    }
    //改变子弹数量
    public void ChangeBulletCount(int amount)
    {
        curBulletCount = Mathf.Clamp(curBulletCount + amount, 0, maxBulletCount);//限制子弹数量范围
        UImanager.instance.UpdateBulletCount(curBulletCount, maxBulletCount);
    }
}

*注意:如果觉得物体遮挡不够真实,可以设置物体属性的锚点为底部就可以了。

        1、Pivot->Bottom

        2、Spirit Sort Point -> Pivot

第十八步:打包文件

步骤:File->Building Settings->选择 add open scence ->build

(下一次要改进的地方:

1、机器人追着玩家跑,如果玩家躲入草丛则机器人发现不了

2、打中箱子掉落隐藏物品

3、实现2.5D饥荒视角

大家还有什么好的想法,评论区积极留言哦)

  • 14
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
文字冒险是一种以文字为基础的游戏体验,玩家通过阅读描述和输入指令的方式进行游戏。这种游戏常常让玩家在一个虚拟世界中扮演主角,通过与游戏中的角色互动解决问题,达到完成故事情节的目标。 在一个文字冒险游戏中,玩家将通过阅读游戏中的文字描述了解游戏世界的背景、情节以及所处的环境。根据游戏情节的发展,玩家需要利用自己的想象力和逻辑推理能力来解决游戏中的谜题和困难。玩家通过输入指令来控制主角进行行动,例如“向前走”、“拿起钥匙”等等。游戏会根据玩家输入的指令进行相应的反馈,继续推动故事情节的发展。 文字冒险游戏依赖于玩家的想象力和阅读理解能力。玩家需要根据游戏中描述的场景和物品来确定下一步行动,同时还需要运用逻辑思维来解决谜题。由于文字冒险游戏没有图像或声音的辅助,玩家必须通过想象力来构建游戏世界,这种体验带来了无限的可能性和创造力。 文字冒险游戏曾经是游戏界的热门类型,尤其在早期个人电脑的时代。随着技术的进步,图形化和声音化的游戏体验逐渐成为主流,文字冒险游戏的人气有所下降。然而,一些游戏开发者仍然推出新的文字冒险游戏,以保留这种传统游戏形式的魅力。 总的来说,文字冒险游戏是一种让玩家通过阅读和输入指令的方式与虚拟世界进行互动的游戏。这种游戏体验依赖于玩家的想象力和阅读理解能力,带来了独特的创造力和挑战。即使在图形化游戏主导的时代,文字冒险游戏仍然保留着一定的市场和受众。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码骑士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值