POLYQUE GAMES
NO TRUE COLOR · SİLAH DOSYASI

Slot Revolver.Rus ruleti × slot makinesi.

No True Color’da tek silah bu. Boşken tetiğe basınca silindirdeki üç makara döner. Üçü aynı sembolde durursa tek bir mermi dolar; durmazsa yeniden çevirirsin. Bu sayfa silahın nasıl yapıldığını anlatıyor: kural, model, hareket, ses ve kod.

%50 üçlü şansı1 mermi1,32 sn sonuç3 makara · 3 sembol

Kitte model (GLB, Blender, OBJ), 8 ses, kodlar, oyun videosu ve hazır prompt'lar var · 6,7 MB

SlotRevolver.glb · 2.459 köşe · 7 malzeme Sürükle: döndür · Kaydır: yakınlaştır · Tetik: makaralar döner
01 / MEKANİK

Tek tetik, iki iş

Aşağıdaki demo oyundaki kuralla aynı çalışır. Sesler de oyundan.

Kural

Çevir. Tuttur. Vur.

1
Boşken tetik makaraları çevirirMakaralar soldan sağa 0,70, 0,95 ve 1,20. saniyede durur.
2
Üçü aynıysa 1 mermi dolarŞans %50. Kaybedenlerin %60’ı kıl payı kaçar: ilk ikisi tutar, son makara kaçar.
3
Dolu tetik ateş ederSahtekârı vurursan yakalanır. Masumu vurursan o oyuncu 1 kalp kaybeder ve su %25 yükselir.

Sonuç çevirme anında belirlenir; makaralar o sembolde durdurulur. Sahtekâr da aynı silahı kullanır ama 60 saniyede bir ateş edebilir.

Slot revolverin yandan görünümü
BOŞ · TETİĞE BASMERMİ
02 / ZAMANLAMA

Bir çevirmenin 1,8 saniyesi

Sesler, makaralar ve silahın duruşu aynı saate bağlı.

Makaralar dönüyorSilah kalkık (çevirme duruşu)Mermi doluyor
03 / OYUNDAN

Gerçek oyun kaydı

Yeşil renk körü oyuncunun gözünden: çevir, doldur, sahtekârı vur.

04 / MODEL

Kartondan, koddan

Model bir 3B programda çizilmedi. Unity’nin hazır kutu, silindir ve küre şekillerinden kodla kuruluyor.

Modelin yandan çizimiModelin üç çeyrek açıdan çizimi
GLB düğümleri · birim metre · +Y yukarı · namlu +Z
DüğümNe işe yararBoyut (m)
FrameGövde: arka çerçeve, üst ve alt kayış, namlu, nişangâh, horoz, kabza, tetik ve korkuluk, makara ara diskleri0,086 × 0,248 × 0,425
Reel0 · Reel1 · Reel2Altıgen makaralar; pivot makara ekseninde, yerel Z ekseni etrafında döner. 60°’de bir yüz: yıldız, kalp, şimşekø 0,076 × 0,036
Bullet0–2Namlunun yanındaki pirinç mermi göstergesi; dolu mermi kadar görünür0,017 × 0,017 × 0,032
FirePointNamlu ucu: atış ışını ve yıldız parlaması buradan çıkarz = 0,29
Dokular · hepsi kodla üretilir
Açık oluklu karton dokusu, kalem karalamalı
card · oluklu karton + karalama
Koyu karton dokusu
dark · koyu karton
Sarı yıldız sembolü
sym0 · yıldız
Kırmızı kalp sembolü
sym1 · kalp
Mavi şimşek sembolü
sym2 · şimşek
05 / HAREKET

Tepme, duruş, parlama

Değerler referans videodan kare kare ölçüldü (30 fps, 1 kare = 33 ms).

ÖğeDeğerNot
Tepme savrulması52° yukarı, kabza etrafındaNişan alırken 0,6 katı
Yayω = 24 rad/sn · ζ = 0,551 karede zirve, ~130 ms’de hafif aşarak döner
Çevirme duruşu: kalkış0,13 snBurun 20° yukarı, namlu 42° sola, 16° yatış
Çevirme duruşu: inişsonuçtan 0,4 sn sonra, 0,12 snDönerken ±0,6° titreme
Namlu parlaması0,11 sn, 3 kareBoy 1 → 0,72 → 0,38; dünyada asılı kalır
Kavramaweapon-body kemiğiKabza merkezi eski tabancanın kabzasına oturur; ölçek 1,3 / 180
06 / SES

Dosyasız sesler

Atış dışındaki her ses oyun açılırken kodla sentezlenir ve oyunun diğer efektleriyle aynı seviyeye çekilir.

07 / KOD

Dört dosya

Unity 6 · C#. Kodlar oyunun kendi sınıflarına bağlı; başka projede referans olarak kullan ya da Claude’a uyarlat.

KidoSlotGun.cs

Durum makinesi: boş, dönüyor, doluyor, dolu. Sonuç seçimi, makara durma anları, mermi ve sesler.

KidoSlotRevolver.cs

Modeli şekillerden kurar, dokuları üretir, makaraları döndürüp hedef yüze oturtur, namlu parlamasını çizer.

SlotViewmodel_excerpt.cs

Birinci şahısta kemiğe bağlama, tepme yayı ve çevirme duruşu.

SlotSfx_excerpt.cs

Ses sentezi yardımcıları ve sekiz slot sesinin tarifleri.

using System;
using UnityEngine;

namespace Kidoroom.Coop
{
    /// <summary>
    /// Slot tabancası (rus ruleti + slot makinesi): silindir 3 makaradır. Silah boşken ateş tuşu (ya da R) makaraları
    /// çevirir; makaralar soldan sağa tek tek durur. Üçü aynı sembolde durursa silah <see cref="ShotsPerWin"/> mermiyle
    /// dolar (mermiler namlunun yanında tek tek belirir), değilse "boş" — tekrar çevrilir. Mermi bitince yeniden boş.
    /// Sonuç çevirme anında belirlenir, makaralar ona göre durdurulur (<see cref="Result"/>). Yalnız yerel oyuncu;
    /// sunucu atışı eskisi gibi doğrular (sahtekâr kilidi vb.).
    /// </summary>
    public static class KidoSlotGun
    {
        public enum Phase { Empty, Spinning, Loading, Loaded }

        public const int Reels = 3, Symbols = 3, ShotsPerWin = 1;   // kullanıcı: %50 şans, 1 mermi
        /// <summary>Üçlü gelme olasılığı (0–1). Rus ruleti hissi: yarı yarıya.</summary>
        public static float WinChance = 0.5f;

        /// <summary>Makaraların durma anları (çevirme başından saniye) ve mermi dolum aralığı.</summary>
        public static readonly float[] StopAt = { 0.7f, 0.95f, 1.2f };
        const float ResultAt = 1.32f, BulletInterval = 0.13f;

        public static Phase State { get; private set; } = Phase.Empty;
        public static int Ammo { get; private set; }
        public static float SpinStartedAt { get; private set; } = -10f;
        public static float LoadedAt { get; private set; } = -10f;
        public static float LastShotAt { get; private set; } = -10f;
        /// <summary>Son çevirmenin sonuçlandığı an (kazanç/kayıp) — silah bundan kısa süre sonra iner.</summary>
        public static float ResolvedAt { get; private set; } = -10f;
        public static bool LastWin { get; private set; }
        /// <summary>Bu çevirmenin sonucu (makara başına sembol 0..2).</summary>
        public static readonly int[] Result = new int[Reels];

        public static event Action SpinStarted, Won, Lost, Fired;
        public static event Action<int> ReelStopped, BulletLoaded;

        static int _stopped, _loaded;
        static float _nextTick;

        public static bool Spinning => State == Phase.Spinning;
        public static float SpinTime => Time.time - SpinStartedAt;
        /// <summary>Makara durdu mu (bu çevirmede).</summary>
        public static bool ReelDone(int reel) => State != Phase.Spinning || reel < _stopped;

        public static void Reset()
        {
            State = Phase.Empty; Ammo = 0; _stopped = 0; _loaded = 0;
        }

