torstai 19. toukokuuta 2016

Let there be limbs!

I finally managed to finish my dismember system. It's not perfect yet but working well enough for my current needs.


So overview of my system:

Bear in mind, that I'm not programmer so some of my code might look really stupid and unefficient.
I've separated every limb in blender to make my work easier in Unity.

I've also parented every mesh same way rig is parented. 


This way Unity will automatically make mesh hierarchy same as rig hierarchy. This is not mandatory but just makes things easier in Unity.

I'm using Puppetmaster to control character physics and FinalIK for Full Body IK movement which I use to control both hands.

Every breakable skinnedMesh has reference to of all of it's child skinnedMeshes which is called MyLimbs.

Every limb in Puppetmaster ragdoll has a reference to same skinnedMesh limb.

When ragdoll limbs gets too damaged it will break and call method BakeLimbs() from it's skinnedMesh counter part.

SkinnedMesh will then make new temporary GameObject, change position of this empty gameObject same as it is in Ragdoll limb and bake current vertex positions of skinnedMesh to a new gameobject and copy skinnedMesh materials.

Bakedmesh will copy rigidbody from puppetmaster and use reflection to copy whole ConfigurableJoint component from puppetmaster ragdoll to bakedMesh with one change,
ConfigurableJoint.connectedBody changed to correct parent.

Items are parented to Puppetmaster ragdoll so I need to check what is current item and change it's FixedJoint.connectedBody to bakedmesh hand gameobject.

Then I apply same velocities as it's on Puppetmaster Ragdoll so the dismembered limb will break naturally instead of just dropping off.

I had quite alot problems with adding collider with correct orientation so I add collider as a child of this new bakedMesh. This new collider copies properties from Puppetmaster ragdoll, and changes layer to correct one. (I did not know about reflection when I was doing collider part but ofc I could use it also here just like with ConfigurableJoint)

And finally disable old skinned mesh renderer.

It was much more work than I first thought but that is how game development always goes :-)

Here is my current codes, quite un-commented but I hope these are clear enough. Leave a comment if you have any questions!

First the code which is on every detachable skinnedmesh
Second code is on every breakable Puppetmaster ragdoll limb which can break.
Third is both Bone and Item components. 
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SkinnedMeshBaker : MonoBehaviour {
public Bone MyBone;
public List<SkinnedMeshRenderer> MyLimbs = new List<SkinnedMeshRenderer>();
public GameObject[] StaticMeshes;
private CapsuleCollider MyCollider;
public Material injured;
// Use this for initialization
void Start ()
{
//MyLimbs.Add(this.GetComponent<SkinnedMeshRenderer>());
foreach (SkinnedMeshRenderer smk in transform.GetComponentsInChildren<SkinnedMeshRenderer>())
{
MyLimbs.Add(smk);
}
StaticMeshes = new GameObject[MyLimbs.Count];
}
public void BakeLimbs(CapsuleCollider[] limbColliders, Item item)
{
injured = Resources.Load("Materials/Injured",typeof(Material)) as Material;
for (int i = 0; i < MyLimbs.Count; i++)
{
Mesh bakedMesh = new Mesh();
MyLimbs[i].transform.position = limbColliders[i].transform.position;
MyLimbs[i].BakeMesh(bakedMesh);
StaticMeshes[i] = new GameObject("dis_"+MyLimbs[i].name);
// Setup its transforms
StaticMeshes[i].transform.position = MyLimbs[i].transform.position;
StaticMeshes[i].transform.rotation = MyLimbs[i].transform.rotation;
StaticMeshes[i].transform.localScale = Vector3.one;
// Parents
if (i == 0)
StaticMeshes[i].transform.parent = null;
else
StaticMeshes[i].transform.parent = StaticMeshes[i - 1].transform;
// Colliders
GameObject collider = new GameObject("col_"+MyLimbs[i].name);
collider.gameObject.layer = 9;
CapsuleCollider myCollider = collider.AddComponent<CapsuleCollider>();
collider.transform.position = limbColliders[i].transform.position;
collider.transform.rotation = limbColliders[i].transform.rotation;
myCollider.center = limbColliders[i].center;
myCollider.radius = limbColliders[i].radius;
myCollider.height = limbColliders[i].height;
myCollider.direction = limbColliders[i].direction;
collider.transform.parent = StaticMeshes[i].transform;
// Setup the rendering components
StaticMeshes[i].AddComponent<MeshFilter>().sharedMesh = bakedMesh;
Material[] tempMaterials = MyLimbs[i].sharedMaterials;
tempMaterials[1] = injured;
StaticMeshes[i].AddComponent<MeshRenderer>().sharedMaterials = tempMaterials;
StaticMeshes[i].GetComponent<MeshRenderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
// Setup rigidbodies
StaticMeshes[i].AddComponent<Rigidbody>().mass = limbColliders[i].GetComponent<Rigidbody>().mass;
// joints...
if (i == 0)
{
//nothing
}
else
{
ConfigurableJoint tempjoint = limbColliders[i].GetComponent<ConfigurableJoint>();
StaticMeshes[i].AddComponent<ConfigurableJoint>().GetCopyOf(tempjoint);
StaticMeshes[i].GetComponent<ConfigurableJoint>().connectedBody = StaticMeshes[i - 1].GetComponent<Rigidbody>();
StaticMeshes[i].GetComponent<ConfigurableJoint>().anchor = new Vector3(0, 0, 0);
}
// Setup item
if (i == (MyLimbs.Count - 1))
{
item.transform.SetParent(StaticMeshes[i].transform);
item.ChangeMyConnectedRigidbody(StaticMeshes[i].GetComponent<Rigidbody>());
}
//apply velocity
StaticMeshes[i].GetComponent<Rigidbody>().velocity = limbColliders[i].GetComponent<Rigidbody>().velocity*2;
StaticMeshes[i].GetComponent<Rigidbody>().angularVelocity = limbColliders[i].GetComponent<Rigidbody>().angularVelocity*2;
MyLimbs[i].gameObject.SetActive(false);
}
}
}
view raw SkinnedMeshBake hosted with ❤ by GitHub



