unity2D备忘志(二)

466 阅读11分钟
随着程序越来越复杂,一下章节不能再贴全部代码了,只贴涉及到的部分,但全部代码会在文末给出
子弹这一部分涉及多个知识点:简单归纳如下 1.给刚体施加力量让其移动 2.利用public一个对象,然后界面绑定的方法传递预制体 3.预制体实例化

十四. 子弹

1.初始化
首先,找到子弹素材,将其做成一个预制体
给他添加刚体 和 碰撞盒
创建一个脚本bullet来控制子弹
2.子弹移动
这里我们用另一种方法来让刚体移动 ==>施加力量
public void BulletMove(Vector2 moveDirection,float moveForce)
    {
        bulletRigid.AddForce(moveDirection*moveForce);//给子弹添加一个力,它就会受力移动
    }
3.主角脚本绑定子弹预制体
由于主角是发射子弹的主体,所以,发射代码写在主角的脚本中,在主角脚本中如何获得子弹的预制体呢? 我们利用public一个对象,然后界面绑定的方法来传递预制体
首先我们public一个gameobject对象
public GameObject bulletPreb;
然后,在界面上将子弹的预制体绑定过去
这种方法简单,高效!接下来就是实例化这个预制体
4.发射子弹
比如:我们按下J键,就发射一个子弹
这里用到了预制体实例化的函数Instantiate(); 实例化后调用子弹移动函数,使其移动
        if (Input.GetKeyDown(KeyCode.J))
        {
            GameObject bullet= Instantiate(bulletPreb,rubyBody.position,Quaternion.identity);
            bullet.GetComponent<bullet>().BulletMove(lookVector,300);
        }
完成了!!!!
但是,效果和我们想象的完全不一样....
效果不尽人意,原因有好几个,我们一个一个的解决
问题1:角色不应该和子弹有刚体碰撞,这里就要把角色和子弹分好层了(其实所有节点创建之时起就都应该分好层)
然后在项目设置中将player和bullet的层间碰撞取消
此外,后来我将bullet之间的碰撞也取消了,这样就能保证某些极端情况下,子弹互相碰撞.
问题2:
效果终于发生了变化,但是子弹怎么不动呢?
而且控制台还报错
问题出在了,子弹初始化的逻辑上
我们获取子弹刚体,不应该在start里 ,而应该在awake里,
这里说一下start和awake的区别:
1.Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前,先执行Awake方法,再执行Start方法 2.脚本的一些成员,如果想在创建之后的代码中立即使用,则必须写在Awake()里面; 3.当脚本设置为不可用时,Awake方法仍然会执行一次,而Start方法则不会执行! 所以我们可以简单理解为:实例物体创建后就awake,执行脚本开始时start, awake是实例对象的初始化,start是脚本的初始化
这里我们想实例对象创建后立即执行所以应该将此初始化写在awake里
错误的
    private void Awake() 
    {
        bulletRigid=GetComponent<Rigidbody2D>();
    }
终于可以了,接下来我们为子弹添加碰撞和自毁程序,在给角色添加射击动画
5. 子弹碰撞和自毁
子弹不能永远的飞行下去,当他碰到别的物体或这飞行一段时间后,应该自毁
首先我们来做飞行一段时间后自毁:
这个很简单
Destroy(gameObject,2f);  //写在awake里即可  awake两秒后就会自毁
再写碰撞自毁:
    private void OnCollisionEnter2D(Collision2D other) 
    {
        Destroy(gameObject);
    }
6. 给角色添加发射动画
再添加角色的射击动画:
        if (Input.GetKeyDown(KeyCode.J))
        {
            GameObject bullet= Instantiate(bulletPreb,rubyBody.position,Quaternion.identity);
            bullet.GetComponent<bullet>().BulletMove(lookVector,300);
            anim.SetTrigger("launch");  //触发发射动画
        }
