Unity3D经典小游戏制作 | 坦克大战【附项目过程&源码】

☀️项目效果展示

  • 这里的gif是用了一款ScreenToGif的软件录制的,处理的比较短
  • 右侧图标分别表示消灭的敌人数量(上) 和 当前的生命值(下),被击中一次生命值-1,变为0的时候玩家死亡,游戏结束


目录

☀️项目效果展示

☀️前言

⭐️相关背景

⭐️相关知识

⭐️版本说明

⭐️许可证激活

⭐️及时保存

☀️项目概况

⭐️项目整体布局

⭐️猜想验证

⭐️分步介绍(结合代码)

❀Barrier.cs

❀Born.cs

❀Bullet.cs

❀Enemy.cs

❀Explosion.cs

❀Heart.cs

❀MapCreation.cs

❀Option.cs

❀Player.cs

❀PlayerManager.cs

☀️学习/制作过程txt记录本分享


☀️前言

⭐️相关背景

  • 博主在大二暑期接触了unity,借鉴实战视频上手花两天复刻了 “坦克大战” 这一经典小游戏,发现做游戏这种过程真的比玩儿游戏还要爽啊,hh,看着一些功能逐渐被实现,游戏越来越完整,真的是挺令人舒适和爽快。由于那时候零基础直接上手,对于一些基础操作真的是不太熟练,很不灵活,幸好有一些前辈的帮助(比如CSDN小伙伴们熟悉的y哥),解决了一些自己难以解决的问题,帮助小虾在迷茫的夜海上拨开神秘的迷雾。

⭐️相关知识

  • 在学习的过程中,几个比较重要的知识点就是:预制体、克隆体、精灵渲染器、渲染层级、脚本、碰撞检测、触发检测、AI设计、UI设计、固定物理帧...

⭐️版本说明

  • 小虾采用的是Unity一个较新的版本-2021.1.16,这里建议小伙伴们下载和教程相同版本的,不然真的会有可能遇到卡点消耗心情哈。
  • 可以和我一样在Unity Hub里面安装,Hub感觉挺好用的。

⭐️许可证激活

  • 这个...有段时间不登好像就要重新激活许可证,遇到同样问题的小伙伴可以进入下图的网址:Unity - Activation 按提示进行操作即可

⭐️及时保存

  • 打开的时候总是激动又煎熬,U3D启动有点慢并且操作过程中很可能就会报废,所以项目过程中一定要及时保存,不然一不小心可能就要重新来过啦!


☀️项目概况

⭐️项目整体布局

  • 整个项目工程的一个概况就在下面了,Hierarchy菜单下是放 预制体(Prefab) 的 克隆体(Clone) 的,这些克隆体可以拖到左上方的大界面中,通过点击播放按钮进行效果检验。左下方的就是整体的动画效果显示了。
  • 做一个小游戏项目,我们首先需要素材,这里推荐初学的小伙伴去siki学院学习,里面会有相应的完整初级案例教程。

⭐️猜想验证

  • 学习的过程就是不断验证猜想并接收新知识的过程,我本来想坦克大战里面的地图就是把预先处理好的 “砖墙、水泥墙、草坪、河流、空气墙(防止Object越过边界的)” 按照一定规律和自己的喜好摆放开来作为整张地图的,但事实打脸了(觉得自己真傻)。
  •  地图实例化(MapCreation) 中,我们使用了 “产生随机位置” 的方法,在避开已有位置的基础上,自动生成其它的地图元素。

代码如下:

 // 产生随机位置的方法
    private Vector3 CreateRandomPosition() // 列表中没有这个位置,才能产生
    {
        while(true)
        {
            // 不生成x=-13,13(变为10)这两列,y=-8,8这两行的位置
            Vector3 createPosition = new Vector3( Random.Range(-9,10), Random.Range(-7,8), 0); // x y z,Random.Range(a,b)中b的实际值是b-1
            if(!HasThePosition(createPosition))
            { 
                return createPosition; // 列表中没有的位置就返回 
            }
            // false 则继续循环
        }
    }

⭐️分步介绍(结合代码)

Barrier.cs

  • 目的:调用音效
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Barrier : MonoBehaviour
{
    public AudioClip hitAudio;

    public void PlayAudio()
    {
        AudioSource.PlayClipAtPoint(hitAudio, transform.position);
    }
}