        /// <summary>Boşsa çevirmeyi başlat. Dolu, çeviriyor ya da doluyorken false.</summary>
        public static bool TryStartSpin()
        {
            if (State != Phase.Empty) return false;
            LastWin = UnityEngine.Random.value < WinChance;
            if (LastWin)
            {
                int s = UnityEngine.Random.Range(0, Symbols);
                for (int i = 0; i < Reels; i++) Result[i] = s;
            }
            else
            {
                // Kaybeden çoğu zaman "kıl payı": ilk ikisi aynı, sonuncusu farklı (heyecan son makarada)
                if (UnityEngine.Random.value < 0.6f)
                {
                    int s = UnityEngine.Random.Range(0, Symbols);
                    Result[0] = s; Result[1] = s; Result[2] = (s + UnityEngine.Random.Range(1, Symbols)) % Symbols;
                }
                else
                {
                    do { for (int i = 0; i < Reels; i++) Result[i] = UnityEngine.Random.Range(0, Symbols); }
                    while (Result[0] == Result[1] && Result[1] == Result[2]);
                }
            }
            State = Phase.Spinning; SpinStartedAt = Time.time; _stopped = 0; _loaded = 0; _nextTick = Time.time;
            KidoSfx.Play(KidoSfx.Cue.SlotPull, 0.8f);
            SpinStarted?.Invoke();
            return true;
        }

        /// <summary>Atış için bir mermi harca. Mermi yoksa false.</summary>
        public static bool Consume()
        {
            if (State != Phase.Loaded || Ammo <= 0) return false;
            Ammo--; LastShotAt = Time.time;
            if (Ammo <= 0) State = Phase.Empty;
            Fired?.Invoke();
            return true;
        }

        /// <summary>Her kare (KidoCombat): makara durdurma, sonuç ve mermi dolumu zamanlaması, dönme tıkırtısı.</summary>
        public static void Tick()
        {
            if (State == Phase.Spinning)
            {
                float t = SpinTime;
                if (_stopped < Reels && Time.time >= _nextTick) { _nextTick = Time.time + 0.065f; KidoSfx.Play(KidoSfx.Cue.ReelTick, 0.45f); }
                while (_stopped < Reels && t >= StopAt[_stopped])
                {
                    KidoSfx.Play(_stopped == 0 ? KidoSfx.Cue.ReelStop0 : _stopped == 1 ? KidoSfx.Cue.ReelStop1 : KidoSfx.Cue.ReelStop2, 0.85f);
                    ReelStopped?.Invoke(_stopped);
                    _stopped++;
                }
                if (t >= ResultAt)
                {
                    ResolvedAt = Time.time;
                    if (LastWin)
                    {
                        State = Phase.Loading; LoadedAt = Time.time; _loaded = 0;
                        KidoSfx.Play(KidoSfx.Cue.Jackpot, 0.8f);
                        Won?.Invoke();
                    }
                    else
                    {
                        State = Phase.Empty;
                        KidoSfx.Play(KidoSfx.Cue.SlotMiss, 0.7f);
                        CoopNoticeUI.Show(Loc.T("NO LUCK! SPIN AGAIN [LEFT CLICK]"));
                        Lost?.Invoke();
                    }
                }
            }
            else if (State == Phase.Loading)
            {
                while (_loaded < ShotsPerWin && Time.time - LoadedAt >= 0.15f + _loaded * BulletInterval)
                {
                    _loaded++; Ammo = _loaded;
                    KidoSfx.Play(KidoSfx.Cue.BulletLoad, 0.8f);
                    BulletLoaded?.Invoke(_loaded);
                }
                if (_loaded >= ShotsPerWin) State = Phase.Loaded;
            }
        }
    }
}
KidoSlotRevolver.cs model + makara animasyonu · 483 satır
using System.Collections.Generic;
using UnityEngine;

namespace Kidoroom.Coop
{
    /// <summary>
    /// Kartondan slot tabancası: kodla kurulan model (oluklu karton + keçeli kalem karalamaları), silindir yerine 3 altıgen
    /// makara (yıldız / kalp / şimşek — renk körlüğünde de şekliyle ayrılır), namlunun yanında 3 mermi göstergesi.
    /// Birinci şahısta canlı (makaralar döner, mermiler tek tek belirir), yerde ve uzak oyuncuların elinde tek statik mesh.
    /// Model ekseni: +Z namlu, +Y üst, köken kabzanın üstü (elin tuttuğu yer). Namlu alevi: dünyada asılı kalan "✕" yıldız.
    /// </summary>
    public sealed class KidoSlotRevolver : MonoBehaviour
    {
        // ------------------------------------------------------------ ölçüler (metre, gerçek bir altıpatlar boyu)
        const float ReelRadius = 0.038f, ReelWidth = 0.036f, ReelGap = 0.005f, AxisY = 0.058f;
        static readonly float[] ReelZ = { -0.004f, 0.037f, 0.078f };
        public static readonly Vector3 MuzzleLocal = new Vector3(0f, 0.062f, 0.29f);

        public Transform[] Reels = new Transform[3];
        public Transform[] Bullets = new Transform[3];
        public Transform Muzzle;
        public Camera ViewCamera;

        // ------------------------------------------------------------ kurulum
        /// <summary>Modeli kur. animated=true: birinci şahıs (makaralar KidoSlotGun'a bağlı döner).</summary>
        public static KidoSlotRevolver Build(Transform parent, bool animated)
        {
            var root = new GameObject("SlotRevolver").transform;
            if (parent != null) root.SetParent(parent, false);
            var gun = root.gameObject.AddComponent<KidoSlotRevolver>();
            gun.enabled = animated;
            var card = Mat("card"); var dark = Mat("dark"); var cream = Mat("cream"); var brass = Mat("brass");

            // gövde
            Box(root, "RearFrame", new Vector3(0, 0.052f, -0.048f), new Vector3(0.046f, 0.09f, 0.05f), 0, card);
            Box(root, "TopStrap", new Vector3(0, AxisY + ReelRadius + 0.009f, 0.037f), new Vector3(0.022f, 0.012f, 0.14f), 0, dark);
            Box(root, "BottomStrap", new Vector3(0, AxisY - ReelRadius - 0.008f, 0.037f), new Vector3(0.024f, 0.012f, 0.14f), 0, card);
            Box(root, "Barrel", new Vector3(0, 0.062f, 0.19f), new Vector3(0.04f, 0.046f, 0.17f), 0, card);
            Box(root, "BarrelRib", new Vector3(0, 0.091f, 0.19f), new Vector3(0.016f, 0.012f, 0.17f), 0, dark);
            Cyl(root, "MuzzleRing", new Vector3(0, 0.062f, 0.278f), new Vector3(0.05f, 0.007f, 0.05f), Quaternion.Euler(90, 0, 0), dark);
            Box(root, "FrontSight", new Vector3(0, 0.1f, 0.265f), new Vector3(0.006f, 0.014f, 0.012f), 0, dark);
            Box(root, "Hammer", new Vector3(0, 0.106f, -0.064f), new Vector3(0.014f, 0.034f, 0.022f), -28, dark);
            // kabza (alt ucu geriye yatık) + topuz
            Box(root, "Grip", new Vector3(0, -0.035f, -0.078f), new Vector3(0.042f, 0.135f, 0.066f), 20, card);
            Box(root, "GripCap", new Vector3(0, -0.1f, -0.103f), new Vector3(0.046f, 0.02f, 0.072f), 20, dark);
            // tetik korkuluğu + tetik
            Box(root, "GuardBottom", new Vector3(0, -0.012f, 0f), new Vector3(0.008f, 0.008f, 0.052f), 0, dark);
            Box(root, "GuardFront", new Vector3(0, 0.002f, 0.024f), new Vector3(0.008f, 0.03f, 0.008f), 0, dark);
            Box(root, "Trigger", new Vector3(0, 0.006f, -0.008f), new Vector3(0.006f, 0.024f, 0.008f), -15, dark);

            // 3 makara (Z ekseni etrafında döner) + aralarında karton diskler
            for (int i = 0; i < 3; i++)
            {
                var reel = new GameObject("Reel" + i).transform;
                reel.SetParent(root, false); reel.localPosition = new Vector3(0, AxisY, ReelZ[i]);
                var body = new GameObject("Body", typeof(MeshFilter), typeof(MeshRenderer));
                body.transform.SetParent(reel, false);
                body.GetComponent<MeshFilter>().sharedMesh = HexPrism();
                body.GetComponent<MeshRenderer>().sharedMaterial = cream;
                for (int k = 0; k < 6; k++)
                {
                    float a = k * 60f * Mathf.Deg2Rad;
                    Vector3 n = new Vector3(Mathf.Cos(a), Mathf.Sin(a), 0f);
                    var face = GameObject.CreatePrimitive(PrimitiveType.Quad); Kill(face);
                    face.name = "Face" + k; face.transform.SetParent(reel, false);
                    face.transform.localPosition = n * (ReelRadius + 0.0006f);
                    face.transform.localRotation = Quaternion.LookRotation(-n, Vector3.forward);
                    face.transform.localScale = new Vector3(2f * ReelRadius * Mathf.Tan(30f * Mathf.Deg2Rad) * 0.96f, ReelWidth * 0.94f, 1f);
                    face.GetComponent<Renderer>().sharedMaterial = SymbolMat(k % 3);
                }
                gun.Reels[i] = reel;
                float start = 90f - ((i * 2) % 3) * 60f;   // durağan duruşta karışık semboller
                reel.localRotation = Quaternion.Euler(0, 0, start);
                gun._angle[i] = start;
            }
            for (int i = 0; i < 4; i++)
                Cyl(root, "Divider" + i, new Vector3(0, AxisY, ReelZ[0] - ReelWidth * 0.5f - ReelGap * 0.5f + i * (ReelWidth + ReelGap)),
                    new Vector3(ReelRadius * 2.25f, 0.0022f, ReelRadius * 2.25f), Quaternion.Euler(90, 0, 0), dark);

            // mermi göstergesi: namlunun sol yanında (kameraya dönük taraf) 3 pirinç kapsül
            for (int i = 0; i < 3; i++)
            {
                var b = GameObject.CreatePrimitive(PrimitiveType.Sphere); Kill(b);
                b.name = "Bullet" + i; b.transform.SetParent(root, false);
                b.transform.localPosition = new Vector3(-0.024f, 0.068f, 0.145f + i * 0.042f);
                b.transform.localScale = new Vector3(0.017f, 0.017f, 0.032f);
                b.GetComponent<Renderer>().sharedMaterial = brass;
                gun.Bullets[i] = b.transform;
            }
            var muzzle = new GameObject("FirePoint").transform; muzzle.SetParent(root, false); muzzle.localPosition = MuzzleLocal;
            gun.Muzzle = muzzle;
            foreach (var r in root.GetComponentsInChildren<Renderer>(true)) r.shadowCastingMode = animated ? UnityEngine.Rendering.ShadowCastingMode.Off : UnityEngine.Rendering.ShadowCastingMode.On;
            return gun;
        }

