欢迎来到淼淼之森的博客小站。  交流请加我微信好友: studyjava。  也欢迎关注同名公众号:Java学习之道

[转]unity游戏开发之经典案例打飞机

  |   0 评论   |   0 浏览
  • ☀️阳光明媚,司机盎然,空气中充斥着荷尔蒙的气息,又到了万物开始交配的季节了~
  • ?咦~ 搞错了!重来~
  • ?一日天气尚好 闲来无事,欲出门吟诗作赋, 偶遇一位漂亮学妹~
  • ?学妹一脸天真,曰(yūe):“学长 geigei~,你打飞机厉害咩?”
  • ?我略显尴尬,曰:“咳咳,还行吧…”
  • ?学妹又曰:“人家也想学打飞机哎,要不然你教教人家嘛~”
  • ?我曰:“这个…是需要天分的!吾不知你天分如何,先来测测你可好?”
  • ?学妹跃跃欲试,曰:“好嘛 geigei~ 尽管来!”
  • ?于是我曰:“打飞机三大要点是什么?请答!”
  • ?学妹表情坚毅,曰:“首先要快,其次要准,最后要狠!”
  • ?我曰:“完美通过!那接下来我就教你怎样做一个打飞机的游戏,以及成为一名-打飞机高玩!”

正文

上文为雨后睡前小故事,下面步入正文

  • 前段时间看CSDN有个大佬用Java做了一个打飞机的游戏,看着他打飞机打的那么爽,我也想来做一个了。
  • 做游戏怎么能少了Unity这位专业人士呢~
  • 所以最近利用闲暇时间(摸鱼时间)也来搞一个打飞机的游戏

?先来理一下思路

打飞机,顾名思义,我们要围绕飞机来展开思路

  1. 首先,要创建一个游戏场景,就是我们即将进行"打飞机"的场景!最好能让场景移动来体现真实感~
  2. 其次,要创建一个我们的主角飞机,当然要显得跟敌人飞机不一样啦。我们的飞机可以上下左右BABA移动,也可以发射炮弹来打飞机!自然也要有血量,不然无敌起来打飞机还有什么乐趣呢,就进不了贤者模式了!
  3. 然后再就是敌人啦,打飞机打飞机,没有飞机怎么打~,那就来加入敌方飞机,让他们随机加入战斗,再加上一些障碍物,让打飞机的场景更加惊心动魄!
  4. 再就是游戏对象的销毁,包括敌人被消灭,子弹销毁等等~
  5. 最后就是要有一个游戏控制器,来控制敌机的生成时间和数量,还有打飞机结束时的胜利和失败等游戏细节控制

好啦,一个简单的游戏思路就构思完成了,那么接下来就是按照这个思路来展开开发计划啦


开始制作

这里我们导入一个Unity官方的飞机大战的资源包,就不用自己用一个简陋的三角裤来当飞机了,可以更高大上一点。
导入后如下图,然后就是按照我们提前想好的思路动手操作啦。

第一步:设置场景

我们导入资源包后第一步是配置游戏场景
Game视图点击下图所示地方可以配置游戏视图分辨率,就是我们玩游戏时候的游戏屏幕,将宽高比设置为600×900。 这里只是提供参考,一个竖版的游戏画面。

也打开Player Setting进行配置
Player Setting也以在Edit -> Project Settings -> Player中打开,也是将宽高比设置为600×900。两种方法都可以

设置完之后Game视图是这样的~,一个简单的游戏屏幕空间设置完成


第二步:配置我方飞机

在这个资源包中已经有飞机了,Assets/Module/Player
所以我们把这个飞机拖到Hierarchy窗口,然后改名为Player

给他加上刚体:Add Component -> Physics -> Rigibody
碰撞体:Add Component -> Physics -> Mesh Collider
加这两个组件是为了实现碰撞效果,所以有必要加上

把Player的组件属性设置好,我们不需要飞机有重力,因为这样会掉下去,所以把刚体(Rigibody)的UseGravity选项给关掉,这样就不使用重力效果了。

下图是飞机的Inspector的最终面板属性

然后资源包里面还有一个飞机尾部喷气的粒子特效,我们给它加到飞机尾部,直接将粒子特效的文件拖到飞机上就行。如下图(游戏背景在下一个步骤,这里录屏了就懒得换啦~):


