Unity3D 太空大战射击游戏

一、前言

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

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

本游戏案例以太空作战为背景,玩家和敌人为不同的飞船形象。敌人分为两种,可发射子弹和不能发射子弹,敌人射中玩家或者碰撞到玩家会消失,玩家生命减少。玩家每消灭一个敌人会获得相应的分数。游戏没有尽头,玩家生命值减少到0,游戏结束。

二、开发环境

操作系统:Windows

Unity 版本:团结引擎1.0.4

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

三、搭建场景

3.1、 新建项目

打开Tuanjie Hub,点击左上角【create project】按钮,选择【3D】模版,输入项目名称SpaceWar和项目保存路径,点击右下【Create project】按钮完成创建。

3.2、搭建开始场景

团结引擎导入资源方式和Unity完全一样,对Unity导出的资源也完全兼容。下载本文附带的资源,点击【Assets】->【Import Package】->【Custom Package...】,选择下载好的资源,点右下角【Import】,将资源导入到项目。

3.2.1 搭建开始界面

  1. 双击打开start场景。我们在场景中创建标题和开始按钮。在Hierarchy面板右键【UI】->【Legacy】-> 【Text】,将Text命名为Title。调整文本大小为宽500,高100,字体大小设置为60, 颜色设置为白色,并是文本居中对齐,位置Y设置为130,如下所示:
  1. 接着创建一个开始按钮

在Canvas上右键【UI】->【Legacy】-> 【Button】,将按钮命名为StartBtn。调整按钮大小为宽200,高40,设置按钮颜色为 665441,透明度调整为225(0.8)。设置按钮的文本为"开始游戏",颜色设置为白色。最后调整按钮Y的位置为 -120, 效果如下:

  1. 添加按钮点击事件

在Project区域的Assets下新建文件夹Scripts,用来保存我们脚本。在Scripts文件夹下右键【Create】->【C# Script】,创建一个脚本,命名为StartMenu.cs。双击打开StartMenu.cs脚本,输入以下代码:

cs 复制代码
using UnityEngine;
using UnityEngine.SceneManagement;

public class StartMenu : MonoBehaviour
{
    public void OnStartBtnClicked()
    {
        SceneManager.LoadScene(1);
    }
}

保存脚本,将脚本挂载到Canvas上。选中StartBtn按钮,在Button组件下的事件部分点击+号添加点击事件,如图:

最后,点击【File】->【Build Settings】,将Assets下的start和level两个场景拖到Build Settings 的 Scenes In Build 下,如图:

一切准备好后,点击运行按钮,看下效果。游戏运行后,点击开始游戏按钮,游戏切换到第二个场景,但第二个场景什么效果也没有,因为我们还没有进行开发,接下来我们就开始开发第二个场景。

3.3、搭建战斗场景

双击打开 level1 场景,资源已经为我们准备好了战斗所需的场景。我们先来看一下场景中的资源。

我们可以看到,在Assets的Prefab文件夹下有许多蓝色的物体,这就是预制体,这些预制体可以重复使用,场景中的蓝色物体就是使用了这些预制体。

可以看到,场景中有三个一样的Enemy,而Prefab文件夹里有个Enemy2,Enemy2是一个飞机的模型预制体。为了充分利用所有的预制体,我们将替换场景中的一个Enemy。场景中的

将Prefab文件夹里的Enermy2直接拖到场景中,将场景中第一个Enermy的坐标复制给Enermy2,然后删除第一个Enermy。如图所示:

我们的游戏是这样的:Enemy 和 Enemy2是两种不同的敌人,Enemy不能发射子弹,但会不断改变方向,Enemy2可以发射子弹,但不能改变方向。无论是Enemy还是Enemy2,当碰撞到玩家的飞机后都会消失,同时玩家也会减少一点生命。

接下来我们就开始开发功能,首先我们要先让玩家动起来。

3.3.1 开发玩家脚本

1. 让玩家动起来

在Scripts文件夹下新建脚本,命名为Player。在Prefab文件夹下选中Player预制体,将Player脚本挂载到Player预制体上,双击打开Player脚本,输入以下代码:

cs 复制代码
using UnityEngine;

public class Player : MonoBehaviour
{
    public float m_Speed = 10.0f;

    void Update()
    {

        // 通过键盘按键控制玩家移动
        float h = 0f, v = 0f;  // 分别标识在水平和垂直方向上的移动距离


        if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) // 键盘字母A键或者左箭头
        {
            h += m_Speed * Time.deltaTime;
        }

        if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) // 键盘字母D键或者右箭头
        {
            h -= m_Speed * Time.deltaTime;
        }

        if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) // 键盘字母W键或者向上箭头
        {
            v -= m_Speed * Time.deltaTime;
        }

        if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) // 键盘字母S键或者向下箭头
        {
            v += m_Speed * Time.deltaTime;
        }

        transform.Translate(h, 0, v);

    }
}