Born.cs

  • 目的:让特效播放一段时间后自我销毁,产生玩家
  • 思路:首先引用玩家playerPrefab,新建一个敌人的数组作为敌人预制体的列表,并设置bool变量判断子弹是谁产生的。游戏一开始时,调用延时Born方法,经过延时销毁出生特效。判断是否是玩家,是则产生玩家,不是就定义一个随机数,随机生成2种敌人中的1种。
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Born : MonoBehaviour
{
    public GameObject playerPrefab; // 先拿一下玩家的引用
    public GameObject[] enemyPrefabList; // 新建一个敌人的数组:敌人预制体的列表
    public bool createPlayer; // 为了判断子弹是谁产生的(初始默认为敌人的)

    // Start is called before the first frame update
    void Start()
    {
        Invoke("BornTank", 1.0f); // 让游戏一开始调用一下延时Born方法
        Destroy(gameObject, 1.0f); // 经过0.8f延时0.8f销毁出生特效
    }

    private void BornTank()
    {
        if(createPlayer) // ture则产生玩家
        {
            Instantiate(playerPrefab, transform.position, Quaternion.identity);
        }
        else // 敌人有两种,定义一个随机数
        {
            int num = Random.Range(0,2); // 0 1(整型情况包含前两个)
            Instantiate(enemyPrefabList[num], transform.position, Quaternion.identity);
        }
    }
}

Bullet.cs

  • 目的:模拟子弹和相关物体的碰撞效果
  • 思路:首先给子弹一个速度,这个速度要适中设置,建议和玩家移动速度相同。设置一个bool变量 isPlayerBullet(默认false,不是敌人的子弹),再分6类完善子弹碰撞检测的方法
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class bullet : MonoBehaviour
{
    public float moveSpeed = 10;
    public bool isPlayerBullet; // 默认false,不是敌人的子弹
    
    void Update()
    {
        transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World); //让子弹沿着自身所在Y轴移动
    }

    //子弹碰撞检测的方法
    private void OnTriggerEnter2D(Collider2D collision) 
    {
        switch (collision.tag)
        {
            case "Tank":
                if (!isPlayerBullet) // 玩家碰到敌人的子弹
                {
                    collision.SendMessage("Die");  // 玩家死亡一次
                    Destroy(gameObject); // 玩家死亡,自身子弹销毁
                }
                break;
            case "Heart":
                collision.SendMessage("Die"); // 老家死亡
                Destroy(gameObject); // 子弹碰老家,子弹销毁
                break;
            case "Enemy":
                if(isPlayerBullet) // 敌人碰到玩家的子弹
                {
                    collision.SendMessage("Die");  // 敌人死亡一次
                    Destroy(gameObject); // 敌人死亡,自身子弹销毁
                }
                break;
            case "Wall":
                Destroy(collision.gameObject); // 子弹碰到,墙就销毁
                Destroy(gameObject); // 子弹碰墙,子弹也销毁
                break;
            case "Barrier":
                if(isPlayerBullet) // 玩家的子弹才有音效
                {
                    collision.SendMessage("PlayAudio"); 
                }
                Destroy(gameObject); // 子弹碰到不可销毁的障碍(铁墙和空气墙),子弹自身销毁
                break;
            default:
                break;
        }

    }
}