第三步:设置场景里的相机和光照效果

相机处理

  • 我们要把相机放到一个合适的位置才能更好地体验游戏,光照是为了游戏效果更好
  • 直接上数据来设置一下——数据是死的,人是活的,具体怎样设置都行,这里只是提供一个参考数值~

设置相机属性值

  1. 重置相机的Transform
  2. Rotation的x设置为90
  3. Position的y设置为10,z设置为5
  4. Projections设置为orthographic(正交投影),因为相机俯视整个场景,不需要perspective(透视投影)
  5. Size设置为10
  6. Clear Flags设置为Solid Color
  7. Background设置为黑色

相机Inspector面板属性:

此时的Game视图如下,是不是黑乎乎的呢,因为还没有处理光照,下一步就是光照

光照处理

  • 光照的话主要是为了让场景里面看着更舒服,不那么违和,所以这里也设置一个参考值
  1. 将场景里的Directional Light改为MainLigjt
  2. 重置Main Light的属性
  3. 将Main Light的Rotation的x设置为20,y设置为-115

Main Light最终参数如下:

这样的话相机和光照就简单处理好了,此时的Game视图如下,是不是跟上面的比效果好点了呢~


第四步:更换游戏背景

此时的Game视图黑乎乎的,跟高逼格差十万八千里好吧,最起码游戏背景需要加上才行
步骤如下:

  • 新建一个Quad,重命名为Background01。方法:在Hierarchy中右键->3D Object->Quad
  • 将tile_nebula_green_dff拖拽到场景中的Background。路径:Assets\Textures\tile_nebula_green_dff
  • tile_nebula_green_dff是资源包中的一张图,可以用来做背景

直接来看Background的Inspector面板属性,照着这个属性设置就行,效果图如下:

复制一个背景,改名为Backgroun02,然后属性面板设置成这样

  • 然后给背景游戏对象加一个脚本,可以让背景动起来
  • 一般游戏应该都是让背景动起来,然后主角没动也显得动起来啦~
  • 通过判断Z轴参数来让两个背景板替换,形成无限循环的效果~

背景移动看效果(此时飞机应该还不会动,在下一步中,录屏啦就没换~):

上代码:

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

public class BGController : MonoBehaviour
{
    private float MoveSpeed = 1.0f;
    private void FixedUpdate()
    {
        transform.Translate(Vector3.down * Time.deltaTime * MoveSpeed);
        if (transform.position.z <= -30)
        {
            transform.position = new Vector3(0, 0, 30);
        }
    }
}

这样的话,一个简单的游戏背景就处理好了,接下来就是开始写代码让我们的飞机动起来啦!


第五步:飞机控制脚本的实现

  • 现在游戏场景设置完毕,就要来写代码操控飞机啦
  • 先让飞机动起来吧,新建一个脚本命名为PlayerController,通过按下键盘上的上下左右键通过Rigidbody控制飞机上下左右移动
  • 再加入一个speed参数作为velocity的系数,,可以在面板上调节飞机的飞行速度

代码如下:

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

public class PlayerController : MonoBehaviour
{
    [Header("移动速度")]
    public float speed;
    private Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
        rb.velocity = movement * speed;
    }
}

此时保存代码运行游戏是这样的:

但是此时有个问题就是,飞机操控没有边界,甚至跑出游戏场景外都有可能,那我们就用代码来控制一下
PlayerController脚本修改如下:

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

[System.Serializable]
public class Boundary
{
    [Header("边界控制")]
    public float xMin, xMax, zMin, zMax;
}

public class PlayerControllder : MonoBehaviour
{
    [Header("移动速度")]
    public float Speed = 5f;
    public Boundary boundary;
    private Rigidbody rb;
  
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
        rb.velocity = movement * Speed;

        
        rb.position = new Vector3(
            Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
            0.0f,
            Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)
            );
    }
}
  • 代码中增加了一个Boundary类,其中有四个变量用于控制边界的四个顶点
  • 还在Boundary类上面加了一个 [System.Serializable] ,加它的目的是让Boundary类能够在Inspector面板上显示

Inspector面板属性设置按照下图所示就可以:

现在飞机可以在边界里自由移动,而且不用担心会跑出边界啦
但是移动的时候感觉很生硬,不够丝滑亮眼呀,这跟我们的高逼格好像又不沾边了…

