技术分享:游戏战场中如何处理角色死亡时各种姿势的“尸体“

最近在做《搜打撤》类的第三人称射击类游戏,遇到一个问题,就是当角色死亡的时候,尸体要留在战场上,同时又希望死亡时,尸体会有一些不同的摆放姿势,如下:

图片

图片

如图所示,同一个模型的两个角色,在死亡后变成两个尸体,但他们的死亡姿势每次都不一样。

对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀

角色的制作思路如下:

Step1:角色我们都基于人形动画来进行制作,角色的模型就有标准的人形的骨骼。当角色没有死亡的时候,直接基于动画组件来播放动画即可。

图片

Step2: 要模拟角色"死亡"后不同的骨骼摆放的位置,姿势各有不同,那么我们要给人形动画的每个关节带上一些物理刚体RigidBody+物理形状。做成"刚体骨骼"。

图片

刚体骨骼与刚体骨骼之间用物理关节连接起来。

图片

单独的针对"人形骨骼"的节点做一个独立的物理分组,不与其它的物理分组产生任何碰撞。

Step3: 编写一个"物理人形骨骼"控制代码:  RagdollBehavior,用来控制这个物理人形骨骼。在角色创建的时候,正常情况下,先禁用"物理人形骨骼"。

        public void Init(Transform ragdollParentTransform)        {            List<Rigidbody> rigidbodies = new List<Rigidbody>();
            ragdollParentTransform.GetComponentsInChildren(rigidbodies);
            rbCases = new List<RigidbodyCase>();
            for (int i = 0; i < rigidbodies.Count; i++)            {                var rigidbody = rigidbodies[i];
                if (rigidbody.gameObject.layer != 14)                    continue;
                var rbCase = new RigidbodyCase(rigidbodies[i]);                rbCase.Disable();
                rbCases.Add(rbCase);            }        }
            public void Disable()            {                rigidbody.isKinematic = true;                rigidbody.useGravity = false;                rigidbody.Sleep();
                if (collider != null)                    collider.enabled = false;            }

运行的时候,正常情况下,"物理人形骨骼"都被禁用,不会对角色正常控制产生任何的影响。

Step4: 当角色死亡以后,用代码来控制"尸体"的摆放效果。

(1)角色死亡后,我们第一时间先把它的动画状态机给关停掉,让动画不再会控制到我们的人形骨骼,把NavMeshAgent, 角色物理碰撞器都关闭。

        if (e.uStatus.status == (int)CharactorStatus.Died) {            e.uNav.NavMeshAgent.isStopped = true;            e.uNav.NavMeshAgent.enabled = false;            e.uAnim.animator.enabled = false;            EntityWrapper.ShowDeathFallAnimation(e);            e.uAnim.unityObject.GetComponent<Collider>().enabled = false;            EntityWrapper.ActivateRagdollOnDeath(e);        }

(2) 把"物理人形骨骼"的每个骨骼的物理刚体,物理碰撞器都开启来。

            public void Activate()            {                rigidbody.isKinematic = false;                rigidbody.useGravity = true;                rigidbody.WakeUp();
                if (collider != null)                    collider.enabled = true;            }

(3)全部把"物理人形骨骼"开启以后,在脚下给"物理人形骨骼"一个"物理爆炸"的力。力的方向,位置,爆炸半径。使用的物理引擎 API:

publicvoidAddExplosionForce(float explosionForce,Vector3 explosionPosition,float explosionRadius);

参数

类型

说明

explosionForcefloat爆炸的基准力量。

 这是在爆炸中心或对特定物体施加力的强度参考值。单位是牛顿(N)。

explosionPositionVector3爆炸中心在世界坐标系中的位置。

 所有力的计算都以此点为原点。

explosionRadiusfloat爆炸的影响半径。

 只有处于此球体范围内的Rigidbody才会受到力的影响。

        public void ActivateWithForce(Vector3 point, float force, float radius)        {            for (int i = 0; i < rbCases.Count; i++)            {                rbCases[i].Activate();                rbCases[i].AddForce(point, force, radius);            }        }                public void AddForce(Vector3 point, float force, float radius)        {            rigidbody.AddExplosionForce(force, point, radius);        }

