场景制作
地图绘制
创建不同的2D Object--Tile map
给不同层设置Sorting Layer (越靠下越在前面显示)
同一个Sorting Layer 的 Order in Layer 可以不同(可实现透明效果的覆盖)
window--2D--Tile Palette
Palette 选择不同绘制的图案
Active Tile map 选择在哪个Layer上绘制
背景布置
给需要添加的背景元素设置设置统一的 Sorting Layer
给火炬等需要发光的物体添加光源(当背景添加材质后可被照亮)(prefab窗口里的 Overrides--Apply all 可以将其添加到预制中)
*Unity--Window--Rendering--Lighting Settings--Ambient Color 可调节主场景的灯光效果 曝光程度
物理组件
地图
给 platforms 添加组件 Tile map Collider 2D
勾选 use by Composite 并添加 Composite Collider 2D(可以将Tile map 的每个小方块变成一个整体的碰撞器)继续绘画的地方也会添加到整体碰撞器中
*添加完 Composite Collider 2D 后会自动添加组件Rigibody 2D,其中的Gravity Scale为1,此时游戏场景在开始后会因重力掉落,所以应将 Body Type 选择为Static,也可将 platforms 直接勾选为static。
人物
为人物添加Rigibody
将 Collision Detection 选择为 Continuous 连续不断判断碰撞
Interpolate(差值 游戏角色在跳起或碰撞墙面等情况时,会发生凹陷)虽不易发现,但可提升手感
添加组件 Box collider 2D,点击 Edit 可自定义碰撞框大小
在 Constraints 中选择 Freeze Rotation Z 可以锁定Z轴(锁定Z轴后,角色不会因为一部分身体在地图边缘位置而翻滚掉落)
*为避免角色在加速撞击到墙面后发生卡在墙上,或因跳跃黏贴在墙上等问题,可在Box collider 2D--Material 中添加物理材质(Friction摩擦力、Bounciness弹力为0)
添加并设置人物的 Layer 为Player
添加并设置 Platforms 的 Layer 为Ground
角色移动(Player Movement)
//在最开始获得 Rigidbody
private Rigidbody2D rb; //定义玩家刚体
在Start中
rb= GetComponent<RigidBody2D>();
//添加移动参数
上面写[Header("")]可在unity面板中显示
定义xVelocity
获取虚拟轴
代码:
Input.GetButton("虚拟轴名称");
Input.GetButtonDown("虚拟轴名称");
Input.GetButtonUp("虚拟轴名称");
上述三种方法都可以通过虚拟轴名称来获取虚拟轴,但是他们只能判断虚拟轴绑定的按键是否被按下,无法判断正向和负向。
代码:
Input.GetAxis("虚拟轴名称")
Input.GetAxisRaw("虚拟轴名称")
这两种方法获取虚拟轴,当你按下虚拟轴绑定的正向按键,他们会返回正值(最大为1,负值最大为0),按下负向按键会返回负值,区别在于:
第一种方法,在你按下正向按键的时候,它返回的值会从0变化到1,而非瞬间变成1;
第二种方法,则是瞬间变化,按下正向按键则瞬间返回1
*通过Unity栏目:Edit->Project Setting->搜索Input Manager可以查看Unity已经设置好的虚拟轴
以Horizontal为例,首先Horizontal表示水平的意思,这只是这个虚拟轴名称,可以随意更改但是要记住因为在代码里会用到。
Descriptive Name 和 Descriptive Negative Name 分别是正负操作的语言描述。每一个虚拟轴设置都是有正向和负向之分。
Negative表示负向,Positive表示正向。
Negative Button和Alt Negative Button对应的就是负向的实际键盘对应键,二者都是设定具体按键的属性。
Alt Negative Button的值被设定为a,那么键盘上的a就是这个虚拟轴的负向按键。
为了区别正向和负向,按钮有一个值。在按钮不按的时候它为0,按下负向按键,它会逐渐变为-1,变化速度由Sensitivity值决定,值越大,变化速度越快;当你松开此按键,它又会从-1逐渐变灰0,复原速度由Gravity值决定,值越大,变化速度越快,positive同理。
Type值表示你的虚拟轴来源于什么,当前选项为Key or Mouse Button意思是,这个虚拟轴绑定的具体按键可以来源于鼠标按键或者键盘按键。
Axis则表示轴向,当前选项为X axis,意思是水平轴向。向右移动,值就会变为正值,最大为1,移动越快,数值越大,最大为1,向左同理。
移动函数
xVelocity = Input.GetAxis("Horizontal");
rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);
在FixUpdate中进行调用
人物翻转函数
if (xVelocity < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
}
if (xVelocity > 0)
{
transform.localScale = new Vector3(1, 1, 1);
}
在移动函数中尽进行调用
*如果需要下蹲,可在Edit->Project Setting,将jump右击选择Duplicate Array Element进行复制,改名为Crouch,并为其设置按键
GetButton(GetKey):按键激活状态,按键按下后持续有效
GetButtonUp(GetKeyUp):按键松开状态,仅在按键弹起时有效
GetButtonDown(GetKeyDown):按键按下状态,仅在按键按下时有效
区别:
Input.GetKey(KeyCode.S)
Input.GetButton(“Crouch”)
*BoxCollider2D 才能使用.size和.offset来访问两个参数
*判断物理或环境有关的函数要放在 FixedUpdate 里调用
连续跳跃
jumpTime = Time.time + jumpHoldDuration;//(按下跳跃那一刻的瞬时时间+跳跃时长)
当 is jump 时
if (jumpTime < Time.time)
{
isJump = false;
}
Time.time 为真实时间,按下play时开始计时,所以 Time.time 一定大于 jump time
AddForce 和 Velocity 的区别
Rigidbody.Velocity= (Vector3*Speed) ;//一直都是固定的速度
Rigidbody.AddForce(Vector3*系数);//受力的影响
Rigidbody.velocity:
这个方法是瞬间给物体一个恒定的速度,将物体提升至该速度。
当按下跳跃键的时候,最多只会提升到我们规定的速度以及朝向我们规定的方向。初始速度不变的情况下,跳跃高度也是恒定的。
Rigidbody.AddForce:
这个方法瞬间给物体一个规定好的力
每按一下跳跃键,它会被施加这个恒定的力,它跳跃的初始速度会越变越大,
每次跳跃的高度和前一次相较变得越来越大(在连续跳跃的情况下)
射线检测(RaycastHit2D)
RaycastHit2D 命名= Raycast (Vector2offset, Vector2.rayDiraction, floatlength, LayerMask layer);
其中 Vector2 offset为射线起点,具体应用为new Vecotr2 (float x, float y)
Vector3.rayDiraction为射线方向,大多应用为Vector3.down; Vector3.up; Vector3.forward; Vector3.back; Vector3.left; Vector3.right
float length是射线长度,是一个float定义的数
LayerMask layer是检测识别触发的layer,需要在最开始用LayerMask 命名一个layer,并且在unity中挂载上已设置的layer。
射线可视化
//四个参数分别为:射线发射起始位置,射线方向,颜色,长度
Debug.DrawRay(Vector3 start, Vector3.dir, Color.color, float duration);
悬挂射线判断