那我们来给飞机移动的时候加上一个倾斜的效果试试看,说干就干,操作起来:

  • 想让飞机倾斜有很多种方法,那我们这里就控制欧拉角,让飞机左右移动的时候绕着Z轴旋转一个角度,就能达到我们想要的效果啦
  • 在代码中加一个float参数用于控制倾斜力度,然后在FixedUpdate中加入这行代码控制飞机的rotation
public float tilt;
  
rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt);

Inspector中将tilt的值修改为4就可以了

来看一下加了倾斜后的效果图:

这样在左右移动的时候是不是就显得丝滑多啦,Nice!


第六步:飞机子弹控制

  • 现在我们的飞机只能移动,但是还不能发射炮弹呢,不能发射炮弹,还叫什么打飞机啊!
  • 那我们就来把炮弹加上,然后使用脚本控制发射炮弹炮弹的预制体在资源包中路径:Assets/Prefabs/Bolt
  • 给炮弹先加上一个简单的脚本,用于控制炮弹发射后可以向前出击!
    speed参数是炮弹的飞行速度,可以在面板上自定义修改,这样炮弹在生成之后就会向前发射飞行了
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Mover : MonoBehaviour {
    [Header("炮弹移动速度")]
    public float speed;
    private Rigidbody rb;

 void Start () {
        rb = GetComponent<Rigidbody>();
        rb.velocity = transform.forward * speed;
 }
}

现在炮弹有了,我们给主角飞机Player创建一个子对象,用于控制炮弹发射时候的出生位置

简单来说就是,设置炮弹发射时候的位置,要位于player飞机前面一点。

就比如现实世界上飞机的炮筒,炮弹都是从炮筒发射出去的,总不能从飞机驾驶舱发出去吧~哈哈哈哈嗝

如下图设置,将炮弹发射点放在飞机靠前一部分就行了,这样显得不会太突兀~

  • 然后我们来修改PlayerController脚本,新增一个发射炮弹的功能
  • Instantiate()方法是Unity中生成预设体的方法,我们把炮弹作为预设体
  • 然后利用代码在点击鼠标左键的时候,执行生成炮弹的方法就可以达到我们想要的发射炮弹的方法了
  • Input.GetButton(“Fire”) 就是执行点击鼠标左键的时候调用,使用Input.GetMouseButton(0) 效果一样
  • 还增加1个private对象nextFire,用于计算炮弹连发时间间隔,一直 秒射 怎么行!加上个时间显得 更 持久~

上代码看一下:

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

[System.Serializable]
public class Boundary
{
    [Header("边界控制")]
    public float xMin, xMax, zMin, zMax;
}

public class PlayerControllder : MonoBehaviour
{
    [Header("炮弹预设体")]
    public GameObject bullet;
    [Header("移动速度")]
    public float Speed = 5f;
    public Boundary boundary;
    public float tilt=4f;
    public Transform shotSpawn;

    private GameObject bolt;
    private Rigidbody rb;
  
    private float fireRate=0.2f;
    private float nextFire;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
    private void Update()
    {
       Fire(); 
    }
    public void Fire()
    {
        if (Input.GetButton("Fire1") && Time.time>nextFire)
        {
            nextFire = Time.time + fireRate;
            bolt = Instantiate(bullet,transform.position,Quaternion.identity);
        }
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
        rb.velocity = movement * Speed;

        
        rb.position = new Vector3(
            Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
            0.0f,
            Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)
            );
        
        rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt);
    }
}

Inspector中指定脚本的public对象,将炮弹和发射点拖过去。方法有多种,这里只演示一种参考

这个时候我们运行游戏,就可以看到下图所示效果啦~


第七步:创建敌人

终于到这一步了,我们的主角飞机都可以自由移动巡逻发炮弹了,那怎么还没有飞机让我打呢,我的大枪都饥渴难耐了!

那我们现在就来加上可以让我们攻击的敌人~
因为资源包内容有限,所以我们这里就加入几种天外陨石来凑数!
操作起来~

创建陨石

陨石的资源路径:Assets/Prefabs/Asteroid 01
这里面的预制体是我添加好的,可以直接拿来用,简单说一下怎样配置的