Enemy.cs

  • 目的:实现敌方坦克的攻击、移动、死亡操作并优化 敌人(Enemy) 的AI
  • 思路:这里要用到精灵渲染器,我们要考虑子弹的旋转角度、敌方坦克上下左右的移动操作,攻击的CD、改变方向的时间计时器以及攻击、移动和死亡的具体方法的实现。最后,为了使玩家获得更加流畅舒适的体验,我们要优化 敌人(Enemy) 的AI。
  • Tips:我们要用到 void FixedUpdate() 这个生命周期函数,它也叫固定物理帧,保证在每一秒都匀称的情况下执行...
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    //属性值
    public float moveSpeed = 3;
    private Vector3 bulletEulerAngles; // 子弹应该旋转的角度
    private float v = -1;
    private float h;

    //引用
    private SpriteRenderer sr;
    public Sprite[] tankSprite; // 表示(上 右 下 左)方向;因为要改变精灵的值,所以设一个精灵数组(也就是拿一个和精灵值同类型的属性)
    public GameObject bulletPrefab;
    public GameObject explosionPrefab;

    //计时器
    private float timeVal; // 攻击CD(计时器)
    private float timeValChangeDirection; // 改变方向的时间计时器(一开始若设为4是为了使其已出现就移动)


    private void Awake() // 一般的引用在awake中赋值比较好,因为所有属性的确定和引用都应该在游戏物体一开始的时候就拿到
    {
        sr = GetComponent<SpriteRenderer>(); // 拿到了精灵渲染的组件
       
        // 接下来去控制它的图片属性就可以了
    }

    void Update()
    {
        // 攻击的时间间隔
        if (timeVal >= 3f)
        {
            
        }
        else
        {
            
        }
    }

    void FixedUpdate() // 生命周期函数,也叫固定物理帧,是在每一秒都匀称的情况下执行的
    {
        Move();
    }

    //坦克的攻击方法
    public void Attack()
    {
        //子弹产生的角度:当前坦克的角度+子弹应该旋转的角度(rotation中的欧拉角要转为四元数的形式表示角度)
        //子弹预制体 坦克位置 旋转角
        timeVal = 0;
    }

    // 坦克的移动方法
    private void Move() // 把移动代码封装成 Move()函数
    {
        
        // 下面的敌人AI只是为了控制h和v的值,下面和player一样根据值选择图片
        if(timeValChangeDirection >= 4) // 攻击一次后改变方向
        {
            int num = Random.Range(0, 8); // 多定义4个,为了让朝下的概率变大
            if(num > 5) // 向下(确保概率较大)
            {

            }
            else if(num == 0) // 向上(确保概率较小)
            {

            }
            else if (num > 0 && num <=2) // 向左
            {

            }
            else if (num > 2 && num <= 4) // 向右
            {

            }

            timeValChangeDirection = 0; // 归零防止鬼畜运动
        }
        else
        {
            // 因为move()的方法是放在void FixedUpdate()里面的 
        }

       
        //控制玩家的移动,Vector3.right 表示X轴方向的移动,Y轴是up,Z轴是forward
        if (v < 0)
        {
            sr.sprite = tankSprite[2];
            bulletEulerAngles = new Vector3(0, 0, -180);
        }
        else if (v > 0)
        {
            sr.sprite = tankSprite[0];
            bulletEulerAngles = new Vector3(0, 0, 0);
        }
        if (v != 0) // 确保按照上下移动的同时不能进行左右移动
        { 
            return;
        }


        transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World); //Time.deltaTime:按每秒移动;逗号右边的第二个方向是以世界坐标轴的方向
        if (h < 0)
        {

        }
        else if (h > 0)
        {

        }
    }

    // 坦克的死亡方法
    private void Die()
    {
        // 敌人每次死亡,就让得分+1
        PlayerManager.Instance.playerScore++;
        // 产生爆炸特效
        Instantiate(explosionPrefab, transform.position, transform.rotation);
        //死亡
        Destroy(gameObject);
    }

    //优化Enemy的AI:若敌人相碰,顺时针旋转一个角度,这样能避免扎堆
    private void OnCollisionEnter2D(Collision2D collision) // 坦克是碰撞检测
    {
        if(collision.gameObject.tag=="Enemy")
        {
            // 时间瞬间达到4,即刻旋转
        }
    }
}

Explosion.cs

  • 目的:实现爆炸效果
  • 思路:这里的爆炸效果是刚出生的爆炸特效,持续短暂时间后消失就好,也就是只作用一小段时间
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Explosion : MonoBehaviour
{
    void Start()
    {
        Destroy(gameObject, 0.167f);
    }
}

Heart.cs

  • 目的:模拟 老家(Heart)
  • 思路:开始的时候就获取一下精灵渲染器组件,一旦触发老家死亡效果,就更换为老家被破坏时的图片,并附加爆炸特效。被击中的时候要调用一下 dieAudio音效。
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Heart : MonoBehaviour
{
    private SpriteRenderer sr;
    public Sprite BrokenSprite; // 为了获取老家(Heart)被破坏时的图片
    public GameObject explosionPrefab; // 拿一下爆炸效果的引用
    public AudioClip dieAudio;

    void Start()
    {
        sr = GetComponent<SpriteRenderer>(); // 开始的时候就获取一下精灵渲染器组件
    }

    public void Die()
    {
        // 更换图片:一旦触发老家死亡效果,就更换为老家被破坏时的图片
        // 产生爆炸特效
        PlayerManager.Instance.isDefeat = true; // 被击中
        // 在某个点调用一下dieAudio
    }
}

