1.修改素材中“back”的【import setting】(?)中的[Pixels Per Unit]属性从100到16(每个单位格中有多少个像素点)
【Tile】
unity提供全套的Tile工具来编辑地图
1.在场景中创建Tilemap游戏对象,场景中将出现一个[Grid]对象并带有一个[TileMap],Grid就是Tile的画布,可以设置画布每个单元的大小(和[Pixels Per Unit]属性?)
2.通过调出【Tile Palette】窗口可以进行Tile的绘制。创建一个新的Palette(调色板)并保存在适当的位置,就可以向其中导入Sprite资源了
3.导入一张包含多个内容的Sprite资源时,应当将其Mode改为[Multiple],然后进入编辑界面进行Slice(切割),切割时注意切割设置,在此案例中选择[Grid By Cell Size]。切割完成后,原来的Sprite素材会包含分割完成的多个素材
4.将切割完成的Sprite素材拖拽导入【Tile Palette】,即可对每一个单元格进行绘制。点击所需要的Tile,选择brush(笔刷),即可在Grid中绘制场景
1.图层排序[Sorting Layer],根据需要选择相应的图层排序(从上到下的图层为从后往前)
2.若想要调整处于同一图层排序的对象的层次,则使用[Order in Layer],此属性越大越在前方
1.(注意[Pixels Per Unit]属性的大小)常创建一个Sprite对象,将Idel文件夹中的角色Sprite拖拽到【Sprite Renderer】中(或直接将一个角色Sprite拖拽入场景生成)
2.创建角色实体:添加碰撞体和刚体
3.添加地形碰撞体:Tilemap Collider
void Movement()
{
float move = Input.GetAxis("Horizontal");
if (move != 0)
{
playerRig.velocity = new Vector2(move * speed, 0);
}
}
1.增加左右移动判断,通过Scale道正人物朝向
void Movement()
{
float move = Input.GetAxis("Horizontal");//不灵敏的变速运动,若使用Raw则会向一个方向一直运动
float direction = Input.GetAxisRaw("Horizontal");
if (move != 0 && direction != 0)
{
playerTransform.localScale = new Vector3(direction, 1, 1);//调整人物朝向
playerRig.velocity = new Vector2(move * speed, 0);
}
}
2.进一步修正,通过deltaTime使移动更加均匀,并将移动函数的调用调整到FixedUpdata中(避免机器产生的差异),但相应基础速度值需要增大很多
void Movement()
{
float move = Input.GetAxis("Horizontal");//不灵敏的变速运动,若使用Raw则会向一个方向一直运动
float direction = Input.GetAxisRaw("Horizontal");
if (move != 0)//移动判断
{
playerRig.velocity = new Vector2(move * speed * Time.deltaTime, playerRig.velocity.y);
playerAnim.SetFloat("Running", Mathf.Abs(direction));
}
if (direction != 0)//朝向判断
{
playerTransform.localScale = new Vector3(direction, 1, 1);//调整人物朝向
}
}
3.(?)通过FixedUpdata进行的移动函数调用会意外停顿
1.添加角色跳跃代码(未修正手感等)
if (Input.GetButton("Jump"))
{
playerRig.velocity = new Vector2(playerRig.velocity.x, jumpForce);
}
其他的跳跃和移动方式?
跳跃和移动手感的问题?
空中摇摆影响下落?移动和跳跃冲突?
1.关于Tile的碰撞器
TileMap可以在现有基础上加一个CompositeCollider2D,并把原来的TileMapCollider2D的Used By Composite钩上,这样每一个瓦片的碰撞框会合并到一起,减少消耗,同时消除原碰撞体情况下可能导致的Bug(?),(注意这里地面刚体组件应当设置static)
1.从Idel状态到Run状态的切换应当立即切换,因此要取消勾选[Has Exit Time],并且将[Setting]中的[Transition Duration](切换时间)设置为0
2.由于转换标签类型为float,因此使用Raw数据作为判据(直接使用GetAxis可能导致无法恢复Idel)
1.设置多条判断Tag
2.通过Collider和Layer进行地面碰撞判断
1.人物原有的方形碰撞体和地形的交互会存在一些问题,再这里为角色再新增一个圆形碰撞体,和方形碰撞体共同构成人物碰撞体(注意人物与地面的碰撞判断使用了原来的方形碰撞体,这里需要修正)
1.可变动x或y轴的镜头跟踪
public Transform player;
void Update()
{
this.transform.position = new Vector3(player.position.x, player.position.y, transform.position.z);
}
考虑到背景,可以采取锁定y轴的镜头跟踪(置位置向量中y坐标为基准值)
1.添加一个2D摄像头(会覆盖原有摄像头)
2.将要跟随的角色拖拽入[Fallow]
3.(待补充)
1.为Cinemachine 2D摄像头添加Confiner组件
2.为游戏背景添加多边形碰撞体,并加入Confiner组件
3.调整碰撞体为触发器
1.在场景中初始化收集物,添加动画,添加碰撞体(触发器)
2.为收集物设置Tag,便于在脚本中判断
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag.Equals("Collections"))
{
Destroy(collision.gameObject);
}
}
注意,此处销毁时应销毁collision.gameObject
3设置计数变量记录收集物获得数
1.将制作完备的Player和收集物保存为预制体
设置更丰富的图层,并丰富场景
1.解决遗留问题(当人物跳跃撞击地形后保持摁下移动,人物将挂在地形上无法正常下落),这是由于物理摩擦问题(?),再这里创建2D物理材质设置相应属性,并配置给Player的碰撞体
1.使角色可以在地面进行跳跃,但不能在空中进行跳跃,可以进行如下两种修改:
(1)角色控制脚本中添加布尔变量couldJump作为判据,当角色处于地面时(利用碰撞判断)时置为true,进行跳跃后在移动控制方法中置为false
(2)直接将地面碰撞检测加入跳跃判断
1.创建画布
2.创建文本框
3.通过脚本控制文本框(相应设置分数等)
1.利用锚点限定UI的相对位置
2.(?)是否应当将静态文本和分数分离为两个文本框?
3.(?)由于碰撞检测的频率问题可能导致收集物带来两次分数加成
解决方案:通过动画事件来触发计数
1.设置敌人,增添刚体和碰撞体脚本,添加动画
1.在脚本中添加消灭敌人方法
注意,再这里通过tag辨别碰撞信息时,触发器和碰撞体有如下差别:
(收集物相关代码)
if (collision.tag.Equals("Cherrys"))
{
Destroy(collision.gameObject);
cherry++;
cherryText.text = "Cherry:" + cherry;
SetCount(10);
}
(消灭敌人相关代码)
if (collision.gameObject.tag.Equals("Enemys"))
{
Destroy(collision.gameObject);
}
碰撞体需要间接通过碰撞对象获得tag
2.进一步修改,碰撞到敌人时应当触发角色“受伤”事件,消灭敌人应当仅限于踩踏,这里借助动画状态机的逻辑进行实现,在人物下落状态时发生碰撞才会消灭
消灭敌人后,可以为角色添加一个类似超级玛丽的弹起的跳跃效果。这里使用跳跃控制代码放入成功消灭敌人的代码块中
1.人物受伤应被弹开,并且触发受伤动画效果
值得一提的是,项目中原本用来控制角色运动的是刚体速度属性,这似乎会产生一些问题,官方文档中并不建议使用改写速度的方式来控制运动,而提倡使用addForce等
这里暂时使用改写速度的方式,判断角色在左边还是右边,然后给角色一个相应方向的速度,在消灭敌人方法中加入:
else
{
playerRig.velocity = new Vector2(transform.position.x - collision.transform.position.x, jumpForce * Time.deltaTime);
isHurt = true;
//playerRig.velocity = new Vector2();
//playerAnim.SetBool("Injured",true);//置于动画切换方法中
}
由于Movement方法在FixedUpdata方法中一直在调用,运动时横向速度在被不断改写,因此加入判断条件,使受伤时Movement不被调用
if (!isHurt)
{
Movement();
}
重置isHurt的代码置于动画切换方法中
else if (isHurt)
{
if (Mathf.Abs(playerRig.velocity.x) < 0.1f)
{
isHurt = false;
}
}
1.角色运动控制中,关于向量的使用(?)
2.游戏运行时,时常会有动画未能正常切换到Idel,人物仍在跑动的情况
这里是由于Running这条tag(float)未能正常降为0(?),可以在脚本中适当位置直接手动设置为0
3.跑动下落偶尔会使人物嵌入地形(?)
1.修改Frog的碰撞体为圆形碰撞体(避免与地形交互时发生的卡顿情况)
2.使Frog左右移动
(1)在脚本中获得Frog刚体组件控制位移,并且使之能够判断边界
方案1:通过两个空物体来限定Frog移动范围(为使空物体易于编辑,这里可以选择一个可视的彩色图标),两个空物体作为Frog的子物体并存储为预制体
public Transform LeftPoint,RightPoint;//获得左右临界点位置
void Movement()
{
if (isLeft)
{
FrogRig.velocity = new Vector2(-speed*Time.deltaTime, FrogRig.velocity.y);
if (this.transform.position.x<=LeftPoint.position.x)//越过左侧点翻转
{
this.transform.localScale = new Vector3(-1, 1, 1);
isLeft = false;
}
}
else
{
FrogRig.velocity = new Vector2(-speed*Time.deltaTime, FrogRig.velocity.y);
if (this.transform.position.x>=RightPoint.rotation.x)
{
this.transform.localScale = new Vector3(1, 1, 1);
isLeft = true;
}
}
}
这里注意,左右临界点设置为Frog的子物体后,会跟随Frog同步运动,因此需要对此进行调整:
方案1:获取到两个Transform后使两个临界点不再是Frog子物体
this.transform.DetachChildren();
方案2:仅获得左右临界点位置值,然后进行销毁
void Start()
{
leftx = LeftPoint.position.x;
rightx = RightPoint.position.x;
Destroy(LeftPoint.gameObject);
Destroy(RightPoint.gameObject);
}
playerAnim.SetBool("Idel", false);
if (playerRig.velocity.y<0.1f && !playerCollider.IsTouchingLayers(ground))
{
playerAnim.SetBool("Falling", true);
}
void SwitchAnim()
{
if (FrogAnim.GetBool("Jumping"))
{
if (FrogRig.velocity.y<0.1f)
{
FrogAnim.SetBool("Jumping", false);
FrogAnim.SetBool("Falling", true);
}
}
if (FrogCollider.IsTouchingLayers(ground) && FrogAnim.GetBool("Falling"))
{
FrogAnim.SetBool("Falling", false);
}
#region
// if (FrogAnim.GetBool("Jumping"))
// {
// if (FrogRig.velocity.y<0)
// {
// FrogAnim.SetBool("Jumping", false);
// FrogAnim.SetBool("Falling", true);
// }
// } else if (FrogCollider.IsTouchingLayers(ground))
// {
// FrogAnim.SetBool("Falling", false);
// }
#endregion
}
void Movement()
{
if (isLeft)
{
//FrogRig.velocity = new Vector2(-speed*Time.deltaTime, FrogRig.velocity.y);
if (transform.position.x <=/*LeftPoint.position.x*/leftx-1)//越过左侧点翻转
{
transform.localScale = new Vector3(-1, 1, 1);
isLeft = false;
}
if (FrogCollider.IsTouchingLayers(ground))
{
FrogAnim.SetBool("Jumping", true);
FrogRig.velocity = new Vector2(-speed * Time.deltaTime, jumpForce * Time.deltaTime);
}
}
else
{
//FrogRig.velocity = new Vector2(speed*Time.deltaTime, FrogRig.velocity.y);
if (transform.position.x >=/*RightPoint.rotation.x*/rightx+1)
{
transform.localScale = new Vector3(1, 1, 1);
isLeft = true;
}
if (FrogCollider.IsTouchingLayers(ground))
{
FrogAnim.SetBool("Jumping", true);
FrogRig.velocity = new Vector2(speed * Time.deltaTime, jumpForce * Time.deltaTime);
}
}
增添如上代码后,在Updata中仅调用SwitchAnim即可
1.Frog的左右跳跃过程中,存在朝向和速度方向不协调的问题。
在这里可以:在转向代码前将水平速度手动调零(未解决?)
1.注意锁定x轴的移动,避免玩家角色施加的作用力使Eagle脱离设定位置
1.添加Enemy死亡动画
2.代码逻辑实现
(1)使Frog内置Death方法和OnJunp方法,分别用于调用死亡特效动画和销毁物体,将原来的通过碰撞体踩踏“Enemy”销毁的逻辑修改,先常见创建一个Frog类的对象并通过GetComponent方法获得碰撞体的相应脚本引用,然后调用其OnJump方法销毁对象
(问题:未能对各种敌人进行统一管理)
(2)将Death和OnJump功能互换,通过在Death动画结束后插入事件实现死亡特效
(解决上述遗留问题)
1.创建一个Enemy类用于统一管理敌人
(1)在Enemy中,定义Protected的Animator变量Anim,在Awake或Start中完成初始化。使Frog和Eagle成为Enemy子类。若想要子类可以继承其Awake或Start等方法并可重写(?),在Enemy的方法中声明virtual关键字,在子类相应方法中声明override关键字,并在子类对应方法中通过base.XX();继承
(2)将Death和JumpOn方法转移到父类中进行统一调用(声明为public),然后更新消灭敌人部分的代码
(问题:Enemy被消灭后播放死亡特效动画时,爆炸效果会根据原来的运动状态发生移动)
1.为Player添加Audio Source(这个举动只会配适给当前场景的Player,不会自动添加在Prefab上。想要配适给所有的Player可以通过Overrides按钮添加)
1.添加怪物死亡音效(注意取消开始时播放),在代码中实现调用(置于OnJump中,若置于Death可能会由于对象销毁而无法正常播放)
2.添加跳跃音效
3.添加受伤音效
4.添加收集物收集音效
5.添加。。。
1.在Canvas中添加一个Panel,并将其放在合适的位置
2.在Panel中增加Text显示文本
3.通过在相应对象上设置触发器用于Panel的显示,将Panel控制脚本设置给相应的对象(Panel的引用需要使用GameObject)
4.通过动画系统为Panel的显示增加渐变效果
1.代码修正
若在FixedUpata中调用移动控制代码时,应使用fixedDealtTime,若使用dealtTime应置于Updata中进行调用、
2.按键设置
在项目设置里修改Input属性,为Jump增加“w”作为副键,并复制一个设置更名为Crouch
3.设置Crouch动画,添加相关代码
void Crouch()
{
if (Input.GetButtonDown("Crouch"))
{
playerAnim.SetBool("Crouching", true);
boxCollider.enabled = false;
} else if (Input.GetButtonUp("Crouch"))
{
playerAnim.SetBool("Crouching", false);
boxCollider.enabled = true;
}
}
将下蹲方法置于Movement方法中引用
4.加入代码限制,使角色无法在卡在夹缝中时起立而被卡主
(1)在角色下添加一个空物体,作为头顶检查点
(2)使用Physics2D中的OverlapCircle方法进行检查
(问题:暂未添加由跑动到下蹲的动画状态转换逻辑)
if (!Physics2D.OverlapCircle(CellingCheck.position, 0.2f, ground))//
{
if (Input.GetButtonDown("Crouch"))
{
playerAnim.SetBool("Crouching", true);
DisCollider.enabled = false;
} else if (Input.GetButtonUp("Crouch"))
{
playerAnim.SetBool("Crouching", false);
DisCollider.enabled = true;
}
}
这里0.2f的判定半径需要根据实际情况有所调整
1.代码修正
在完成了前述代码后,控制角色移动时,保持下蹲姿势从夹缝中走出后,角色无法自动恢复Idel状态。这里可以对代码作出如下修改:
if (!Physics2D.OverlapCircle(CellingCheck.position, 0.5f, ground))
{
if (/*Input.GetButtonDown("Crouch")*/Input.GetButton("Crouch"))
{
playerAnim.SetBool("Crouching", true);
DisCollider.enabled = false;
} else /*if (Input.GetButtonUp("Crouch"))*/
{
playerAnim.SetBool("Crouching", false);
DisCollider.enabled = true;
}
}
将GetButtonDown和GetButtonUp的使用调整为GetButton(下蹲需要长摁),此时脱离夹缝地形可以自动恢复站立
2.掉落死亡机制和场景重启
(1)在场景下端设置“死线”,用于判定角色下落死亡,并重置场景
if (collision.tag.Equals("DeadLine"))
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
将以上逻辑包装为一个方法,通过Invoke调用来实现延迟调用,然后禁用所有音频
//场景重置
if (collision.tag.Equals("DeadLine"))
{
GetComponent<AudioSource>().enabled = false;//禁用所有音频
Invoke("Restart", 2);
}
3.关卡切换
在切换点设立触发器,挂载脚本,通关按下按键切换场景
(问题:关于文本框的动画触发)
这里可以使用两种方法:
(1)通过切换门的触发器,在触发器中时,按下E切换场景(无效,可能由于按键类操作需要在Updata中)
(2)将脚本加载到EnterDialog上,触发代码置于Updata中
这里使用(1)的思路,在Updata中检测碰撞
if (doorCollider.IsTouching(playMainCollider))
{
if (Input.GetKeyDown(KeyCode.E))
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);//切换到下一个编号的场景
}
}
1.将Tilemap的材质修改为Default-Diffuse(默认的漫反射类型),并新建同类型的材质给场景中包括Player的其他对象
2.在适当位置添加点光源,2D开发中点光源仍是一个3D物体,其在2D平面上的照射和其z轴位置有关,因此需要注意调整光源的z轴
1.Animator条件瘦身
在动画切换中,可以将代码和状态机中“Idel”相关的进行删除(因为Idel是默认的动画效果)(未更正)
2.跳跃手感的优化
在官方文档中,有关物理计算如Rigbody相关的内容置于FixedUpdata中进行调用更为顺滑,而GetButton相关的方法则在Updata中调用更合适(都需要使用deltaTime或fixedUpdata平衡),因此需要调整代码内容
此中,跳跃可以修改为GetButton来进一步调整手感
3.Enemy死亡后Bug消除
Enemy死亡后仍会进行一定的物理运动,在Death中销毁Enemy前禁用其Collider来阻止这种运动
4.避免角色移动速度太快时Cherry计数器的重复触发(并添加收集动画)(未更正)
建立一个统御收集物的类用于管理所有收集物,将PlayerController中的相关逻辑迁移至其中
(问题:跳跃功能出现bug,会莫名其妙跳的很低,偶尔会跳的异常高,并时常陷入Tilemap)
——使场景中不同层次的景观以不同的速度移动,来造成视觉上的偏差
1.整理场景中的各类对象
2.新建脚本Parallax控制视差
public Transform CameraTransform;
public float MoveRate;
private float startPointX,startPointY;
public bool lockY = false;
// Start is called before the first frame update
void Start()
{
startPointX = transform.position.x;
startPointY = transform.position.y;
}
// Update is called once per frame
void Update()
{
if (lockY)
{
transform.position = new Vector2(startPointX + CameraTransform.position.x * MoveRate, transform.position.y);
}
else
{
transform.position = new Vector2(startPointX + CameraTransform.position.x * MoveRate, startPointY + CameraTransform.position.y * MoveRate);
}
}
(问题:部分场景的起始位置会发生很大改变)
1.新建场景Menu,通过UI制作主菜单
2.在Canvas中,新建两个Panel,其一作为主菜单背景,其二作为主菜单控制面板
3.完善主菜单页面显示
4.完善主菜单功能,编写场景载入脚本,并在按钮上添加相应点击事件(其中,退出事件在游戏测试中无法生效,需要完成导出后实现)
public void PlayGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
public void QuitGame()
{
Application.Quit();
}
public void UIEnable()
{
GameObject.Find("Canvas/MainMenu/UI").SetActive(true);
}
5.加入主菜单的动画效果
1.根据25中步骤完善菜单的基本页面构建,游戏画面中设置Pause按钮用于调出菜单,菜单中设置Continue按钮用于返回游戏
2.在脚本中添加按钮需要的方法(通过Time.timeScale控制游戏暂停)
1.创建一个AudoMixer用于音量控制
2.将Player中的BGM的输出设置为该混音器
3.将暂停菜单的Slider的端值设置为混音器的声音端值
4.在代码中引用混音器,创建方法用于控制声音输出的大小
5.将混音器中的Volume一项设置为可被代码修改的(?)
6.在Slider中添加拖动事件,选择代码中设置的方法“bgmVolume”(?这里将会有上下两个方法供选择,应选择上面的一个)
(2019版本后,需要获取Slider的组件引用,然后进行值传递,而不会在事件选择时提供两个方法)
1.二段跳和跳跃手感的优化
(1)定义一个管理跳跃的bool变量,通过物理检测的方式判断跳跃是否可以进行
(2)定义一个变量用于可跳跃次数的管理,以此实现二段跳
(3)重新编写跳跃方法
(在代码中,可以通过Alt+上下来调整光标所在行的行位置)
在FixedUpdata中
isGround = Physics2D.OverlapCircle(GroundCheck.position, 0.2f, ground);
新的Jump方法
void NewJump()
{
if (isGround)
{
extraJump = 1;
}
if (Input.GetButtonDown("Jump") && extraJump>0)
{
playerRig.velocity = Vector2.up * jumpForce;//Vector.up等效于new Vector(0,1)
extraJump--;
playerAnim.SetBool("Jumping", true);
}
if (Input.GetButtonDown("Jump") && extraJump==0 && isGround)//?
{
playerRig.velocity = Vector2.up * jumpForce;
playerAnim.SetBool("Jumping", true);
}
}
2.单向平台的实现
(1)新建管理单向平台的Tilemap
(2)为该Tilemap添加PlatformEffector2D组件,取消Use Collider Mask(勾选后可从单向平台上跳下),勾选TilemapCollier的Used By Effector(注意最后修改Layer)