        static void Kill(GameObject go) { var c = go.GetComponent<Collider>(); if (c != null) { if (Application.isPlaying) Destroy(c); else DestroyImmediate(c); } }

        static void Box(Transform p, string name, Vector3 pos, Vector3 size, float pitch, Material m)
        {
            var g = GameObject.CreatePrimitive(PrimitiveType.Cube); Kill(g);
            g.name = name; g.transform.SetParent(p, false); g.transform.localPosition = pos; g.transform.localRotation = Quaternion.Euler(pitch, 0, 0); g.transform.localScale = size;
            g.GetComponent<Renderer>().sharedMaterial = m;
        }

        static void Cyl(Transform p, string name, Vector3 pos, Vector3 size, Quaternion rot, Material m)
        {
            var g = GameObject.CreatePrimitive(PrimitiveType.Cylinder); Kill(g);
            g.name = name; g.transform.SetParent(p, false); g.transform.localPosition = pos; g.transform.localRotation = rot; g.transform.localScale = size;
            g.GetComponent<Renderer>().sharedMaterial = m;
        }

        static Mesh _hex;
        /// <summary>Z ekseni boyunca uzanan altıgen prizma (düz yüz normalleri; yüz merkezi yarıçapı ReelRadius).</summary>
        static Mesh HexPrism()
        {
            if (_hex != null) return _hex;
            var v = new List<Vector3>(); var n = new List<Vector3>(); var uv = new List<Vector2>(); var t = new List<int>();
            float R = ReelRadius / Mathf.Cos(30f * Mathf.Deg2Rad), hz = ReelWidth * 0.5f;
            for (int k = 0; k < 6; k++)
            {
                float a0 = (k * 60f - 30f) * Mathf.Deg2Rad, a1 = (k * 60f + 30f) * Mathf.Deg2Rad;
                Vector3 p0 = new Vector3(Mathf.Cos(a0) * R, Mathf.Sin(a0) * R, 0), p1 = new Vector3(Mathf.Cos(a1) * R, Mathf.Sin(a1) * R, 0);
                Vector3 nn = new Vector3(Mathf.Cos(k * 60f * Mathf.Deg2Rad), Mathf.Sin(k * 60f * Mathf.Deg2Rad), 0);
                int b = v.Count;
                v.Add(p0 + Vector3.back * hz); v.Add(p1 + Vector3.back * hz); v.Add(p1 + Vector3.forward * hz); v.Add(p0 + Vector3.forward * hz);
                for (int i = 0; i < 4; i++) n.Add(nn);
                uv.Add(new Vector2(0, 0)); uv.Add(new Vector2(1, 0)); uv.Add(new Vector2(1, 1)); uv.Add(new Vector2(0, 1));
                t.Add(b); t.Add(b + 2); t.Add(b + 1); t.Add(b); t.Add(b + 3); t.Add(b + 2);
            }
            foreach (float side in new[] { -1f, 1f })
            {
                int c = v.Count; v.Add(new Vector3(0, 0, side * hz)); n.Add(new Vector3(0, 0, side)); uv.Add(new Vector2(0.5f, 0.5f));
                for (int k = 0; k < 6; k++)
                {
                    float a = (k * 60f - 30f) * Mathf.Deg2Rad;
                    v.Add(new Vector3(Mathf.Cos(a) * R, Mathf.Sin(a) * R, side * hz)); n.Add(new Vector3(0, 0, side)); uv.Add(new Vector2(0.5f + Mathf.Cos(a) * 0.5f, 0.5f + Mathf.Sin(a) * 0.5f));
                }
                for (int k = 0; k < 6; k++)
                {
                    int i0 = c + 1 + k, i1 = c + 1 + (k + 1) % 6;
                    if (side > 0) { t.Add(c); t.Add(i0); t.Add(i1); } else { t.Add(c); t.Add(i1); t.Add(i0); }
                }
            }
            _hex = new Mesh { name = "ReelHex" };
            _hex.SetVertices(v); _hex.SetNormals(n); _hex.SetUVs(0, uv); _hex.SetTriangles(t, 0); _hex.RecalculateBounds();
            return _hex;
        }

        // ------------------------------------------------------------ malzeme ve dokular (kodla çizilir, bir kez)
        static readonly Dictionary<string, Material> _mats = new Dictionary<string, Material>();
        static Material LitTemplate()
        {
            var tpl = Resources.Load<Material>("ShaderKeep/Lit");
            return tpl != null ? new Material(tpl) : new Material(Shader.Find("Universal Render Pipeline/Lit"));
        }

        static Material Mat(string key)
        {
            if (_mats.TryGetValue(key, out var m) && m != null) return m;
            m = LitTemplate(); m.name = "SlotRevolver_" + key;
            m.SetFloat("_Smoothness", key == "brass" ? 0.55f : 0.12f);
            switch (key)
            {
                case "card": m.SetTexture("_BaseMap", CardboardTex(new Color(0.86f, 0.67f, 0.42f), true)); m.SetColor("_BaseColor", Color.white); break;
                case "dark": m.SetTexture("_BaseMap", CardboardTex(new Color(0.55f, 0.37f, 0.21f), false)); m.SetColor("_BaseColor", Color.white); break;
                case "cream": m.SetColor("_BaseColor", new Color(0.95f, 0.9f, 0.8f)); break;
                default: m.SetColor("_BaseColor", new Color(0.98f, 0.86f, 0.52f)); m.SetFloat("_Metallic", 0.35f); break;
            }
            _mats[key] = m;
            return m;
        }

        static Material SymbolMat(int symbol)
        {
            string key = "sym" + symbol;
            if (_mats.TryGetValue(key, out var m) && m != null) return m;
            m = LitTemplate(); m.name = "SlotRevolver_" + key;
            m.SetTexture("_BaseMap", SymbolTex(symbol)); m.SetColor("_BaseColor", Color.white); m.SetFloat("_Smoothness", 0.2f);
            _mats[key] = m;
            return m;
        }