保存脚本,点击运行游戏看下效果。运行游戏后,在Game窗口,我们通过方向键或者ASDW按键就可以控制玩家的移动了,是不是很简单。

2. 发射子弹

我们继续在Player.cs脚本中添加发射子弹的功能。首先我们需要一个子弹的预制体来生成子弹。其次子弹不是每帧都生成,因此需要一个生成子弹的时间间隔。当我们在点击鼠标左键或者按空格键的时候,发射子弹。代码如下:

cs 复制代码
using UnityEngine;

public class Player : MonoBehaviour
{
    public float m_Speed = 10.0f;  // 移动速度
    public Transform m_RocketTrans; // 子弹预制体
    public float m_RocketRate = 0f;   // 子弹的发射频率

    void Update()
    {

        // 通过键盘按键控制玩家移动
        ...
        // 发射子弹
        m_RocketRate -= Time.deltaTime;
        if (m_RocketRate <= 0)
        {
            m_RocketRate = 0.1f; // 重置发射时间
            if (Input.GetKey(KeyCode.Space) || Input.GetMouseButton(0))
            {
                Instantiate(m_RocketTrans, transform.position, transform.rotation);

            }
        }
    }
}

选中Player预制体,在 我们把Prefab文件夹下的Rocket预制体赋值给Player脚本的RocketTrans变量。

接着,我们让子弹动起来。当发射子弹后,子弹朝着前方飞去。新建子弹的脚本文件,命名为Rocket.cs。选中Rocket预制体,将脚本挂在到预制体上。双击打开Rocket.cs脚本,输入以下代码:

cs 复制代码
using UnityEngine;

public class Rocket : MonoBehaviour
{
    public float m_Speed = 10.0f;  // 子弹的移动速度
    public float m_life = 1f;      // 子弹的生存时间


    void Update()
    {
        m_life -= Time.deltaTime;
        if (m_life <= 0)  
        {
            Destroy(gameObject);  // 销毁子弹
        }

        transform.Translate(0, 0, -m_Speed * Time.deltaTime); // 子弹移动
    }
}

保存场景,运行游戏看下效果。运行游戏后,按空格或者鼠标左键,玩家飞机会发射子弹,并且子弹会一直朝前飞行。

3.3.2 开发敌人脚本

1**. 让敌人动起来**

我们场景中有两种敌人。我们先给形状是飞碟的预制体添加脚本,飞碟不会发射子弹,但在飞行过程中会改变方向。飞碟的预制体为Enermy,因此我们新建一个Enermy.cs脚本,并把他挂载到Enermy预制体上。双击打开Enermy.cs 脚本,输入代码如下:

cs 复制代码
using UnityEngine;

public class Enermy : MonoBehaviour
{
    public float m_Speed = 1f;  // 飞行速度
    public float m_rotSpeed = 30f; // 旋转速度
    public float m_changeTimer = 1.5f; // 变向时间间隔


    void Update()
    {
        UpdateMove();
    }

    public virtual void UpdateMove()
    {
        m_changeTimer -= Time.deltaTime;
        if (m_changeTimer <= 0)
        {
            m_changeTimer = 3f;

            m_rotSpeed = -m_rotSpeed;
        }

        // 旋转方向
        transform.Rotate(Vector3.up, m_rotSpeed * Time.deltaTime, Space.World);

        // 移动
        transform.Translate(0, 0, -m_Speed * Time.deltaTime);
    }
}

同样,我们接着给敌人Enermy2添加脚本。Enermy2是飞机模型,这种敌人可以发射子弹,但不能改变方向。我们新建脚本,命名为SuperEnermy.cs。将脚本挂载到Enermy2预制体上。双击打开SuperEnermy.cs脚本,输入以下代码:

cs 复制代码
using UnityEngine;

public class SuperEnermy : Enermy
{
    public Transform m_rockey;      // 子弹预制体
    public float m_fireTimer = 2f;  // 发射子弹的时间间隔

    private Transform m_player;  // 因为敌人的子弹要朝着玩家发射,因此需要玩家的引用

    private void Awake()
    {
        m_player = GameObject.FindGameObjectWithTag("Player").transform;
    }

    public override void UpdateMove()
    {
        m_fireTimer -= Time.deltaTime;
        if (m_fireTimer <= 0)
        {
            m_fireTimer = 2f;

            if (m_player != null)
            {
                Vector3 pos = transform.position - m_player.position;  // 玩家相对飞机的方向
                Instantiate(m_rockey, transform.position, Quaternion.LookRotation(pos));
            }
        }

        transform.Translate(0, 0, -m_Speed * Time.deltaTime);
    }

}

保存脚本,然后把EnermyRocket预制体赋值给SuperEnermy.cs脚本的 m_rocket 字段,如图:

运行游戏,可以看到敌人的飞机每隔2秒就会产生子弹,但子弹还不会移动。下面我们就给敌人的子弹添加脚本。

2. 敌人的子弹

新建EnermyRocket.cs脚本,选中EnermyRocket预制体,将脚本挂载到此预制体上。双击打开EnermyRocket.cs脚本,只需要让本脚本继承Rocket类就可以让子弹动起来了。

3.3.3 添加碰撞事件

我们知道,对于敌人和玩家都是有一定生命的,当敌人生命为0或者碰到玩家时,敌人要进行销毁。但玩家生命为0时,游戏结束。

下面我们就添加一下生命值和碰撞事件。

1. 添加子弹碰撞事件

打开Rocket.cs脚本,这是子弹脚本,我们添加表示子弹威力的字段 m_power,然后添加子弹的碰撞事件,代码如下:

cs 复制代码
using UnityEngine;

public class Rocket : MonoBehaviour
{
    public float m_Speed = 10.0f;  // 子弹的移动速度
    public float m_life = 1f;      // 子弹的生存时间

    public int m_power = 1;    // 子弹威力

    void Update()
    {
        ...
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.tag.CompareTo("Player") == 0)    // 对敌人无效,玩家子弹碰撞敌人我们在其他脚本实现
            return;

        Destroy(gameObject);  // 销毁子弹
    }
}

2. 添加敌人子弹事件

打开EnemyRocket.cs脚本,添加如下代码:

cs 复制代码
using UnityEngine;

public class EnermyRocket : Rocket
{
    private void OnTriggerEnter(Collider other)
    {
        if (other.tag.CompareTo("Enermy") == 0)
        {
            return;
        }

        Destroy(gameObject);
    }
}

3. 添加敌人事件

打开Enemy.cs脚本,添加如下代码:

cs 复制代码
using UnityEngine;

public class Enermy : MonoBehaviour
{
    public float m_Speed = 1f;  // 飞行速度
    public float m_rotSpeed = 30f; // 旋转速度
    public float m_changeTimer = 1.5f; // 变向时间间隔

    public int m_life = 10;

    void Update()
    {
        UpdateMove();
    }

    public virtual void UpdateMove()
    {
       ...
    }

    // 触发器事件
    private void OnTriggerEnter(Collider other)
    {
        if (other.tag.CompareTo("PlayerRocket") == 0)
        {
            Rocket rocket = other.GetComponent<Rocket>();
            if (rocket != null)
            {
                m_life -= rocket.m_power;  // 根据玩家子弹威力减少生命
                if (m_life <= 0)
                {
                    Destroy(gameObject); // 生命为0,销毁
                }
            }
        }
        else if(other.tag.CompareTo("Player") == 0)
        { 
            m_life = 0;
            Destroy(gameObject);
        }
    }
}

4. 添加玩家事件

打开Player.cs脚本,添加如下代码:

cs 复制代码
using UnityEngine;

public class Player : MonoBehaviour
{
    public float mSpeed = 10.0f;
    public Transform m_RocketTrans; // 子弹预制体
    public float m_RocketRate = 0f;   // 子弹的发射频率

    public int m_life = 10;   // 玩家的生命

    void Update()
    {
        ...
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.tag.CompareTo("PlayerRocket") == 0)
        {
            m_life -= 1;
            if (m_life <= 0)
            {
                Destroy(gameObject);
            }
        }
    }
}

保存脚本,运行游戏,可以看到,玩家发射子弹已经可以消灭敌人了。

3.4 生成大量敌人

目前场景中只有三个敌人,接下来我们就开始让游戏源源不断的生成敌人,已增加游戏的趣味性。

1. 添加生成敌人脚本

在Scripts文件夹下新建脚本 EnemySpawn.cs ,双加打开脚本,输入以下代码:

cs 复制代码
using UnityEngine;

public class EnemySpawn : MonoBehaviour
{
    public Transform m_Enermy;   // 敌人的预制体
    public float m_SpawnTime = 5f;  // 生成敌人的时间间隔

    private void Update()
    {
        m_SpawnTime -= Time.deltaTime;
        if (m_SpawnTime <= 0)
        { 
            m_SpawnTime = Random.value * 10f;  // 随机生成时间
            if (m_SpawnTime < 5)
                m_SpawnTime = 5;       // 最小生成时间间隔为5s

            Instantiate(m_Enermy, transform.position, Quaternion.identity);  // 生成敌人
        } 
    }
}

在Prefabs文件夹下,选中Spawn1预制体,将脚本挂载到预制体上,然后把Enemy预制体拖入脚本的Enermy字段。同理给Spawn2挂载脚本,把Enermy2填入Enermy字段,如下:

保存项目,运行游戏,可以看到已经可以源源不断的生成敌人了。至此,我们已经完成了游戏的主要功能开发,接下来我们将对游戏进行完善,如添加分数和游戏结束界面。