这个敌人预制体上面都加了刚体(Rigidbody)和碰撞体(Capsule Collider)

  1. Asteroid -> Add Component -> Rigidbody,取消勾选Use Gravity,以防止陨石掉落地。Angular Drag设置为0,用于忽略空气阻力。
  2. Asteroid -> Add Component -> Capsule Collider,勾选上Is Trigger,Radius设置为0.45,Height设置为1.5


然后给陨石添加上一个可以随机自由翻转的脚本,让游戏加一些亮点。

Random.insideUnitSphere 用于返回一个随机的Vector3变量来让陨石达成随机翻转的效果!
上代码:

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

public class RandomRotator : MonoBehaviour {
 public float tumble;
 private Rigidbody rb;

 void Start () {
        rb = GetComponent<Rigidbody>();
  rb.angularVelocity = Random.insideUnitSphere * tumble;
 }
}

效果图:

创建敌机

现在我们已经有了陨石了,那就再来加一个敌机。
考虑两个点:

  • 1.敌人会发射炮弹攻击我们
  • 2.敌人会随机改变游走方向

这样我们的游戏更真实,而且难度会变高
操作起来~

资源包中有敌人素材Assets/Prefabs/Enemy Ship

这个跟陨石一样,参数也差不多,也是我事先做好的预制体
也是都加了刚体(Rigidbody)和碰撞体(Capsule Collider),不多赘述了,直接看参数

这样的话敌人就算有了,但是我们要给他加上发射炮弹随机移动的脚本
上代码:
发射炮弹的脚本:

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

public class WeaponController : MonoBehaviour
{
 public GameObject shot;
 public Transform shotSpawn;
 public float fireRate;
 public float delay;

 void Start()
 {
  InvokeRepeating("Fire", delay, fireRate);
 }

 void Fire()
 {
  Instantiate(shot, shotSpawn.position, shotSpawn.rotation);

  GetComponent<AudioSource>().Play();
 }
}

控制敌人随机改变方向的脚本:

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

public class EvasiveManeuver : MonoBehaviour
{
    public Boundary boundary;
    public float tilt;
    public float dodge;
    public float smoothing;
    public Vector2 startWait;
    public Vector2 maneuverTime;
    public Vector2 maneuverWait;

    private float currentSpeed;
    private float targetManeuver;
    
    private void Start()
    {
        currentSpeed = GetComponent<Rigidbody>().velocity.z;
        StartCoroutine(Evade());
    }

    IEnumerator Evade()
    {
        yield return new WaitForSeconds(Random.Range(startWait.x,startWait.y));
        while (true)
        {
            targetManeuver = Random.Range(1, dodge) * -Mathf.Sign(transform.position.x);
            yield return new WaitForSeconds(Random.Range(maneuverTime.x, maneuverTime.y));
            targetManeuver = 0;
            yield return new WaitForSeconds(Random.Range(maneuverTime.x,maneuverTime.y));  
        }
    }

    void FixedUpdate()
    {
        float newManeuver = Mathf.MoveTowards(GetComponent<Rigidbody>().velocity.x, targetManeuver, smoothing * Time.deltaTime);
        GetComponent<Rigidbody>().velocity = new Vector3(newManeuver, 0.0f, currentSpeed);
        GetComponent<Rigidbody>().position = new Vector3
        (
            Mathf.Clamp(GetComponent<Rigidbody>().position.x, boundary.xMin, boundary.xMax),
            0.0f,
            Mathf.Clamp(GetComponent<Rigidbody>().position.z, boundary.zMin, boundary.zMax)
        );

        GetComponent<Rigidbody>().rotation = Quaternion.Euler(0, 0, GetComponent<Rigidbody>().velocity.x * -tilt);
    }
}

好了,现在敌人的操作基本是构建好了,下面说一下销毁敌人和炮弹的方法就差不多啦~


第八步:销毁

现在还缺少一个销毁敌人的脚本,敌人销毁有两种可能

  • 一种是被我们给打爆,当场销毁
  • 另一种是敌人与我们"路过",飞出场外

我们没把他们打死但是他们出了游戏边界以后也会自动销毁,不然就白白消耗性能

说到销毁,这里还有一个问题。
我们上面给主角飞机发射炮弹的时候,炮弹也还没有加销毁功能,在发出去以后也是白白消耗性能,所以这里一块处理了。