        /// <summary>Oluklu karton: dikey oluk çizgileri + lif gürültüsü; açık kartonda keçeli kalem karalamaları.</summary>
        static Texture2D CardboardTex(Color baseColor, bool doodles)
        {
            const int S = 128;
            var t = new Texture2D(S, S, TextureFormat.RGBA32, true) { wrapMode = TextureWrapMode.Repeat, filterMode = FilterMode.Bilinear, anisoLevel = 4 };
            var px = new Color[S * S];
            var rng = new System.Random(doodles ? 7 : 11);
            for (int y = 0; y < S; y++)
                for (int x = 0; x < S; x++)
                {
                    float groove = 0.055f * Mathf.Sin(x * Mathf.PI * 2f / 8f);
                    float fiber = (float)rng.NextDouble() * 0.05f - 0.025f;
                    float k = 1f + groove + fiber;
                    px[y * S + x] = new Color(baseColor.r * k, baseColor.g * k, baseColor.b * k, 1f);
                }
            if (doodles)
            {
                var ink = new Color(0.22f, 0.13f, 0.07f);
                // zikzaklar, spiraller, kısa çizgiler (videodaki kalemle süslenmiş karton hissi)
                for (int d = 0; d < 7; d++)
                {
                    int x0 = rng.Next(8, S - 40), y0 = rng.Next(8, S - 16), kind = d % 3;
                    for (int s = 0; s < 60; s++)
                    {
                        float u = s / 59f; float x, y;
                        if (kind == 0) { x = x0 + u * 32; y = y0 + ((int)(u * 8) % 2 == 0 ? u * 8 % 1 : 1 - u * 8 % 1) * 8; }
                        else if (kind == 1) { float a = u * 12f, r = 1f + u * 7f; x = x0 + 10 + Mathf.Cos(a) * r; y = y0 + 6 + Mathf.Sin(a) * r; }
                        else { x = x0 + u * 26; y = y0 + Mathf.Sin(u * 18f) * 2.2f; }
                        Dot(px, S, x, y, 1.1f, ink);
                    }
                }
            }
            t.SetPixels(px); t.Apply(true);
            return t;
        }

