前言
大家好,上一次unity练习记录中我添加了背景以及第五个boss,丰富了一下游戏内容,但是玩的时候发现好像有一些空洞,所以决定再次添加一些东西。本次练习主要是想要搞两个UI,以及背景音乐,然后再添加一个新的boss。其实我也很想再添加一些小怪,这些就是后话了。先把前面三个功能添加进去先!那么开始本次练习记录。
GUI--boss血条以及波次
之前也在书中学习了一些关于unity的GUI知识,但是最后发现好像现在unity已经不用传统的UI了,都改成了TMP了。所以在网上随便找了一些文章,查看了一下TMP的用法。
首先,TMP全称为TextMeshPro,是unity新推出的一种GUI对象,优点比其之前的UI多出了很多设置,同时清晰度也会自动匹配变化,不会出现方法之后变糊的情况。
使用TMP前需要像之前的UI一样,需要使用新的接口,在开头行输入-using TMPro;
这样才可以调用TMP的组件以及其各种功能。
获取TMP组件的方式一般是用-gameObject.GetComponent<TextMeshProUGUI>();
而gameObject则是对应TMP的实例。需要在代码中实例化。
而修改text就直接使用就可以了。
一般代码类似于:
using TMPro;
public TextMeshProUGUI bossLife;
public GameObject LifeCount;LifeCount = GameObject.Find("bossLife");
rig = gameObject.GetComponent<Rigidbody>();//获取刚体对象
bossLife = LifeCount.GetComponent<TextMeshProUGUI>();
bossLife.text = "Wall : 100%";
对于Boss的血量就可以直接使用这一种方式简单呈现,其实波次也是一样,大部分都可以直接使用以上格式修改ui内容,实现想要的效果。
然后对于boss血量的计算:因为我分为了两种boss,一种是会自己消失的,蠕虫,以及固定血量的。分别由三种计算方式:
自己消失的Boss血量计算:
string tem = "King Slime : " + ((int)(((liveTime - (Time.time - timeSummon)) / liveTime) * 100)).ToString() + "%";
bossLife.text = tem;蠕虫boss的血量计算:
string tem = " ";
int num = 0;
for(int i = 1; i < 15; i++)
{
if (nowWorm[i] != null)
{
num += 2;
}
}
float numt = num / 30f;
tem = "Eater of Worlds : " + ((int)(numt * 100)).ToString() + "%";
bossLife.text = tem;固定血量Boss计算:
string tem = "Wall : " + (int)((health / 80f)*100) + "%";
bossLife.text = tem;
这样就可以实现简单的Boss血量显示的功能了。文章最后有完整的内容显示视频:
然后就是波次的代码:
波次代码和boss大同小异,定义好变量之后就可以了,以下为代码:
string tem = "WAVE : " + wave.ToString();
waveC.text = tem;
背景音乐
背景音乐分为两种,一种是BOSS的音乐,一种是过场的音乐,这两种需要分开来播放。为了不让一个脚本过于复杂,我新创建了一个脚本---MusicPlayer,管理音乐播放,直接关联到摄像机处,方便获取波次生成脚本的实例。
这里插入一下,根据书上所说的,unity最基本的单元式组件,而且GetComponent<>()这一个泛化函数可以实例化组件然后直接使用,因此按照我的看法,就是我们可以获取到组件的实例,然后读懂组件里面的所有属性,就可以使得开发过程很方便,就例如这一个音乐源的组件,在书上并没有教过,我觉得它也只是一个组件,因此可以直接实例化然后修改内容。
基于以上观点去设计的背景音乐功能,这是我的一种看法。
那么废话不多说,在脚本中获取之前写好的波次脚本,就可以根据波次修改音乐内容。
然后关于AudioSource组件,是需要在游戏对象里面添加的,初始是不会自带的。
这一个组件相当于一个多功能的音响,我们需要在里面放入一张唱片,让他播放起来,所以具有一个特殊的类: AudioClip,这就是“唱片类”,里面可以存放你的mp3文件,放入之后就可以通过打开音响的开关.Play().如果想要换碟或者停止播放就关掉开关.Stop()。还有很多方法包含在内,有需要可以查找文章或者前往unity官网学习。
根据以上的基础,设计出音乐播放器的代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class MusicPlayer : MonoBehaviour//这是一个播放音乐的脚本,放在摄像机里面
{
[Header("设置")]
public AudioSource musicPlayer;
public int index;
public AudioClip[] BackGroundMusic;
public AudioClip[] BossMusic;
public CactusSummoner cas;//记录是在摄像机里面的脚本
public int maxNum;
public bool isPlay;
// Start is called before the first frame update
void Start()
{
musicPlayer = gameObject.GetComponent<AudioSource>();//获取组件
cas = gameObject.GetComponent<CactusSummoner>();//获取脚本,这个脚本里面有boss的信息
index = 0;
maxNum = 5;
isPlay = false;
}// Update is called once per frame
void Update()
{
if (isPlay == false)//只有音乐变为未播放才会进入
{
if (musicPlayer.clip != null)
{
musicPlayer.Stop();//停止音乐
}if (cas.isBoss == true)
{
musicPlayer.clip = BossMusic[cas.BossNum];//播放对应boss的音乐
musicPlayer.Play();//播放音乐
isPlay = true;
}
else
{
musicPlayer.clip = BackGroundMusic[index];
musicPlayer.Play();
isPlay = true;
}
}
}
public void IndexPlus()//循环播放
{
if (index >= maxNum)
{
index = 0;
}
else
{
index++;
}
}
}
以上就是这一个音乐播放的脚本了。
第五个Boss
虽然设计了ui以及音乐播放器,我还是想要设计一个新的BOSS,这次设计的是泰拉瑞亚里面困难模式前最终boss--肉山,这一个boss其实挺无趣的,所以我也没有太大方式下手,而且我这一个游戏是相对运动的2d横板游戏,并不会实时更新地形,而泰拉瑞亚里面的肉山则是会一直追赶玩家的boss。没错我需要自己设计一些新的机制。
巫毒娃娃恶魔
在泰拉瑞亚里面,肉山的召唤需要将向导的巫毒娃娃丢入演讲,而这个娃娃由一种特殊的恶魔持有。我在想,要是只是让肉山出现,就好像有点太普通了,所以我想要加一个前置的东西,让玩家先击败带有巫毒娃娃的恶魔,让巫毒娃娃掉入岩浆里面,然后肉山出现。这样的演出也算是丰富了游戏内容。既然这样?那就来设计好这个演出吧!
首先恶魔的ai没什么,大部分是围着玩家转,时不时发射一把朝玩家飞去的镰刀。有一种让玩家觉得“这个boss怎么这么弱”的感觉,然后杀掉之后肉山突然出现。挺有意思。
这就是恶魔的代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class DevilMove : MonoBehaviour
{
[Header("设置")]
public GameObject Projectile;//镰刀对象
public GameObject Doll;
public Vector3 Pos;//记录自身位置
public GameObject Dino;//记录恐龙对象
public Vector3 dinoPos;//记录恐龙对象的位置
public float health;
public Rigidbody rig;
public Rigidbody proRig;//弹丸的刚体对象
public float timeNow;
public float timePast;//发射间隔
public Vector3 summonPos;//记录生成位置
public Vector3 StandNext;
private float rat = 0f;
private int facing = 1;//表示的是对象处于主角的前后方
private int lastfacing = 1;
public bool isDead;
// Start is called before the first frame update
void Start()
{
Pos = gameObject.transform.position;
Dino = GameObject.Find("dino");//获取恐龙对象
dinoPos = Dino.transform.position;
health = 10f;
timeNow = Time.time;
timePast = 2f;
rig = gameObject.GetComponent<Rigidbody>();
summonPos = new Vector3(32f, 8f, 0);
StandNext = Vector3.zero;
gameObject.transform.position = summonPos;//得到初始位置
Move();//进行第一次移动
}// Update is called once per frame
void Update()
{
if (health <= 0)
{
GameObject doll = Instantiate(Doll);
doll.transform.position = gameObject.transform.position;
proRig = doll.GetComponent<Rigidbody>();
proRig.velocity = Vector3.down * 3f;
Destroy(gameObject);
}
Pos = gameObject.transform.position;
FacingPlayer();
if(Time.time - timeNow >= timePast)
{
Move();
Attack();//攻击
}
if((Pos - StandNext).magnitude <= 0.2f)
{
rig.velocity = Vector3.zero;
}
}
void Move()
{
dinoPos = Dino.transform.position;//获取恐龙目前坐标
Vector3 tem = dinoPos;
tem.x += Random.Range(-1, 2);
tem.y += Random.Range(2, 4);//获取一个随机数
StandNext = tem;
Vector3 vel =tem - gameObject.transform.position;//获取一个速度
vel.Normalize();//单位化
rig.velocity = vel*3f;//获得一个速度
timeNow = Time.time;//重置时间
}
void FacingPlayer()//面向玩家
{
Vector3 pos = gameObject.transform.position;//获取自己位置
dinoPos = Dino.transform.position;
if (pos.x - dinoPos.x <= 0)//表示在后面
{
facing = -1;//更改朝向
}
else if (pos.x - dinoPos.x > 0)//表示在前面
{
facing = 1;//更改朝向
}if (facing != lastfacing)//两个朝向不一致
{
//print("trans aug");测试是否转角
rat += lastfacing * 180f;
gameObject.transform.rotation = Quaternion.Euler(0, rat, 0);//旋转180度
lastfacing = facing;//更改上一次朝向
}
}
void Attack()
{
dinoPos = Dino.transform.position;//获取恐龙坐标
Vector3 vel = Vector3.zero;
GameObject po = Instantiate(Projectile);
proRig = po.GetComponent<Rigidbody>();
po.transform.position = gameObject.transform.position;//获取坐标
vel = dinoPos - po.transform.position;//获取一个速度
vel.Normalize();
proRig.velocity = vel * 5f;
}
void OnTriggerEnter(Collider collider)//触发器
{
if (collider.tag == "projectile")
{
GameObject tem = collider.gameObject;//获取游戏对象
smilePro sp = tem.GetComponent<smilePro>();
health -= sp.Damage;
}
}
}
这样恶魔就会一边移动一边发射镰刀了。
恶魔死后会掉落一个巫毒娃娃。然后召唤肉山。
肉山与恶魔的控制台
自从蠕虫boss之后,我发现使用一个空对象作为“控制台”的思路能适用于很多情况,能做出很多类似于演出之类的效果。这次肉山的演出也是用一个控制台控制的。这个控制台将演出分为好几个等级,不同的等级进行不同的演出。第一等级:恶魔出现,第二等级:巫毒娃娃掉落,第三等级:肉山出现朝玩家快速冲刺,第四等级:肉山回到固定位置,开始朝玩家攻击,第五等级:肉山行动,第六等级:肉山死亡,控制台自毁
根据以上的思路,编写处以下的代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class wallControler : MonoBehaviour
{
[Header("设置")]
public GameObject Devil;
public GameObject wall;//分别记录三个对象
public Vector3 wallSummonPos;
public Rigidbody wallRig;
public int level;
public bool isDestroy;
public int isRush;
public GameObject wl;
public Vector3 wallStand;
// Start is called before the first frame update
void Start()
{
level = 1;//等级一代表恶魔生成
isDestroy = false;
wallSummonPos = new Vector3(32.7f,6f,0);
wallStand = new Vector3(30f, 6f, 0);
Instantiate(Devil);//生成恶魔
}// Update is called once per frame
void Update()
{
if (level < 3)
{
Check();//等级小于三的时候进入检查
}
else if (level == 3)
{
WallSummon();
}
else if (level == 4)
{
Vector3 wal =wl.transform.position;
if (wal.x <= 17.25f)
{
wl.transform.position = wallSummonPos;//回到原点
wallRig.velocity = Vector3.left * 2f;
level++;
}
}
else if (level == 5)
{
if ((wl.transform.position - wallStand).magnitude <= 0.2f)
{
wallRig.velocity = Vector3.zero;
level++;
}
}
else if(level == 6)
{
GameObject wl = GameObject.Find("Wall(Clone)");
if (wl == null)
{
Destroy(gameObject);
}
}
}
void Check()
{
GameObject de = GameObject.Find("Devil(Clone)");
if (de == null&&level==1)//恶魔被消灭
{
level++;//等级加一
}
if (level == 2)
{
de = GameObject.Find("Doll(Clone)");
if(de == null)
{
level++;
}
}
}
void WallSummon()//一个使得boss初始生成的方法
{
wl = Instantiate(wall);
wl.transform.position = wallSummonPos;//获得生成坐标
wallRig = wl.GetComponent<Rigidbody>();//获得刚体
wallRig.velocity = Vector3.left * 20f;
level++;
}
}
肉山行为
跟泰拉瑞亚本来的肉山行为类似的地方就是,都会冲向玩家。但是让玩家无故掉血这样的,可能有点可恶,因此我设计让肉山会生成一种小怪,会在玩家周围盘旋一段时间,要是玩家不小心触碰到他,就会将玩家牢牢固定住(无法移动),然后肉山就会朝玩家快速冲刺。强制玩家收到一次攻击。其他技能就是,肉山会发射激光,还有就是肉山会朝玩家目前位置冲刺,然后缓缓回到原点。这就是关于肉山最简单的设计了。
以下为小怪的代码,参考了玩家混乱的方式,直接将玩家脚本实例化,然后设置一个状态变量,要是为true就无法移动。同时朝肉山返回一个变量,让肉山冲刺。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class hungerMove : MonoBehaviour
{
[Header("设置")]
public GameObject dino;
public GameObject wall;
public Rigidbody rig;
public float timeNow;
public float timePast;
public float timeSum;
public bool isCatch;
// Start is called before the first frame update
void Start()
{
dino = GameObject.Find("dino");
wall = GameObject.Find("Wall(Clone)");
rig = gameObject.GetComponent<Rigidbody>();
timeNow = Time.time;
timeSum = Time.time;
isCatch = false;
timePast = 2f;
Move();
}// Update is called once per frame
void Update()
{
if(Time.time - timeSum >= 4f)
{
Dino dd = dino.GetComponent<Dino>();
dd.isCatch = false;
Destroy(gameObject);
}
if ((gameObject.transform.position - dino.transform.position).magnitude >= 0.2)
{
TrackPlayer();
}
if (isCatch == true)
{
rig.MovePosition(dino.transform.position);
}
if(Time.time - timeNow >= timePast)
{
Move();
timeNow = Time.time;
}
}
void OnTriggerEnter(Collider collider)//触发器
{
if (collider.tag == "dino")
{
rig.velocity = Vector3.zero;
GameObject di = collider.gameObject;
Dino dd = dino.GetComponent<Dino>();
if (dd.isCatch == true)
{
return;
}
dd.isCatch = true;
wallMove wal = wall.GetComponent<wallMove>();
wal.Catch = true;
isCatch = true;
}
}
void TrackPlayer()//这个函数其实逻辑跟上面那个基本一致,应该可以用授权的方式写,但是我没有这样(不是好参考
{
Vector3 dinoPos = dino.transform.position;
Vector3 wormPos = gameObject.transform.position;//获取玩家当前位置
float length = (dinoPos - wormPos).magnitude;//获得长度
float Ang = Mathf.Abs((dinoPos.y - wormPos.y) / length);//获得一个sin值
float A2 = (Mathf.Asin(Ang) * Mathf.Rad2Deg);//反sin函数加上弧度制获得角度
if (wormPos.y >= dinoPos.y)//假设蠕虫在所需要点的上方
{
A2 += 90f;//加上九十度
}
//在左边
//计算确切角度;
if (wormPos.y <= dinoPos.y)//在下方需要修改
{
A2 = 90f - A2;
}
if (wormPos.x <= dinoPos.x)//在左边
{
gameObject.transform.rotation = Quaternion.Euler(0, 0, -A2);
}
else
{
gameObject.transform.rotation = Quaternion.Euler(0, 0, A2);
}
}
void Move()
{
Vector3 dinoPos = dino.transform.position;
dinoPos.x += Random.Range(-1, 2);
dinoPos.y += Random.Range(1, 3);
Vector3 vel = dinoPos - gameObject.transform.position;
vel.Normalize();
rig.velocity = vel * 5f;
}
}
这就是小怪的代码了。
这有一个会使得对象看向玩家的代码,我发现很多都需要这样的代码,我把它包装成一个独立脚本了:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerTrack : MonoBehaviour
{
[Header("设置")]
public GameObject dino;
public Vector3 dinoPos;
// Start is called before the first frame update
void Start()
{
dino = GameObject.Find("dino");
dinoPos = dino.transform.position;
TrackPlayer();
}// Update is called once per frame
void Update()
{
}
void TrackPlayer()//眼睛紧跟着玩家
{
Vector3 eyePos = gameObject.transform.position;//获取眼睛当前位置
dinoPos = dino.transform.position;//获取玩家坐标
float length = (dinoPos - eyePos).magnitude;//获得长度
float Ang = Mathf.Abs((dinoPos.y - eyePos.y) / length);//获得一个sin值
float A2 = (Mathf.Asin(Ang) * Mathf.Rad2Deg);//反sin函数加上弧度制获得角度
if (eyePos.y <= dinoPos.y)//假如在下方
{
A2 += 90f;//加上九十度
}
//在左边
//计算确切角度;
if (eyePos.y >= dinoPos.y)//在上方需要修改
{
A2 = 90f - A2;
}
if (eyePos.x <= dinoPos.x)//在左边
{
gameObject.transform.rotation = Quaternion.Euler(0, 0, A2);
}
else
{
gameObject.transform.rotation = Quaternion.Euler(0, 0, -A2);
}}
}
肉山本身的代码:
肉山的代码就跟上面说的一样,与之前设置的boss没有太多区别:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;public class wallMove : MonoBehaviour//这里就是肉山boss的主要脚本
{
[Header("设置")]
public GameObject Hunger;//小弟,恶鬼
public GameObject laser;//激光
public GameObject dino;
public Vector3 dinoPos;
public float health;
public Vector3 vel;
public int roll;
private bool isRush;
public bool isBack;
public float timeNow;
public float timePast;
public float laserTimePast;
public float laserTimeNow;
public Vector3 RushPos;
private int laserNum;
public bool Catch;//独特机制
public bool dashBack;
private bool dashNow;
private Rigidbody rig;
[Header("UI设置")]
public TextMeshProUGUI bossLife;
public GameObject LifeCount;
// Start is called before the first frame update
void Start()
{
dino = GameObject.Find("dino");//以下全为初始化设置
dinoPos = dino.transform.position;
health = 80f;
vel = Vector3.zero;
roll = 0;
isRush = false;
isBack = false;
Catch = false;
dashNow = false;
dashBack = false;
timeNow = Time.time;
timePast = 2f;
laserTimeNow = timeNow;
laserTimePast = 0.2f;
laserNum = 0;
LifeCount = GameObject.Find("bossLife");
rig = gameObject.GetComponent<Rigidbody>();//获取刚体对象
bossLife = LifeCount.GetComponent<TextMeshProUGUI>();
bossLife.text = "Wall : 100%";
}// Update is called once per frame
void Update()
{
UpDateUI();
if (health <= 0)
{
bossLife.text = " ";
Destroy(gameObject);
}
if (Catch == true)//玩家被抓住
{
Dash();//冲刺
}
if (Time.time - timeNow >= timePast&&Catch==false)
{
Move();
}
}
void Move()
{
switch (roll)
{
case 0://第一个技能
if (Time.time - laserTimeNow >= laserTimePast)
{
laserTimeNow = Time.time;//重置时间
Vector3 laserPos = gameObject.transform.position;
laserPos.x -= 1.3f;laserPos.y += 2f;
GameObject la = Instantiate(laser);
la.transform.position = laserPos;
Rigidbody laR = la.GetComponent<Rigidbody>();
Vector3 vel = dino.transform.position - laserPos;
vel.Normalize();laR.velocity = vel*20f;
laserNum++;
}
if (laserNum >= 5f)
{
laserNum = 0;
timeNow = Time.time;
laserTimeNow = Time.time;
roll = Random.Range(0, 3);
}
break;
case 1://第二种攻击,生成小鬼
for(int i = 0; i < 2; i++)
{
GameObject hu = Instantiate(Hunger);
hu.transform.position = gameObject.transform.position;//获得boss位置
//hu有独立脚本无需要做其他处理
}
timeNow = Time.time;//重置时间
roll = Random.Range(0, 3);
break;
case 2://朝玩家冲刺的
if (isRush == false)
{
isRush = true;
rig.velocity = Vector3.left * 10f;
RushPos = dino.transform.position;//获取一个方位
}
if(isRush == true)
{
Vector3 pos = gameObject.transform.position;
if((pos.x - RushPos.x) <= 1f)
{
isBack = true;
rig.velocity = Vector3.zero;//速度下降为0
rig.velocity = Vector3.right * 5f;//回去原来的位置
RushPos = new Vector3(30f, 6f, 0);
}
}
if (isBack == true)
{
if(((gameObject.transform.position - RushPos).magnitude) <= 0.2f)
{
rig.velocity = Vector3.zero;
isBack = false;isRush = false;
timeNow = Time.time;
roll = Random.Range(0, 3);
}
}
break;
default:
break;
}
}
void Dash()
{
Vector3 pos = gameObject.transform.position;
if (dashBack == true)
{
if (pos.x <= 30f)
{
rig.velocity = Vector3.zero;
Catch = false;
dashBack = false;
dashNow = false;
timeNow = Time.time;
roll = 0;
return;
}
}
if (pos.x <= 17.25)
{
gameObject.transform.position = new Vector3(32.7f, 6f, 0);
rig.velocity = Vector3.left * 5f;
dashBack = true;
}
if (dashNow == false)
{
rig.velocity = Vector3.left * 20f;
dashNow = true;
}
}
void OnTriggerEnter(Collider collider)//触发器
{
if (collider.tag == "projectile")
{
GameObject tem = collider.gameObject;//获取游戏对象
smilePro sp = tem.GetComponent<smilePro>();
health -= sp.Damage;
}
}
void UpDateUI()
{
string tem = "Wall : " + (int)((health / 80f)*100) + "%";
bossLife.text = tem;
}
}
这样,三个新功能就设计好啦!!
以下是使用的贴图:
以下为实例场景:
以下为测试的完整视频:
(审核中)
单肉山的测试视频:
(审核中)
结语
终于完成本次练习啦,这次用了不少新的组件,我还会继续学习,不断添加新内容,感谢大家支持!
标签:Unity3d,背景音乐,google,gameObject,Vector3,transform,GameObject,position,public From: https://blog.csdn.net/m0_67738084/article/details/136781229