第一次接触Unity,并且跟着视频资料做出了第一款2D像素游戏,因为还有很多不懂的地方,以及在学习过程中感觉有待优化的地方,所以想要通过这篇文章对自己进行一个总结,内容其实都是写给以后的自己。😜
在学习过程中呢,有收获,也遇到问题,这些呢,有可以分为编写代码类,和Unity使用类的
毕竟是初学,感觉还是收获满满的,在这里会选出个人感觉很实用很关键的方法,或者是在之后Unity学习过程中重复使用的方面,所以大多是很基本的知识,以扎实的过程记录。
这是一款简单的2D像素横板游戏,可以收集物品,消灭敌人,在移动上实现站立,跑动,下蹲,跳跃。
Unity使用
1.Tilemap
- 场景中的Tilemap如果有缝隙,调整Grid的CellSize,把X和Y都改成0.99
- 如果想实现,地图上有的元素,例如草,标识牌等不需要碰撞体,那么可以创建多个Tilemap,分别画出地形,以及丰富地图的元素,前者正常添加Tilemap Collider2D,后者就不需要。
- 实现单向平台。新建Tilemap,画出希望的单向平台,添加Tilemap Collider2D和Platform Effector 2D组件,同时勾选上Tilemap Collider2D中的Used By Effector
2.Animation
- 添加组件Animator -> 创建AnimationController,拖动添加到Animator -> 打开Animation窗口,Create -> 拖动动画素材到在Animation窗口下,Smaples 设置动画速率 -> 若只执行一次动画,则点击动画,勾选Loop Time
- 动画与动画之间的转换,去掉Has Exit Time(如果勾选了这一项,在动画转换时会等待当前动画播放完毕之后才转换到下一个动画,在这里是立即转换,所以不勾选),Transition Duration过度持续时间 (过度有多长)设置成 0
3.Cinemachine
- 添加插件window-PackageManager-Cinemachine,Dead Zone:图中最中间的矩形,锁定的人物会以一个黄点标记出,最终黄点会稳定在这个区域内
Soft Zone:中部蓝色的区域,人物移动到这个区域内,镜头会缓慢跟随移动,尽量使人物回到中间的Dead Zone - 镜头的移动,不能超过地图的边界。对背景添加碰撞器:Polygon Colider2d (多边形),勾选isTriger防止角色和背景碰撞,摄像机添加扩展:Add Extension-Cinemachine Confiner,填入Polygon
4.收集物品
- 2D Objec -> Sprite,修改Tag为收集物品的Tag,添加Box Collider 2D,勾选上is Trigger在代码中识别,如果任务触碰的碰撞器的标签是收集物品,则销毁游戏项目:Destroy(collision.gameObject);
5.消灭敌人
- 与收集物品同理,需要添加消灭敌人的条件,即踩到敌人头上,(处在下落状态,碰到碰撞器的标签是敌人),在这里实体化一个Enemy类,调用里面的JumpOn()函数,播放敌人死亡的音效,打开死亡的开关,播放死亡动画,在动画的最后加入事件帧,在调用函数,销毁该敌人的游戏项目
6.角色摩擦
- 因为创建了两个碰撞器,上部Box Collider 2D,下部Circle Collider 2D,如果是只有上部的碰撞器和地面的碰撞器接触,在给一个方向的力,就会有摩擦力。例如下图
所以需要新建一个物理材质Create -> Material,将其中的摩擦Friction设置为0,并添加到Box Collider 2D中
6.UI
- 包括开始菜单,游戏暂停菜单,音量音效滑动条,编写脚本,挂载到Canvas上,比如按钮,On Click选择点击后的函数,在Menu中找到编写的函数
public void PauseGame()
{
pauseMenu.SetActive(true);
Time.timeScale=0f;
}
- 再比如音量的滑动条,修改最大和最小值[-80,0], Value Changed选择改变的值,在Menu中的Dynamic float选择编写的函数,这样改变的值就是当前Inspector中的Value
public void SetVolume(float value)
{
audioMixer.SetFloat("MainVolume",value);
}
7.AI
- 仅实现了敌人在规定范围内的活动。以青蛙为例,青蛙会在规定好的左右范围内进行反复运动,具体实现是给青蛙对象添加左右两个Object作为节点,当青蛙移动超过左右结点时,则会进行转向。
8.Audio Source 新建GameObject,重命名为SoundManager
- BGM,添加模块Audio Source,将选定的BGM拖入AudioClip;在Project下新建AudioMixer;选择AudioSource的Output为Matser(新建的AudioMixer会自创建);打开AudioMixer窗口,选中Master,右键Volume,选择Expose ‘Volume (of Master)’ to script将该值修改为代码可以调用的参数,在Exposed Parameters中可以右键重命名该变量
- 音效,添加模块Sudio Source,用来统一管理所有人物音效(受伤,跳跃,收集物品),编写脚本,添加各种音效为AudioClip,编写播放对应音效的函数,因为这是不同的脚本,为了函数调用的方便,将类实例化,就可以用实体直接访问类中的函数。
public static SoundManager instance;
public void Awake()
{
instance=this;
}
例如:人物脚本中调用音效
SoundManager.instance.CollectionAduio();
接着是在Audio Mixer中新建Groups,选择为音效的Output
9.2D光效 使用的是2018版本,如果是高版本的可以使用Universal RP
- 首先是需要为地图添加材质,使地图变暗,可以接受并衍射光,这样添加光源之后,就可以实现照亮的效果,以地图为例,在Tilemap Renderer中可以选择预置的Default-Diffuse
- 对于我们操控的人物,收集物品,敌人等,这些每次添加的2D项目渲染出来都是一个Sprites。需要新建材质,Creat -> Material -> Shader -> Sprites -> Diffuse 新建完成。拖进Sprites Renderer中的Material中,这样添加光源之后,人物等也会被照亮。
- 如果有的光源失效,在Project Settings -> Quality -> Rendering ->Pixel Light Count 控制光源的数量,可以适当调整
C#代码
1.Update()和FixedUpdate()
- FixedUpdate,是在固定的时间间隔执行(0.02s),不受游戏帧率的影响,通常在处理Rigidbody等物理有关的
- Update,是在每次渲染新的一帧时会调用,这个更新的频率和当前设备的性能有关以及被渲染的物体,这样同一游戏在不同的机器上效果不一样。
2.跳跃(可适用多数2D横版游戏)
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
private Rigidbody2D rb;
private Collider2D coll;
private Animator ani;
public float speed,jumpForce;
public Transform groundCheck;
public LayerMask ground;
public bool isGround,isJump;
bool jumpPressed;
int jumpCount;
void Start()
{
rb=GetComponent<Rigidbody2D>();
coll=GetComponent<Collider2D>();
ani=GetComponent<Animator>();
}
void Update()
{
if(Input .GetButtonDown("Jump")&&jumpCount>0)
{
jumpPressed=true;
}
}
private void FixedUpdate() {
isGround=Physics2D.OverlapCircle(groundCheck.position,0.1f,ground);
Movement();
Jump();
}
void Movement()
{
float horizontalmove=Input.GetAxisRaw("Horizontal");
rb.velocity=new Vector2(horizontalmove*speed,rb.velocity.y);
if(horizontalmove!=0)
{
transform.localScale=new Vector3(horizontalmove,1,1);
}
}
void Jump()
{
if(isGround)
{
jumpCount=2;
isJump=false;
}
if(jumpPressed&&isGround)
{
isJump=true;
rb.velocity=new Vector2(rb.velocity.x,jumpForce);
jumpCount--;
jumpPressed=false;
}else if(jumpPressed&&jumpCount>0&&!isGround){
rb.velocity=new Vector2(rb.velocity.x,jumpForce);
jumpCount--;
jumpPressed=false;
}
}
}
这个代码优化手感的主要想法是根据Input.GetB uttonDown和FixedUpdate。 Input.GetButtonDown Call this function from the Update function, since the state gets reset each frame. It will not return true until the user has released the key and pressed it again. 这样按下跳跃键可以第一时间收到信号,接着是优化手感的精髓,在FixedUpdate里调用的移动,跳跃的代码,这样每一固定的帧反馈给玩家就是最即时的状态,手感就会很流畅。
3.翻转朝向
float horizontalmove=Input.GetAxis("Horizontal");
float facedirection=Input.GetAxisRaw("Horizontal");
if(facedirection!=0)
{
rb.transform.localScale=new Vector3(facedirection,1,1);
}
4.跑步动画向下蹲动画过渡 为了贴近真实,按下下蹲键之后,人物的移动速度也就下降,这样在跑步的过程中,会有一个减速的过程,在速度降到一定值之后,会转换到下蹲动画
rb.velocity=new Vector2(horizontalmove*speed*Time.fixedDeltaTime*(anim.GetBool("crouching")?0.5f:1),rb.velocity.y);
5.跑步动画停止 设定值的时候如果用facedircetion去给running设定值的话,玩家一放开键盘方向键的时候run的动画就会立刻停止,而用horizontalmove去给running设定值的话是当小狐狸停下来的时候run的动画才会停止。
anim.SetFloat("running",Mathf.Abs(facedirection));
跑步的方法确实很多,举个例子,这里可以使用int或bool代替float,判断跑步时running赋值为1,否则赋值为0,动画切换条件就是看running是一还是零。这样就可以不用一直调用函数Abs,减少了运算负担
6.敌人死亡动画播放 个人感觉敌人的死亡动画,是一种比较特殊的动画,①因为在播放这个动画同时,需要考虑原先存活敌人的判定框,再次触碰会重复播放死亡动画,②死亡动画会根据原先的轨迹继续移动,③动画结束最后要销毁敌人项目。想要解决以上的问题,分别需要实现,判断击杀敌人之后删除碰撞体;锁定x,y轴(游戏是2D横板,z轴已经锁定);最后播放死亡动画,在动画的结束添加event事件,调用销毁项目的函数。实测有效代码如下:
public void JumpOn()
{
anim.SetTrigger(Death);
gameObject.GetComponentCollider2D().enabled = false;
gameObject.GetComponentRigidbody2D().constraints = RigidbodyConstraints2D.FreezeAll;
}
8.弹出提示框 为人物添加Player的Tag,添加碰撞器在希望弹出提示框的地方,并且将is Trigger勾选,同时挂载脚本,识别人物的碰撞体
public GameObject enterDialog;
void OnTriggerEnter2D(Collider2D other) {
if(other.tag=="Player")
{
enterDialog.SetActive(true);
}
}
private void OnTriggerExit2D(Collider2D other) {
if(other.tag=="Player")
{
enterDialog.SetActive(false);
}
}
为提示框添加渐变动画 -Add Animation ,在Ainmation里面点击Preview,可以录制动画,在不同时间间隔之后,将提示框的背景色逐步加深,最后弹出文本,可以实现渐变效果
9.进入下一关卡 当人物移动到指定位置,或者达成某一条件之后,按下指定的键,就会进入下一场景。
void Update()
{
if(Input.GetKeyDown(KeyCode.E))
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex+1);
}
}
在File -> Build Settings 中,将制作好的关卡场景多选拖进之后,会在后面显示出,该场景的序号
10.重生 掉落死亡,会触发重生,延迟运行&&重启当前场景。新建一个GamObject,添加碰撞器,调整大小至人物掉落出场景后会碰撞到,勾选isTrigger,新建并设置Tag为DeadLine
if(collision.tag=="DeadLine")
{
Invoke(nameof(Restart),2f);
}
void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
在某些情况下,需要记录位置,返回上衣场景之前的位置,类似于CheckPoint
11.地图更加有层次感 背景图片并不是固定,设定一个范围值(0,1),使图片跟着摄像头在范围之内移动。可以选择是否锁定Y轴。
public Transform Cam;
public float moveRate;
private float startPointX,startPointY;
public bool LockY;
void Start()
{
startPointX=transform.position.x;
startPointY=transform.position.y;
}
void Update()
{
if(LockY)
transform.position=new Vector2(startPointX+Cam.position.x*moveRate,transform.position.y);
else
transform.position=new Vector2(startPointX+Cam.position.x*moveRate,startPointY+Cam.position.y*moveRate);
}
未解决的问题
收集物品计数异常 因为任务添加了方形和圆形两个碰撞器,会偶尔发生一种情况,两个碰撞器同时触碰到同一个触发器(同一个樱桃),就会执行两次计数。 受伤的逻辑判断 个人的思路是,使用Trigger控制受伤动画的播放,Any State到受伤状状态,受伤动画只播放一次,(loop time取消勾选),之后连线切换到idel状态 敌人没有完善的AI 目前敌人只是在设定好的范围内反复移动,如果人物和敌人出现在同一屏幕内,敌人并不会追踪人物。暂时还没有实现的思路 暂停菜单 ①按下暂停按键之后,Time.timeScale=0f;但是如果按下跳跃键,会进入跳跃状态,播放跳跃动画。②在跳跃的时候不能点击暂停键
|