Unity3D坦克大战游戏开发——学习笔记(中)
11、子弹朝向问题的解决在坦克大战的学习笔记上中,我们解决了子弹的朝向问题,这里就不介绍了,直接进入下一节的学习。12、加入攻击CD,触发器与碰撞器的区别13、制作空气墙,添加标签14、编写玩家无敌方法和死亡方法15、区别玩家与敌人的子弹16、修复BUG,制作敌人的子弹17、制作敌人,添加玩家产生效果18、用同样的产生特效随机生成两种敌人19、编写敌人AI20、修复敌人BUG...
11、子弹朝向问题的解决
在坦克大战的学习笔记上中,我们解决了子弹的朝向问题,这里就不介绍了,直接进入下一节的学习。
12、加入攻击CD,触发器与碰撞器的区别
在解决了子弹朝向问题后,我们现在就需要让子弹动起来了,对子弹添加一个脚本控制他的运动。
public float moveSpeed = 10;//子弹移动速度
void Update()
{
transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);
}
然后在PlayerAI里面加入攻击CD的功能,我们在Update里面调用攻击函数,不在FixedUpdate里面调用了:
private float timeVal;//子弹发射计时器
void Update()//每一帧的处理时间不同
{
if (timeVal >= 0.4f)
{
TankAttack();
}
else
timeVal += Time.deltaTime;
}
private void TankAttack()//坦克攻击函数
{
if(Input.GetKeyDown(KeyCode.Space))//如果玩家按下了空格键
{
//子弹的角度=坦克的角度+子弹应该旋转的角度
Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles+ bulletEulerangles));//子弹进行实例化
timeVal = 0;
}
}
然后就可以看到坦克攻击有CD限制,不能连续发射导弹:
因为我们不需要子弹有碰撞的效果,所以我们给子弹设置触发器组件。碰撞器顾名思义是检测碰撞的,在碰撞时会产生力的效果,常常会阻挡物体通过,而触发器则是在碰撞的时候响应一个特定的函数,不一定会阻碍物体通过,往往是一些别的操作。
我们给Bullet的预制体添加触发器和刚体组件:
然后进入到BulletAI脚本进行触发器触发函数的编写,因为我们是2D游戏,所以主要通过OnTriggerEnter2D、OnTriggerExit2D和OnTriggerStay2D这三个函数进行实现。
13、制作空气墙,添加标签
为了能够实现子弹碰到不同物体有不同的反映,我们需要对场景内的物体设置标签,遇到了某个物体,通过标签识别是什么对象,然后实现相应的方法与事件。
标签的设定主要通过Tag的Add Tag进行新建标签,我们设置一些需要的标签,在Tag换上对应的标签即可。
然后我们现在给游戏做一个空气墙边界,让坦克和子弹都无法透过空气墙,我们复制Barrier的预制体,把他的Sprite Renderer组件给移除掉,放在四周,这样就形成了空气墙。
14、编写玩家无敌方法和死亡方法
我们首先编写一下玩家死亡的方法,挡子弹触碰到Tank也就是我们玩家时,会死亡,我们在PlayerAI里面写入坦克死亡函数,死亡时还会有爆炸特效,所以我们把死亡后爆炸特效也实现一下。
public GameObject explosionPrefab;//爆炸特效的预制体
private void TankDie()//坦克的死亡方法
{
if (isDefended)//如果玩家无敌则不会死亡
return;
//产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
//死亡
Destroy(gameObject);
}
然后在BulletAI脚本里面的碰撞检测函数里面调用方法
private void OnTriggerEnter2D(Collider2D collision)
{
switch(collision.tag)//根据碰撞物体的标签来响应事件
{
case "Barrier":
break;
case "Grass":
break;
case "Heart":
break;
case "Wall":
break;
case "Tank":
collision.SendMessage("TankDie");
break;
case "Enemy":
break;
default:
break;
}
}
另外爆炸特效在爆炸完之后还要进行销毁,我们新建一个脚本挂到爆炸特效的预制体上
void Start()
{
Destroy(gameObject, 0.167f);//在0.167s的爆炸后删除特效
}
这样我们就完成了死亡的函数,接下来坦克在开始出生时会有无敌的时间,我们设置一下,并且无敌时会有外面的一个护盾特效,我们也实现一下护盾的效果:
private bool isDefended=true;//玩家是否无敌状态
private float defendTimeVal=0;//玩家无敌时间
public GameObject defendPrefab;//保护特效的预制体
void Update()//每一帧的处理时间不同
{
//是否无敌
if(isDefended)
{
defendPrefab.SetActive(true);//无敌则显示护盾特效
defendTimeVal += Time.deltaTime;
if (defendTimeVal>=3)//无敌时间过了就设置状态为不无敌
{
defendPrefab.SetActive(false);//取消护盾显示
isDefended = false;
defendTimeVal = 0;
}
}
//设置攻击CD为0.4s
if (timeVal >= 0.4f)
{
TankAttack();
}
else
timeVal += Time.deltaTime;
}
我们将护盾预制体拖到玩家的预制体身上,然后当无敌时间存在时让他显示,无敌事件过了就不显示,可以看到3s后护盾消失。
15、区别玩家与敌人的子弹
玩家的子弹碰到玩家时应该是不会造成死亡的,敌人的子弹碰到敌人也不会死亡,那么我们就需要区分子弹时敌人的还是玩家的,我们在BulletAI脚本里面设置一个isPlayerBullet变量用于区别。
public bool isPlayerBullet;//判断是否为玩家的子弹
private void OnTriggerEnter2D(Collider2D collision)
{
switch(collision.tag)//根据碰撞物体的标签来响应事件
{
case "Barrier":
Destroy(gameObject);//销毁子弹
break;
case "AirBarrier":
Destroy(gameObject);//销毁子弹
break;
case "Grass":
break;
case "Heart":
collision.SendMessage("HeartDie");
break;
case "Wall":
Destroy(collision.gameObject);//销毁墙
Destroy(gameObject);//销毁子弹
break;
case "Tank":
if(isPlayerBullet)
collision.SendMessage("TankDie");
break;
case "Enemy":
break;
default:
break;
}
}
然后我们新建一个脚本挂载到我们的老家Heart上,用来检测是否被攻击了,攻击则触发死亡事件
private SpriteRenderer spriteRenderer;//渲染的组件
public Sprite HeartBroken;//死亡图片
// Start is called before the first frame update
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();//获取渲染的组件
}
// Update is called once per frame
void Update()
{
}
public void HeartDie()//老家中枪,游戏结束
{
spriteRenderer.sprite = HeartBroken;
}
下面是效果:
16、制作敌人的子弹
我们将之前的子弹预制体分为敌人的和玩家的,分别设置IsPlayerBullet属性却分开来:
17、制作敌人,添加玩家出生效果
我们首先创建敌人的预制体,选择好渲染的图片源,生成两个不同的敌人。然后添加脚本EnemyAI来控制敌人的移动:
我们先完善一下之前子弹攻击完老家之后的一个爆炸效果,在HeartAI脚本里面进行编写:
public GameObject explosionPrefab;//爆炸特效的预制体
public void HeartDie()//老家中枪,游戏结束
{
spriteRenderer.sprite = HeartBroken;
Instantiate(explosionPrefab, transform.position, transform.rotation);
}
接着我们添加一下玩家出生的一个特效,新建一个Born脚本,挂载到Born特效上:
//引用
public GameObject playerPrefab;//玩家的预制体
// Start is called before the first frame update
void Start()
{
Invoke("TankBorn", 1f);//延迟调用1s后产生坦克
Destroy(gameObject, 1f); //延迟1s销毁出生特效
}
// Update is called once per frame
void Update()
{
}
private void TankBorn()//产生坦克
{
Instantiate(playerPrefab, transform.position, Quaternion.identity);//实例化玩家
}
然后将玩家的预制体拖到脚本的playerPrefab上,就可以在出生特效那里产生玩家了。
18、用同样的产生特效随机生成两种敌人
敌人制作好了,我们需要用同样的产生特效也生成敌人。
敌人的AI运动我们先暂时copy一份玩家的AI,然后给敌人设置必要的一些预制体:
然后我们修改Born脚本,并且给Born挂上敌人的预制体:
//属性
public bool createPlayer;//是否产生玩家
//引用
public GameObject playerPrefab;//玩家的预制体
public GameObject[] enemyPrefabList;//敌人预制体的列表
// Start is called before the first frame update
void Start()
{
Invoke("TankBorn", 1f);//延迟调用1s后产生坦克
Destroy(gameObject, 1f); //延迟1s销毁出生特效
}
// Update is called once per frame
void Update()
{
}
private void TankBorn()//产生坦克
{
if(createPlayer)
Instantiate(playerPrefab, transform.position, Quaternion.identity);//实例化玩家
else
{
int num = Random.Range(0, 2);
Instantiate(enemyPrefabList[num], transform.position, Quaternion.identity);//实例化敌人
}
}
通过勾选CreatePlayer来设置是让敌人生成还是玩家生成:
19、编写敌人AI
这小节我们主要来编写敌人的AI,敌人的移动肯定不能通过玩家控制,而是需要他自己移动或者攻击,我们通过编写EnemyAI实现。
//属性值
public float moveSpeed = 3;//坦克的移动速度
private Vector3 bulletEulerangles;//子弹应该旋转的欧拉角
private float v;//敌人垂直位移
private float h;//敌人水平位移
//计时器
private float timeVal=0f;//子弹发射计时器
private float timeValE;//敌人子弹发射计时器
private float DirChangeTime=4f;//改变方向的时间间隔
//引用
private SpriteRenderer spriteRender;//坦克的渲染组件
public Sprite[] tankSprite;//坦克的精灵 上右下左
public GameObject bulletPrefab;//子弹的预制体
public GameObject explosionPrefab;//爆炸特效的预制体
Stack<float> directionKey = new Stack<float>();
private void Awake()
{
spriteRender = GetComponent<SpriteRenderer>();//获取坦克的渲染组件
timeValE = (float)Random.Range(1, 5);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()//每一帧的处理时间不同
{
//设置攻击时间间隔
if (timeVal >= timeValE)
{
TankAttack();
timeValE= Random.Range(1, 5);
}
else
timeVal += Time.deltaTime;
}
private void FixedUpdate()//生命周期函数,在Update之后执行,固定了每一帧的时间
{
TankMove();
}
private void TankMove()//坦克移动控制函数
{
if (DirChangeTime >= 3)
{
int num = Random.Range(0, 8);
if (num > 5)//坦克向下走
{
v = -1;
h = 0;
}
else if (num == 0)//坦克向上走
{
v = -1;
h = 0;
}
else if (num > 0 && num <= 2) //坦克向左走
{
v = 0;
h = -1;
}
else if (num > 2 && num <= 4)//坦克向下走
{
v = 0;
h = 1;
}
DirChangeTime = 0;
}
else
DirChangeTime += Time.fixedDeltaTime;
//开始移动
transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);//垂直移动
//调整坦克的朝向图片
if (v < 0)
{
spriteRender.sprite = tankSprite[2];
bulletEulerangles = new Vector3(0, 0, 180);
}
else if (v > 0)
{
spriteRender.sprite = tankSprite[0];
bulletEulerangles = new Vector3(0, 0, 0);
}
if (v != 0)//如果已经按下了水平方向,则不允许有垂直操作,直接返回
return;
//开始移动
transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);//水平移动
//调整坦克的朝向图片
if (h < 0)
{
spriteRender.sprite = tankSprite[3];
bulletEulerangles = new Vector3(0, 0, 90);
}
else if (h > 0)
{
spriteRender.sprite = tankSprite[1];
bulletEulerangles = new Vector3(0, 0, -90);
}
}
private void TankAttack()//坦克攻击函数
{
//子弹的角度=坦克的角度+子弹应该旋转的角度
Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bulletEulerangles));//子弹进行实例化
timeVal = 0;
}
private void TankDie()//坦克的死亡方法
{
//产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
//死亡
Destroy(gameObject);
}
然后给BulletAI里面添加子弹碰到玩家和敌人时不同的效果:
private void OnTriggerEnter2D(Collider2D collision)
{
switch(collision.tag)//根据碰撞物体的标签来响应事件
{
case "Barrier":
Destroy(gameObject);//销毁子弹
break;
case "AirBarrier":
Destroy(gameObject);//销毁子弹
break;
case "Grass":
break;
case "Heart":
collision.SendMessage("HeartDie");
Destroy(gameObject);//销毁子弹
break;
case "Wall":
Destroy(collision.gameObject);//销毁墙
Destroy(gameObject);//销毁子弹
break;
case "Tank":
if(!isPlayerBullet)
collision.SendMessage("TankDie");//玩家死亡
Destroy(gameObject);//销毁子弹
break;
case "Enemy":
if(isPlayerBullet)
collision.SendMessage("TankDie");//敌人死亡
Destroy(gameObject);//销毁子弹
break;
default:
break;
}
}
下面是设计好的敌人AI自动运动与发射子弹的效果,因为没有设置游戏边界,所以最后他们都跑不见了。
20、修复敌人BUG
这里的BUG我们上一小节解决了,只需要把EnemyAI中的垂直和水平输入语句去掉就可以了。
更多推荐
所有评论(0)