PS:该案例来自学习的课本和我自己的理解。首次文章创作,还请多多支持呀各位美女、帅哥们!!!
*废话不多说先上游戏实机操作视频*
射击游戏实机操作_Trim
游戏内容大致如下:
(1)完全使用键盘控制,由W、A、S、D键控制角色的方向移动,J键控制射击。(这样做主要是为了简化游戏输入部分的逻辑。)
(2)玩家具有多种武器,如手枪、霞弹枪和自动步枪,每种武器可以按Q键切换。
(3)场景上除了玩家角色还有若干敌人。敌人会向玩家方向移动并射击玩家。
(4)玩家角色和敌人都有生命值,中弹后生命值减少,减为零时则死亡。
那么接下来就看看实现过程吧!文末有游戏半成品分享,不嫌弃就玩玩看哈。
目录
1.角色脚本
2.跟随摄像机
3.武器系统
武器
子弹
4.射击的实现
5.游戏全局管理器
场景中,平台平面设为20x20的Plane
1.角色脚本
首先我们要创建出游戏玩家和敌人,包含(生命、移速等)属性,并给予他们特定的移动机制如下
玩家:自由移动,但活动限制在平台区域。
敌人:始终朝向敌人移动,但速度相对较慢。
玩家脚本(Player)代码如下:
//移动速度
public float c_speed;
//最大血量
public float m_maxHp ;
//输入方向变量
Vector3 c_input;
//判断是否死亡
bool LifeState=true;
//当前血量
private float c_Hp;
void Start()
{
//刚开始满血
c_Hp = m_maxHp;
}
void Update()
{
//将键盘的横向、纵向输入,保存在input中
c_input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
bool fireKeyDown = Input.GetKeyDown(KeyCode.J);
bool fireKeyPressed = Input.GetKey(KeyCode.J);
bool changeWeapon = Input.GetKeyDown(KeyCode.Q);
//判断是否dead,还活着就可以动
Debug.Log("现在还剩血量:" + c_Hp);
void Move()
{
//先归一化输入变量,让输入更直接,避免同时斜向移动时速度超过最大值
c_input =c_input.normalized;
transform.position += c_input * c_speed * Time.deltaTime;
//令角色前方与移动方向一致
if (c_input.magnitude > 0.1f)
{
transform.forward = c_input;
}
//限制移动范围
Vector3 c_VectorTemp=transform.position ;
const float m_border = 20f;
if(c_VectorTemp.x < -m_border)
{
c_VectorTemp.x = -m_border;
}
if (c_VectorTemp.x > m_border)
{
c_VectorTemp.x = m_border;
}
if (c_VectorTemp.z < -m_border)
{
c_VectorTemp.z = -m_border;
}
if (c_VectorTemp.z > m_border)
{
c_VectorTemp.z = m_border;
}
transform.position = c_VectorTemp;
}
}
敌人脚本(Enemy)代码如下:
public float speed = 6;
public float maxHp=4;
Vector3 input;
Transform player;
float hp;
bool lifeState = true;
void Start()
{
player = GameObject.Find("Player").transform;
hp = maxHp;
}
void Update()
{
EnemyMove();
Debug.Log("敌人还剩血量:" + hp);
}
//敌人的移动机制
void EnemyMove()
{
//敌人的移动是始终朝向玩家的,他想干掉我们
input = player.position - transform.position;
input = input.normalized;
transform.position += input * speed * Time.deltaTime;
if (input.magnitude > 0.1f)
{
transform.forward = input;
}
}
2.跟随摄像机
将摄像机调整至合适位置后,利用三维向量的差值,以玩家为原点为摄像机变换位置
赋予主摄像机如下脚本(FollowPlayer)代码 :
public Transform followTaeget;
Vector3 offset;
void Start()
{
//设置偏移量
offset = transform.position - followTaeget.position;
}
void LateUpdate()
{
transform.position = followTaeget.position + offset;
}
上面代码中,摄像机的移动放在LateUpdate事件中,避免了帧数问题引起的游戏视角界面卡顿。
3.武器系统
敲黑板了!!!!!! 这一块灰常的重要哦。武器系统是玩家和敌人公用的一块。
武器
0.手枪:单射,有射击间隔 ,我感觉没啥用的一把枪。
1.霰弹枪:单射且同时射出五发子弹,射程短的可怜!
2.自动步枪:连射,用起来挺爽的!
子弹
创建两个预制体分为PlayerBullet和EnemyBullet
属性:具有生命周期
这里先实现武器系统组件脚本,代码如下:
public class Weapon : MonoBehaviour
{
//子弹的预制体
public GameObject prefabBullet;
//几种武器的CD长度
public float pistolFireCD = 0.2f;
public float shotgunFireCD = 0.5f;
public float rifleFireCD = 0.1f;
//上次开火时间
float lastFireTime;
//当前的武器
public int curGun // 0:手枪,1:霰弹枪,2:自动步枪
{
get;
private set;
}
//设置开火函数
//keyDown指代单射,keyPressed指代连射
public void Fire(bool keyDown,bool keyPressed)
{
switch (curGun)
{
case 0:
if (keyDown)
{
PistolFire();
}
break;
case 1:
if (keyDown)
{
ShotgunFire();
}
break;
case 2:
if(keyPressed)
{
RifleFire();
}
break;
}
}
//更换武器
public int Change()
{
if (curGun != 3)
{
curGun++;
}
else
{
curGun = 0;
}
return curGun;
}
//手枪射击专用函数
public void PistolFire()
{
if (lastFireTime + pistolFireCD > Time.time)
return;
lastFireTime = Time.time;
GameObject bullet = Instantiate(prefabBullet, null);
bullet.transform.position = transform.position + transform.forward * 1f;
bullet.transform.forward = transform.forward;
}
//霰弹射击专用函数
public void ShotgunFire()
{
if (lastFireTime + shotgunFireCD > Time.time)
return;
lastFireTime = Time.time;
//创建5颗子弹,相互间间隔10度,分布于前方扇形区域
for(int i = -2; i <= 2; i++)
{
GameObject bullet = Instantiate(prefabBullet, null);
Vector3 dir = Quaternion.Euler(0, i * 10, 0) * transform.forward;
bullet.transform.position = transform.position + dir * 1f;
bullet.transform.forward = dir;
//霰弹枪的子弹射击距离很短,因此修改子弹的生命周期
Bullet b = bullet.GetComponent<Bullet>();
b.lifeTime = 0.3f;
}
}
//自动步枪射击专用函数
public void RifleFire()
{
if (lastFireTime + rifleFireCD > Time.time)
return;
lastFireTime = Time.time;
GameObject bullet = Instantiate(prefabBullet, null);
bullet.transform.position = transform.position + transform.forward * 1.2f;
bullet.transform.forward = transform.forward;
}
void Start()
{
}
void Update()
{
}
}
接下是为子弹创建脚本,代码如下:
public class Bullet : MonoBehaviour
{
public float c_bulletSpeed = 10f;
//设置子弹的生命周期
public float lifeTime = 2;
//子弹生成的时间
float startTime;
void Start()
{
startTime = Time.time;
}
void Update()
{
//子弹移动
transform.position+= c_bulletSpeed * transform.forward * Time.deltaTime;
//自毁装置
if (startTime + lifeTime < Time.time)
Destroy(gameObject);
}
//中弹事件
private void OnTriggerEnter(Collider other)
{
//同类子弹碰撞不销毁
if (CompareTag(other.tag))
return;
Destroy(gameObject);
}
}
上面的“中弹事件”在后文的射击的实现会用到滴!
4.射击的实现
有了武器,那么敌人和玩家的脚本都可以进行更新了,运用武器系统实例对象实现攻击。同时,在相互射击的过程中,如果一方中弹,则扣一滴血。
己方子弹射中自己不造成伤害,子弹击中对方后消失,子弹相互碰撞也消失。
子弹碰撞逻辑:
碰撞事件是相对的,因此子弹碰撞的逻辑可以写在 Bullet 脚本、Player脚本或 Enemy 脚本中。在制作时最好单独考虑碰撞问题,然后统一编写。要制作的效果如下。
1、玩家角色的子弹击中敌人角色,会让敌人角色掉血。
2、敌人角色的子弹击中玩家角色,会让玩家角色掉血。
3、玩家角色的子弹不会击中玩家角色,敌人角色的子弹也不会击中敌人角色。
4、玩家角色的子弹与敌人角色的子弹可以相互抵消,但是同类子弹不能抵消。这一设计可以根据读者的偏好添加或取消。
从以上分析可以看出,“敌人角色的子弹”与“玩家角色的子弹”是截然不同的,最好用某种机制区分出二者。课本作者采用的方法是把这两种子弹做成两个不同的预制体,一个命名为PlayerBullet,另一个命名为EnemyBullet。然后利用标签(Tag)做出区分,前者的Tag是PlayerBullet,后者的Tag是EnemyBullet。
那么,请在之前的Player脚本中添加如下代码:
Weapon weapon;
void Start()
{
//给武器类对象变量引用脚本
weapon = GetComponent<Weapon>();
}
void Update()
{
if (LifeState==true )
{
Move();
weapon.Fire(fireKeyDown, fireKeyPressed);
if (changeWeapon)
{
weapon.Change();
}
}
else { Time.timeScale = 0; }
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("EnemyBullet"))
{
if (c_Hp <= 0) { return; }
c_Hp-=1;
if (c_Hp == 0) { LifeState = false; }
}
}
相应的,对敌人的脚本(Enemy)也添加如下代码:
public float fireTime = 1f;
float m_time ;
Weapon weapon;
void Start()
{
weapon = gameObject.GetComponent<Weapon>();
m_time = Time.time;
}
void Update()
{
EnemyMove();
endFire();
Debug.Log("敌人还剩血量:" + hp);
if (hp <= 0)
{
// Instantiate(prefabBoomEffect, transform.position, transform.rotation);
Destroy(gameObject);
Debug.Log("恭喜你赢了!重要的事情说三遍");
Debug.Log("恭喜你赢了!重要的事情说三遍");
Debug.Log("恭喜你赢了!重要的事情说三遍");
}
}
//控制攻击间隔
void endFire()
{
if (m_time + fireTime < Time.time)
{
Fire();
m_time = Time.time;
}
}
//敌人的攻击机制
void Fire()
{
weapon.Fire(true, true);
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("PlayerBullet"))
{
if (hp <= 0) { return; }
hp--;
if (hp == 0) { lifeState = false; }
}
}
5.游戏全局管理器
我还不会这一部分,那咱们就总结一下吧!【doge】
游戏的脚本一共是涉及下面六个
涉及的必要游戏物体有Player、Enemy、Plane
游戏半成品下载地址:https://pan.baidu.com/s/1FtJMS23XGMH4FB2TKnuXDA
提取码:PZPZ
PS:终于写完了,睡觉了,困死了!!!!!!!