Step5: "物理爆炸"结束2秒后,再禁用"物理人形骨骼",这样角色就会躺在地面上,骨骼摆放为不同的姿势。

    protected static void ActivateRagdollOnDeath(CharactorEntity e)    {        EnableRagdoll(e.uAnim.ragdoll, deathExplosionForce, e.uAnim.unityObject.transform.position + e.uAnim.unityObject.transform.forward);        e.uAnim.ragdollCase = Tween.DelayedCall(2.0f, () =>        {            e.uAnim.ragdoll?.Disable();        });    }

END

,时长02:06

最后,附上"人形物理骨骼"控制的完整代码,可以直接以独立的模块运行到项目中。

    public class RagdollBehavior    {        private List<RigidbodyCase> rbCases;
        public void Init(Transform ragdollParentTransform)        {            List<Rigidbody> rigidbodies = new List<Rigidbody>();
            ragdollParentTransform.GetComponentsInChildren(rigidbodies);
            rbCases = new List<RigidbodyCase>();
            for (int i = 0; i < rigidbodies.Count; i++)            {                var rigidbody = rigidbodies[i];
                if (rigidbody.gameObject.layer != 14)                    continue;
                var rbCase = new RigidbodyCase(rigidbodies[i]);                rbCase.Disable();
                rbCases.Add(rbCase);            }        }
        public void Activate()        {            for (int i = 0; i < rbCases.Count; i++)            {                rbCases[i].Activate();            }        }
        public void ActivateWithForce(Vector3 point, float force, float radius)        {            for (int i = 0; i < rbCases.Count; i++)            {                rbCases[i].Activate();                rbCases[i].AddForce(point, force, radius);            }        }
        public void Disable()        {            if (rbCases.IsNullOrEmpty())                return;
            for (int i = 0; i < rbCases.Count; i++)            {                if (rbCases[i] != null && rbCases[i].rigidbody != null)                    rbCases[i].Disable();            }        }
        public void Reset()        {            if (rbCases.IsNullOrEmpty())                return;
            for (int i = 0; i < rbCases.Count; i++)            {                rbCases[i].Reset();            }        }
        private class RigidbodyCase        {            public Rigidbody rigidbody;            public Collider collider;            public Vector3 localPosition;            public Quaternion localRotation;            public Vector3 localScale;
            public RigidbodyCase(Rigidbody rigidbody)            {                this.rigidbody = rigidbody;
                collider = rigidbody.GetComponent<Collider>();
                localPosition = rigidbody.transform.localPosition;                localRotation = rigidbody.transform.localRotation;                localScale = rigidbody.transform.localScale;            }
            public void Disable()            {                rigidbody.isKinematic = true;                rigidbody.useGravity = false;                rigidbody.Sleep();
                if (collider != null)                    collider.enabled = false;            }
            public void Activate()            {                rigidbody.isKinematic = false;                rigidbody.useGravity = true;                rigidbody.WakeUp();
                if (collider != null)                    collider.enabled = true;            }
            public void AddForce(Vector3 point, float force, float radius)            {                rigidbody.AddExplosionForce(force, point, radius);            }
            public void Reset()            {                rigidbody.transform.localPosition = localPosition;                rigidbody.transform.localRotation = localRotation;                rigidbody.transform.localScale = localScale;            }        }    }

更多射击类技术点【本课程将学到】:

(1):完整的第三人称射击类的"搜打撤"玩法的策划案,数值与成长体系;

(2):完整的第三人成射击类的”地图编辑器“的工具开发;

(3):学到Unity/Cocos双引擎3D第三人称射击类游戏的开发与设计;

(4):基于全免素材,如何来做商业化设计;

(5):基于Unity发布微信小游戏方案与踩坑;

(6):获得Cocos使用Unity编辑器导出数据来构建更高效的工作流;

最近《搜打撤》类的游戏已经开始上榜微小/抖小的畅玩榜了。搞起来!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值