其实这里有问题,就是没有限制角色的发射频率,因为此段代码写在update里,所以很好实现.这个功能以后完善.
7. 子弹击中敌人
子弹击中敌人,应该做以下事情,
1.子弹击中消失;
2. 敌人播放被击中的动画 //这里注意,敌人被击中时不要再继续执行移动等动作.
3.敌人播放完动画后消失
我们在敌人脚本中建立了一个布尔变量记录角色被击中的状态
private bool fixedState;//角色被击中否,为被击中:false 击中了:true
同时在start里初始化为false
fixedState=false;
再給敌人增加一个函数,当被击中时触发
    public void BeFixed()
    {
        anim.SetTrigger("fixed");    //播放击中动画
        fixedState=true;             //将敌人的状态修改为被击中
        Destroy(gameObject,1f);      //1秒后自爆
    }
接下来我们要做的,就是再子弹击中他时触发事件了~
在子弹脚本中:
    private void OnCollisionEnter2D(Collision2D other) {
        Destroy(gameObject); 
        if(other.gameObject.tag=="Enemy"){                     //判断一下打中的是不是敌人
            other.gameObject.GetComponent<enemy1>().BeFixed(); //敌人被击中调用BeFixed函数
        }
    }

十五. 敌人特效

在场景中创建一个特效
我们在其属性框中,将其纹理选项打开
选中精灵模式
将下面的特效图片框增加到2
把我们提前准备好的精灵图片传递进去
将开始帧设为0~2随机,这样久能随机上面的两张特效图片了(点后面的小倒三角)
在render选项卡中,选好图层
然后设置特效细节,好多特效参数都能随机,增加趣味性
把模拟空间设为world,可以使得特效挂载在物体上随物体移动时,有火车拉烟的那种效果
设定特效消失的时间点
在shape选项卡中,可以设计特效的形状样式
在color over lifeTime选项卡中,可以设计特效随进程的颜色及透明度变换
如下图:上面的游标是透明度,下面的游标是颜色(设置起来非常简单)
特效调整完毕后,我们将它制作为预制件,将预制件直接挂在想添加特效的节点下即可
特效挂在节点下后,记得调节好特效在父节点坐标系中的位置
这样一只浓烟滚滚的小机器人就做好了
特效在脚本中的管理
比如我们要做机器人被击中后,停止冒烟(因为角色发射的是齿轮,射击是为了帮它修复....)
这里我们用属性面板绑定的方法传递特效
先public一个特效
public ParticleSystem effectSmoke;   //机器人冒烟特效
再在属性面板中绑定该特效
然后,我们就可以操作和管理此特效了
    public void BeFixed()
    {
        anim.SetTrigger("fixed");
        fixedState=true;
        Destroy(gameObject,2f);
        if(effectSmoke.isPlaying) effectSmoke.Stop();
    }

十六 吃草莓特效

还是同样的配方,创建特效调参数
添加三张素材
这里我们调节形状时选择了圆形
调节基本特效时,把booping关掉,只播放一次
选择爆发选项卡,添加一种爆发
选择 size over LifeTime 控制动画中的元素大小曲线
最后将调到满意的效果做成预制件
再脚本中,我们public一个特效接口,
public ParticleSystem strawEffect;
在属性面板中将特效预制件传递给它
最后我们想触发时,直接实例化一个特效就可以了
    private void OnTriggerEnter2D(Collider2D other) {
        if (other.tag=="Player")
        {
            if (other.GetComponent<MyRuby>().getHp()<other.GetComponent<MyRuby>().getMaxHp())
            {
                other.GetComponent<MyRuby>().changeHp(1);
                Instantiate(strawEffect,transform.position,Quaternion.identity);  //角色吃草莓时实例化一个特效预制件
                Destroy(this.gameObject);
            }
        }
    }
最终结果,闪闪惹人爱!~

十七. 血条