MapCreation.cs

  • 目的:地图实例化
  • 思路:我们在已知固定位置的地方手动生成,并将它们放在一个列表中,其他的位置用随机生成位置的方法生成地图,这样每次玩家进入游戏的时候,就是一张崭新的地图了,这就丰富了游戏的多样性和可玩性。我们将敌人位置的生成也放在这部分代码中。
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MapCreation : MonoBehaviour // 地图实例化
{
    // 用来装饰初始化地图所需物体的数组
    // 0.老家 1.墙 2.障碍 3.出生效果 4.河流 5.草 6.空气墙
    public GameObject[] item;

    // 已经有东西的位置列表
    private List<Vector3> itemPositionList = new List<Vector3>();

    private void Awake()
    {
        InitMap();
    }

    private void InitMap()
    {
        // 实例化老家(CreateItem其实还是用的Instantiate的方法)
        CreateItem(item[0], new Vector3(0, -8, 0), Quaternion.identity); // Quaternion.identity:无旋转

        //用墙把老家围起来
        CreateItem(item[1], new Vector3(-1, -8, 0), Quaternion.identity);
        CreateItem(item[1], new Vector3(1, -8, 0), Quaternion.identity);
        for (int i = -1; i < 2; i++)
        {
            CreateItem(item[1], new Vector3(i, -7, 0), Quaternion.identity);
        }

        // 实例化外围墙 x=-14,14(变为11),y=-9,9
        for (int i = -11; i < 12; i++) //最上面一行
        {
            CreateItem(item[6], new Vector3(i, 9, 0), Quaternion.identity);
        }
        for (int i = -11; i < 12; i++) //最下面一行
        {
            CreateItem(item[6], new Vector3(i, -9, 0), Quaternion.identity);
        }
        for (int i = -8; i < 9; i++) //最左面一行
        {
            CreateItem(item[6], new Vector3(-11, i, 0), Quaternion.identity);
        }
        for (int i = -8; i < 9; i++) //最右面一行
        {
            CreateItem(item[6], new Vector3(11, i, 0), Quaternion.identity);
        }

        // 初始化玩家(通过实例化出生特效实例化玩家)
        GameObject go = Instantiate(item[3], new Vector3(-2, -8, 0), Quaternion.identity); // 产生出生特效
        go.GetComponent<Born>().createPlayer = true;// 特效上的bool值设置为true:产生玩家

        // 产生敌人(在固定的三个位置先产生,之后每隔一段时间在三个位置随机产生)
        CreateItem(item[3], new Vector3(-10, 8, 0), Quaternion.identity);
        CreateItem(item[3], new Vector3(0, 8, 0), Quaternion.identity);
        CreateItem(item[3], new Vector3(10, 8, 0), Quaternion.identity);

        InvokeRepeating("CreateEnemy", 4, 5);

        // 实例化地图
        for (int i = 0; i < 60; i++)
        {
            CreateItem(item[1], CreateRandomPosition(), Quaternion.identity);
        }
        for (int i = 0; i < 20; i++)
        {
            CreateItem(item[2], CreateRandomPosition(), Quaternion.identity);
        }
        for (int i = 0; i < 20; i++)
        {
            CreateItem(item[4], CreateRandomPosition(), Quaternion.identity);
        }
        for (int i = 0; i < 20; i++)
        {
            CreateItem(item[5], CreateRandomPosition(), Quaternion.identity);
        }

    }


    private void CreateItem(GameObject createGameObject,Vector3 createPosition,Quaternion createRotation) // 封装一个方法:使播放时Hierarchy菜单下的东西不散落
    {
        // 根据上面的参数实例化游戏物体
        // Parent是当前的游戏物体
        // 把所有出现过的物体位置存进位置列表
    }
    

    // 产生随机位置的方法
    private Vector3 CreateRandomPosition() // 列表中没有这个位置,才能产生
    {
        while(true)
        {
            // 不生成x=-13,13(变为10)这两列,y=-8,8这两行的位置
            Vector3 createPosition = new Vector3( Random.Range(-9,10), Random.Range(-7,8), 0); // x y z,Random.Range(a,b)中b的实际值是b-1
            if(!HasThePosition(createPosition))
            { 
                return createPosition; // 列表中没有的位置就返回 
            }
            // false 则继续循环
        }
    }

    
    // 用来判断位置列表中是否有这个位置
    private bool HasThePosition(Vector3 createPos)
    {
        for (int i = 0; i < itemPositionList.Count; i++) // 遍历当前的位置列表
        {
            if(createPos == itemPositionList[i]) // 如果随机产生位置的值等于当前列表某个位置的值
            {
                return true;
            }
        }
        return false;
    }


    // 产生敌人的方法
    private void CreateEnemy()
    {
        int num = Random.Range(0, 3); // 0 1 2
        Vector3 EnemyPos = new Vector3(); // 装随机产生的位置:向量/坐标
        if (num == 0)
        {

        }
        else if (num == 1)
        {

        }
        else 
        {

        }
        CreateItem(item[3], EnemyPos, Quaternion.identity);
    }
}