销毁炮弹

  • 我们需要子弹离开游戏区的时候销毁它们,所以给整个游戏范围加一个包围盒,让子弹出了包围盒自动销毁就可以了。
  • 创建一个Cube,然后将它的Mesh Renderer去掉,就可以形成一个透明的包围盒啦~
  • Hierarchy -> Create -> 3D Object -> Cube,重命名为Boundary,重置Transform,去掉勾选Mesh Renderer,并勾选Box Collider的Is Trigger,用于检测飞出子弹的碰撞。
  • 设置Boundary的大小:position = (0,0,5),Scale=(15,1,20)。同时给Boundary添加上标签Tag:Boundary,下面会用到

效果图如下:

然后给这个Boundary添加上一个脚本用于控制销毁游戏对象

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

public class DestroyByBoundary : MonoBehaviour
{
    void OnTriggerExit(Collider other)
    {
        Destroy(other.gameObject);
    }
}

销毁敌人

上面写了一个包围盒用于控制销毁炮弹,那现在我们的敌人碰到炮弹或者碰到玩家也应该被销毁,所以给敌人也加一个脚本用于控制销毁。
这里给主角炮弹也加一个Tag标签-bullet,用于控制销毁的时候判断当前游戏物体是否应该被销毁。

在敌人被销毁的时候,我们加一个爆炸特效,这样显得逼格就搞起来了,也很简单,只要在触发销毁的时候,生成一个爆炸的特效就可以了。

特效在资源包路径:Assets/Prefabs/VFX/Explosions中
将爆炸特效直接在Inspector面板拖到脚本上就行了

上代码:

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

public class DestroyByContact : MonoBehaviour
{
    public GameObject explosion;
    public GameObject playerExplosion;
    private GameController _gameController;
    
    private void Awake()
    {
        _gameController = GameObject.Find("gameController").GetComponent<GameController>();
    }
    
    void OnTriggerEnter(Collider other)
    {
        
        if ( other.tag == "bullet")
        {
            Instantiate(playerExplosion,transform.position,transform.rotation);
            Destroy(other.gameObject);
            Destroy(gameObject);
        }
        
        if (other.tag == "Player")
        {
            Instantiate(playerExplosion, transform.position, transform.rotation);
            Destroy(gameObject);
        }
    }
}

到这里的话,创建敌人和销毁敌人就算简单完成了,还在敌人销毁的时候加了个小特效~


第九步:游戏控制器

以上将游戏的基本要素都操作完了,能到这一步有没有感觉一个小游戏马上就要被我们创造出来了呀~

接下来就是创造一个游戏控制器,用于控制整体敌人的生成啦~
上代码:

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

public class GameController : MonoBehaviour
{
    [Header("敌人的预设体")]
    public GameObject[] hazards;
    public Vector3 spawnValues;
    [Header("一批敌人的数量")]
    public int hazardCound = 10;
    [Header("一批中,单个敌人生成间隔")]
    public float spawnWait = 0.5f;
    [Header("开始的暂停时间")]
    public float startWait = 1f;
    [Header("两批敌人之间的间隔时间")]
    public float waveWait = 4;
    [Header("背景音乐")]
    public AudioSource alarmBGM;

    private Text gameOverText;
    private Transform continues;
    private Text _againText;

    private GameObject player;
    private bool gameOver=false;

    void Start()
    {
        StartCoroutine(SpawnWaves()); 
        player = GameObject.FindWithTag("Player");
        gameOverText = GameObject.Find("Panel/gameOver").GetComponent<Text>();

        continues = GameObject.Find("Panel/again").transform ;
        _againText= GameObject.Find("again/againText").GetComponent<Text>();
        
        alarmBGM.loop = true;
    }
    private void Update()
    {
        fraction.text = crtScore1.ToString();
        hp.value = crtHp;
        Death();
    }
    private void again()
    {
        _againText.text = "是否重来!";
        continues.transform.GetChild(1).gameObject.SetActive(true);
        continues.transform.GetChild(2).gameObject.SetActive(true);
    }