摄像机跟随
1.windows 打开 Package Manager
点左上角+ 选择add package by name
输入com.unity.cinemachine 点击 add 安装
2.在Hierarchy栏右键 创建2D camera
3.将人物拖拽到 follow️ 中
camera distance 数值调大可以使场景变大
4.add Extension cinemachineConfiner
5.创建空项目 Camera Bounds
勾选 is Trigger
edit collider 可以设置区域
6.将 Bounds 拖拽到confiner 中的 Bounding shape
PlayerMovement 完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private Rigidbody2D rb;
private BoxCollider2D coll;//获得原有碰撞体尺寸
[Header("移动参数")]
public float speed = 8f;
public float crouchSpeedDivisor = 3f;//下蹲速度参数
[Header("跳跃参数")]
public float jumpForce = 6.3f;//单按跳跃的力
public float jumpHoldForce = 1.9f;//长按跳跃的力
public float jumpHoldDuration = 0.1f;//跳跃间隔
public float crouchJumpBoost = 2.5f;//下蹲跳跃增量
public float hangingJumpForce = 15f;//悬挂时跳跃
[Header("状态")]
public bool isCrouch;
public bool isOnGround;
public bool isJump;
public bool isHeadBlocked;
public bool isHanging;
float jumpTime;
public float xVelocity; //定义x轴的速度
//碰撞体尺寸 用于下蹲和站立的变化
Vector2 colliderStandSize;//站立时尺寸
Vector2 colliderStandOffset;//站立时坐标
Vector2 colliderCrouchSize;//下蹲时尺寸
Vector2 colliderCrouchOffset;//下蹲时坐标
[Header("环境检测")]
public float footOffset = 0.4f;//中心到左右脚距离
public float headClearance = 0.5f;//头顶检测距离
public float groundDistance = 0.2f;//与地面的检测距离
float playerHeight;//头顶位置
public float eyeHeight = 1.5f;//眼睛高度
public float grabDistance = 0.4f;//距离墙的距离
public float reachOffset = 0.7f;
public LayerMask groundLayer;
//按键设置
bool jumpPressed;//单次按下跳跃
bool jumpHeld;//长按跳跃
bool crouchHeld;//长按下蹲
bool crouchPress;
RaycastHit2D Raycast(Vector2 offset,Vector2 rayDirection,float length,LayerMask layer)
{
Vector2 pos = transform.position;
RaycastHit2D hit = Physics2D.Raycast(pos + offset, rayDirection, length, layer);
Color color = hit ? Color.red : Color.green;
Debug.DrawRay(pos + offset, rayDirection * length);
return hit;
}
//环境判断
void PhysicsCheck()
{
//左右脚射线
RaycastHit2D leftCheck = Raycast(new Vector2(-footOffset, 0f), Vector2.down, groundDistance, groundLayer);
RaycastHit2D rightCheck = Raycast(new Vector2(footOffset, 0f), Vector2.down, groundDistance, groundLayer);
if (leftCheck||rightCheck)
{
isOnGround = true;
}
else
{
isOnGround = false;
}
//头顶射线
RaycastHit2D headCheck = Raycast(new Vector2(0f, coll.size.y), Vector2.up, headClearance, groundLayer);
if (headCheck)
{
isHeadBlocked = true;
}
else
{
isHeadBlocked = false;
}
float direction = transform.localScale.x;//射线起点方向 左或右
Vector2 grabDir = new Vector2(direction, 0f);//射线发射方向
RaycastHit2D blockedCheck = Raycast(new Vector2(footOffset * direction, playerHeight), grabDir, grabDistance, groundLayer);
RaycastHit2D wallCheck = Raycast(new Vector2(footOffset * direction, eyeHeight), grabDir, grabDistance, groundLayer);
RaycastHit2D ledgeCheck = Raycast(new Vector2(reachOffset * direction, playerHeight), Vector2.down, grabDistance, groundLayer);
//悬挂判断及悬挂时状态
if(!isOnGround && rb.velocity.y<0f && ledgeCheck && wallCheck && !blockedCheck)
{
Vector3 pos = transform.position;
pos.x += ((wallCheck.distance-0.05f) * direction);
pos.y -= ledgeCheck.distance;
transform.position = pos;
rb.bodyType = RigidbodyType2D.Static;//悬挂后静止
isHanging = true;
}
}
//角色移动函数
void GroundMovement()
{
if (isHanging)
{
return;
}
//判断下蹲和站立状态
if (crouchHeld && !isCrouch && isOnGround)
{
Crouch();
}
else if (!crouchHeld && isCrouch && !isHeadBlocked)
{
StandUp();
}
//角色进行移动和翻转
xVelocity = Input.GetAxis("Horizontal");//-1f 1f 获得虚拟轴 “”中为虚拟轴名称
if (isCrouch)
{
xVelocity /= crouchSpeedDivisor;
}
rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);
FilpDirection();
}
//角色翻转函数
void FilpDirection()
{
if (xVelocity < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
}
if (xVelocity > 0)
{
transform.localScale = new Vector3(1, 1, 1);
}
}
//下蹲函数
void Crouch()
{
isCrouch = true;
coll.size = colliderCrouchSize;
coll.offset = colliderCrouchOffset;
}
//站立函数
void StandUp()
{
isCrouch = false;
coll.size = colliderStandSize;
coll.offset = colliderStandOffset;
}
void MidAirMOvement()
{
if (isHanging)
{
if (jumpPressed)
{
rb.bodyType = RigidbodyType2D.Dynamic;
rb.velocity = new Vector2(rb.velocity.x, hangingJumpForce);
isHanging = false;
crouchPress = false;
}
if (crouchPress)
{
rb.bodyType = RigidbodyType2D.Dynamic;
isHanging = false;
crouchPress = false;
}
}
if (jumpPressed && isOnGround && !isJump && !isHeadBlocked)
{
if(isCrouch)
{
StandUp();
rb.AddForce(new Vector2(0f, crouchJumpBoost), ForceMode2D.Impulse);
}
isOnGround = false;
isJump = true;
jumpTime = Time.time + jumpHoldDuration;
rb.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
jumpPressed = false;
AudioManager.PlayJumpAudio();
}
else if (isJump)
{
if (jumpHeld)
{
rb.AddForce(new Vector2(0f, jumpHoldForce), ForceMode2D.Impulse);
}
if (jumpTime < Time.time)
{
isJump = false;
}
}
}
void Start()
{
rb = GetComponent<Rigidbody2D>();
coll = GetComponent<BoxCollider2D>();
playerHeight = coll.size.y;
colliderStandSize = coll.size;//站立时完整碰撞体尺寸
colliderStandOffset = coll.offset;//站立时完整碰撞体坐标
colliderCrouchSize = new Vector2(coll.size.x, (coll.size.y) * 0.5f);//添加新的尺寸
colliderCrouchOffset = new Vector2(coll.offset.x, coll.offset.y * 0.5f);
}
void Update()
{
if (GameManager.GameOver())
{
return;
}
if (!jumpPressed)
{
jumpPressed=Input.GetButtonDown("Jump");
}
if (Input.GetButtonDown("Crouch"))
{
crouchPress = true;
}
jumpHeld = Input.GetButton("Jump");
crouchHeld = Input.GetButton("Crouch");
crouchPress = Input.GetButtonDown("Crouch");
}
private void FixedUpdate()
{
if (GameManager.GameOver())
{
return;
}
PhysicsCheck();
GroundMovement();
MidAirMOvement();
}
}
动画的实现(Player Animation)
Parameters 包含了我们在 Animator 中使用的所有“参数”,在拥有多个动画短片的控制器中,正是通过 Parameters 中的参数实现了不同动画间的转变。
可以通过点击“+”创建4种类型的参数,它们分别是 Float、Int、Bool 和 Trigger。前三个均属于基本数据类型,最后一个 Trigger 则是一个与 Bool 类似的参数,同样拥有 True 和 False 两种状态。但是不像 Bool 在设置为 True 后会一直维持,Trigger 在被触发后会迅速重置为未触发状态。
*传递字符型的时候可能会出现问题,使用数字编号给 Animator 赋值(ID)
Blend Tree
在 Animator 空白部分右击 Create State--Form New Blend Tree
Add Motion Field 添加动画