Option.cs

  • 目的:选项监听实现
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Option : MonoBehaviour
{
    private int choice = 1;
    public Transform posOne;
    public Transform posTwo;

    // Update is called once per frame
    void Update()
    {
        // 监听开始界面上下移动的选项
        if (Input.GetKeyDown(KeyCode.W))
        {
            choice = 1;
            transform.position = posOne.position;
        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
            choice = 2;
            transform.position = posTwo.position;
        }
        if (choice==1 && Input.GetKeyDown(KeyCode.Space))
        {
            SceneManager.LoadScene(1); // 加载 Main 场景
        }
    }
}

Player.cs

  • 目的:模拟玩家坦克Player
  • 思路:玩家坦克Player 的实现可以借鉴 敌方坦克Enemy
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour {
    
    //属性值
    public float moveSpeed = 3;
    private Vector3 bulletEulerAngles; // 子弹应该旋转的角度
    private float timeVal; // 攻击CD(计时器)
    private float defendTimeVal = 3; // 无敌CD(计时器)
    private bool isDefended = true; // 无敌状态

    //引用
    // 拿到渲染器
    // 拿到需要用到的图片,(上 右 下 左)方向;因为要改变精灵的值,所以设一个精灵数组(也就是拿一个和精灵值同类型的属性)
    public GameObject bulletPrefab;
    public GameObject explosionPrefab;
    public GameObject defendEffectPrefab;
    // 控制音效的播放
    // 拿到音效播放的资源

    private void Awake() // 一般的引用在awake中赋值比较好,因为所有属性的确定和引用都应该在游戏物体一开始的时候就拿到
    {
        // 拿到了精灵渲染的组件
        
        // 接下来去控制它的图片属性就可以了
    }

    void Update()
    {
        // 无敌CD
        if(isDefended)
        {
            defendEffectPrefab.SetActive(true); // 控制无敌状态时护盾打开
            defendTimeVal -= Time.deltaTime;
            if(defendTimeVal <= 0)
            {
                isDefended = false;
                // 控制无敌状态时护盾关闭
            }
        }
    }

    private void FixedUpdate() {// 生命周期函数,也叫固定物理帧,是在每一秒都匀称的情况下执行的
        
        if(PlayerManager.Instance.isDefeat)
        {
            return; // true则return:游戏失败,禁止玩家所有操作
        }
        Move();

        // 攻击CD
        if (timeVal >= 0.4f)
        {
            Attack();
        }
        else
        {
            timeVal += Time.fixedDeltaTime;
        }
    }


    //坦克的攻击方法
    public void Attack()
    {
        if() //若检测到玩家输入为空格键
        {
            //子弹产生的角度:当前坦克的角度+子弹应该旋转的角度(rotation中的欧拉角要转为四元数的形式表示角度)
            //子弹预制体 坦克位置 旋转角
            timeVal = 0;
        }
    }


    // 坦克的移动方法
    private void Move() // 把移动代码封装成 Move()函数
    {
        // 监听玩家垂直轴值的输入
        transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);

        if (v < 0) {
            
        }
        else if (v > 0) {
            
        }

        // v和h不为0,播放...音效 (在判断v是否为0之前判断是考虑了它的优先级)
        if(Mathf.Abs(v)>0.05f)
        {
            // 若音效播放时又调用,声音会很杂,所以要判断...
            if(!moveAudio.isPlaying)
            {
                moveAudio.Play();
            }
        }

        if (v != 0) { // 确保按照上下移动的同时不能进行左右移动
            return;
        }

        //监听玩家水平轴值的输入h来控制在X轴上的位移的方向,按右方向为1,左为-1

        //控制玩家的移动,Vector3.right 表示X轴方向的移动,Y轴是up,Z轴是forward
        transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World); //Time.deltaTime:按每秒移动;逗号右边的第二个方向是以世界坐标轴的方向
        if (h < 0) {

        }
        else if (h > 0) {

        }

        if (Mathf.Abs(h) > 0.05f)
        {
            moveAudio.clip = tankAudio[1]; 
            if (!moveAudio.isPlaying)
            {
                moveAudio.Play();
            }
        }
        else
        {
            moveAudio.clip = tankAudio[0];
            if (!moveAudio.isPlaying)
            {
                moveAudio.Play();
            }
        }

    }

    // 坦克的死亡方法
    private void Die()
    {
        // 是否处于无敌状态
        if (isDefended)
        {
            return;
        }
        // 击中的话,就让玩家管理里面的属性变为true
        PlayerManager.Instance.isDead = true;

        // 产生爆炸特效
        Instantiate(explosionPrefab, transform.position, transform.rotation);
        //死亡
        Destroy(gameObject);
    }

}

