Own MSC-style player figure instead of cloned NPCs; stop objects flung away by physics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+142
-60
@@ -3,10 +3,11 @@ using UnityEngine;
|
||||
namespace MSCO
|
||||
{
|
||||
/// <summary>
|
||||
/// Figur eines Mitspielers mit echter Körperhaltung. Aus Kopfposition (Kamera) und Bodenhöhe wird
|
||||
/// jedes Frame die Pose berechnet: Ducken beugt die Knie, nach vorne Lehnen neigt den Oberkörper,
|
||||
/// Laufen bewegt Beine und Arme, beim Halten greift der rechte Arm (mit Ellbogen) zum Item, im Auto
|
||||
/// sitzt die Figur. Die Körperteile sind abgerundete Kapseln zwischen den Gelenken.
|
||||
/// Figur eines Mitspielers im Stil der My-Summer-Car-Leute (selbst gebaut, keine NPC-Kopie):
|
||||
/// Karohemd/Arbeitsjacke, Jeans, Arbeitsschuhe, Schnurrbart, Kappe oder Haare, leichter Bauch.
|
||||
/// Aus Kopfposition (Kamera) und Bodenhöhe wird jedes Frame die Pose berechnet: Ducken beugt die Knie,
|
||||
/// nach vorne Lehnen neigt den Oberkörper, Laufen bewegt Beine und Arme, beim Halten greift der rechte Arm
|
||||
/// zum Item, im Auto sitzt die Figur.
|
||||
/// </summary>
|
||||
public class RemotePlayer : MonoBehaviour
|
||||
{
|
||||
@@ -21,16 +22,24 @@ namespace MSCO
|
||||
const float HipWidth = 0.1f, ShoulderWidth = 0.21f;
|
||||
const float AnkleHeight = 0.07f;
|
||||
|
||||
static readonly Color Skin = new Color(0.93f, 0.76f, 0.62f);
|
||||
static readonly Color Jeans = new Color(0.18f, 0.25f, 0.42f);
|
||||
static readonly Color Boots = new Color(0.24f, 0.16f, 0.1f);
|
||||
static readonly Color Skin = new Color(0.86f, 0.66f, 0.52f);
|
||||
static readonly Color Jeans = new Color(0.2f, 0.26f, 0.38f);
|
||||
static readonly Color Boots = new Color(0.22f, 0.15f, 0.09f);
|
||||
static readonly Color Sole = new Color(0.07f, 0.06f, 0.05f);
|
||||
static readonly Color Dark = new Color(0.08f, 0.08f, 0.08f);
|
||||
static readonly Color Metal = new Color(0.62f, 0.62f, 0.6f);
|
||||
static readonly Color[] HairColors =
|
||||
{
|
||||
new Color(0.23f, 0.15f, 0.09f), new Color(0.45f, 0.33f, 0.2f), new Color(0.12f, 0.1f, 0.08f), new Color(0.55f, 0.5f, 0.45f)
|
||||
};
|
||||
|
||||
Color jacket;
|
||||
int variant;
|
||||
Transform headPivot, view;
|
||||
Transform pelvis, torso, belt, neck;
|
||||
Transform pelvis, torso, belt, neck, belly, collar, placket, buckle;
|
||||
readonly Transform[] thigh = new Transform[2], shin = new Transform[2], boot = new Transform[2];
|
||||
readonly Transform[] upperArm = new Transform[2], forearm = new Transform[2], hand = new Transform[2];
|
||||
readonly Transform[] sole = new Transform[2], cuff = new Transform[2];
|
||||
|
||||
Vector3 targetFeet;
|
||||
Vector3 targetHead;
|
||||
@@ -42,13 +51,10 @@ namespace MSCO
|
||||
Vector3 lastPosition;
|
||||
float walkPhase, walkAmount, speed;
|
||||
|
||||
// Echtes Spielmodell (NPC aus der Szene); null = eigene Figur als Rückfall
|
||||
GameCharacter model;
|
||||
|
||||
public int HeldId { get { return last.HeldId; } }
|
||||
public PlayerState LastState { get { return last; } }
|
||||
public Vector3 HeadPosition { get { return model != null ? model.HeadPosition : headPivot.position; } }
|
||||
public string ModelDescription { get { return model != null ? "game model: " + model.Description : "fallback figure (no NPC template found)"; } }
|
||||
public Vector3 HeadPosition { get { return headPivot.position; } }
|
||||
public string ModelDescription { get { return "MSCO figure, variant " + variant; } }
|
||||
|
||||
// ---------------------------------------------------------------- Aufbau
|
||||
|
||||
@@ -57,43 +63,48 @@ namespace MSCO
|
||||
var root = new GameObject(name);
|
||||
var rp = root.AddComponent<RemotePlayer>();
|
||||
rp.jacket = jacketColor;
|
||||
rp.variant = Mathf.Abs(variant);
|
||||
rp.Build();
|
||||
rp.SetTarget(state);
|
||||
root.transform.position = rp.targetFeet;
|
||||
root.transform.rotation = Quaternion.Euler(0f, rp.targetYaw, 0f);
|
||||
|
||||
// Wenn möglich einen echten NPC aus dem Spiel als Figur nehmen, sonst die eigene Figur
|
||||
try { rp.model = GameCharacter.TryCreate(root.transform, variant); }
|
||||
catch (System.Exception e) { MSCLoader.ModConsole.Warning("[MSCO] Game model failed: " + e.Message); }
|
||||
if (rp.model != null)
|
||||
foreach (Renderer r in root.GetComponentsInChildren<Renderer>(true))
|
||||
if (!r.transform.IsChildOf(rp.model.Root)) r.enabled = false;
|
||||
rp.headWorld = rp.targetHead;
|
||||
rp.pitch = rp.targetPitch;
|
||||
rp.view.rotation = rp.targetViewRot;
|
||||
rp.lastPosition = root.transform.position;
|
||||
if (rp.model == null) rp.Pose(0f);
|
||||
rp.Pose(0f);
|
||||
return rp;
|
||||
}
|
||||
|
||||
void Build()
|
||||
{
|
||||
Transform r = transform;
|
||||
Color jacketDark = jacket * 0.75f;
|
||||
Color jacketDark = jacket * 0.7f;
|
||||
jacketDark.a = 1f;
|
||||
Color hairColor = HairColors[variant % HairColors.Length];
|
||||
bool wearsCap = variant % 3 != 2;
|
||||
bool sunglasses = variant % 4 == 1;
|
||||
Texture2D plaid = Plaid(jacket);
|
||||
|
||||
// Körper: Karohemd/Jacke, Jeans, Gürtel, leichter Bauch (wie die Leute in MSC)
|
||||
pelvis = Capsule("Pelvis", r, Jeans);
|
||||
torso = Capsule("Torso", r, jacket);
|
||||
belt = Capsule("Belt", r, Dark);
|
||||
torso = Textured(Capsule("Torso", r, Color.white), plaid, new Vector2(2f, 3f));
|
||||
belly = Textured(Sphere("Belly", r, Color.white, 1f), plaid, new Vector2(2f, 2f));
|
||||
belt = Capsule("Belt", r, new Color(0.2f, 0.13f, 0.08f));
|
||||
buckle = Box("Buckle", r, Metal);
|
||||
placket = Box("Buttons", r, jacketDark);
|
||||
collar = Capsule("Collar", r, jacketDark);
|
||||
neck = Capsule("Neck", r, Skin);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
thigh[i] = Capsule("Thigh", r, Jeans);
|
||||
shin[i] = Capsule("Shin", r, Jeans);
|
||||
shin[i] = Capsule("Shin", r, Jeans * 0.92f);
|
||||
boot[i] = Box("Boot", r, Boots);
|
||||
upperArm[i] = Capsule("UpperArm", r, jacket);
|
||||
forearm[i] = Capsule("Forearm", r, jacketDark);
|
||||
sole[i] = Box("Sole", r, Sole);
|
||||
upperArm[i] = Textured(Capsule("UpperArm", r, Color.white), plaid, new Vector2(1f, 2f));
|
||||
forearm[i] = Textured(Capsule("Forearm", r, Color.white), plaid, new Vector2(1f, 2f));
|
||||
cuff[i] = Capsule("Cuff", r, jacketDark);
|
||||
hand[i] = Sphere("Hand", r, Skin, 0.1f);
|
||||
}
|
||||
|
||||
@@ -101,31 +112,96 @@ namespace MSCO
|
||||
headPivot = new GameObject("Head").transform;
|
||||
headPivot.SetParent(r, false);
|
||||
Transform skull = Sphere("Skull", headPivot, Skin, 0.24f);
|
||||
skull.localScale = new Vector3(0.22f, 0.26f, 0.24f);
|
||||
Transform hair = Sphere("Hair", headPivot, new Color(0.25f, 0.17f, 0.1f), 0.24f);
|
||||
hair.localPosition = new Vector3(0f, 0.02f, -0.025f);
|
||||
hair.localScale = new Vector3(0.23f, 0.25f, 0.23f);
|
||||
Transform cap = Sphere("Cap", headPivot, jacketDark, 0.25f);
|
||||
cap.localPosition = new Vector3(0f, 0.07f, -0.005f);
|
||||
cap.localScale = new Vector3(0.245f, 0.16f, 0.255f);
|
||||
Transform brim = Box("CapBrim", headPivot, jacketDark);
|
||||
brim.localPosition = new Vector3(0f, 0.075f, 0.13f);
|
||||
brim.localScale = new Vector3(0.2f, 0.02f, 0.11f);
|
||||
brim.localRotation = Quaternion.Euler(-8f, 0f, 0f);
|
||||
skull.localScale = new Vector3(0.21f, 0.255f, 0.235f);
|
||||
Transform jaw = Sphere("Jaw", headPivot, Skin, 0.2f);
|
||||
jaw.localPosition = new Vector3(0f, -0.065f, 0.02f);
|
||||
jaw.localScale = new Vector3(0.18f, 0.13f, 0.19f);
|
||||
Transform hair = Sphere("Hair", headPivot, hairColor, 0.24f);
|
||||
hair.localPosition = new Vector3(0f, 0.025f, -0.03f);
|
||||
hair.localScale = new Vector3(0.22f, 0.24f, 0.22f);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Transform eye = Sphere("Eye", headPivot, Dark, 0.035f);
|
||||
eye.localPosition = new Vector3(i == 0 ? -0.045f : 0.045f, 0.015f, 0.105f);
|
||||
float side = i == 0 ? -1f : 1f;
|
||||
Transform ear = Sphere("Ear", headPivot, Skin * 0.96f, 0.05f);
|
||||
ear.localPosition = new Vector3(side * 0.105f, -0.005f, 0f);
|
||||
ear.localScale = new Vector3(0.035f, 0.065f, 0.05f);
|
||||
Transform eye = Sphere("Eye", headPivot, Dark, 0.028f);
|
||||
eye.localPosition = new Vector3(side * 0.042f, 0.018f, 0.105f);
|
||||
Transform brow = Box("Brow", headPivot, hairColor);
|
||||
brow.localPosition = new Vector3(side * 0.043f, 0.045f, 0.108f);
|
||||
brow.localScale = new Vector3(0.045f, 0.012f, 0.012f);
|
||||
brow.localRotation = Quaternion.Euler(0f, 0f, side * -8f);
|
||||
}
|
||||
Transform nose = Box("Nose", headPivot, Skin * 0.93f);
|
||||
nose.localPosition = new Vector3(0f, -0.012f, 0.12f);
|
||||
nose.localScale = new Vector3(0.035f, 0.055f, 0.045f);
|
||||
// Der typische Schnurrbart
|
||||
Transform mustache = Box("Mustache", headPivot, hairColor);
|
||||
mustache.localPosition = new Vector3(0f, -0.052f, 0.113f);
|
||||
mustache.localScale = new Vector3(0.085f, 0.024f, 0.03f);
|
||||
Transform mouth = Box("Mouth", headPivot, new Color(0.45f, 0.25f, 0.22f));
|
||||
mouth.localPosition = new Vector3(0f, -0.078f, 0.108f);
|
||||
mouth.localScale = new Vector3(0.05f, 0.01f, 0.01f);
|
||||
|
||||
if (sunglasses)
|
||||
{
|
||||
Transform glasses = Box("Sunglasses", headPivot, Dark);
|
||||
glasses.localPosition = new Vector3(0f, 0.018f, 0.113f);
|
||||
glasses.localScale = new Vector3(0.14f, 0.035f, 0.012f);
|
||||
}
|
||||
if (wearsCap)
|
||||
{
|
||||
// Kappe ("lippis") in der Jackenfarbe
|
||||
Transform cap = Sphere("Cap", headPivot, jacketDark, 0.25f);
|
||||
cap.localPosition = new Vector3(0f, 0.075f, -0.005f);
|
||||
cap.localScale = new Vector3(0.235f, 0.15f, 0.245f);
|
||||
Transform brim = Box("CapBrim", headPivot, jacketDark);
|
||||
brim.localPosition = new Vector3(0f, 0.078f, 0.13f);
|
||||
brim.localScale = new Vector3(0.19f, 0.018f, 0.11f);
|
||||
brim.localRotation = Quaternion.Euler(-10f, 0f, 0f);
|
||||
Transform patch = Box("CapPatch", headPivot, new Color(0.9f, 0.88f, 0.8f));
|
||||
patch.localPosition = new Vector3(0f, 0.105f, 0.112f);
|
||||
patch.localScale = new Vector3(0.06f, 0.035f, 0.01f);
|
||||
patch.localRotation = Quaternion.Euler(-25f, 0f, 0f);
|
||||
}
|
||||
Transform nose = Box("Nose", headPivot, Skin * 0.95f);
|
||||
nose.localPosition = new Vector3(0f, -0.02f, 0.12f);
|
||||
nose.localScale = new Vector3(0.035f, 0.05f, 0.04f);
|
||||
|
||||
// Unsichtbarer "Blick" mit der exakten Kameradrehung - daran hängt das gehaltene Item
|
||||
view = new GameObject("View").transform;
|
||||
view.SetParent(r, false);
|
||||
}
|
||||
|
||||
/// <summary>Kleines Karomuster (Flanellhemd) in der Grundfarbe; wird auf die Kapseln gekachelt.</summary>
|
||||
static Texture2D Plaid(Color baseColor)
|
||||
{
|
||||
const int size = 16;
|
||||
var tex = new Texture2D(size, size, TextureFormat.RGB24, false);
|
||||
Color light = Color.Lerp(baseColor, Color.white, 0.18f);
|
||||
Color dark = baseColor * 0.62f;
|
||||
for (int y = 0; y < size; y++)
|
||||
{
|
||||
for (int x = 0; x < size; x++)
|
||||
{
|
||||
bool bandX = (x / 4) % 2 == 0, bandY = (y / 4) % 2 == 0;
|
||||
Color c = bandX && bandY ? dark : bandX || bandY ? baseColor : light;
|
||||
if (x % 8 == 1 || y % 8 == 1) c = Color.Lerp(c, Color.black, 0.45f); // dünne dunkle Linien
|
||||
c.a = 1f;
|
||||
tex.SetPixel(x, y, c);
|
||||
}
|
||||
}
|
||||
tex.filterMode = FilterMode.Point;
|
||||
tex.wrapMode = TextureWrapMode.Repeat;
|
||||
tex.Apply();
|
||||
return tex;
|
||||
}
|
||||
|
||||
static Transform Textured(Transform part, Texture2D tex, Vector2 tiling)
|
||||
{
|
||||
Material m = part.GetComponent<Renderer>().material;
|
||||
m.mainTexture = tex;
|
||||
m.mainTextureScale = tiling;
|
||||
return part;
|
||||
}
|
||||
|
||||
static Transform Capsule(string name, Transform parent, Color color) { return Primitive(PrimitiveType.Capsule, name, parent, color); }
|
||||
|
||||
static Transform Box(string name, Transform parent, Color color) { return Primitive(PrimitiveType.Cube, name, parent, color); }
|
||||
@@ -186,18 +262,7 @@ namespace MSCO
|
||||
view.rotation = Quaternion.Slerp(view.rotation, targetViewRot, t);
|
||||
|
||||
UpdateWalk(dt);
|
||||
if (model != null) model.Animate(speed); // Spiel-Animation (Idle/Walk) läuft vor LateUpdate
|
||||
else Pose(dt);
|
||||
}
|
||||
|
||||
/// <summary>Spielmodell: nach der Animation Ducken/Lehnen/Kopf/Arme anpassen.</summary>
|
||||
void LateUpdate()
|
||||
{
|
||||
if (model == null) return;
|
||||
Vector3 heldPos;
|
||||
Quaternion heldRot;
|
||||
bool holding = TryGetHeldPose(out heldPos, out heldRot);
|
||||
model.Pose(transform, headWorld, pitch, last.Seated, holding, heldPos);
|
||||
Pose(dt);
|
||||
}
|
||||
|
||||
void UpdateWalk(float dt)
|
||||
@@ -240,10 +305,21 @@ namespace MSCO
|
||||
Vector3 forward = Vector3.Cross(right, up).normalized;
|
||||
|
||||
// Rumpf
|
||||
Segment(torso, hip + up * 0.08f, neckBase, 0.38f, 0.24f);
|
||||
Segment(belt, hip + up * 0.06f, hip + up * 0.1f, 0.37f, 0.23f);
|
||||
Segment(pelvis, hip - right * HipWidth, hip + right * HipWidth, 0.2f, 0.22f);
|
||||
Segment(neck, neckBase, neckTop, 0.09f, 0.09f);
|
||||
Quaternion torsoRot = Quaternion.LookRotation(forward, up);
|
||||
Segment(torso, hip + up * 0.08f, neckBase, 0.4f, 0.25f);
|
||||
belly.localPosition = hip + up * 0.2f + forward * 0.06f;
|
||||
belly.localRotation = torsoRot;
|
||||
belly.localScale = new Vector3(0.34f, 0.28f, 0.27f);
|
||||
Segment(belt, hip + up * 0.05f, hip + up * 0.1f, 0.38f, 0.26f);
|
||||
buckle.localPosition = hip + up * 0.075f + forward * 0.13f;
|
||||
buckle.localRotation = torsoRot;
|
||||
buckle.localScale = new Vector3(0.06f, 0.045f, 0.015f);
|
||||
placket.localPosition = Vector3.Lerp(hip + up * 0.1f, neckBase, 0.62f) + forward * 0.122f;
|
||||
placket.localRotation = torsoRot;
|
||||
placket.localScale = new Vector3(0.025f, 0.26f, 0.01f);
|
||||
Segment(pelvis, hip - right * HipWidth, hip + right * HipWidth, 0.22f, 0.24f);
|
||||
Segment(collar, neckBase - up * 0.02f, neckBase + up * 0.035f, 0.17f, 0.15f);
|
||||
Segment(neck, neckBase, neckTop, 0.1f, 0.1f);
|
||||
|
||||
// Kopf
|
||||
headPivot.localPosition = head;
|
||||
@@ -265,9 +341,12 @@ namespace MSCO
|
||||
Vector3 knee = Joint(hipJoint, ankle, ThighLength, ShinLength, Vector3.forward);
|
||||
Segment(thigh[i], hipJoint, knee, 0.17f, 0.17f);
|
||||
Segment(shin[i], knee, ankle, 0.14f, 0.14f);
|
||||
boot[i].localPosition = ankle + new Vector3(0f, -0.02f, 0.05f);
|
||||
boot[i].localPosition = ankle + new Vector3(0f, -0.015f, 0.05f);
|
||||
boot[i].localRotation = Quaternion.identity;
|
||||
boot[i].localScale = new Vector3(0.12f, 0.1f, 0.27f);
|
||||
sole[i].localPosition = ankle + new Vector3(0f, -0.065f, 0.05f);
|
||||
sole[i].localRotation = Quaternion.identity;
|
||||
sole[i].localScale = new Vector3(0.13f, 0.025f, 0.29f);
|
||||
|
||||
// Arme
|
||||
Vector3 shoulder = neckBase - up * 0.06f + right * (ShoulderWidth * side);
|
||||
@@ -289,7 +368,10 @@ namespace MSCO
|
||||
Vector3 elbow = Joint(shoulder, handTarget, UpperArmLength, ForearmLength, new Vector3(side * 0.4f, -0.2f, -1f));
|
||||
Segment(upperArm[i], shoulder, elbow, 0.12f, 0.12f);
|
||||
Segment(forearm[i], elbow, handTarget, 0.11f, 0.11f);
|
||||
Vector3 wrist = Vector3.Lerp(elbow, handTarget, 0.86f);
|
||||
Segment(cuff[i], wrist, Vector3.Lerp(elbow, handTarget, 0.94f), 0.12f, 0.12f);
|
||||
hand[i].localPosition = handTarget;
|
||||
hand[i].localScale = new Vector3(0.085f, 0.1f, 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user