using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using RootMotion;
using RootMotion.Dynamics;
public class JointBreak : MonoBehaviour
{
public PuppetMaster puppet;
public SkinnedMeshRenderer mySkinnedMesh;
private Item myItem;
private CapsuleCollider[] _colliders;
[SerializeField]
private Text myText;
[SerializeField]
private Text myText2;
public bool breakMe = false;
public bool breakMe2 = false;
// Use this for initialization
void Start()
{
if (puppet == null)
{
Debug.Log(transform.name + ": Puppet empty");
puppet = GetComponentInParent<PuppetMaster>();
}
_colliders = GetComponentsInChildren<CapsuleCollider>();
CheckForItem();
}
public void CheckForItem()
{
foreach (Item child in GetComponentsInChildren<Item>())
myItem = child;
}
// Update is called once per frame
void Update()
{
if (myText)
{
myText.text = this.GetComponent<Rigidbody>().velocity.ToString();
myText2.text = this.GetComponent<Rigidbody>().angularVelocity.ToString();
}
if (breakMe)
{
if (Input.GetKeyDown(KeyCode.U))
{
mySkinnedMesh.GetComponent<SkinnedMeshBaker>().BakeLimbs(_colliders, myItem);
puppet.RemoveMuscleRecursive (this.GetComponent<ConfigurableJoint> (), false);
}
}
if (breakMe2)
{
if (Input.GetKeyDown(KeyCode.I))
{
mySkinnedMesh.GetComponent<SkinnedMeshBaker>().BakeLimbs(_colliders, myItem);
puppet.RemoveMuscleRecursive (this.GetComponent<ConfigurableJoint> (), false);
}
}
}
void OnJointBreak(float breakForce)
{
Debug.Log("A joint '"+transform.name+ "' has just been broken!, force: " + breakForce);
DismemberMe();
//transform.parent = null;
}
public void DismemberMe()
{
mySkinnedMesh.GetComponent<SkinnedMeshBaker>().BakeLimbs(_colliders, myItem);
puppet.RemoveMuscleRecursive (this.GetComponent<ConfigurableJoint> (), false);
}
}
view raw JointBreak hosted with ❤ by GitHub

using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class Bone : MonoBehaviour {
public Bone[] myBones;
// Use this for initialization
void Awake()
{
myBones = GetComponentsInChildren<Bone>();
}
--------
item
--------
using UnityEngine;
using System.Collections;
public class Item : MonoBehaviour {
// Use this for initialization
private FixedJoint fj;
void Start ()
{
fj = GetComponent<FixedJoint>();
}
public void ChangeMyConnectedRigidbody(Rigidbody rb)
{
fj.connectedBody = rb;
}
view raw Bone and Item hosted with ❤ by GitHub

lauantai 14. toukokuuta 2016

Blog for my own game development rants.

Here I will update the progress of my personal game project which is heavily inspired by the old game Die by the Sword

This project is still at really early stage.
Currently I have arm movement working with rotation limits.
Player can rotate shoulder on two axes,wrist on three axes and extend/curtail arm.

Current controls are:
Press 1 to make right hand active
Press 2 to make left hand active

While left mouse button is held down:
Mouse Y adjusts shoulder pitch
Mouse X adjusts shoulder yaw

While right mouse button is held down:
Mouse Y adjusts wrist roll
Mouse X adjusts wrist yaw
Mouse Wheel adjusts wrist pitch

While middle mouse button is held down:
Mouse Y adjusts extending/curtailing of arm

Player can simultaneously adjust shoulder and wrist

Here is a video of arm movement.

Next job is to finish elbow target which gets a bit weird when wrist is rotated that hand palm is facing up. Problem can be seen in the video below.

After that to an interesting topic, dismembering of limbs.

Here is link to my current to-do list which I will update when ever I manage to think something nice I would want to have in my project.