新建一个UI image
这时,场景会自动建立下面的节点结构
这时我们先要调整血条UI适应屏幕尺寸 在canvas属性里调节
将准备好的素材图给image,并调整好位置
再建一个image 绑定头像 再建一个image充当血条,整体效果如下.
其中血条因为要变化,所以将其图片类型改为填充型,并将填充方式改为水平
最终结构是这样的
接下来,我们写代码:
我们建立一个脚本专门管理UI 名为uicontroller,将它直接挂在canvas上
首先,我们要获取UI相关的数据类型,需要引用UI相关的命名空间
using UnityEngine.UI;
其次,为了方便多个脚本里调用UI中的函数,我们直接单例一个UI
public static UIcontroller instance{get;private set;}
void Start()
    {
        instance=this;
    }
然后我们在脚本里写好血条更新的方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UIcontroller : MonoBehaviour
{
    public Image hpBar;   //注意:hpbar采用的绑定法传递
    public static UIcontroller instance{get;private set;}
    // Start is called before the first frame update
    void Start()
    {
        instance=this;
    }
    public void HpShow(int nowHp,int maxHp){
        Debug.Log(nowHp+"?"+maxHp);
        hpBar.fillAmount=(float) nowHp / maxHp;
    }
}
然后再主角的脚本中调用这个方法,主角初始化及血量变化时都需要调用此方法
    public void changeHp(int change){
        RubyHp=Mathf.Clamp(RubyHp+change,RubyMinHp,RubyMaxHp);
        UIcontroller.instance.HpShow(RubyHp,RubyMaxHp);
    }
这样血条就制作完成了!

十八 添加音效

  1. 添加背景音乐
建立一个空物体,添加audio source组件,为其audioclip传递事先准备好的bgm
2.添加吃草莓的音效
创建一个管理音效的脚本,里面写好音乐播放的方法,将此脚本单例,方便各处调用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class audioManager : MonoBehaviour
{
    public static audioManager instance{get;private set;}
    // Start is called before the first frame update
    void Start()
    {
        instance=this;
    }
    public void AudioPlay(AudioClip clip){
        GetComponent<AudioSource>().PlayOneShot(clip);
    }
}
实例化之后,就可以给大家使用了,
在草莓脚本中,添加一个传递音频的接口,并把对应的素材传递给它
public AudioClip eatAudioClip;
在碰撞触发时调用音频播放,并给它传递素材音频对象
 audioManager.instance.AudioPlay(eatAudioClip);
3.给角色添加受伤和射击音效,子弹击中音效,原理同上
4.机器人行走音效
给机器人直接加一个Audio source,并给它素材,同时,为了增加立体声效果,应将混合特效调为3D,同时调节播音范围
我们可以在3D声音设置里调整具体效果.
此外,为了增加沉浸感,给主角加一个audio listener 来收声音,把camera的audio listener禁用掉.
5. 主角行走音效
主角受玩家控制,有移动和静止两个状态,所以要做一下判断.此外这段音频较长(相较于update),所以应该判断一下是否在播放,此外停止移动应该立即停止音效.
其他和敌人移动基本一致
    private AudioSource walkAudioSource;
    private void Start() {
        walkAudioSource=GetComponent<AudioSource>();
    }
    void Update()
    {
         if(moveVector.magnitude!=0){
            lookVector=moveVector;
            anim.SetFloat("lookX",lookVector.x);
            anim.SetFloat("lookY",lookVector.y);
            if(!walkAudioSource.isPlaying){
                walkAudioSource.Play();
            }
        }
        else
        {
            walkAudioSource.Stop();
        }
    }

十九 NPC交互

1.创建NPC
首先我们制作一个NPC,这和制作敌人的方法是一样的
然后为其添加碰撞盒.
2.添加主角射线
我们跟NPC交互的思路是:主角发射一束隐藏的射线,碰到NPC时,触发对话.
这里涉及到给主角添加射线束的知识
我们创建一个射线,
private RaycastHit2D rubyRay;
再在update里设定射线的属性,一直发射射线,如果碰到NPC就会显示对话,我们先测试一下
rubyRay=Physics2D.Raycast(rubyBody.position,lookVector,1f,LayerMask.GetMask("NPC"));
if (rubyRay.collider!=null)
        {
            Debug.Log("NPC");
        }