PlayerManager.cs

  • 目的:玩法管理和UI设计
  • 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI; // 为了更新 UI,拿一下它的引用,使用 UI,就要拿一下Unity的运营空间
using UnityEngine.SceneManagement;

public class PlayerManager : MonoBehaviour
{
    //属性值
    public int lifeValue = 3;
    public int playerScore = 0;
    public bool isDead;
    public bool isDefeat;

    //引用
    public GameObject born;
    public Text playerScoreText;
    public Text playerLifeValueText;
    public GameObject isDefeatUI; // 拿一下游戏物体的引用

    //单例
    private static PlayerManager instance;
    public static PlayerManager Instance { get => instance; set => instance = value; }

    private void Awake()
    {
        Instance = this;
    }

    // Update is called once per frame
    void Update() // 在其中实时监听一些状态
    {
        if(isDefeat) // 失败的话
        {
            isDefeatUI.SetActive(true); // 让失败的图片显示
            return;
        }
        
        if(isDead) // 如果死亡
        {
            Recover();
        }

        // 实时更新消灭敌人的显示和生命值
        playerScoreText.text = playerScore.ToString(); // .ToString():把int转化为字符串
        playerLifeValueText.text = lifeValue.ToString();
    }

    private void Recover()
    {
        if (lifeValue<=0)
        {
            // 游戏失败,返回主界面
            isDefeat = true;
            Invoke("ReturnToTheMainMenu", 3); // 延时3s调用这个方法
        }
        else
        {
            lifeValue --;
            GameObject go = Instantiate(born, new Vector3(-2, -7.9f, 0), Quaternion.identity); // 接收born的脚本
            go.GetComponent<Born>().createPlayer = true;
            isDead = false;
        }
    }

    private void ReturnToTheMainMenu()
    {
        SceneManager.LoadScene(0); // 返回开始界面
    }

}

☀️学习/制作过程txt记录本分享

流水账,真的可以忽略了

坦克大战:做动画特效时,播放动画后新建了一个另一个特效,再次播放时,新特效不仅不会播放,新建的内容也会自动消失,可能是播放和保存起了冲突

****************如何解决昨晚的截图问题
(已解决:没有添加vs)

****************如何解决玩家的朝向问题
(方法1.切换图片的渲染和显示,可以在Inspector查看;方法2.直接让图片沿着Z轴旋转)
图片的渲染工作是由Sprite renderer这个精灵渲染器主页来控制的,通过改变其中Sprite的值就可以改变图片的渲染