        static void Dot(Color[] px, int S, float cx, float cy, float r, Color c)
        {
            for (int y = Mathf.FloorToInt(cy - r - 1); y <= cy + r + 1; y++)
                for (int x = Mathf.FloorToInt(cx - r - 1); x <= cx + r + 1; x++)
                {
                    if (x < 0 || y < 0 || x >= S || y >= S) continue;
                    float d = Mathf.Sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy));
                    float a = Mathf.Clamp01(r + 0.5f - d);
                    if (a > 0) px[y * S + x] = Color.Lerp(px[y * S + x], c, a);
                }
        }

        public static readonly string[] SymbolNames = { "STAR", "HEART", "BOLT" };
        static readonly Color[] SymbolColors = { new Color(1f, 0.76f, 0.12f), new Color(0.92f, 0.2f, 0.27f), new Color(0.24f, 0.55f, 0.96f) };

        /// <summary>Makara yüzü: krem kart, kahverengi çerçeve, ortada kalın konturlu sembol (yıldız / kalp / şimşek).</summary>
        static Texture2D SymbolTex(int symbol)
        {
            const int W = 128, H = 104;
            var t = new Texture2D(W, H, TextureFormat.RGBA32, true) { wrapMode = TextureWrapMode.Clamp, filterMode = FilterMode.Bilinear, anisoLevel = 4 };
            var px = new Color[W * H];
            var cream = new Color(0.97f, 0.93f, 0.83f); var frame = new Color(0.45f, 0.29f, 0.15f); var ink = new Color(0.16f, 0.1f, 0.06f);
            Color fill = SymbolColors[symbol];
            for (int y = 0; y < H; y++)
                for (int x = 0; x < W; x++)
                {
                    bool border = x < 5 || y < 5 || x >= W - 5 || y >= H - 5;
                    // sembol alanı: merkez (64,52), birim ölçek 40 px
                    float u = (x - W * 0.5f) / 40f, v = (y - H * 0.5f) / 40f;
                    float inside = Sdf(symbol, u, v);          // <0 içeride
                    Color c = border ? frame : cream;
                    if (inside < -0.09f) c = fill;
                    else if (inside < 0.02f) c = ink;           // kalın kontur
                    // hafif parlama (sembolün sol üstü)
                    if (inside < -0.09f && Sdf(symbol, u + 0.12f, v - 0.12f) > -0.02f) c = Color.Lerp(c, Color.white, 0.35f);
                    px[y * W + x] = c;
                }
            t.SetPixels(px); t.Apply(true);
            return t;
        }

        /// <summary>Yaklaşık işaretli uzaklık (birim ölçek): yıldız, kalp, şimşek.</summary>
        static float Sdf(int symbol, float u, float v)
        {
            switch (symbol)
            {
                case 0: return PolySdf(Star(), u, v);
                case 1:
                {
                    // kalp: (x²+y²−1)³ − x²y³ ≤ 0, ölçeklenip hafif aşağı kaydırılmış
                    float x = u * 1.25f, y = (v + 0.08f) * 1.25f;
                    float a = x * x + y * y - 1f;
                    float f = a * a * a - x * x * y * y * y;
                    return f < 0 ? -Mathf.Pow(-f, 1f / 3f) * 0.35f : Mathf.Pow(f, 1f / 3f) * 0.35f;
                }
                default: return PolySdf(Bolt(), u, v);
            }
        }

        static Vector2[] _star, _bolt;
        static Vector2[] Star()
        {
            if (_star != null) return _star;
            _star = new Vector2[10];
            for (int i = 0; i < 10; i++)
            {
                float a = (90f + i * 36f) * Mathf.Deg2Rad, r = i % 2 == 0 ? 0.95f : 0.42f;
                _star[i] = new Vector2(Mathf.Cos(a) * r, Mathf.Sin(a) * r - 0.05f);
            }
            return _star;
        }

        static Vector2[] Bolt()
        {
            return _bolt ??= new[]
            {
                new Vector2(0.2f, 0.95f), new Vector2(-0.5f, -0.05f), new Vector2(-0.02f, -0.05f), new Vector2(-0.25f, -0.95f),
                new Vector2(0.55f, 0.15f), new Vector2(0.08f, 0.15f), new Vector2(0.38f, 0.95f),
            };
        }

        /// <summary>Çokgene işaretli uzaklık (içeride negatif).</summary>
        static float PolySdf(Vector2[] p, float u, float v)
        {
            var q = new Vector2(u, v);
            float d = float.MaxValue; bool inside = false;
            for (int i = 0, j = p.Length - 1; i < p.Length; j = i++)
            {
                Vector2 a = p[j], b = p[i], e = b - a, w = q - a;
                float h = Mathf.Clamp01(Vector2.Dot(w, e) / e.sqrMagnitude);
                d = Mathf.Min(d, (w - e * h).magnitude);
                if ((a.y > v) != (b.y > v) && u < (b.x - a.x) * (v - a.y) / (b.y - a.y) + a.x) inside = !inside;
            }
            return inside ? -d : d;
        }

        // ------------------------------------------------------------ yerde / uzak oyuncuda: tek statik mesh
        static Mesh _baked; static Material[] _bakedMats;
        /// <summary>Statik model (3 mermi dolu, karışık semboller) tek mesh'e birleştirilmiş; yerdeki tabancalar ve uzak eller paylaşır.</summary>
        public static (Mesh mesh, Material[] mats) Baked()
        {
            if (_baked != null) return (_baked, _bakedMats);
            var gun = Build(null, false);
            var root = gun.transform; root.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
            var combine = new List<CombineInstance>(); var mats = new List<Material>();
            var toRoot = root.worldToLocalMatrix;
            foreach (var mf in root.GetComponentsInChildren<MeshFilter>(true))
            {
                var mr = mf.GetComponent<MeshRenderer>(); if (mr == null || mf.sharedMesh == null) continue;
                combine.Add(new CombineInstance { mesh = mf.sharedMesh, subMeshIndex = 0, transform = toRoot * mf.transform.localToWorldMatrix });
                mats.Add(mr.sharedMaterial);
            }
            root.gameObject.SetActive(false);
            if (Application.isPlaying) Destroy(root.gameObject); else DestroyImmediate(root.gameObject);
            _baked = new Mesh { name = "SlotRevolverBaked", indexFormat = UnityEngine.Rendering.IndexFormat.UInt32 };
            _baked.CombineMeshes(combine.ToArray(), false, true);
            _baked.RecalculateBounds();
            _bakedMats = mats.ToArray();
            return (_baked, _bakedMats);
        }

        // ------------------------------------------------------------ birinci şahıs: makara ve mermi canlandırması
        readonly float[] _angle = new float[3], _stopFrom = new float[3], _stopTo = new float[3], _stopAt = new float[3];
        readonly bool[] _stopping = new bool[3];
        readonly float[] _bulletAnim = { -1f, -1f, -1f };
        float _pulseAt = -10f;
        Vector3 _bulletScale = new Vector3(0.017f, 0.017f, 0.032f);

        void OnEnable()
        {
            KidoSlotGun.ReelStopped += OnReelStopped; KidoSlotGun.BulletLoaded += OnBulletLoaded; KidoSlotGun.Won += OnWon;
            for (int i = 0; i < 3; i++) if (Reels[i] != null) _angle[i] = Reels[i].localEulerAngles.z;
        }

        void OnDisable()
        {
            KidoSlotGun.ReelStopped -= OnReelStopped; KidoSlotGun.BulletLoaded -= OnBulletLoaded; KidoSlotGun.Won -= OnWon;
        }

        /// <summary>Kameraya bakan yüzün açısı (makara düzleminde): kazanan sembol bu yöne gelir.</summary>
        float PaylineAngle(int reel)
        {
            if (ViewCamera == null || Reels[reel] == null) return 180f;
            Vector3 d = transform.InverseTransformDirection(ViewCamera.transform.position - Reels[reel].position);
            return Mathf.Atan2(d.y, d.x) * Mathf.Rad2Deg;
        }

        void OnReelStopped(int reel)
        {
            if (reel < 0 || reel > 2) return;
            int symbol = KidoSlotGun.Result[reel];
            float phi = PaylineAngle(reel);
            float best = float.MaxValue, target = 0f;
            foreach (int k in new[] { symbol, symbol + 3 })
            {
                // ileri dönme yönünde (açı artan) en yakın hedef, en az 40° yol
                float want = phi - k * 60f;
                float to = want + 360f * Mathf.Ceil((_angle[reel] + 40f - want) / 360f);
                if (to - _angle[reel] < best) { best = to - _angle[reel]; target = to; }
            }
            _stopping[reel] = true; _stopFrom[reel] = _angle[reel]; _stopTo[reel] = target; _stopAt[reel] = Time.time;
        }

        void OnBulletLoaded(int count) { int i = Mathf.Clamp(count - 1, 0, 2); _bulletAnim[i] = Time.time; }
        void OnWon() { _pulseAt = Time.time; }

        void LateUpdate()
        {
            float dt = Time.deltaTime;
            for (int i = 0; i < 3; i++)
            {
                if (Reels[i] == null) continue;
                if (_stopping[i])
                {
                    // durma: 0,18 sn, hafif geri sekme (easeOutBack)
                    float u = Mathf.Clamp01((Time.time - _stopAt[i]) / 0.18f);
                    const float c1 = 1.9f, c3 = c1 + 1f;
                    float e = 1f + c3 * Mathf.Pow(u - 1f, 3f) + c1 * Mathf.Pow(u - 1f, 2f);
                    _angle[i] = Mathf.LerpUnclamped(_stopFrom[i], _stopTo[i], e);
                    if (u >= 1f) { _stopping[i] = false; _angle[i] = _stopTo[i] % 360f; }
                }
                else if (KidoSlotGun.Spinning && !KidoSlotGun.ReelDone(i))
                {
                    float spinUp = Mathf.Clamp01(KidoSlotGun.SpinTime / 0.12f);
                    _angle[i] += dt * (980f + i * 90f) * spinUp;
                }
                float pulse = Time.time - _pulseAt < 0.35f ? 1f + 0.1f * Mathf.Sin((Time.time - _pulseAt) / 0.35f * Mathf.PI) : 1f;
                Reels[i].localRotation = Quaternion.Euler(0, 0, _angle[i]);
                Reels[i].localScale = Vector3.one * pulse;
            }
            // mermiler: dolu olanlar görünür; yeni gelen "pop" (0 → 1,35 → 1), atılan anında kaybolur
            int ammo = KidoSlotGun.Ammo;
            for (int i = 0; i < 3; i++)
            {
                var b = Bullets[i]; if (b == null) continue;
                bool on = i < ammo;
                if (b.gameObject.activeSelf != on) b.gameObject.SetActive(on);
                if (!on) continue;
                float k = 1f;
                if (_bulletAnim[i] > 0f)
                {
                    float u = (Time.time - _bulletAnim[i]) / 0.16f;
                    k = u >= 1f ? 1f : (u < 0.6f ? Mathf.Lerp(0f, 1.35f, u / 0.6f) : Mathf.Lerp(1.35f, 1f, (u - 0.6f) / 0.4f));
                }
                b.localScale = _bulletScale * k;
            }
        }

        // ------------------------------------------------------------ namlu alevi: dünyada asılı "✕" yıldız (3 kare)
        static Texture2D _starTex; static Material _flashMat; static Mesh _quad;

        /// <summary>Atış anındaki namlu konumunda, silahtan bağımsız kalan yıldız parlaması (videodaki gibi: silah tepince alev havada kalır).</summary>
        public static void MuzzleStar(Vector3 position, Camera cam, int layer)
        {
            if (cam == null) return;
            if (_starTex == null) _starTex = StarTexture();
            if (_flashMat == null) _flashMat = KidoFxUtil.ParticleMaterial(new Color(1f, 0.9f, 0.62f, 1f), true, _starTex);
            if (_quad == null) { var q = GameObject.CreatePrimitive(PrimitiveType.Quad); _quad = q.GetComponent<MeshFilter>().sharedMesh; Destroy(q); }
            var go = new GameObject("MuzzleStar", typeof(MeshFilter), typeof(MeshRenderer));
            go.layer = layer;
            go.GetComponent<MeshFilter>().sharedMesh = _quad;
            var r = go.GetComponent<MeshRenderer>(); r.sharedMaterial = _flashMat; r.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
            go.transform.position = position;
            go.AddComponent<StarFlash>().Cam = cam;
        }

        sealed class StarFlash : MonoBehaviour
        {
            public Camera Cam; float _born; float _spin;
            void Start() { _born = Time.time; _spin = Random.Range(-12f, 12f); Tick(); }
            void LateUpdate() => Tick();
            void Tick()
            {
                float age = Time.time - _born;
                if (Cam == null || age > 0.11f) { Destroy(gameObject); return; }
                // boy: kameraya uzaklığa göre (görüşün ~%18'i), 3 karede küçülür
                float dist = Vector3.Distance(Cam.transform.position, transform.position);
                float k = age < 0.035f ? 1f : age < 0.07f ? 0.72f : 0.38f;
                transform.localScale = Vector3.one * dist * 0.42f * k;
                transform.rotation = Quaternion.LookRotation(transform.position - Cam.transform.position, Cam.transform.up) * Quaternion.Euler(0, 0, 45f + _spin);
            }
        }

        /// <summary>4 kollu yıldız: beyaz sıcak çekirdek, sivri kollar, yumuşak hale (alfa = parlaklık).</summary>
        static Texture2D StarTexture()
        {
            const int S = 128;
            var t = new Texture2D(S, S, TextureFormat.RGBA32, false) { wrapMode = TextureWrapMode.Clamp };
            var px = new Color[S * S];
            for (int y = 0; y < S; y++)
                for (int x = 0; x < S; x++)
                {
                    float u = (x + 0.5f) / S * 2f - 1f, v = (y + 0.5f) / S * 2f - 1f, r = Mathf.Sqrt(u * u + v * v);
                    float armU = Mathf.Pow(Mathf.Clamp01(1f - Mathf.Abs(u)), 1.6f) * Mathf.Exp(-Mathf.Abs(v) * 14f / Mathf.Max(0.25f, 1f - Mathf.Abs(u)));
                    float armV = Mathf.Pow(Mathf.Clamp01(1f - Mathf.Abs(v)), 1.6f) * Mathf.Exp(-Mathf.Abs(u) * 14f / Mathf.Max(0.25f, 1f - Mathf.Abs(v)));
                    float core = Mathf.Exp(-r * r * 22f), halo = Mathf.Exp(-r * r * 4.5f) * 0.35f;
                    float a = Mathf.Clamp01(armU + armV + core + halo);
                    float white = Mathf.Clamp01(core * 1.6f + (armU + armV) * 0.6f);
                    px[y * S + x] = new Color(1f, Mathf.Lerp(0.72f, 1f, white), Mathf.Lerp(0.35f, 1f, white), a);
                }
            t.SetPixels(px); t.Apply(false);
            return t;
        }
    }
}
SlotViewmodel_excerpt.cs kavrama, tepme, çevirme duruşu
// KidoViewmodel.cs içinden alıntı — slot tabancasının birinci şahıs bağlanması, tepme ve çevirme duruşu.
// Tam dosya oyunun deposunda; burada yalnız slot tabancasına ait kısımlar var.

        KidoSlotRevolver _slot;
        Vector3 _slotPivot;            // kabzanın örnek-yerel konumu: tepme savurması bu noktanın etrafında döner
        float _flick, _flickVel, _present;
                const float FlickPeak = 52f, FlickOmega = 24f, FlickDamping = 0.55f;

        // ------------------------------------------------------------ slot tabancası (paketin tabancasının yerine)
        /// <summary>Hata ayıklama: el kemikleri ve paket tabanca parçaları silah uzayında; old=true paketin tabancasını gösterir.</summary>
        public string SlotDebug(int old)
        {
            if (_slot == null) return "no slot";
            var weapon = _slot.transform.parent;
            var sb = new System.Text.StringBuilder();
            sb.AppendLine($"weapon={weapon.name} parent={(weapon.parent ? weapon.parent.name : "-")} slotLp={_slot.transform.localPosition:F3} slotLr={_slot.transform.localEulerAngles:F1} ls={_slot.transform.localScale.x:F3}");
            foreach (var t in _instance.GetComponentsInChildren<Transform>(true))
                if (t.name is "hand.R" or "palm.02.R" or "palm.03.R" or "f_index.01.R" or "f_middle.01.R" or "thumb.01.R" or "thumb.02.R" or "forearm.R" or "hand.L" or "palm.02.L")
                    sb.AppendLine($"{t.name} wl={weapon.InverseTransformPoint(t.position):F3}");
            foreach (var r in weapon.GetComponentsInChildren<Renderer>(true))
            {
                if (r.transform.IsChildOf(_slot.transform)) continue;
                bool arms = r.name.ToLowerInvariant().Contains("arm");
                if (old >= 0 && !arms) r.enabled = old == 1;
                var bc = weapon.InverseTransformPoint(r.bounds.center);
                sb.AppendLine($"R {r.name} {r.GetType().Name} {(r.enabled ? "on" : "off")} c={bc:F3} ext={r.bounds.extents:F3} path={r.transform.parent?.name}");
                if (r is SkinnedMeshRenderer smr && smr.sharedMesh != null)
                {
                    var bw = smr.sharedMesh.GetAllBoneWeights(); var counts = new System.Collections.Generic.Dictionary<int, int>();
                    foreach (var w in bw) if (w.weight > 0.5f) counts[w.boneIndex] = counts.TryGetValue(w.boneIndex, out var c) ? c + 1 : 1;
                    foreach (var kv in counts) if (kv.Key < smr.bones.Length && smr.bones[kv.Key] != null) sb.AppendLine($"   bone {smr.bones[kv.Key].name} n={kv.Value} parent={smr.bones[kv.Key].parent?.name}");
                    sb.AppendLine($"   root={(smr.rootBone ? smr.rootBone.name : "-")}");
                    // köşe noktaları silah uzayında (analiz için)
                    var baked = new Mesh(); smr.BakeMesh(baked, true);
                    var vs = baked.vertices; var vb = new System.Text.StringBuilder();
                    foreach (var v in vs) { var wl = weapon.InverseTransformPoint(smr.transform.TransformPoint(v)); vb.Append(wl.x.ToString("F4", System.Globalization.CultureInfo.InvariantCulture)).Append(' ').Append(wl.y.ToString("F4", System.Globalization.CultureInfo.InvariantCulture)).Append(' ').Append(wl.z.ToString("F4", System.Globalization.CultureInfo.InvariantCulture)).Append('\n'); }
                    System.IO.File.WriteAllText("Logs/verts-" + r.name + ".txt", vb.ToString());
                    Destroy(baked);
                }
            }
            if (old >= 0) _slot.gameObject.SetActive(old != 1);
            foreach (var t in _instance.GetComponentsInChildren<Transform>(true))
                if (t.name == "weapon-body")
                {
                    var inv = Quaternion.Inverse(weapon.rotation) * t.rotation;
                    sb.AppendLine($"weapon-body wl={weapon.InverseTransformPoint(t.position):F4} wr=({inv.x:F5},{inv.y:F5},{inv.z:F5},{inv.w:F5}) scale={t.lossyScale.x / weapon.lossyScale.x:F4}");
                }
            var grip = _slot.transform.Find("Grip");
            if (grip != null) sb.AppendLine($"slotGrip wl={weapon.InverseTransformPoint(grip.position):F3}");
            return sb.ToString();
        }

        /// <summary>
        /// Paketin tabanca modelini gizler, aynı silah kemiğine kartondan slot tabancasını takar: namlu FirePoint yönüne,
        /// üst kameranın üstüne, boy paketin tabancasına oranlanır. Kollar ve animasyonlar paketinki kalır.
        /// </summary>
        void AttachSlotRevolver()
        {
            Transform weapon = null;
            foreach (var t in _instance.GetComponentsInChildren<Transform>(true))
                if (t.name.EndsWith("_WeaponObject")) { weapon = t; break; }
            if (weapon == null) foreach (var t in _instance.GetComponentsInChildren<Transform>(true)) if (t.name == "Weapon") { weapon = t; break; }
            if (weapon == null) weapon = _instance.transform;
            bool any = false; Bounds b = default;
            foreach (var r in weapon.GetComponentsInChildren<Renderer>(true))
            {
                if (r.name.ToLowerInvariant().Contains("arm")) continue;
                var wb = r.bounds;
                for (int i = 0; i < 8; i++)
                {
                    var corner = wb.center + Vector3.Scale(wb.extents, new Vector3((i & 1) == 0 ? -1 : 1, (i & 2) == 0 ? -1 : 1, (i & 4) == 0 ? -1 : 1));
                    var lp = weapon.InverseTransformPoint(corner);
                    if (!any) { b = new Bounds(lp, Vector3.zero); any = true; } else b.Encapsulate(lp);
                }
                r.enabled = false;   // paketin tabancası gizli
            }
            if (!any) b = new Bounds(Vector3.zero, Vector3.one * 0.2f);
            _slot = KidoSlotRevolver.Build(weapon, true);
            // Paketin tabancası "weapon-body" kemiğine derili: model de oraya bağlanır ki el animasyonlarıyla birlikte
            // hareket etsin. Yerleşim ölçüldü (28 Eyl): kabza merkezi eski tabancanın kabzasına (avuç içi) oturur,
            // namlu kemik uzayında eski namluyla aynı eksende. Kemik ölçeği 180 (rig 100 × görünüm 1,8).
            Transform body = null;
            foreach (var t in _instance.GetComponentsInChildren<Transform>(true)) if (t.name == "weapon-body") { body = t; break; }
            if (body != null)
            {
                _slot.transform.SetParent(body, false);
                _slot.transform.localRotation = new Quaternion(-0.16234f, -0.00755f, -0.04540f, 0.98566f);
                _slot.transform.localPosition = new Vector3(0.000007f, 0.000075f, 0.000365f);
                _slot.transform.localScale = Vector3.one * (1.3f / 180f);
            }
            else
            {
                Vector3 size = b.size; int axis = size.x >= size.y && size.x >= size.z ? 0 : (size.y >= size.z ? 1 : 2);
                Vector3 fwd = Vector3.zero;
                float sign = _firePoint != null ? Mathf.Sign(weapon.InverseTransformPoint(_firePoint.position)[axis] - b.center[axis]) : 1f;
                fwd[axis] = sign == 0 ? 1f : sign;
                Vector3 up = weapon.InverseTransformDirection(_camera.transform.up);
                up = Vector3.ProjectOnPlane(up, fwd); if (up.sqrMagnitude < 1e-4f) up = Vector3.up; up.Normalize();
                float scale = Mathf.Max(0.001f, size[axis]) / 0.415f * 1.08f;
                var rot = Quaternion.LookRotation(fwd, up);
                var modelCenter = new Vector3(0f, 0.02f, 0.078f);
                _slot.transform.localRotation = rot;
                _slot.transform.localScale = Vector3.one * scale;
                _slot.transform.localPosition = b.center - rot * (modelCenter * scale);
            }
            _slot.ViewCamera = _camera;
            foreach (var t in _slot.GetComponentsInChildren<Transform>(true)) t.gameObject.layer = 6;
            _firePoint = _slot.Muzzle;
            var gripT = _slot.transform.Find("Grip");
            _slotPivot = _instance.transform.InverseTransformPoint(gripT != null ? gripT.position : _slot.transform.position);
            _flick = 0f; _flickVel = 0f; _present = 0f;
        }

        /// <summary>
        /// Videodaki tepme: atışta tüm el+silah kabza noktası etrafında 1 karede ~52° yukarı savrulur, sönümlü yayla ~130 ms'de
        /// hafif aşarak döner. Makaralar dönerken silah yana yatıp silindiri kameraya gösterir.
        /// </summary>
        void ApplySlotPose()
        {
            if (_slot == null || _instance == null) return;
            float dt = Mathf.Min(Time.deltaTime, 0.05f);
            float acc = -FlickOmega * FlickOmega * _flick - 2f * FlickDamping * FlickOmega * _flickVel;
            _flickVel += acc * dt; _flick += _flickVel * dt;
            // Referans videodaki çevirme anı: silah ~4 karede (0,13 sn) kalkar, namlu sol-yukarı döner ve silindirin yanı
            // kameraya gelir; makaralar bu duruşta döner, sonuçtan (+mermi) kısa süre sonra 3-4 karede iner.
            bool presenting = KidoSlotGun.State == KidoSlotGun.Phase.Spinning || KidoSlotGun.State == KidoSlotGun.Phase.Loading
                              || Time.time - KidoSlotGun.ResolvedAt < 0.4f;
            _present = Mathf.MoveTowards(_present, presenting ? 1f : 0f, dt * (presenting ? 7.7f : 8.5f));
            float p = _present * _present * (3f - 2f * _present);
            // çevirme sırasında hafif titreme (makaralar dönüyor)
            float buzz = KidoSlotGun.Spinning ? Mathf.Sin(Time.time * 55f) * 0.6f * p : 0f;
            var t = _instance.transform;
            Vector3 pos = t.localPosition; Quaternion rot = t.localRotation; float sc = t.localScale.x;
            Vector3 pivot = pos + rot * (_slotPivot * sc);
            // dinlenme: eski tabancanın duruşu (+ hafif yatık); çevirme: burun yukarı, namlu sola, yan kameraya
            var q = Quaternion.Euler(-_flick - 2f - 20f * p, -3f - 42f * p, _flick * 0.22f + 7f + 16f * p + buzz);
            t.localRotation = q * rot;
            t.localPosition = pivot + q * (pos - pivot) + (new Vector3(0.01f, 0.005f, 0f) + new Vector3(-0.08f, 0.075f, -0.07f) * p) * sc;
        }


        // OnShotFired içinde (atış anı):
            if (_slot != null)
            {
                // tepme savurması anında zirvede; alev atış anındaki namlu konumunda havada kalır
                _flick = FlickPeak * (IsAiming ? 0.6f : 1f); _flickVel = 0f;
                if (_firePoint != null) KidoSlotRevolver.MuzzleStar(_firePoint.position, _camera, 6);
            }
            else SpawnShell();