    IEnumerator SpawnWaves()
    {
        int number = 0;
        yield return new WaitForSeconds(startWait);
        while (number<10)
        {
            for (int i=0;i<hazardCound; i++)
            {
                GameObject hazard = hazards[Random.Range(0, hazards.Length)];
                Vector3 spawnPosition = new Vector3(
                Random.Range(-spawnValues.x,spawnValues.x),spawnValues.y,spawnValues.z);
                Instantiate(hazard,spawnPosition,Quaternion.identity);
                yield return new WaitForSeconds(spawnWait);
            }
            number++;
            yield return new WaitForSeconds(waveWait);
            if (gameOver)
            {
                Invoke("again", 2);
                break;
            }
        }
    }
}
  • 上面代码增加了hazardCount, spawnWait, startWait, waveWait变量
  • 分别用于表示敌人数量敌人生成的间隔时间游戏一开始的暂停时间两批次敌人的间隔时间
  • 利用协程使得一波又一波敌人根据时间来创建,还加入了背景音乐 ~

来看一下效果吧~是不是有那味了呢!


第十步:UI处理——自由发挥

主界面UI

  • 既然游戏场景有了,一个小游戏怎么能没有UI界面呢~
  • 新建一个游戏场景SceneUI界面处理,然后点开始游戏的时候进行切换就可以啦~
  • 下面我搭建了一个简单的UI场景参考~只是简单的UGUI使用,就不多做赘述了
  • 只要能点击开始游戏的时候能切换游戏场景就可以啦~

下面看下我的UI效果 :

然后点击开始按钮的时候调用切换场景的方法,这里要注意需要把场景添加进去哦

File/Build Setting /Add Open Scene
如下图:

private void OnClickStartGame()
    {
       
        SceneManager.LoadScene("Main");
    }

游戏界面UI

  • 既然主界面的UI处理完了,那游戏界面也需要UI
  • 那我们最好给我们的飞机加上血量,还有击杀敌人加分数~
  • 一个正常的游戏逻辑都是这样处理的嘛~
  • 这是把游戏控制器的脚本GameController给改了一下,声明了一个Text文本用于记录分数,在击杀敌人的时候调用即可。
  • PlayerControllder脚本中加入血量,在被敌人攻击或者碰到敌人时调用扣血方法
  • 还新加入了炮弹音效销毁音效等等,在这里不多做介绍啦,后面会单独写声音控制,不然怕大家看的太多都不想看啦~
  • 这个也不多做赘述啦,都是个人喜好添加的,直接上代码

游戏控制器代码:

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

public class GameController : MonoBehaviour
{
    [Header("敌人的预设体")]
    public GameObject[] hazards;
    public Vector3 spawnValues;
    [Header("一批敌人的数量")]
    public int hazardCound = 10;
    [Header("一批中,单个敌人生成间隔")]
    public float spawnWait = 0.5f;
    [Header("开始的暂停时间")]
    public float startWait = 1f;
    [Header("两批敌人之间的间隔时间")]
    public float waveWait = 4;
    [Header("背景音乐")]
    public AudioSource alarmBGM;

    private Text fraction;
    private Slider hp;

    [HideInInspector]
    public int crtScore1 = 0;
    [HideInInspector]
    public int Score=0;
    private int crtHp=100;
    private Text gameOverText;
    private Transform continues;
    private Text _againText;

    private GameObject player;
    private bool gameOver=false;

    void Start()
    {
        StartCoroutine(SpawnWaves()); 
        fraction = GameObject.Find("Panel/fraction/score").GetComponent<Text>();
        hp = GameObject.Find("Panel/HP").GetComponent<Slider>();
        player = GameObject.FindWithTag("Player");
        gameOverText = GameObject.Find("Panel/gameOver").GetComponent<Text>();

        continues = GameObject.Find("Panel/again").transform ;
        _againText= GameObject.Find("again/againText").GetComponent<Text>();
        
        alarmBGM.loop = true;
    }

    private void Update()
    {
        fraction.text = crtScore1.ToString();
        hp.value = crtHp;
        Death();
        
        
        
    }

    
    
    
    
    public void GetScore(int scores)
    {
        crtScore1 += scores;
    }
    
    
    
    
    public void GetHp(int damage)
    {
        crtHp -= damage;
    }
    private void Death()
    {
        if (crtHp<0)
        {
            Destroy(player);
            
            alarmBGM.Stop();

            gameOver = true;
            gameOverText.text = "Game Over!"+"总分数"+crtScore1;

            if (crtScore1>Score)
            {
                Score = crtScore1;
                
                PlayerPrefs.SetInt("High",Score);
            }
        }
    }
    private void again()
    {
        _againText.text = "是否重来!";
        continues.transform.GetChild(1).gameObject.SetActive(true);
        continues.transform.GetChild(2).gameObject.SetActive(true);
    }

