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
// 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.