☀️项目效果展示
- 这里的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图片具有一定自适性
完结,撒花❀~