Other players as real MSC characters
GameCharacter clones a MSC human (bodymesh + skeleton, no scripts, physics or animation), puts it into its bind pose, aligns and scales it, and drives the bones every frame from the joint points of the existing MSCO figure (hip, knee, ankle, shoulder, elbow, hand, head pitch, torso lean) by rotating each limb from its rest direction. Limb bones are picked geometrically, so bone naming conventions do not matter. Variant per player id; console 'model classic|game' switches, the MSCO figure is the automatic fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,13 @@
|
||||
Web-Panel (Einstellungen), **OFFICIAL** vergeben nur die MSCO-Admins und wird hervorgehoben
|
||||
- Braucht keinen neuen Server: die Infos kommen vom Master (msco.mbm.wtf) und funktionieren mit 2.8-Servern
|
||||
|
||||
**Mitspieler als echte MSC-Menschen**
|
||||
- Andere Spieler sehen aus wie die Leute im Spiel: MSCO nimmt eine Kopie eines MSC-Menschen (Körper, Gesicht,
|
||||
Kleidung) und bewegt dessen Skelett mit der bisherigen Animation - laufen, springen, ducken, sitzen, Items halten
|
||||
- Jeder Spieler bekommt (nach Spieler-Nummer) einen anderen Menschen; bei allen Mitspielern derselbe
|
||||
- Konsole (F7): `model classic` zeigt wieder die MSCO-Figur, `model game` die MSC-Menschen; `rig` zeigt, wer welches
|
||||
Modell hat. Findet MSCO keinen passenden Menschen, bleibt automatisch die MSCO-Figur
|
||||
|
||||
**Beifahrer**
|
||||
- **Automatisch einsteigen**: in die offene Beifahrertür an den Sitz treten - man setzt sich von selbst hin, keine Taste
|
||||
nötig. Aussteigen mit Leertaste oder der Beifahrer-Taste (P)
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace MSCO
|
||||
"fps [on|off] - show the frames per second (toggle)\n" +
|
||||
"/admin - admin menu (kick / ban / teleport / cheats)\n" +
|
||||
"vehicles | rig - debug info about vehicles / player models\n" +
|
||||
"model [game|classic] - other players as MSC characters or the MSCO figure\n" +
|
||||
"vars <filter> - synced vehicle values, e.g. vars oil (debug)\n" +
|
||||
"debug - toggle sync debug output\n" +
|
||||
"clear - clear this console";
|
||||
@@ -127,6 +128,13 @@ namespace MSCO
|
||||
case "rig":
|
||||
foreach (string line in mp.DescribeRigs()) print(line);
|
||||
break;
|
||||
case "model":
|
||||
// Mitspieler als echte MSC-Menschen ("game") oder als MSCO-Figur ("classic")
|
||||
if (args.Length > 1) RemotePlayer.UseGameModels = args[1].ToLower() != "classic";
|
||||
else RemotePlayer.UseGameModels = !RemotePlayer.UseGameModels;
|
||||
mp.RebuildAvatars();
|
||||
print("Player models: " + (RemotePlayer.UseGameModels ? "MSC characters (model classic = MSCO figure)" : "MSCO figure (model game = MSC characters)"));
|
||||
break;
|
||||
case "vehicles":
|
||||
foreach (string line in mp.DescribeVehicles()) print(line);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MSCO
|
||||
{
|
||||
/// <summary>
|
||||
/// Echte Spielfigur für Mitspieler: eine Kopie eines MSC-Menschen (SkinnedMeshRenderer "bodymesh" mit Skelett
|
||||
/// pelvis/knee_left/ankle_left/shoulder_left/arm_left/hand_left/head ...). Die Knochen werden jedes Frame aus der
|
||||
/// Pose der MSCO-Figur gesetzt (Hüfte, Knie, Knöchel, Schulter, Ellbogen, Hand, Kopf) - laufen, springen, ducken,
|
||||
/// sitzen und Items halten funktionieren dadurch genau wie vorher. Die Bewegung wird über Richtungen übertragen
|
||||
/// (Ruhelage aus der Bind-Pose des Modells), dadurch ist egal, wie die Knochen im Modell ausgerichtet sind.
|
||||
/// </summary>
|
||||
public class GameCharacter
|
||||
{
|
||||
/// <summary>Gelenkpunkte der MSCO-Figur im lokalen Raum des Spielers (jedes Frame von RemotePlayer.Pose).</summary>
|
||||
public class Rig
|
||||
{
|
||||
public Vector3 Hip, NeckBase, Head;
|
||||
public Vector3 Up = Vector3.up;
|
||||
public float Pitch;
|
||||
public readonly Vector3[] HipJoint = new Vector3[2], Knee = new Vector3[2], Ankle = new Vector3[2];
|
||||
public readonly Vector3[] Shoulder = new Vector3[2], Elbow = new Vector3[2], Hand = new Vector3[2];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Vorlagen im Spiel finden
|
||||
|
||||
static readonly string[] RequiredBones =
|
||||
{ "pelvis", "head", "knee_left", "knee_right", "ankle_left", "ankle_right", "hand_left", "hand_right" };
|
||||
|
||||
static List<SkinnedMeshRenderer> templates;
|
||||
static float nextTemplateSearch;
|
||||
|
||||
/// <summary>Passende Menschen im Spiel (sortiert nach Pfad: bei allen Spielern gleiche Reihenfolge).</summary>
|
||||
static List<SkinnedMeshRenderer> Templates()
|
||||
{
|
||||
if (templates != null && templates.Count > 0 && templates.TrueForAll(t => t != null)) return templates;
|
||||
if (Time.realtimeSinceStartup < nextTemplateSearch) return templates ?? new List<SkinnedMeshRenderer>();
|
||||
nextTemplateSearch = Time.realtimeSinceStartup + 10f;
|
||||
|
||||
var found = new List<KeyValuePair<string, SkinnedMeshRenderer>>();
|
||||
var seenMeshes = new HashSet<Mesh>();
|
||||
foreach (SkinnedMeshRenderer smr in Resources.FindObjectsOfTypeAll<SkinnedMeshRenderer>())
|
||||
{
|
||||
if (smr == null || smr.sharedMesh == null || !smr.name.StartsWith("bodymesh") || smr.name.Contains("kid")) continue;
|
||||
if (smr.hideFlags != HideFlags.None || smr.bones == null || smr.bones.Length < 10) continue;
|
||||
if (smr.sharedMesh.bindposes == null || smr.sharedMesh.bindposes.Length != smr.bones.Length) continue;
|
||||
if (!HasBones(smr)) continue;
|
||||
Transform root = CloneRoot(smr);
|
||||
if (root == null || !SafeToClone(root)) continue;
|
||||
if (!seenMeshes.Add(smr.sharedMesh)) continue; // gleiches Aussehen nur einmal
|
||||
found.Add(new KeyValuePair<string, SkinnedMeshRenderer>(PathOf(smr.transform), smr));
|
||||
}
|
||||
found.Sort((a, b) => string.CompareOrdinal(a.Key, b.Key));
|
||||
templates = found.ConvertAll(kv => kv.Value);
|
||||
if (templates.Count > 0) MscoLog.Info("[MSCO] Player models: " + templates.Count + " MSC characters found");
|
||||
return templates;
|
||||
}
|
||||
|
||||
static bool HasBones(SkinnedMeshRenderer smr)
|
||||
{
|
||||
var names = new HashSet<string>();
|
||||
foreach (Transform b in smr.bones) if (b != null) names.Add(b.name);
|
||||
foreach (string r in RequiredBones) if (!names.Contains(r)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Kleinster gemeinsamer Vorfahre von Mesh und Skelett - das wird kopiert.</summary>
|
||||
static Transform CloneRoot(SkinnedMeshRenderer smr)
|
||||
{
|
||||
Transform bone = smr.rootBone != null ? smr.rootBone : smr.bones[0];
|
||||
if (bone == null) return null;
|
||||
for (Transform a = smr.transform; a != null; a = a.parent)
|
||||
if (bone == a || bone.IsChildOf(a)) return a;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kopieren darf keine Spiel-Logik starten: entweder hängen in dem Teilbaum keine Skripte (PlayMaker ...),
|
||||
/// oder er ist ohnehin gerade inaktiv (dann wird er für die Kopie kurz ausgeschaltet, ohne Folgen).
|
||||
/// </summary>
|
||||
static bool SafeToClone(Transform root)
|
||||
{
|
||||
if (!root.gameObject.activeInHierarchy) return true;
|
||||
return root.GetComponentsInChildren<MonoBehaviour>(true).Length == 0;
|
||||
}
|
||||
|
||||
static string PathOf(Transform t)
|
||||
{
|
||||
string path = t.name;
|
||||
for (Transform p = t.parent; p != null; p = p.parent) path = p.name + "/" + path;
|
||||
return path;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Instanz
|
||||
|
||||
class Bone
|
||||
{
|
||||
public Transform T;
|
||||
public Quaternion RestRot; // relativ zum Spieler
|
||||
public Vector3 RestPos; // relativ zum Spieler
|
||||
public Vector3 RestDir; // zum Kind-Gelenk (Arme/Beine)
|
||||
}
|
||||
|
||||
GameObject clone;
|
||||
Transform owner;
|
||||
Bone pelvis, head;
|
||||
readonly List<Bone> spine = new List<Bone>();
|
||||
readonly Bone[] upperLeg = new Bone[2], lowerLeg = new Bone[2], foot = new Bone[2];
|
||||
readonly Bone[] upperArm = new Bone[2], lowerArm = new Bone[2];
|
||||
Vector3 pelvisOffset;
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>Figur aus einer Spielvorlage bauen (variant wählt den Menschen). null, wenn keine passt.</summary>
|
||||
public static GameCharacter Create(Transform owner, int variant)
|
||||
{
|
||||
List<SkinnedMeshRenderer> list = Templates();
|
||||
if (list.Count == 0) return null;
|
||||
var c = new GameCharacter();
|
||||
try
|
||||
{
|
||||
if (c.Build(owner, list[Mathf.Abs(variant) % list.Count])) return c;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MscoLog.Warning("[MSCO] Player model could not be built: " + e.Message);
|
||||
}
|
||||
c.Destroy();
|
||||
return null;
|
||||
}
|
||||
|
||||
bool Build(Transform owner, SkinnedMeshRenderer template)
|
||||
{
|
||||
this.owner = owner;
|
||||
Transform tplRoot = CloneRoot(template);
|
||||
GameObject tpl = tplRoot.gameObject;
|
||||
|
||||
// Kopieren, ohne dass Skripte der Vorlage anlaufen (siehe SafeToClone)
|
||||
bool wasActive = tpl.activeSelf;
|
||||
bool toggle = wasActive && !tpl.activeInHierarchy;
|
||||
if (toggle) tpl.SetActive(false);
|
||||
try { clone = (GameObject)UnityEngine.Object.Instantiate(tpl); }
|
||||
finally { if (toggle) tpl.SetActive(true); }
|
||||
clone.name = "MSCO_Model";
|
||||
Strip(clone);
|
||||
|
||||
SkinnedMeshRenderer smr = null;
|
||||
foreach (SkinnedMeshRenderer s in clone.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||
if (s.sharedMesh == template.sharedMesh) { smr = s; break; }
|
||||
if (smr == null) return false;
|
||||
Description = "MSC character " + PathOf(template.transform);
|
||||
|
||||
// Knochen nach Namen
|
||||
var byName = new Dictionary<string, Transform>();
|
||||
foreach (Transform b in smr.bones) if (b != null && !byName.ContainsKey(b.name)) byName[b.name] = b;
|
||||
|
||||
// In die Bind-Pose (Ruhelage des Modells) setzen: Eltern vor Kindern
|
||||
Matrix4x4[] bind = smr.sharedMesh.bindposes;
|
||||
var order = new List<int>();
|
||||
for (int i = 0; i < smr.bones.Length; i++) if (smr.bones[i] != null) order.Add(i);
|
||||
order.Sort((a, b) => Depth(smr.bones[a]).CompareTo(Depth(smr.bones[b])));
|
||||
foreach (int i in order)
|
||||
{
|
||||
Matrix4x4 m = smr.transform.localToWorldMatrix * bind[i].inverse;
|
||||
smr.bones[i].position = m.GetColumn(3);
|
||||
smr.bones[i].rotation = Quaternion.LookRotation(m.GetColumn(2), m.GetColumn(1));
|
||||
}
|
||||
|
||||
// Gliedmaßen geometrisch bestimmen: in jeder Kette (Becken -> ... -> Knöchel -> Zehen bzw. Rumpf -> ... -> Hand
|
||||
// -> Finger) sind die zwei längsten aufeinanderfolgenden Stücke Ober-/Unterschenkel bzw. Ober-/Unterarm.
|
||||
// So ist egal, ob ein Knochen nach seinem Anfang oder seinem Ende benannt ist.
|
||||
clone.transform.SetParent(owner, true);
|
||||
Transform headT = byName["head"], pelvisT = byName["pelvis"];
|
||||
var trunk = new HashSet<Transform> { pelvisT };
|
||||
for (Transform t = headT; t != null && t != pelvisT; t = t.parent) trunk.Add(t);
|
||||
Transform[][] legs = { Limb(byName["ankle_left"], trunk), Limb(byName["ankle_right"], trunk) };
|
||||
Transform[][] arms = { Limb(byName["hand_left"], trunk), Limb(byName["hand_right"], trunk) };
|
||||
foreach (Transform[] l in legs) if (l == null) return false;
|
||||
foreach (Transform[] l in arms) if (l == null) return false;
|
||||
|
||||
Vector3 up = (headT.position - pelvisT.position).normalized;
|
||||
Vector3 right = (arms[1][0].position - arms[0][0].position).normalized;
|
||||
Vector3 forward = Vector3.Cross(right, up).normalized;
|
||||
if (forward.sqrMagnitude < 0.5f) return false;
|
||||
Quaternion facing = Quaternion.LookRotation(forward, up);
|
||||
float legLength = (legs[0][1].position - legs[0][0].position).magnitude + (legs[0][2].position - legs[0][1].position).magnitude;
|
||||
if (legLength < 0.01f) return false;
|
||||
float scale = 0.84f / legLength; // Bein der MSCO-Figur (Oberschenkel + Schienbein + etwas Knöchel)
|
||||
|
||||
// Erst drehen und skalieren, dann Ruhelage relativ zum Spieler merken
|
||||
clone.transform.localScale = clone.transform.localScale * scale;
|
||||
clone.transform.rotation = owner.rotation * Quaternion.Inverse(facing) * clone.transform.rotation;
|
||||
clone.transform.position += owner.position - pelvisT.position;
|
||||
|
||||
pelvis = MakeBone(pelvisT, null);
|
||||
head = MakeBone(headT, null);
|
||||
for (Transform s = headT.parent; s != null && s != pelvisT; s = s.parent) spine.Add(MakeBone(s, null));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
upperLeg[i] = MakeBone(legs[i][0], legs[i][1]);
|
||||
lowerLeg[i] = MakeBone(legs[i][1], legs[i][2]);
|
||||
foot[i] = MakeBone(legs[i][2], null);
|
||||
upperArm[i] = MakeBone(arms[i][0], arms[i][1]);
|
||||
lowerArm[i] = MakeBone(arms[i][1], arms[i][2]);
|
||||
}
|
||||
Vector3 hipCenter = (upperLeg[0].RestPos + upperLeg[1].RestPos) * 0.5f;
|
||||
pelvisOffset = pelvis.RestPos - hipCenter;
|
||||
|
||||
smr.updateWhenOffscreen = true; // Knochen bewegen sich weit weg von der gespeicherten Hülle
|
||||
foreach (Renderer r in clone.GetComponentsInChildren<Renderer>(true)) r.enabled = true;
|
||||
clone.SetActive(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gliedmaße als [Anfang oberes Stück, Gelenk (Knie/Ellbogen), Ende (Knöchel/Handgelenk)]: Kette vom Rumpf bis zum
|
||||
/// Ende (über end hinaus entlang des längsten Kindes), dann die zwei längsten aufeinanderfolgenden Stücke.
|
||||
/// </summary>
|
||||
static Transform[] Limb(Transform end, HashSet<Transform> trunk)
|
||||
{
|
||||
var chain = new List<Transform>();
|
||||
for (Transform t = end; t != null && !trunk.Contains(t); t = t.parent) chain.Insert(0, t);
|
||||
for (Transform t = end; t.childCount > 0; )
|
||||
{
|
||||
Transform next = null;
|
||||
float best = -1f;
|
||||
foreach (Transform c in t)
|
||||
{
|
||||
float d = (c.position - t.position).sqrMagnitude;
|
||||
if (d > best) { best = d; next = c; }
|
||||
}
|
||||
chain.Add(next);
|
||||
t = next;
|
||||
}
|
||||
if (chain.Count < 3) return null;
|
||||
int bestK = -1;
|
||||
float bestLen = 0f;
|
||||
for (int k = 0; k + 2 < chain.Count; k++)
|
||||
{
|
||||
float len = Mathf.Min((chain[k + 1].position - chain[k].position).magnitude, (chain[k + 2].position - chain[k + 1].position).magnitude);
|
||||
if (len > bestLen) { bestLen = len; bestK = k; }
|
||||
}
|
||||
return bestK < 0 ? null : new[] { chain[bestK], chain[bestK + 1], chain[bestK + 2] };
|
||||
}
|
||||
|
||||
static int Depth(Transform t)
|
||||
{
|
||||
int d = 0;
|
||||
for (; t != null; t = t.parent) d++;
|
||||
return d;
|
||||
}
|
||||
|
||||
Bone MakeBone(Transform t, Transform child)
|
||||
{
|
||||
var b = new Bone
|
||||
{
|
||||
T = t,
|
||||
RestRot = Quaternion.Inverse(owner.rotation) * t.rotation,
|
||||
RestPos = owner.InverseTransformPoint(t.position)
|
||||
};
|
||||
if (child != null) b.RestDir = owner.InverseTransformPoint(child.position) - b.RestPos;
|
||||
return b;
|
||||
}
|
||||
|
||||
/// <summary>Alles außer Aussehen entfernen (Skripte, Physik, Kollision, Ton, Animation).</summary>
|
||||
static void Strip(GameObject go)
|
||||
{
|
||||
foreach (Joint j in go.GetComponentsInChildren<Joint>(true)) UnityEngine.Object.DestroyImmediate(j);
|
||||
foreach (Component c in go.GetComponentsInChildren<Component>(true))
|
||||
{
|
||||
if (c == null || c is Transform || c is Renderer || c is MeshFilter) continue;
|
||||
UnityEngine.Object.DestroyImmediate(c);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- jedes Frame
|
||||
|
||||
public void Apply(Rig rig)
|
||||
{
|
||||
if (clone == null) return;
|
||||
Quaternion root = owner.rotation;
|
||||
Quaternion lean = Quaternion.FromToRotation(Vector3.up, rig.Up);
|
||||
|
||||
pelvis.T.position = owner.TransformPoint(rig.Hip + lean * pelvisOffset);
|
||||
pelvis.T.rotation = root * lean * pelvis.RestRot;
|
||||
for (int i = spine.Count - 1; i >= 0; i--) spine[i].T.rotation = root * lean * spine[i].RestRot; // von unten nach oben
|
||||
head.T.rotation = root * Quaternion.Euler(rig.Pitch, 0f, 0f) * head.RestRot;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Aim(upperLeg[i], rig.Knee[i] - rig.HipJoint[i], root);
|
||||
Aim(lowerLeg[i], rig.Ankle[i] - rig.Knee[i], root);
|
||||
foot[i].T.rotation = root * foot[i].RestRot; // Füße bleiben flach
|
||||
Aim(upperArm[i], rig.Elbow[i] - rig.Shoulder[i], root);
|
||||
Aim(lowerArm[i], rig.Hand[i] - rig.Elbow[i], root);
|
||||
}
|
||||
}
|
||||
|
||||
static void Aim(Bone b, Vector3 desired, Quaternion root)
|
||||
{
|
||||
if (desired.sqrMagnitude < 1e-6f || b.RestDir.sqrMagnitude < 1e-6f) return;
|
||||
b.T.rotation = root * Quaternion.FromToRotation(b.RestDir, desired) * b.RestRot;
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (clone != null) UnityEngine.Object.Destroy(clone);
|
||||
clone = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,7 @@
|
||||
<Compile Include="NetSession.cs" />
|
||||
<Compile Include="ObjectSync.cs" />
|
||||
<Compile Include="Passenger.cs" />
|
||||
<Compile Include="GameCharacter.cs" />
|
||||
<Compile Include="SaveSync.cs" />
|
||||
<Compile Include="ServerBrowser.cs" />
|
||||
<Compile Include="SteamLobby.cs" />
|
||||
|
||||
@@ -211,6 +211,12 @@ namespace MSCO
|
||||
return lines;
|
||||
}
|
||||
|
||||
/// <summary>Konsole "model": Figuren neu bauen (kommen mit dem nächsten Spielerzustand wieder).</summary>
|
||||
public void RebuildAvatars()
|
||||
{
|
||||
foreach (PlayerInfo p in players.Values) DestroyAvatar(p);
|
||||
}
|
||||
|
||||
/// <summary>Konsole "vars <filter>": synchronisierte Fahrzeugwerte (Diagnose).</summary>
|
||||
public List<string> DescribeVehicleValues(string filter) { return vehicleState.Describe(filter, 60); }
|
||||
|
||||
@@ -2319,9 +2325,9 @@ namespace MSCO
|
||||
if (!players.TryGetValue(from, out p)) return;
|
||||
if (p.Avatar == null)
|
||||
{
|
||||
// Platzhalter-Figur: Kapsel mit Kopf und Hand
|
||||
// Echter MSC-Mensch (oder die eigene MSCO-Figur, wenn keiner passt / "model classic")
|
||||
p.Avatar = RemotePlayer.Create("MSCO_Player_" + from, PlayerColor(from), state, from);
|
||||
MscoLog.Info("[MSCO] " + p.Name + " is in the game");
|
||||
MscoLog.Info("[MSCO] " + p.Name + " is in the game (" + p.Avatar.ModelDescription + ")");
|
||||
}
|
||||
p.Avatar.SetTarget(state);
|
||||
}
|
||||
|
||||
+35
-1
@@ -62,7 +62,18 @@ namespace MSCO
|
||||
public int HeldId { get { return last.HeldId; } }
|
||||
public PlayerState LastState { get { return last; } }
|
||||
public Vector3 HeadPosition { get { return headPivot.position; } }
|
||||
public string ModelDescription { get { return "MSCO figure, variant " + variant; } }
|
||||
public string ModelDescription { get { return model != null ? model.Description : "MSCO figure, variant " + variant; } }
|
||||
|
||||
/// <summary>Mitspieler als echte MSC-Menschen zeigen (Konsole "model game|classic", gespeichert).</summary>
|
||||
public static bool UseGameModels
|
||||
{
|
||||
get { return PlayerPrefs.GetInt("MSCO_GameModels", 1) == 1; }
|
||||
set { PlayerPrefs.SetInt("MSCO_GameModels", value ? 1 : 0); PlayerPrefs.Save(); }
|
||||
}
|
||||
|
||||
// Echte Spielfigur (null = eigene MSCO-Figur sichtbar); die MSCO-Figur liefert dann nur noch die Gelenkpunkte
|
||||
GameCharacter model;
|
||||
readonly GameCharacter.Rig rig = new GameCharacter.Rig();
|
||||
|
||||
// ---------------------------------------------------------------- Aufbau
|
||||
|
||||
@@ -73,6 +84,7 @@ namespace MSCO
|
||||
rp.jacket = jacketColor;
|
||||
rp.variant = Mathf.Abs(variant);
|
||||
rp.Build();
|
||||
if (UseGameModels) rp.UseGameModel();
|
||||
rp.SetTarget(state);
|
||||
root.transform.position = rp.targetFeet;
|
||||
root.transform.rotation = Quaternion.Euler(0f, rp.targetYaw, 0f);
|
||||
@@ -180,6 +192,16 @@ namespace MSCO
|
||||
view.SetParent(r, false);
|
||||
}
|
||||
|
||||
void UseGameModel()
|
||||
{
|
||||
model = GameCharacter.Create(transform, variant);
|
||||
if (model == null) return;
|
||||
// Eigene Figur unsichtbar (sie bewegt sich weiter und gibt die Gelenke vor)
|
||||
Transform modelRoot = transform.Find("MSCO_Model");
|
||||
foreach (Renderer r in GetComponentsInChildren<Renderer>(true))
|
||||
if (modelRoot == null || !r.transform.IsChildOf(modelRoot)) r.enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>Kleines Karomuster (Flanellhemd) in der Grundfarbe; wird auf die Kapseln gekachelt.</summary>
|
||||
static Texture2D Plaid(Color baseColor)
|
||||
{
|
||||
@@ -379,6 +401,11 @@ namespace MSCO
|
||||
// Kopf
|
||||
headPivot.localPosition = head;
|
||||
headPivot.localRotation = Quaternion.Euler(pitch, 0f, 0f);
|
||||
rig.Hip = hip;
|
||||
rig.NeckBase = neckBase;
|
||||
rig.Head = head;
|
||||
rig.Up = up;
|
||||
rig.Pitch = pitch;
|
||||
|
||||
float swing = Mathf.Sin(walkPhase) * walkAmount * (1f - airAmount);
|
||||
for (int i = 0; i < 2; i++)
|
||||
@@ -400,6 +427,9 @@ namespace MSCO
|
||||
}
|
||||
Vector3 knee = Joint(hipJoint, ankle, ThighLength, ShinLength, Vector3.forward);
|
||||
Segment(thigh[i], hipJoint, knee, 0.15f, 0.16f);
|
||||
rig.HipJoint[i] = hipJoint;
|
||||
rig.Knee[i] = knee;
|
||||
rig.Ankle[i] = ankle;
|
||||
Segment(shin[i], knee, ankle, 0.12f, 0.13f);
|
||||
// Stiefel: Kapsel von der Ferse zur Spitze, darunter eine flache Sohle
|
||||
Segment(boot[i], ankle + new Vector3(0f, -0.025f, -0.06f), ankle + new Vector3(0f, -0.03f, 0.14f), 0.12f, 0.1f);
|
||||
@@ -427,12 +457,16 @@ namespace MSCO
|
||||
|
||||
Vector3 elbow = Joint(shoulder, handTarget, UpperArmLength, ForearmLength, new Vector3(side * 0.4f, -0.2f, -1f));
|
||||
Segment(upperArm[i], shoulder, elbow, 0.11f, 0.11f);
|
||||
rig.Shoulder[i] = shoulder;
|
||||
rig.Elbow[i] = elbow;
|
||||
rig.Hand[i] = handTarget;
|
||||
Segment(forearm[i], elbow, handTarget, 0.095f, 0.095f);
|
||||
Segment(cuff[i], Vector3.Lerp(elbow, handTarget, 0.82f), Vector3.Lerp(elbow, handTarget, 0.9f), 0.105f, 0.105f);
|
||||
hand[i].localPosition = handTarget;
|
||||
hand[i].localRotation = Quaternion.LookRotation(Vector3.forward, (handTarget - elbow).sqrMagnitude > 0f ? elbow - handTarget : Vector3.up);
|
||||
hand[i].localScale = new Vector3(0.085f, 0.125f, 0.11f);
|
||||
}
|
||||
if (model != null) model.Apply(rig);
|
||||
}
|
||||
|
||||
/// <summary>Zwei-Knochen-Gelenk (Knie/Ellbogen): Punkt zwischen a und b, zur Seite "bend" geknickt.</summary>
|
||||
|
||||
Reference in New Issue
Block a user