SlotSfx_excerpt.cs ses sentezi
// KidoSfx.cs içinden alıntı — slot tabancası seslerinin sentezi (dosyasız; açılışta kodla üretilir).
// Tone(tampon, başlangıç sn, süre sn, frekans Hz, genlik, dalga, glideTo, decay, vibrato) · Noise(tampon, başlangıç, süre, genlik, lowpass, decay)

        // ------------------------------------------------------------ sentez
        enum Wave { Sine, Triangle, Square, Bell }

        static float N(int semitonesFromA4) => 440f * Mathf.Pow(2f, semitonesFromA4 / 12f);
        // Notalar (A4 = 0): C5=3, E5=7, G5=10, C6=15, E6=19, G6=22, C7=27, G4=-2, F#4=-3, F4=-4, E4=-5
        // Build(Cue cue) içindeki slot tabancası dalları:
                // ---------------------------------------------------------------- slot tabancası
                case Cue.SlotPull:
                    // kol çekilir: mandal tıkı + makaralara yol veren yükselen vınlama
                    b = Buffer(0.4f);
                    Noise(b, 0f, 0.02f, 0.55f, decay: 80f);
                    Tone(b, 0f, 0.05f, 1700f, 0.25f, Wave.Bell, decay: 70f);
                    Tone(b, 0.03f, 0.32f, 170f, 0.22f, Wave.Square, glideTo: 520f, decay: 4f);
                    Tone(b, 0.03f, 0.32f, 85f, 0.30f, Wave.Sine, glideTo: 260f, decay: 4f);
                    for (int i = 0; i < 4; i++) Noise(b, 0.06f + i * 0.06f, 0.012f, 0.30f, decay: 120f);
                    break;
                case Cue.ReelTick:
                    // çark dişi: kısa tahta-metal tık
                    b = Buffer(0.06f);
                    Noise(b, 0f, 0.008f, 0.45f, decay: 160f);
                    Tone(b, 0f, 0.04f, 2300f, 0.18f, Wave.Sine, decay: 110f);
                    Tone(b, 0f, 0.03f, 780f, 0.20f, Wave.Triangle, decay: 120f);
                    break;
                case Cue.ReelStop0: case Cue.ReelStop1: case Cue.ReelStop2:
                    // makara oturur: gümleme + zil; her makarada perde bir basamak yükselir
                    {
                        int k = cue == Cue.ReelStop0 ? 0 : cue == Cue.ReelStop1 ? 1 : 2;
                        b = Buffer(0.45f);
                        Tone(b, 0f, 0.16f, 150f, 0.75f, Wave.Sine, glideTo: 95f, decay: 22f);
                        Noise(b, 0f, 0.03f, 0.45f, lowpass: .25f, decay: 70f);
                        Tone(b, 0.01f, 0.4f, N(10 + k * 4), 0.24f, Wave.Bell, decay: 7f);
                        Tone(b, 0.01f, 0.4f, N(22 + k * 4), 0.08f, Wave.Bell, decay: 9f);
                    }
                    break;
                case Cue.Jackpot:
                    // üçlü: hızlı yükselen zil arpeji + jeton şıngırtısı + parlak akor
                    b = Buffer(1.5f);
                    {
                        int[] jp = { 3, 7, 10, 15, 19, 22, 27 };
                        for (int i = 0; i < jp.Length; i++) Tone(b, i * 0.05f, 0.25f, N(jp[i]), 0.22f, Wave.Bell, decay: 6f);
                        for (int i = 0; i < 18; i++) Tone(b, 0.30f + (Rand() * 0.5f + 0.5f) * 0.8f, 0.14f, 3000f + (Rand() * 0.5f + 0.5f) * 2600f, 0.09f, Wave.Bell, decay: 22f);
                        foreach (int n in new[] { 15, 19, 22 }) Tone(b, 0.36f, 1.0f, N(n), 0.15f, Wave.Triangle, vibrato: 6f, decay: 2.2f);
                        Tone(b, 0.36f, 1.0f, N(3), 0.18f, Wave.Square, decay: 3f);
                    }
                    break;
                case Cue.SlotMiss:
                    // tutmadı: aşağı kayan "bıvıp"
                    b = Buffer(0.7f);
                    Tone(b, 0f, 0.22f, N(3), 0.30f, Wave.Triangle, glideTo: N(-2), decay: 3f);
                    Tone(b, 0.24f, 0.42f, N(-2), 0.32f, Wave.Triangle, glideTo: N(-14), vibrato: 7f, decay: 3f);
                    Tone(b, 0.24f, 0.42f, N(-26), 0.20f, Wave.Square, glideTo: N(-31), decay: 4f);
                    break;
                case Cue.BulletLoad:
                    // mermi silindire girer: çift "klik-klak"
                    b = Buffer(0.22f);
                    Noise(b, 0f, 0.012f, 0.6f, decay: 140f);
                    Tone(b, 0f, 0.06f, 3200f, 0.18f, Wave.Bell, decay: 80f);
                    Noise(b, 0.07f, 0.015f, 0.7f, lowpass: .35f, decay: 120f);
                    Tone(b, 0.07f, 0.1f, 1300f, 0.24f, Wave.Bell, decay: 45f);
                    Tone(b, 0.07f, 0.08f, 220f, 0.30f, Wave.Sine, decay: 40f);
                    break;