PlayerAnimation 完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAnimation : MonoBehaviour
{
Animator anim;
PlayerMovement movement;
Rigidbody2D rb;
int groundID;
int hangingID;
int crouchID;
int speedID;
int fallID;
public void StepAudio()//走路音效
{
AudioManager.PlayerFootstepAudio();
}
public void CrouchStepAudio()//下蹲走路声音
{
AudioManager.PlayerCrouchFootstepAudio();
}
void Start()
{
anim = GetComponent<Animator>();
movement = GetComponentInParent<PlayerMovement>();//访问Player Movement
rb = GetComponentInParent<Rigidbody2D>();
groundID = Animator.StringToHash("isOnGround");//将字符型转换为数值型,获得变量的编号
hangingID = Animator.StringToHash("isHanging");
crouchID = Animator.StringToHash("isCrouching");
speedID = Animator.StringToHash("speed");
fallID = Animator.StringToHash("verticalVelocity");
}
void Update()
{
anim.SetFloat(speedID, Mathf.Abs(movement.xVelocity));
//anim.SetBool("isOnGround", movement.isOnGround);
anim.SetBool(groundID, movement.isOnGround);//传递布尔值
anim.SetBool(hangingID, movement.isHanging);
anim.SetBool(crouchID, movement.isCrouch);
anim.SetFloat(fallID, rb.velocity.y);
}
}
音效控制(AudioManager)
AudioManager 中创建的各部分音效 public 函数要在对应位置进行调用
*当前对象的引用指的是关键字 this 所代表的对象。可以使用关键字 this 来访问当前对象的属性、方法或索引器。
*若希望在场景切换时不销毁音效
DontDestroyOnLoad(gameObject);//项目保留,使声音一直存在
AudioManager 完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
public class AudioManager : MonoBehaviour
{
static AudioManager current;
//存放声音
[Header("环境声音")]
public AudioClip ambientClip;
public AudioClip musicClip;
[Header("FX音效")]
public AudioClip deathFXClip;
public AudioClip orbFXClip;
public AudioClip doorFXClip;
public AudioClip startLevelClip;
public AudioClip winClip;
[Header("Robbie音效")]
public AudioClip[] walkStepClips;
public AudioClip[] crouchStepClip;
public AudioClip jumpClip;
public AudioClip deathclip;
public AudioClip deathVoiceClip;
public AudioClip jumpVoiceClip;
public AudioClip orbVoiceClip;
AudioSource ambientSource;
AudioSource musicSource;
AudioSource fxSource;
AudioSource playerSource;
AudioSource voiceSource;
public AudioMixerGroup ambientGroup, musicGroup, FxGroup, playerGroup, voiceGroup;
//脚步声音
public static void PlayerFootstepAudio()
{
int index = Random.Range(0, current.walkStepClips.Length);
current.playerSource.clip = current.walkStepClips[index];
current.playerSource.Play();
}
//下蹲时脚步声音
public static void PlayerCrouchFootstepAudio()
{
int index = Random.Range(0, current.crouchStepClip.Length);
// current.playerSource.clip = current.crouchStepClip[index];
current.playerSource.Play();
}
//环境声音
void StartLevelAudio()
{
current.ambientSource.clip = current.ambientClip;
current.ambientSource.loop = true;
current.ambientSource.Play();
current.musicSource.clip = current.musicClip;
current.musicSource.loop = true;
current.musicSource.Play();
current.fxSource.clip = current.startLevelClip;
current.fxSource.Play();
}
//胜利时声音
public static void PlayerWonAudio()
{
current.fxSource.clip = current.winClip;
current.fxSource.Play();
current.playerSource.Stop();
}
//跳越声音
public static void PlayJumpAudio()
{
current.playerSource.clip = current.jumpClip;
current.playerSource.Play();
current.voiceSource.clip = current.jumpVoiceClip;
current.voiceSource.Play();
}
//死亡声音
public static void PlayDeathAudio()
{
current.playerSource.clip = current.deathclip;
current.playerSource.Play();
current.voiceSource.clip = current.deathVoiceClip;
current.voiceSource.Play();
current.fxSource.clip = current.deathFXClip;
current.fxSource.Play();
}
public static void PlayOrbAudio()
{
current.playerSource.clip = current.orbFXClip;
current.playerSource.Play();
current.voiceSource.clip = current.orbVoiceClip;
current.voiceSource.Play();
}
public static void PlayDoorOpenAudio()
{
current.fxSource.clip = current.doorFXClip;
current.fxSource.PlayDelayed(1.1f);//延迟1.1秒播放
}
private void Awake()
{
//避免死亡后重复添加音乐
if (current != null)
{
Destroy(gameObject);
return;
}
current = this;
DontDestroyOnLoad(gameObject);//项目保留,使声音一直存在
//添加组件
ambientSource = gameObject.AddComponent<AudioSource>();
musicSource= gameObject.AddComponent<AudioSource>();
fxSource= gameObject.AddComponent<AudioSource>();
playerSource= gameObject.AddComponent<AudioSource>();
voiceSource= gameObject.AddComponent<AudioSource>();
ambientSource.outputAudioMixerGroup = ambientGroup;
musicSource.outputAudioMixerGroup = musicGroup;
fxSource.outputAudioMixerGroup = FxGroup;
playerSource.outputAudioMixerGroup = playerGroup;
voiceSource.outputAudioMixerGroup = voiceGroup;
StartLevelAudio();
}
}
死亡机制
在场景中添加Spikes等陷阱,将其 Layer 设定为 Traps(Overrides--Apply All)
OnTriggerEnter2D:触发器
OnCollisionEnter2D:碰撞器
触发器是碰撞器的一个功能
在想要做碰撞检测时使用碰撞器
碰撞器生效的必要条件:碰撞的双方A,B都必须有Collider,其中有一方要带有rigidbody
当想要做碰撞检测却又不想产生碰撞效果时,就可以用isTrigger,在这个状态下触发检测生效,碰撞检测失效
触发死亡
在Unity项目里对某个GameObject进行隐藏时,用gameObject.SetActive(false)可以达到目的
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.layer == trapsLayer)
{
gameObject.SetActive(false);//将游戏角色的启用关闭
}
}
死亡时触发效果
Instantiate 一般用于对Prefab(预制体)的实例化
使用 Instantiate 时,其属性和原物体一致,由于是实例化一个prefab,所以可以想到它包含的参数应该有:① prefab是什么 ②位置和方向 ③挂载在哪里
Instantiate(预制体,transform.position,transform.rotation);
死亡后游戏场景重置
调用 UnityEngine 里的 SceneManagement
using UnityEngine.SceneManagement;
再在 OnTriggerEnter2D 中进行调用
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
File-- Building settings -- Add Open Scenes
收集物品
将物品添加到游戏场景中
使用 gameObject.SetActive(false)可以达到目的
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.layer == player)
{
gameObject.SetActive(false);
}
}
*同样使用 Instantiate 可触发拾取时特效
视觉效果
局部&全局特效
1.window--Package Manager--Post Processing 进行安装
2.在摄像机中添加组件 Post Processing Layer
3.创建Layer:Post Processing
4.在 Hierarchy 中创建一个空项目,将其 Layer 设置为 Post Processing Layer(用于承载特效)
5.为其添加 Post Process Volume(如果需要让其用于全局就勾选Is Global,如果需要让其用于局部就添加 collider,选择范围)
*priority 为优先级,数值越大,它就会优先在其他视觉特效之上进行播放)
6.在 Profile 中添加特效
相机抖动
1.添加2D摄像机 Virtual Camera
2.在 Extensions 中添加 Impulse Listener(用于感知)
3.在需要被感知的物体中添加Cinemachine Collision Impulse Source,并在Signal Shape--Raw Signal 中添加特效(Frequency Gain数值越大,震动频率越大;Amplitude Gain越大,震动幅度越大)
*如果没有Signal Shape,将 Impulse Type 改成 Legacy 即可出现
4.在 Trigger Object Filter 中选择触发的Layer
统筹管理(Game Manager)
GameManager 的物体的 PlayerManager 脚本的 Player 成员,可以通过另外一个脚本的实例化,进行调用其公共成员。
*在实际应用中,经常要查找Manager管理物体,用于调用参数,GameObject.FindGameObjectWithTag 通过标签或者名字查找物体,效率较低,实例化instance 则是一种高效的方法,广泛应用于调用其他的物体的情况
Invoke函数是一种用于延迟或重复执行某个方法的函数
Invoke(string methodName, float time);
methodName是要调用的方法名,time是延迟时间
GameManager 完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
static GameManager instance;
SceneFader fader;
List<Orb> orbs;
Door lockedDoor;
float gameTime;
bool gameIsOver;
// public int orbNum;
public int deathNum;
public static void PlayerDied()
{
instance.fader.FadeOut();
instance.deathNum++;
UIManager.UpdateDeathUI(instance.deathNum);
instance.Invoke("RestartScene", 1.5f);//延迟1.5秒重新加载场景
}
//重新加载当前场景
void RestartScene()
{
instance.orbs.Clear();//场景开始时归零宝珠
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
public static void RegisterSceneFader(SceneFader obj)
{
instance.fader = obj;
}
public static void RegisterOrb(Orb orb)
{
if (instance == null)
{
return;
}
if (!instance.orbs.Contains(orb))
{
instance.orbs.Add(orb);
}
UIManager.UpdateOrbUI(instance.orbs.Count);
}
//获得宝珠
public static void playerGrabbedOrb(Orb orb)
{
if (!instance.orbs.Contains(orb))
{
return;
}
instance.orbs.Remove(orb);
if (instance.orbs.Count == 0)//当宝珠收集完门开启
{
instance.lockedDoor.Open();
}
UIManager.UpdateOrbUI(instance.orbs.Count);
}
//注册门
public static void RegisterDoor(Door door)
{
instance.lockedDoor = door;
}
public static void PlayerWon()
{
instance.gameIsOver = true;
UIManager.DisplayGameOver();
AudioManager.PlayerWonAudio();
}
//游戏结束
public static bool GameOver()
{
return instance.gameIsOver;
}
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
return;
}
instance = this;
orbs = new List<Orb>();
DontDestroyOnLoad(this);
}
private void Update()
{
if (gameIsOver)
{
return;
}
// orbNum = instance.orbs.Count;
//显示时间
gameTime += Time.deltaTime;
UIManager.UpdateTimeUI(gameTime);
}
}
UI管理(UI Manager)
界面创建
1.将UI Manager Script 添加到预制中
2.将所需显示的预制中的文本框添加到 TextMeshProUGUI 中
3.创建Update UI 的函数,用于显示计数的变化
4.在Game Manager 对应的位置进行调用
*当游戏结束一切行为都应停止,在相关函数的开头判断return
*为了使游戏时间显示更稳定,建议使用Time.deltaTime
Time.deltaTime是帧与帧相减出来的,既是后一帧时间减去前一帧时间得出来的,可以使物体的运行情况不受帧率影响,并且在相同的时间内,运行情况都相同
假设有两台电脑,一台性能优越,另一台垃圾点,各运行一秒,但他们在一秒钟运行的帧数是不一样的
性能优越的电脑:
每秒的帧数多,帧与帧间隔就短Time.deltaTime数值就小,假设这个数值是0.1,乘与速度1,那么每帧速度是0.1, 假设一秒运行30帧,那么速度就是3。
性能差些的电脑:
电脑每秒的帧数少,帧与帧间隔就长Time.deltaTime数值就大,假设这个数值是0.3,乘与速度1,那么每帧速度是0.3, 假设一秒运行10帧,速度也是3。
结果相同
UIManager完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class UIManager : MonoBehaviour
{
static UIManager instance;
public TextMeshProUGUI orbText, timeText, deathText, gameoverText;//用于存放文本框预制
//宝石剩余数量
public static void UpdateOrbUI(int orbCount)
{
instance.orbText.text = orbCount.ToString();
}
//死亡数量
public static void UpdateDeathUI(int deathCount)
{
instance.deathText.text = deathCount.ToString();//将整形变为字符型
}
//时间
public static void UpdateTimeUI(float time)
{
int minutes = (int)(time / 60);
float seconds = time % 60;
instance.timeText.text = minutes.ToString("00") + ":" + seconds.ToString("00");
}
//游戏结束
public static void DisplayGameOver()
{
instance.gameoverText.enabled = true;//是否启动
}
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this);
}
}
附其他部分代码
PlayerHealth
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerHealth : MonoBehaviour
{
public GameObject deathVFXPrefab;//死亡烟雾特效
int trapsLayer;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.layer == trapsLayer)
{
Instantiate(deathVFXPrefab, transform.position, transform.rotation);//烟雾
//Instantiate(deathVFXPrefab, transform.position,Quaternion.Euler(0,0,Random.Range(-45,45)));//残影
gameObject.SetActive(false);//将游戏角色的启用关闭
AudioManager.PlayDeathAudio();
// SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
GameManager.PlayerDied();
}
}
void Start()
{
trapsLayer = LayerMask.NameToLayer("Traps");//获得Traps这个Layer的图层编号
}
}
SceneFader
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneFader : MonoBehaviour
{
Animator anim;
int faderID;
private void Start()
{
anim = GetComponent<Animator>();
faderID = Animator.StringToHash("Fade");
GameManager.RegisterSceneFader(this);
}
public void FadeOut()
{
anim.SetTrigger(faderID);
}
}
WinZone
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WinZone : MonoBehaviour
{
int playerLayer;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.layer == playerLayer)
{
Debug.Log("Player won!");
GameManager.PlayerWon();
}
}
void Start()
{
playerLayer = LayerMask.NameToLayer("Player");
}
}
Orbs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Orb : MonoBehaviour
{
int player;
public GameObject explosionVFXPrefab;
void Start()
{
player = LayerMask.NameToLayer("Player");
GameManager.RegisterOrb(this);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.layer == player)
{
Instantiate(explosionVFXPrefab, transform.position, transform.rotation);
gameObject.SetActive(false);
AudioManager.PlayOrbAudio();
GameManager.playerGrabbedOrb(this);
}
}
}
Door
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Door : MonoBehaviour
{
Animator anim;
int openID;
public void Open()
{
anim.SetTrigger(openID);//触发器
//播放audio
AudioManager.PlayDoorOpenAudio();
}
void Start()
{
anim = GetComponent<Animator>();
openID = Animator.StringToHash("Open");
GameManager.RegisterDoor(this);
}
}
FPS
using UnityEngine;
using UnityEngine.UI;
public class FPS : MonoBehaviour
{
public Text fpsText;
float deltaTime;
void Update ()
{
deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;
SetFPS();
}
void SetFPS()
{
float msec = deltaTime * 1000.0f;
float fps = 1.0f / deltaTime;
fpsText.text = string.Format("FPS: {0:00.} ({1:00.0} ms)", fps, msec);
}
}