使用团结引擎开发Unity 3D射击游戏

一、前言

       本案例是初级案例,意在引导想使用unity的初级开发者能较快的入门,体验unity开发的方便性和简易性能。

      本次我们将使用团结引擎进行开发,帮助想体验团结引擎的入门开发者进行较快的环境熟悉。

     本游戏是一个俯视角度的射击游戏。主角始终位于屏幕中心位置,玩家使用键盘控制主角移动,并可开枪射击场景中的敌人。玩家具有多种武器,如手枪、霰弹枪和自动步枪,玩家可以切换武器。敌人也会向玩家方向移动并射击玩家。当玩家角色或敌人的生命值减为零时则死亡。当玩家死亡后,游戏结束。

二、开发环境

        操作系统:Windows

        Unity 版本:团结引擎1.0.0

        团结引擎的安装参考《团结引擎的安装》

三、场景搭建

     3.1 创建项目

        使用团结引擎新建项目和使用Unity Hub一样。打开Tuanjie Hub,点击左上角【create project】按钮,选择【3D】模版,输入项目名称和项目保存路径,点击右下【Create project】按钮完成创建。

         3.2 搭建场景

        1. 我们创建一个plane作为地面 。在Hierarchy面板上依次【右键】-> 【3D Object】->【plane】,将【Plane】创命名为【Ground】,并将x、z缩放大小变为4。我们将地面颜色改成灰色。新建文件夹,命名为【Materals】用来存放材质。在材质文件夹右键,依次选择【Create】->【Material】, 并命名为【Ground】。修改材质颜色为灰色(颜色值:4B4747),并将材质拖拽到地面上。

        2. 同样, 我们创建一个胶囊体(Capsule)来作为主角,并命名为Player。创建一个立方体(Cube)放置在Player上,并调整到合适的位置。Cube将作为Player的正面,以便分辨方向。创建一个材质,命名为Face,改变材质样色为蓝色(也可以设置成自己喜欢的颜色),然后把这个材质赋值给Cube。

        3.  调整摄像机角度

        由于我们是俯视角度的射击游戏,因此需要调整摄像机的位置。选中摄像机,将摄像机调整到Player后方合适的位置。

        4. 创建敌人 

        敌人角色与玩家角色造型基本一致,只是将白色换成红色。我们复制场景中的Player(选中Player, 按 ctrl + D),命名为Enermy。然后创建一个材质Enermy, 然后将材质的颜色改为红色。把材质赋给场景中的Enermy物体。新建标签(Tag),命名为Enermy,将玩家Player的Tag设置为“Player”, Enermy 的Tag设置为“Enermy”,方便区分玩家和敌人。之后我们新建文件夹,命名为 Prefabs,将Enermy制作成预知体(直接把Hierarchy中的Enermy物体拖到Prefabs文件夹中)。

 四、编写脚本,实现功能

        上面基本场景已经搭建完毕,接下来我们就开始编写脚本,实现我们的游戏功能。

        4.1 让玩家动起来

        首先,我们来编写玩家脚本,让玩家动起来。新建文件夹,命名为【Scripts】,用来保存我自己编写的脚本文件。在【Scripts】右键->【Create】->【C# Script】,命名为Player。然后将Player脚本挂载到Player物体上。

         和《3D小球跑酷》类似,我们使用键盘来控制Player的移动。双击打开Player脚本,输入以下内容:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed = 10f;  // 玩家的移动速度
    public int maxHp = 10;    // 玩家的最大血量


    private int hp;      // 玩家的当前血量
    private bool idDead = false;



    void Start()
    {
        hp = maxHp;  // 初始血量为最大值
    }

    void Update()
    {
        if (!idDead)
        {
            Move();
        }
    }

    void Move()
    {
        var input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));  // 接受键盘输入
        input = input.normalized;
        Vector3 currentPos = transform.position;
        currentPos += input * speed * Time.deltaTime;

        if (input.magnitude > 0.1f)
        {
            transform.forward = input;   // 使玩家面向移动方向
        }

        // 限制玩家的移动范围,范围为方圆20以内
        const float distance = 20f;

        if (currentPos.x > distance)
            currentPos.x = distance;

        if (currentPos.x < -distance)
            currentPos.x = -distance;

        if(currentPos.z > distance)
            currentPos.z = distance;

        if(currentPos.z < -distance)
            currentPos.z = -distance;

        transform.position = currentPos;
    }
}

        保存脚本,运行游戏,按键盘上的方向键或者A、D、S、W键,可以看到Player朝着指定的方向移动起来了。

