这篇文章上次修改于 212 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
最近在做一个原型,参加了Global Game Jam,都用了布娃娃这个要素.下面是一些相关的GIF.
这篇杂谈就想聊一下如何做一个基本的2D布娃娃,
先看看别的游戏
我认为比较有意思和代表意义的布娃娃游戏大概有以下几个:
- Mutilate-a-Doll 2
看图可知........当然这个游戏不只有虐布娃娃那么单调的玩法,还有各种创意工坊地图,甚至"开放世界"模式,感兴趣可以去玩玩.
- 甜瓜游乐场
这款游戏是使用Unity制作的,很沙盒,看着也挺幽默....
- Stick Ranger
我个人最喜欢的一款,这是一款Action RPG game(是的),玩家初始有四个火柴人勇者,每个勇者可以选择自己的职业,还有玩家对战,仅靠拖动就可以玩,而且火柴人关节可以拉伸,并且有弹性,游戏体验很好,感兴趣可以尝试.
那么怎么搓个布娃娃?
引入了上述三款游戏,就可以参考一下了.
Mutilate-a-Doll 2中布娃娃的整个身体是一个个相同的单元组成的,每个单元可以燃烧,感染毒素,被图钉钉住(好惨),甜瓜游乐场里每个身体部位被选择关节连接在一起,Unity中的Hinge Joint 2D
可以实现这个功能,而Stick Ranger是用Java写的,游戏中玩家可以拖动的就是火柴人的头部,而其他身体部位是可伸缩和有弹性的,被连接到了头的部分,武器也是被连接在火柴人的手上的,玩家不用操作,火柴人有一种自平衡机制.
所以可以通过Unity自带的一些组件,比如Hinge Joint 2D,并自己写一些C#脚本来控制火柴人的刚体组件来实现其平衡,搓出一个基本的2D布娃娃.
布娃娃,启动!
在参考一些相关视频和游戏以后
How to Make an Active 2D Stickman Ragdoll in Unity | Balance, Run, Jump (youtube.com)
Unity ragdolls! Make your characters floppy ;) (youtube.com)
就可以开搓了
第一步 准备相关素材
准备好图中所示的图片素材,可以在Sprite Editor中将其分割成各个身体组件,这里不再赘述.当然,你也可以使用Unity建模法(其实图中就是这么做的).然后你就拥有了一个布娃娃身体框架.
我使用的层级是这样的,可见教程中布娃娃身体共有12个组件,你也可以添加更多身体部位.
我使用的层级是这样的,可见教程中布娃娃身体共有12个单元,你也可以添加更多身体部位.
第二步 为身体部位添加组件
按住 Ctrl
加鼠标左键可选中对应的所有身体部位,在 Inspector 里面给它们批量添加Rigidbody 2D
和 Box Collider 2D
和 Hinge Joint 2D
组件.如果想要忽略一些身体部位的碰撞,可以直接删除其碰撞箱组件或暂时禁用,又或者可以在Layer Overrides
(如下图)中设置忽略的层级Exclude Layers(可以把身体部位分配到相应的层级,请自行分配).
之后,删除身体部位的Hinge Joint 2D
组件,该教程中身体是布娃娃的中心部位.
对于其他的身体部位的Hinge Joint 2D
组件,请先关闭Auto Configure Connected Anchor
,然后按从外到内
的顺序,将距离目前关节最近的身体部位拖动到Connected Rigid Body
中进行组件绑定,之后在Scene
窗口中将Anchor(关节)(本物体)
和Connected Anchor(被连接的关节)(目标物体)
对应的位置拖动到对应位置.空心绿圆是Anchor
,实心圆是Connected Anchor
.可以把它们像如图所示中重合.位置于关节连接处.
第三步 为身体添加平衡
设置组件调整平衡
到这里布娃娃主体已经完成,可是不能保持平衡(当然你可以就到此为止,保持其幽默性?),你可以在身体部位的Rigid Body 2D
组件中锁定Z轴旋转(Freeze Rotation),和限制腿部关节旋转角度来避免其滑倒(如图),如要限制旋转,记住勾选Use Limits
通过脚本调整平衡
- Balance.cs
using UnityEngine;
public class Balance : MonoBehaviour
{
public float force;
public Rigidbody2D rb;
public float targetRotation;
private void Update() {
rb.MoveRotation(Mathf.LerpAngle(rb.rotation,targetRotation,Time.deltaTime*force));
}
}
- Force.cs
using UnityEngine;
public class Force : MonoBehaviour
{
public Rigidbody2D rb;
public float force = 10f;
public enum Direction { Right, Left, Up, Down } ;
public Direction direction;
public Vector2 directionVector;
public HingeJoint2D joint;
private void Awake() {
rb = GetComponent<Rigidbody2D>();
switch (direction) {
case Direction.Right:
directionVector = Vector2.right;
break;
case Direction.Left:
directionVector = Vector2.left;
break;
case Direction.Up:
directionVector = Vector2.up;
break;
case Direction.Down:
directionVector = Vector2.down;
break;
}
}
private void FixedUpdate() {
if(joint.enabled)
rb.AddForce(directionVector * force);
}
}
Balance
可以加在身体和腿部上,功能为逐渐将其旋转回目标角度,保持站立
Force
是一个通用的施力脚本,可以放在需要添加力的身体关节上并设置力的方向和大小,比如给头部一个向上的力.
第四步(可选) 控制布娃娃
基本的PlayerController
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
//创建基本变量
InputControl inputControl;
Rigidbody2D rb;
//创建移动变量
public float moveSpeed;
public Vector2 inputDirection;
public float flyForce;
public float downForce;
//速度限制
public float maxFlySpeed;
public float maxDownSpeed;
public float maxMoveSpeed;
private void OnEnable() {
inputControl.Enable();
}
private void OnDisable() {
inputControl.Disable();
}
private void Awake() {
inputControl = new InputControl();
rb = GetComponent<Rigidbody2D>();
}
private void Update() {
//获取移动输入
inputDirection = inputControl.Gameplay.Move.ReadValue<Vector2>();
if(Keyboard.current.spaceKey.ReadValue() == 1){
Fly();
}
}
private void FixedUpdate() {
Move();
}
private void Move()
{
//移动
rb.AddForce(new Vector2(inputDirection.x * moveSpeed,0),ForceMode2D.Force);
if(inputDirection.y<0){
//给予向下的力
rb.AddForce(Vector2.down * downForce,ForceMode2D.Force);
}
}
private void Fly()
{
rb.AddForce(Vector2.up * flyForce, ForceMode2D.Force);
}
//速度限制
private void LateUpdate() {
if(rb.velocity.y > maxFlySpeed){
rb.velocity = new Vector2(rb.velocity.x,maxFlySpeed);
}
if(rb.velocity.y < -maxDownSpeed){
rb.velocity = new Vector2(rb.velocity.x,-maxDownSpeed);
}
if(rb.velocity.x > maxMoveSpeed){
rb.velocity = new Vector2(maxMoveSpeed,rb.velocity.y);
}
if(rb.velocity.x < -maxMoveSpeed){
rb.velocity = new Vector2(-maxMoveSpeed,rb.velocity.y);
}
}
}
也可以用头部检测鼠标用鼠标拖动布娃娃....请自行发挥想象
额外 添加更多功能
到第三步基本布娃娃已经完成,如果想要更多功能,这里简单描述一下
- 腿部的交替: 可以写一个脚本根据身体刚体的x轴速度来用两个点运动于一个椭圆的轨迹,参考脚本如下
using UnityEngine;
public class LegController : MonoBehaviour{
public HingeJoint2D joint1;
public HingeJoint2D joint2;
public float offsetY;
//角度
public float angle;
public float twoPI = Mathf.PI * 2;
public float speed;
public float a,b,c;
public float speedRange = 2.0f;
public bool touchingGround;
public Rigidbody2D rb;
private void Update() {
speed = rb.velocity.x;
if(angle*c >= 10*twoPI||angle*c <= -10*twoPI){
angle = 0;
}
}
private void FixedUpdate() {
if(rb.velocity.y>-speedRange&&rb.velocity.y<speedRange)
angle -= speed;
var xp1 = a * Mathf.Cos(angle * c);
var yp1 = b * Mathf.Sin(angle * c);
var xp2 = a * Mathf.Cos(angle * c + Mathf.PI);
var yp2 = b * Mathf.Sin(angle * c + Mathf.PI);
joint1.connectedAnchor = new Vector2(xp1,yp1+offsetY);
joint2.connectedAnchor = new Vector2(xp2,yp2+offsetY);
}
private void OnTriggerStay2D(Collider2D other) {
if(other.gameObject.layer == 7){
touchingGround = true;
}
}
private void OnTriggerExit2D(Collider2D other) {
if(other.gameObject.layer == 7){
touchingGround = false;
}
}
}
- 身体关节的断裂:当到达对应条件时将关节禁用,还可以给断裂关节一个力和剩余生命时长让断裂关节有更好的观感,参考代码片段如下
if (health <= stage1&&!stage1Broken)
{
//断掉铰链关节
leftLeg2.gameObject.layer = 14; // 被忽略的层级
rightLeg2.gameObject.layer = 14; // 避免关节产生判定和碰撞
var leftLeg2Rb = leftLeg2.GetComponent<Rigidbody2D>();
var rightLeg2Rb = rightLeg2.GetComponent<Rigidbody2D>();
leftLeg2.enabled = false;
rightLeg2.enabled = false;
leftLeg2Rb.drag = 0;
rightLeg2Rb.drag = 0;
leftLeg2Rb.sharedMaterial = bounce; // 让断裂关节有弹性
rightLeg2Rb.sharedMaterial = bounce;
randomForce = Random.Range(minForce, maxForce); // 施加随机的力
randomDirection = new Vector2(Random.Range(-1f, 1f), Random.Range(-1f, 1f));
leftLeg2Rb.AddForce(randomDirection * randomForce, ForceMode2D.Impulse);
rightLeg2Rb.AddForce(randomDirection * randomForce, ForceMode2D.Impulse);
body.mass += (leftLeg2Rb.mass + rightLeg2Rb.mass)*0.4f;
stage1Broken = true;
loseHealthSpeed = 0.25f; // 流血
audioSource.PlayOneShot(audioClip);
}
没有评论