撰写了文章 更新于 2018-11-18 17:01:06
[unity编程]2d平台游戏
对比起来,有了unity后(免费)做游戏确实非常简单,听说小学生都会用。2d平台游戏要怎么做呢?简单的做法就是把角色加个刚体,加个按键移动脚本,剩下的就由物理系统解决啦。这想得真是太美了(其实写出来已经开始麻烦,还要控制显示动画),并不是不行,只是这样做出来的只能是shit游戏。如果有斜坡,当然就和3D里一样人物在坡顶飞起来,下坡一跳一跳,在平面也有可能卡着。于是最好的办法是自己写整个移动和碰撞的部分,那有多麻烦可以看教程AABB版:
https://gamedevelopment.tutsplus.com/tutorials/basic-2d-platformer-physics-part-1--cms-25799
个人觉得,选择unity就是因为懒,不值得,也许用别人的修改成自己想要的效果,比如
http://ludumdare.com/compo/2015/04/15/unity-2d-platformer-controller-free-on-github/
https://github.com/prime31/CharacterController2D
还有其他不少,但重要的还是本身不会有bug,和看得懂,这就因人而异了。
实例
- 为了接近经典游戏,碰撞体只能用长方形,也就是box。
- 2D平台游戏可以没斜坡,但希望即使有也不会太不“正常”。
- 陷进去是很常见的bug,就算最后也去不掉只能说尽力了,还能怎么样……
- 爬梯子,单向平台,移动平台先不考虑,梯子可能最简单。
材料有unity官方2d教程
里面采用了自定义“物理”,也不是很长,只是也没完成,想真自定义还是得看懂了自己改。
PlayerPlatformerController继承了PhysicsObject,主要是方向和跳的输入和动画,主要看PhysicsObject就可以了。
可以看到35行Update()里【ComputeVelocity ();】接受输入,46行FixedUpdate()里进行计算和移动。
void FixedUpdate()
{
velocity += gravityModifier * Physics2D.gravity * Time.deltaTime;
velocity.x = targetVelocity.x;
grounded = false;
Vector2 deltaPosition = velocity * Time.deltaTime;
Vector2 moveAlongGround = new Vector2 (groundNormal.y, -groundNormal.x);
Vector2 move = moveAlongGround * deltaPosition.x;
Movement (move, false);
move = Vector2.up * deltaPosition.y;
Movement (move, true);
}
void Movement(Vector2 move, bool yMovement)
{
float distance = move.magnitude;
if (distance > minMoveDistance)
{
int count = rb2d.Cast (move, contactFilter, hitBuffer,
distance + shellRadius);
hitBufferList.Clear ();
//并不需要另外用list,即使需要复制可以用另一个数组。
for (int i = 0; i < count; i++) {
hitBufferList.Add (hitBuffer [i]);
}
for (int i = 0; i < hitBufferList.Count; i++)
{
Vector2 currentNormal = hitBufferList [i].normal;
if (currentNormal.y > minGroundNormalY)
{
grounded = true;
if (yMovement)
{
//需要改,如在空中normal不会改变。
groundNormal = currentNormal;
currentNormal.x = 0;
}
}
float projection = Vector2.Dot (velocity, currentNormal);
if (projection < 0)
{
velocity = velocity - projection * currentNormal;
}
float modifiedDistance = hitBufferList [i].distance
- shellRadius;
distance = modifiedDistance < distance ?
modifiedDistance : distance;
}
}
rb2d.position = rb2d.position + move.normalized * distance;
}
moveAlongGround看名字就是沿地面方向移动,要沿地面方向就需要知道地面的方向,groundNormal就是碰撞结果里最后撞到的得到的normal,这normal比较神奇,是垂直表面而向外的方向,也根据这个normal来判断地面有多斜从而判断有没“站稳”【grounded】,和projection有关的不用管,纯浪费时间(不太明用处,普通移动也不需要弹起),也就是那91-95行就当不存在。
整个过程就是,先加上重力造成的速度,从输入取得速度的x,把着地判断改成否,先沿着地面角度移动速度的x,然后垂直移动速度的y。
里面各种奇怪的无法理解的地方,比如除了上面看不懂用处的projection,就没有其他地方可以阻止速度的y不断增大。groundNormal是进行垂直移动检测碰撞结果里最后撞到的得到的normal,就是说,并不保证就是会撞到(最近距离)的那个。有些情况比如有地面,但是非常陡峭,又或者跳起时头顶到了没有处理。要说难不难改知道了需要改哪就并不难改,而且也有一定自由,比如可以反弹,但当然不能像它那样写,可以沿着表面移动相应距离,这个感觉复杂一点,又或者顶到就y归零或方向反向,地面太斜时另外作一种情况处理。具体代码就我想想算了不写了。
虽然这可能是最简洁易懂的方法,但会有陷入地形的情况,按理说它是检测了直接移动位置的,应该不会出现这种状况……既然无法解释,那也就无法修复,也许哪位大佬能解决。另外这种方法是自定义物理,自然要抛弃原来的物理,刚体是设成kinematic,需要和其他不是kinematic的刚体碰才能“自动”检测到,那如果想做个山寨马○奥,怪也是这样用kinematic,那怎么办呢?问题我提出了,但答案我没有,真的想山寨马○奥又不想搞这些,那也许去asset商店看看。
PS:
- 用Rigidbody2D.MovePosition
也许可以。很慢,如果按这里先移x再移y的话跳起来竟然完全不能左右移动,所以猜是这太慢的原因,不确定,而且仔细想想没什么用,因为正常应是不会碰到(不会陷入),总之可以在编辑器里设置Use Full Kinematic Contacts。 - 比起在Cast时加shellRadius,在最后动position时+nomal*smalldistance的效果好像好些(还是不确定不会陷进去)。
- FixedUpdate里应用fixedDeltaTime。
- 即使不考虑斜面,也至少需要cast两次,因为按习惯只要上下方向没被挡住那还可以向上下方向移动。
- 因为“陷进去”就不能靠normal判断,按坐标判断貌似也没问题啊,但从来没见过这种代码,让人很不安。