注意:

        以上移动方式没有考虑阻挡,为放置玩家或者敌人移动到地面的边缘掉下去,我们使用了简单的方式来限定他们的可移动范围。

      4.2 相机跟随

        运行游戏,发现Player在移动时,相机是固定的。下面我们把相机改为随着Player移动而移动。在《3D小球跑酷》我们采用的是把相机直接作为Player的子物体的方式,这里我们采用另一中方式(当然,你也可以使用作为子物体的方式),就是编写相机跟随脚本。

        在Scripts文件夹下新建C#脚本,命名为FlowCamera。将脚本挂在到Camera上,双击打开脚本,输入以下代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FlowCamera : MonoBehaviour
{
    public Transform target;

    Vector3 offset;

    
    void Start()
    {
        offset = transform.position - target.position;
    }

    
    void Update()
    {
        transform.position = target.position + offset ;
    }
}

        将Player物体拖拽到脚本参数target字段。保存再次运行游戏,发现相机已经可以跟随Player移动了。

      4.3 制作子弹

        创建一个球体,大小缩放为0.2倍。命名为Bullet, 这将作为我们发射的子弹。新建脚本Bullet,将脚本挂在到Bullet物体上。双击打开脚本,输入以下内容:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float lifeTime = 2f; // 子弹的生命周期
    public float speed = 10f;  // 子弹的飞行速度

    float startTime; // 子弹的生成时间


    // Start is called before the first frame update
    void Start()
    {
        startTime = Time.time;
    }

    // Update is called once per frame
    void Update()
    {
        transform.position += speed * transform.forward * Time.deltaTime;

        // 超过一定时间后
        if (startTime + lifeTime < Time.time)
        { 
            Destroy(gameObject); // 销毁子弹
        }
    }

    
}

        将球体拖到Prefabs文件夹,做成预制体。

      4.4 实现武器系统

        下面我们实现一下Player切换武器的功能,这是我们游戏的一个重要功能。我们按Q键可以切换武器。武器有三种,分别是手枪(Pistol),自动步枪(Rifle)和散弹枪(shotgun)。

        新建脚本,命名为Weapon。双击打开脚本,输入以下内容:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Weapon : MonoBehaviour
{
    public GameObject bulletPrefab;  // 子弹预制体

    public float pistolFireCD = 0.2f; // 手枪发射子弹的时间间隔
    public float rifleFireCD = 0.1f;  // 自动步枪发射子弹的时间间隔
    public float shotgunFireCD = 0.5f; // 散弹枪发射子弹的时间间隔

    float lastFireTime; // 上次开火时间

    public int curGun;  //当前使用的武器 0:手枪  1:自动步枪  2:散弹枪

    // 开火方法,由角色脚本调用, 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;
            default:
                break;
        }
    }

    // 更换武器
    public int ChangWeapon()
    {
        curGun += 1;
        if (curGun >= 3)
            curGun = 0;

        return curGun;
    }

    // 手枪射击
    private void PistolFire()
    {
        // 子弹发射时间间隔判定
        if (lastFireTime + pistolFireCD > Time.time)
        {
            return;
        }

        lastFireTime = Time.time;

        GameObject bullet = GameObject.Instantiate(bulletPrefab);
        bullet.transform.position = transform.position + transform.forward * 1.0f;  // 子弹的位置,位于角色前方1米
        bullet.transform.forward = transform.forward;  // 子弹的方向

    }

    // 自动步枪射击
    private void RifleFire()
    {
        // 子弹发射时间间隔判定
        if (lastFireTime + rifleFireCD > Time.time)
        {
            return;
        }

        lastFireTime = Time.time;

        GameObject bullet = GameObject.Instantiate(bulletPrefab);
        bullet.transform.position = transform.position + transform.forward * 1.0f;  // 子弹的位置,位于角色前方1米
        bullet.transform.forward = transform.forward;  // 子弹的方向
    }

    // 散弹枪射击,一次发射5颗子弹,子弹间隔10
    private void ShotgunFire()
    {
        // 子弹发射时间间隔判定
        if (lastFireTime + shotgunFireCD > Time.time)
        {
            return;
        }

        lastFireTime = Time.time;

        for (int i = -2; i <= 2; i++)
        {
            GameObject bullet = GameObject.Instantiate(bulletPrefab);
            var dir = Quaternion.Euler(0, i * 10, 0) * transform.forward;

            bullet.transform.position = transform.position + dir * 1.0f;  // 子弹的位置,位于角色前方1米
            bullet.transform.forward = dir;  // 子弹的方向
        }
    }
}

        修改Player脚本调用武器系统,使角色发射子弹。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    ......

    // 声明武器对象
    private Weapon weapon;

    void Start()
    {
        hp = maxHp;  // 初始血量为最大值
        weapon = GetComponent<Weapon>();  // 获取武器对象
    }

    void Update()
    {
        if (!idDead)
        {
            Move();
            Fire();  // 开火,发射子弹
            ChangeWeapon(); // 切换武器
        }
    }

    void Move()
    {
        ......
    }

    void Fire()
    {
        bool keyDown = Input.GetKeyDown(KeyCode.J);
        bool keyPressed = Input.GetKey(KeyCode.J);
        weapon.Fire(keyDown, keyPressed);
    }

    void ChangeWeapon()
    {
        bool changWeapon = Input.GetKeyDown(KeyCode.Q);
        if (changWeapon)
            weapon.ChangWeapon();
    }
}

        将武器脚本(Weapon)挂载到Player物体上,同时把子弹预制体拖到武器脚本的bulletPrefab字段上。保存场景,运行游戏,按Q键可以切换枪支,按J键可以发射子弹。读者可以自行调整玩家的移动速度和子弹的发射速度,以获得更好的游戏体验。

      4.5 实现敌人的脚本

        敌人脚本和Player脚本类似,单敌人会始终朝着玩家前进,并朝着玩家发射子弹。在Scripts文件夹下新建脚本文件,命名为Enermy,并将其挂载到Enermy预制体上。双击打开脚本,输入以下内容:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enermy : MonoBehaviour
{
    private Transform playerTransform;  // player 的 transform 组件

    public float speed = 2f;  // 敌人的移动速度
    public int maxHp = 1;    // 敌人的最大血量


    private int hp;      // 敌人的当前血量
    private bool isDead = false;

    private Weapon weapon;

    void Start()
    {
        hp = maxHp;  // 初始血量为最大值
        weapon = GetComponent<Weapon>();
        playerTransform = GameObject.Find("Player").transform;
    }

    void Update()
    {
        if (!isDead)
        {
            Move();
            Fire();
        }
    }

    void Move()
    {
        Vector3 dir = playerTransform.position - transform.position;
        var currentPos = transform.position + dir.normalized * speed * Time.deltaTime;

        // 限制敌人的移动范围,范围为方圆20以内
        const float distance = 20f;

        if (currentPos.x > distance)
            currentPos.x = distance;

        if (currentPos.x < -distance)
            currentPos.x = -distance;

        if (currentPos.z > distance)
            currentPos.z = distance;

        if (currentPos.z < -distance)
            currentPos.z = -distance;

        transform.position = currentPos;
        transform.forward = dir.normalized;
    }

    void Fire()
    {
        weapon.Fire(true, true);
    }


}

        然后,把武器脚本挂载到Enermy预制体上,保存。再场景中放置几个敌人(直接把Enermy预制体拖到场景中即可),运行游戏,发现敌人开始朝着玩家移动,并不断发射子弹。

      4.6 子弹的碰撞逻辑

        虽然现在我们可以控制Player移动,并且能发射子弹,敌人也能够发射子弹,但是子弹碰到敌人或者Player都没有反应,接下来我们就开始添加子弹碰撞的逻辑。

        子弹和物体的碰撞分为以下几种情况:

        1)  Player的子弹击中敌人

        2)敌人的子弹击中Player

        3)玩家的子弹和Player的子弹碰撞

        4)Player的子弹不会击中Player,敌人的子弹也不会击中敌人。

        为了区分敌人子弹和Player的子弹,我们采用标签(Tag)来进行区分。我们新建两个标签,分别为PlayerBullet和EnermyBullet。选中之前的子弹预制体,重命名为PlayerBullet, 并将Tag设置为PlayerBullet。复制PlayerBullet,重命名为EnermyBullet,并将Tag设置为EnermyBullet。

       4.6.1 修改Enermy预制体

       双击Enermy预制体,将脚本Weapon上的BulletPrefab字段设置为EnermyBullet。保存退出。

      4.6.2 修改Bullet脚本

       双击Bullet脚本,添加碰撞逻辑。添加以下代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    .....

    private void OnTriggerEnter(Collider other)
    {
        if (CompareTag(other.tag))  // 如果子弹的tag和碰撞到的物体的Tag相同
            return;

        Destroy(gameObject);
    }

}

        4.6.3 修改Player脚本

        当敌人的子弹碰到玩家的时候,玩家血量减1,当血量为0时,玩家死亡,游戏结束。双击打来Player脚本,添加以下代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    ......

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("EnermyBullet"))
        {
            if (hp <= 0) return;
            hp--;
            if (hp <= 0)
            {
                idDead = true;
                Debug.Log("Game Over!");
            }
        }
    }
}

        4.6.4 修改Enermy脚本

        当玩家的子弹碰到敌人的时候,敌人血量减1。当敌人的血量为0时,敌人死亡。双击打开Enermy脚本,添加以下代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enermy : MonoBehaviour
{
    ......

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("PlayerBullet"))
        {
            if (hp <= 0) return;
            hp--;
            if (hp <= 0)
            {
                Destroy(gameObject);
            }
        }
    }
}

           保存场景,运行游戏,发现子弹碰撞后并没有什么效果,这是因为我们没有添加刚体,并设置触发器。

        4.6.5 设置触发器

        选中敌人的子弹和Player的子弹的预制体,添加刚体组件(RigidBody),并勾选 Collider 上的Is Trigger 复选框。选择Enermy预制体,并勾选 Collider 上的Is Trigger 复选框。选择Player,并勾选 Collider 上的Is Trigger 复选框。保存场景再次运行游戏,发现Player 被击中之后,血量减少,当血量减少到0是,控制台输出“Game Over”。

        4.7 生成大量敌人

        现在我们的游戏已经初具模型了,单要手动把敌人预制体拖到场景中,如果要有许多敌人就显得太麻烦了,而且拖到场景中的敌人有限,杀完了就没有了。 我们要有一个可以源源不断生成敌人的机制,每隔一段时间就生成一个敌人,这样就可以一直玩下去了。

        在场景中新建一个空物体,命名为EnemySpawn,同时新建脚本文件,也命名为EnemySpawn,并挂载到EnemySpawn物体上。脚本内容如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnermySpawn : MonoBehaviour
{
    public GameObject enermyPrefab;

    public float spawTimer = 2.0f;

    List<GameObject> enermies = new List<GameObject> ();
    
    void Update()
    {
        if (enermies.Count >= 10) return;  // 场景中最多10个敌人
        spawTimer -= Time.deltaTime;
        if (spawTimer <= 0)
        {
            spawTimer = 2.0f;

            GameObject enermy = Instantiate(enermyPrefab);
            enermy.transform.position = new Vector3(-20, 1, -20);
        }
    }
}

        保存脚本和场景,运行游戏,发现没隔2秒就会有一个敌人生成,并且场景中最多会有10个敌人,当场景中的敌人数量少于10个时,又会有新的敌人生成出来,这样就可以一直玩下去了。

五、结束语

        好了,射击游戏到这里就结束了,虽然还有很多不足,但作为初学者的练习案例,也用到了许多Unity的基础知识,希望您能够获得一些启发,对您的学习有所帮助。您也可以发挥自己的想想力,完善本案例,比如增加积分、音效等等,使游戏更加丰富。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值