08 / CLAUDE İLE YAP

Kendi oyununa ekle

Unity projesinde Claude Code ile. Prompt’u kopyala, yapıştır, sonucu Play’de gör.

1Kiti indir ve ZIP’i aç.
2Klasörü projende Assets/SlotRevolver/ altına kopyala.
3Claude Code’u projenin kök klasöründe aç.
4Aşağıdaki prompt’lardan birini yapıştır.
PROMPT 1

Hazır kiti projeye ekle (model + kod + ses)

Kiti indirdin ve kendi Unity projene hızlıca eklemek istiyorsun. Claude Code modeli, kodu ve sesleri senin silah sistemine uyarlar.

Unity projeme "slot tabancası" eklemek istiyorum: rus ruleti ile slot makinesini birleştiren, karton görünümlü oyuncak bir revolver. Kit `Assets/SlotRevolver/` altında:

- `model/SlotRevolver.glb` — düğümler: `Frame` (gövde), `Reel0`, `Reel1`, `Reel2` (makaralar; pivotları makara ekseninde, kendi yerel Z eksenleri etrafında dönerler; her makara 6 yüzlü, 60°'de bir yüz, yüzler yıldız-kalp-şimşek sırasıyla tekrar eder), `Bullet0`–`Bullet2` (namlunun yanındaki mermi göstergeleri), `FirePoint` (namlu ucu). Boyu ~0,42 m; +Y yukarı, namlu +Z.
- `code/KidoSlotGun.cs` — durum makinesi (Empty → Spinning → Loading → Loaded); referans olarak kullan.
- `code/SlotViewmodel_excerpt.cs` — birinci şahıs bağlama, tepme ve çevirme duruşu (referans).
- `sounds/*.wav` — SlotPull, ReelTick, ReelStop0/1/2, Jackpot, SlotMiss, BulletLoad, Fire_Revolver.

Yapmanı istediklerim:
1. GLB'yi içe aktar (gerekirse `com.unity.cloud.gltfast` paketini ekle) ve birinci şahıs silah prefabı hazırla. Önce projemdeki mevcut silah ve girdi sistemini incele, ona uyarla.
2. Mantık: silah boşken ateş tuşu makaraları çevirir. Makaralar 0,70 / 0,95 / 1,20 sn'de soldan sağa durur; 1,32 sn'de sonuç gelir. %50 olasılıkla üçü aynı sembolde durur ve 1 mermi dolar; değilse "TUTMADI" yazar ve yeniden çevrilir. Kaybedenlerin %60'ı kıl payı olsun (ilk ikisi aynı, sonuncu farklı). Sonuç çevirme anında belirlensin, makaralar o sembolde dursun.
3. Makara animasyonu: dönerken saniyede 980°+, dururken 0,18 sn easeOutBack ile hedef yüze otursun; duran yüz kameraya baksın.
4. Atış: mermi varsa ateş et ve mermiyi düş. Tepme olarak tüm silahı kabza etrafında ~52° yukarı savur; sönümlü yay kullan (ω = 24, ζ = 0,55). Namlu ucunda 0,11 sn'lik dört kollu yıldız parlaması olsun ve dünyada asılı kalsın.
5. Çevirme duruşu: silah 0,13 sn'de kalksın, namlu sol-yukarı dönsün ve silindir kameraya baksın; sonuçtan 0,4 sn sonra 0,12 sn'de insin.
6. Sesler: çevirme başında SlotPull, dönerken 65 ms'de bir ReelTick, her durmada sırayla ReelStop0/1/2, üçlüde Jackpot, kayıpta SlotMiss, mermi dolunca BulletLoad, atışta Fire_Revolver.
7. Bitince Play'de dene; çevirme, kazanma, kaybetme ve atış anlarının ekran görüntüsünü göster.
PROMPT 2

Modeli sıfırdan kodla kurdur (model dosyası olmadan)

Model dosyası kullanmak istemiyorsun. Claude, gövdeyi ölçüleriyle birlikte Unity’nin hazır şekillerinden kodla kurar; dokuları da kendisi üretir.

Unity (URP) için karton görünümlü oyuncak bir "slot revolver" modelini yalnız kodla, Unity'nin ilkel şekillerinden kuran bir C# sınıfı yaz: `SlotRevolver.Build(Transform parent)`. Dosya ya da model içe aktarma olmasın; dokular da kodla üretilsin.

Eksenler: metre, +Y yukarı, namlu +Z, orijin kabzanın üstü. Parçalar (konum → boyut, kutular; açı X ekseninde):
- RearFrame (0, 0.052, −0.048) → (0.046, 0.09, 0.05)
- TopStrap (0, 0.105, 0.037) → (0.022, 0.012, 0.14) · BottomStrap (0, 0.012, 0.037) → (0.024, 0.012, 0.14)
- Barrel (0, 0.062, 0.19) → (0.04, 0.046, 0.17) · BarrelRib (0, 0.091, 0.19) → (0.016, 0.012, 0.17)
- MuzzleRing: silindir (0, 0.062, 0.278), çap 0.05, kalınlık 0.007, X'te 90°
- FrontSight (0, 0.1, 0.265) → (0.006, 0.014, 0.012) · Hammer (0, 0.106, −0.064) → (0.014, 0.034, 0.022), −28°
- Grip (0, −0.035, −0.078) → (0.042, 0.135, 0.066), 20° · GripCap (0, −0.1, −0.103) → (0.046, 0.02, 0.072), 20°
- Tetik korkuluğu ve tetik: GuardBottom (0, −0.012, 0) → (0.008, 0.008, 0.052), GuardFront (0, 0.002, 0.024) → (0.008, 0.03, 0.008), Trigger (0, 0.006, −0.008) → (0.006, 0.024, 0.008), −15°
- 3 makara: eksen y = 0.058, z = −0.004 / 0.037 / 0.078; altıgen prizma, yarıçap 0.038, genişlik 0.036; her yüze bir sembol dörtgeni (yıldız / kalp / şimşek, k % 3). Aralarına 4 ince karton disk.
- 3 pirinç mermi göstergesi: küre (0.017, 0.017, 0.032), konum (−0.024, 0.068, 0.145 + i × 0.042).
- FirePoint: (0, 0.062, 0.29).

Dokular (kodla üret): açık oluklu karton (dikey oluk çizgileri + lif gürültüsü + keçeli kalem karalamaları), koyu karton, krem zemin üzerinde mürekkep konturlu sembol kartları (sarı yıldız, kırmızı kalp, mavi şimşek; işaretli uzaklık alanıyla çiz). Çarpıştırıcıları kaldır; birinci şahısta gölgeleri kapat.
PROMPT 3

Sesleri sentezlet (ses dosyası olmadan)

Ses dosyası taşımak istemiyorsun. Claude, bu tariflerle sesleri oyun açılırken sentezleyen bir sınıf yazar.

Unity'de ses dosyası kullanmadan, açılışta kodla AudioClip üreten bir ses sınıfı yaz. Yardımcılar: `Tone(tampon, başlangıç, süre, frekans, genlik, dalga, glideTo, decay, vibrato)` ve `Noise(tampon, başlangıç, süre, genlik, lowpass, decay)`; dalgalar Sine, Triangle, Square, Bell. Her sesi 50 ms'lik en yüksek RMS'e göre seviyele: tıklar −19 dBFS, olay sesleri −14, jackpot −11. Slot tabancası için şu sesleri üret:
- SlotPull (0,4 sn): mandal tıkı (kısa gürültü + 1700 Hz zil) ve 170 → 520 Hz yükselen kare dalga vınlaması, arkasında 4 tık.
- ReelTick (0,06 sn): 8 ms gürültü + 2300 Hz sinüs + 780 Hz üçgen, hızlı sönüm.
- ReelStop0/1/2 (0,45 sn): 150 → 95 Hz gümleme + kısa gürültü + zil; zil her makarada bir basamak yükselsin (G5, B5, D♯6).
- Jackpot (1,5 sn): 50 ms arayla yükselen 7 notalık zil arpeji, ardından 18 rastgele yüksek jeton şıngırtısı ve vibratolu majör akor.
- SlotMiss (0,7 sn): aşağı kayan iki üçgen nota ("bıvıp") ve altında alçak kare dalga.
- BulletLoad (0,22 sn): 70 ms arayla iki "klik-klak": gürültü vuruşu + yüksek zil, ikincisinde alçak gövde tonu.