草庐IT

Unity之简单射击游戏案例

胖崽配猪 2023-05-26 原文

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:终于写完了,睡觉了,困死了!!!!!!! 

有关Unity之简单射击游戏案例的更多相关文章

  1. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  2. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  3. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  4. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

    我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

  5. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  6. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  7. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  8. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  9. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  10. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

随机推荐