*************难点:碰撞检测和触发检测
一、发生碰撞检测必要条件:1.两个发生碰撞的物体都需要有碰撞器  2.其中一方(最好是运动的一方)要有刚体【要在运动的一方是因为:经常性的不运动后,刚体会休眠,这是无法发生碰撞检测】
二、注意点:碰撞器分为两大类,2D要配2D的,否则没用
三、要想发生碰撞检测和触发检测,要调整渲染层级的顺序,顺序相同才会发生触发检测。

************************
在Hierarchy中,对克隆体的操作不会直接应用到预制体上,因为Hierarchy中的只是克隆体,点apply可以应用;在预制体上操作,会自动存到克隆体上

************************
play时player发生抖动是因为加上刚体之后有受力,希望每一帧受力均匀,也就是每秒受力大小是均匀的,就要在物理帧中执行【以为update中的Time.deltaTime例来解释为何要在FixedUpdate的生命周期中执行:update在执行时,会由于电脑性能之类的差异,每一帧的时间是不同的,所以要设固定物理帧,在其中执行每一帧占用的时间固定】。这也告诉我们,以后再用到刚体进行移动且有力的交互时,把东西放在固定物理帧的生命周期中运行,这样就不会产生抖动的效果。

**************怎样处理物体斜着运动
在h或v上加上优先级

**************2D渲染

***************
每次对克隆体做修改之后,都应该apply一下,让其应用到预制体上

***************
老家被敌人攻打之后,会变样,就要给它一个渲染。
2D游戏进行渲染时,首先要拿一个精灵渲染器组件。
渲染有优先级【比如坦克大战中,玩家要穿过草坪,子弹要横跨河流,这就需要渲染一些次序;特效的渲染的优先级要高于玩家,也就是说,特效要能够覆盖玩家】
精灵渲染中有一个Sorting Layer,表示渲染的层级(大层级),可以通过这个层级的先后顺序控制渲染;Order in layer表示层级的顺序,叫做小层级,也就是说在大层级下面有一些控制渲染的顺序。
层级(数值)越大,越后渲染,越不容易被覆盖【这个容不容易要根据游戏的需求,由环境决定】

*********************子弹朝向问题
通过坦克的运动方向来确定子弹的朝向
3D引擎中的2D图片要绕着Z轴旋转(这点切换Scene中的2/3D即可知道,但这两个状态下的转动方向就像照镜子一样是相反的,因为2D->3D的时候,已经是绕着X轴旋转180度了,因此在Unity 3D下,2D图片的旋转方向应为逆时针,和3D的相反)

******************************
每新建一个脚本,都让VS识别一下,避免发生不必要的错误

**********************用transform.Translate( , )函数时
若要沿着世界坐标轴移动,前面填Space.World,后面可以填Space.Self或者不填;若沿着玩家所在轴方向,前面是玩家....,后面必须要有Space.World

***********************攻击CD
设一个计时器(时间间隔)

***********************触发器
1. 和碰撞器的区别:碰撞器产生碰撞效果;而触发器碰到的时候,会发生触发检测
2. 必要条件:参考碰撞检测

************************空气墙
Ctrl D一个,删去精灵渲染器,移到预制体(Prefab)里

************************
P18 6min

*****************************
UnassignedReferenceException: The variable button of CityScript has not been assigned.
因为我自己挂了两个CityScript脚本在不同的组件上面导致出现了这个问题。所以有出现这个问题的童鞋,找找看看有没有挂两个一样的脚本。

*****************************特效飞出去的问题???
做了一个explosion特效(放在了Prefab的Effect下)会飞出去,new了一个Newexplosion特效,只能拖进Prefab,不能拖进Effect和explosion并列(unity会有警告)

**************************数组越界的问题
存的数目和数组用到的元素数目不符合

**************************设置了多个敌人,挂了相同的Enemybulllet脚本???

**************************地图实例化
长:18*2   宽:7.8*2

*************************Player和Enemy移动的范围有问题???

*************************Player的出生特效(为啥出生特效丢了,看视频去解决)???

*************************随机生成的地图元素会错乱(因为没有用整型,用了浮点型)

************************* API
InvokeRepeating(方法名,延时几秒后第一次调用此方法,没隔多久调用一次)

************************* API
Invoke  每隔一段时间调用一次某方法,在update里面写一个计时器,让其每隔一段时间调用一次方法

*********************** UI图片右对齐
缩放界面的时候UI图片具有一定自适性


完结,撒花❀~

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>