四、完善游戏

4.1 搭建UI

4.1.1 添加分数面板

在Hierarchy窗口右键 ->[UI]->[Legacy]->[Text], 新建Text作为我们本局游戏的分数。在创建Text的同时会自动创建一个Canvas。我们在Canvas下新建一个空的物体,命名为ScorePanel,并设置RectTransform 的stretch如图:

将Text放置在 ScorePanel下,并命名为Score。设置颜色为白色,文本内容暂时设置为Score:0,字体大小调整为36,并调整文本位置和大小,如图:

4.1.2 搭建游戏结束界面。

在Hierarchy窗口右键 ->[UI]->[Panel], 新建Panel作为我们游戏结束的背景。在创建Panel的同时会自动创建一个Canvas。我们在Canvas下新建一个空的物体,命名为GameOver,并设置RectTransform 的stretch如图:

将Panel放置在 GameOver下,并命名为Bg。设置颜色为黑色,透明度调整为 130。

在GameOver下新建一个文本组件(【UI】->【Legacy】->【Text】),命名为GameOverText,文本内容为"游戏结束",设置字体大小为60,对齐方式改为上下居中,左右居中对齐, 颜色为白色,并调整文本组件的大小和位置如图:

在GameOver下新建一个按钮组件(【UI】->【Legacy】->【Button】),命名为AgainBtn,将按钮文本改为"再来一局",颜色为白色,调整按钮颜色为 665441,透明度设为235,并调整按钮大小和位置,如图:

搭建完成后的最终效果和结构如下:

4.2 添加脚本

4.2.1 添加GameManager脚本

新建GameManager.cs脚本,并将脚本挂载到场景中的GameManager物体上,双击打开脚本,输入以下代码,并给Again按钮添加点击绑定事件OnAgainBtnClicked。

cs 复制代码
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }  // GameManager 单例

    public Text m_scoreText;
    public GameObject m_ameOverPanel;

    public int m_score = 0;   // 得分
    
    

    private void Awake()
    {
        Instance = this;
    }


    void Start()
    {
        m_scoreText = GameObject.Find("Score").GetComponent<Text>();
        m_ameOverPanel = GameObject.Find("GameOver");
    }

    public void AddScore(int score)
    {
        m_score += score;
        m_scoreText.text = m_score.ToString();
    }

    public void GameOver()
    {
        m_ameOverPanel.SetActive(true);
    }

    public void OnAgainBtnClicked()
    { 
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }

}

我们在敌人销毁的时候增加分数。打开敌人脚本Enermy.cs,添加增加分数代码:

cs 复制代码
using UnityEngine;

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

    // 触发器事件
    private void OnTriggerEnter(Collider other)
    {
        if (other.tag.CompareTo("PlayerRocket") == 0)
        {
            Rocket rocket = other.GetComponent<Rocket>();
            if (rocket != null)
            {
                m_life -= rocket.m_power;  // 根据玩家子弹威力减少生命
                if (m_life <= 0)
                {
                    Destroy(gameObject); // 生命为0,销毁
                    GameManager.Instance.AddScore(m_point);  // 增加分数
                }
            }
        }
        else if(other.tag.CompareTo("Player") == 0)
        { 
            m_life = 0;
            Destroy(gameObject);
        }
    }
}

当玩家生命值为0的时候,游戏结束,打开游戏结束面。我们打开Player.cs 脚本。

cs 复制代码
using UnityEngine;

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

    private void OnTriggerEnter(Collider other)
    {
        if (other.tag.CompareTo("PlayerRocket") == 0)
        {
            m_life -= 1;
            if (m_life <= 0)
            {
                Destroy(gameObject);
                GameManager.Instance.GameOver();
            }
        }
    }
}

好了,到此为止,我们就可以愉快的玩游戏了,快去试试吧。

五、结束语

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

相关推荐
躺下睡觉~9 小时前
Unity-Transform类-父子关系
java·unity·游戏引擎
躺下睡觉~9 小时前
Unity-Transform类-缩放和看向
unity·游戏引擎
君莫愁。12 小时前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎
咩咩觉主12 小时前
Unity实战案例全解析:PVZ 植物卡片状态分析
unity·c#·游戏引擎
蓝裕安15 小时前
伪工厂模式制造敌人
开发语言·unity·游戏引擎
谢泽浩19 小时前
Unity 给模型贴上照片
unity·游戏引擎
z2014z19 小时前
Unity Resource System 优化笔记
unity·游戏引擎
王维志20 小时前
Unity 高亮插件HighlightPlus介绍
unity·游戏引擎
DisonTangor20 小时前
Ruffle 继续在开源软件中支持 Adobe Flash Player
游戏·adobe
zaizai100721 小时前
我的demo保卫萝卜中的技术要点
unity