随着程序越来越复杂,一下章节不能再贴全部代码了,只贴涉及到的部分,但全部代码会在文末给出
子弹这一部分涉及多个知识点:简单归纳如下
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);
}
这样血条就制作完成了!
建立一个空物体,添加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();
}
}
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();
}
}
用的都是之前的知识和方法,可以毫无难度的自己做出来,
甚至我是直接复制了草莓的预制件,进行了魔改 公用了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().getHp()().getMaxHp())
{
if(gameObject.name=="CollectibleBullet")
{
other.GetComponent().changeBullet(10);
}
else
{
other.GetComponent().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;
}
}