3. 添加NPC对话框
给NPC加一个UI canvas,这个canvas出现在场景中,所以我们将它的渲染模式改为world space,并设置好大小
然后我们给canvas加一个背景图片 ui image, 图片采用九宫格模式,再设置一下拉伸模式,按住alt,选择将图片铺满画布(由于热键冲突,下面的截图没有按alt)
在给canvas添加一个显示文本的组件 为了显示效果好,给text组件添加了一个outline组件来描边
最终结构
按照上述方法,建立了两个对话框,提示对话框和触发对话框,平时显示提示对话框,触发后显示触发对话框.
给NPC创建一个脚本,里面写好对话框触发方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NPC : MonoBehaviour
{
    public void ShowDialogue(){
        transform.Find("showCanvas").gameObject.SetActive(true);
        transform.Find("hideCanvas").gameObject.SetActive(false);
        Invoke("hideDialogue",4f);                      //对话框会在4秒后自动消失
    }
    public void hideDialogue(){
        transform.Find("showCanvas").gameObject.SetActive(false);
        transform.Find("hideCanvas").gameObject.SetActive(true);
    }
}
最后在主角脚本中触发这些方法即可
为了防止主角一直发射线消耗性能,改为了用快捷键触发的方式
        if (Input.GetKeyDown(KeyCode.E))
        {
            if (rubyRay.collider!=null)
                {
                    rubyRay.collider.gameObject.GetComponent<NPC>().ShowDialogue();
                }
        }

二十 子弹数量及UI

用的都是之前的知识和方法,可以毫无难度的自己做出来,
甚至我是直接复制了草莓的预制件,进行了魔改 公用了collectThings脚本和strawberryEffect
以下是部分代码:
主角脚本:
定义了 当前子弹和总子弹变量
    private int currentBullet=10;
    private int maxBullet=99;
增加了changeBullet方法
    public void changeBullet(int bullet)
    {
        currentBullet =Mathf.Clamp(currentBullet+bullet,0,maxBullet);
        UIcontroller.instance.SetBulletTxt(currentBullet,maxBullet);
    }
武器攻击时做了判断
        if (Input.GetKeyDown(KeyCode.J))
        {
            if(currentBullet>0)
            {
                GameObject bullet= Instantiate(bulletPreb,rubyBody.position+new Vector2(0,0.5f),Quaternion.identity);
                bullet.GetComponent<bullet>().BulletMove(lookVector,300);
                anim.SetTrigger("launch");
                audioManager.instance.AudioPlay(lunachAudioClip);
                changeBullet(-1);
            }
            else
            {
                Debug.Log("Out of Bullet!!!");
            }
        }
collectThings脚本:
草莓和子弹公用collectThings,代码简单将草莓和子弹区分开.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class collectThings : MonoBehaviour
{
    // Start is called before the first frame update
    public ParticleSystem strawEffect;
    public AudioClip eatAudioClip;
    private void OnTriggerEnter2D(Collider2D other) {
        if (other.tag=="Player")
        {
            if (other.GetComponent<MyRuby>().getHp()<other.GetComponent<MyRuby>().getMaxHp())
            {
                if(gameObject.name=="CollectibleBullet")
                {
                    other.GetComponent<MyRuby>().changeBullet(10);
                }
                else
                {
                    other.GetComponent<MyRuby>().changeHp(1);
                }
                
                Instantiate(strawEffect,transform.position,Quaternion.identity);
                audioManager.instance.AudioPlay(eatAudioClip);
                Destroy(this.gameObject);
            }
        }
    }
}
UIcontroller脚本增加更新子弹显示的方法
    public void SetBulletTxt(int currentBullet,int maxBullet){
        if(currentBullet<=maxBullet & currentBullet>=0){
            bulletTxt.text=currentBullet+" / "+maxBullet;
        }        
    }