    IEnumerator SpawnWaves()
    {
        int number = 0;
        yield return new WaitForSeconds(startWait);
        while (number<10)
        {
            for (int i=0;i<hazardCound; i++)
            {
                GameObject hazard = hazards[Random.Range(0, hazards.Length)];
                Vector3 spawnPosition = new Vector3(
                Random.Range(-spawnValues.x,spawnValues.x),spawnValues.y,spawnValues.z);
                Instantiate(hazard,spawnPosition,Quaternion.identity);
                yield return new WaitForSeconds(spawnWait);
            }
            number++;
            yield return new WaitForSeconds(waveWait);
            if (gameOver)
            {
                Invoke("again", 2);
                break;
            }
        }
    }
}

角色控制器PlayerControllder代码:

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


[System.Serializable]
public class Boundary
{
    [Header("边界控制")]
    public float xMin, xMax, zMin, zMax;
}

public class PlayerControllder : MonoBehaviour
{
    [Header("炮弹预设体")]
    public GameObject bullet;
    [Header("移动速度")]
    public float Speed = 5f;
    public Boundary boundary;
    public float tilt=4f;
    public Transform shotSpawn;
    [Header("玩家开炮声音")]
    public AudioClip fireClip;

    private GameObject bolt;
    private Rigidbody rb;
  
    private float fireRate=0.2f;
    private float nextFire;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    private void Update()
    {
       Fire();
    }
    public void Fire()
    {
        if (Input.GetButton("Fire") && Time.time>nextFire)
        {
            nextFire = Time.time + fireRate;
            bolt = Instantiate(bullet,transform.position,Quaternion.identity);
            AudioSource.PlayClipAtPoint(fireClip,transform.position); 
        }
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
        rb.velocity = movement * Speed;

        
        rb.position = new Vector3(
            Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
            0.0f,
            Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)
            );
        
        rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt);
    }
}

最后一步:游戏展示

  • 当当当,游戏展示留到了最后一步,也不枉我做了一天写了一天才弄出来这么个打飞机的游戏~
  • 能从头看到这里的大哥都是认真学习的好同志和真爱了,我把整个游戏的思路和做法全写了出来,可能有些细节地方没有注意到,可以评论区说一下,还请谅解啦~
  • 由于只能上传5M以内的照片,我把录屏压缩了一下可能卡卡的,这里就简单演示下啦~可以看下面的视频展示!


这个图片看起来卡卡的效果不好,来看看战斗场景内视频演示叭:

整体游戏演示如下:


  • 本篇文章做了一个打飞机的游戏,不知道还合不合诸位胃口呢~
  • 如果能跟着博客写的自己动手做出来,Unity入门应该没问题了~不过离大佬还是天差地别!
  • 头一次一篇文章写这么多,都有点吧自己绕晕了!
  • 可能有些细节地方没有注意到,还请谅解啦~

想要工程源码学习的可以在GitHub上下载此工程源码https://github.com/Zhanganyun98/Unity-Aircraft-War


  • ?我:怎么样,学妹,看完这篇博客你学会怎样制作打飞机游戏了吗?
  • ?学妹一脸欣喜,曰:会啦,哥哥你可真棒!你玩打飞机的样子简直帅呆了,下次还要和你一起玩打飞机!
  • ?我:emm…好咧,记得给我文章三连点个赞支持下就行,下次带你玩更刺激的!
  • ?学妹:好咧哥哥,那下次再见啦~

作者: God Y.

来源: https://blog.csdn.net/zhangay1998/article/details/117648208



标题:[转]unity游戏开发之经典案例打飞机
作者:shirlnGame
地址:https://www.mmzsblog.cn/articles/2021/06/08/1623115305450.html

如未加特殊说明,文章均为原创,转载必须注明出处。均采用CC BY-SA 4.0 协议

本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。若本站转载文章遗漏了原文链接,请及时告知,我们将做删除处理!文章观点不代表本网站立场,如需处理请联系首页客服。
• 网站转载须在文章起始位置标注作者及原文连接,否则保留追究法律责任的权利。
• 公众号转载请联系网站首页的微信号申请白名单!

个人微信公众号 ↓↓↓                 

微信搜一搜爱上游戏开发