Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

存在しないアセットへの参照と
未公開アセットでのネタバレに
どう立ち向かうか / How to...

Kuniwak
February 03, 2023

存在しないアセットへの参照と
未公開アセットでのネタバレに
どう立ち向かうか / How to prevent missing assets and spoilers by assets

弊社における3大バグの一角であるマスターデータとアセットの不整合を早期発見するライブラリの仕組みを紹介します。クラッシュや見た目の問題を引き起こす存在しないアセットへの参照や、データ解析によるネタバレを招くネタバレアセットの混入にお困りの方はぜひ聞いてみてください。

https://meetup.unity3d.jp/jp/events/1387

Kuniwak

February 03, 2023
Tweet

More Decks by Kuniwak

Other Decks in Programming

Transcript

  1. ΩϟϥΫλʔ*% ໊લ )1 ΞΠίϯ  'PP  'PP  #BS"

     #BS  #BS#  #BS Ϛελʔσʔλͷྫ
  2. 11  ϚελʔσʔλʹΞηοτ΁ͷ 
 ෦෼จࣈྻ͕هࡌ͞Ε͍ͯΔ  ෦෼จࣈྻʹ৭ʑҾͬ෇͚ͯ 
 ΞηοτΞυϨε͕׬੒͢Δ 

    ͜ͷΞηοτΞυϨεΛ΋ͱʹ 
 Ξηοτ͕ಡΈࠐ·ΕΔ ΩϟϥΫλʔ*% ʜ ΞΠίϯ  ʜ 'PP  ʜ #BS  ʜ #BS ΩϟϥΫλʔ*%ͷ 
 ΞΠίϯͷΞηοτ 
 ΞυϨεΛੜ੒ "TTFUT*DPOT #BS QOH
  3. 12 ΩϟϥΫλʔ*% ʜ ΞΠίϯ  ʜ 'PP  ʜ #BS

     ʜ #BS "TTFUT*DPOT'PPQOH "TTFUT*DPOT#BSQOH "TTFUT*DPOT#BSQOH ΞηοτΞυϨε
  4. ඞཁͳ΋ͷʹඞཁͳ΋ͷ 24 ϚελʔσʔλશମΛಘΔ"1* Ϛελʔσʔλ͔ΒಘΒΕΔΞηοτ 
 ΞυϨεશͯΛܭࢉ͢Δ"1* ͋ΔΞηοτ͔Βࢀর͞Ε͏Δ 
 ͢΂ͯͷΞηοτΛܭࢉ͢Δ"1* ଘࡏ͢ΔΞηοτ͔ΒΞηοτ

    
 ΞυϨεΛٯࢉ͢Δ"1* ࢀর͞Ε͏ΔΞηοτ͢΂ͯͷ 
 ΞηοτΞυϨε ଘࡏ͢ΔΞηοτ͢΂ͯͷ 
 ΞηοτΞυϨε ൺֱ͢Δͱ 
 ෆ੔߹͕Θ͔Δ
  5. 32 ΩϟϥΫλʔ*% ʜ ΞΠίϯ  ʜ 'PP  ʜ #BS

     ʜ #BS "TTFUT*DPOT'PPQOH "TTFUT*DPOT#BSQOH "TTFUT*DPOT#BSQOH ΞηοτΞυϨε ৽ͨʹΞηοτ͕௥Ճ͞ΕͨͷͰ ΞηοτΞυϨεΛ৽ઃ͍ͨ͠
  6. 35 *OTQFDUPS J &YBNQMF3FTPMWFS 3BX3FTPMWFS4FFE1BSBNT 0QFO ʜ ʜ {} 

    ʮܕύϥϝʔλ DPEF ʯʹ͸ʮJOUʯ΍ʮ'PP#BSʯ ͷΑ͏ͳίʔυʹॻ͘ͷͱಉ͡ه๏Ͱೖྗ͍ͯͩ͘͠͞ Ϋϥε໊ &YBNQMF3FTPMWFS ϥϕϧ ύϥϝʔλએݴ ύϥϝʔλ ΞηϯϒϦ ܕ NTDPSMJC 4ZTUFN4USJOH *OTQFDUPSͰΆͪΆͪຒΊΔ ύϥϝʔλ
  7. 36 ηάϝϯτ ηάϝϯτ ηάϝϯτछผ ݻఆจࣈྻ 4UBUJD ηάϝϯτ ηάϝϯτछผ ύϥϝʔλࢀর "TTFUT*DPOT

    1BSBNFUFS ύϥϝʔλ4ZTUFN4USJOH ϑΥʔϚοτ 5P4USJOH ͰจࣈྻԽ ηάϝϯτ ηάϝϯτछผ ݻఆจࣈྻ 4UBUJD QOH $ϑΝΠϧΛ࡞੒ ݻఆจࣈྻ ύϥϝʔλ ݻఆจࣈྻ
  8. 37 ηάϝϯτ ηάϝϯτ ηάϝϯτछผ ݻఆจࣈྻ 4UBUJD ηάϝϯτ ηάϝϯτछผ ύϥϝʔλࢀর "TTFUT*DPOT

    1BSBNFUFS ύϥϝʔλ4ZTUFN4USJOH ϑΥʔϚοτ 5P4USJOH ͰจࣈྻԽ ηάϝϯτ ηάϝϯτछผ ݻఆจࣈྻ 4UBUJD QOH $ϑΝΠϧΛ࡞੒ ຒΊऴΘͬͨΒίʔυੜ੒
  9. 38 [global::PathResolver.ResolverGenerated(Label="StreamingAssets")] public static class ExampleResolver1 { public static string

    Resolve(global::System.String param1) { var result = new StringBuilder(); result.Append(ResolveSegment0()); result.Append(ResolveSegment1(param1)); result.Append(ResolveSegment2()); return result.ToString(); } public static string ResolveSegment0() { return "Assets/Icons/"; } public static string ResolveSegment1(param1) { return param1.ToString(); ੜ੒͞Εͨίʔυ
  10. 39 [global::PathResolver.ResolverGenerated(Label="StreamingAssets")] public static class ExampleResolver1 { public static string

    Resolve(global::System.String param1) { var result = new StringBuilder(); result.Append(ResolveSegment0()); result.Append(ResolveSegment1(param1)); result.Append(ResolveSegment2()); return result.ToString(); } public static string ResolveSegment0() { return "Assets/Icons/"; } public static string ResolveSegment1(param1) { return param1.ToString(); ੜ੒͞Εͨίʔυ ύϥϝʔλΛ༩͑ͯΞηοτΞυϨεΛܭࢉ ݻఆจࣈྻ෦෼ ύϥϝʔλ෦෼ ݻఆจࣈྻ෦෼
  11. 40 [global::PathResolver.ResolverGenerated(Label="StreamingAssets")] public static class ExampleResolver1 { public static string

    Resolve(global::System.String param1) { var result = new StringBuilder(); result.Append(ResolveSegment0()); result.Append(ResolveSegment1(param1)); result.Append(ResolveSegment2()); return result.ToString(); } public static string ResolveSegment0() { return "Assets/Icons/"; } public static string ResolveSegment1(param1) { return param1.ToString(); ੜ੒͞Εͨίʔυ BUUSJCVUFΛ͚͓͍ͭͯͯ͢΂ͯͷܭࢉنଇΛ 
 ಘΒΕΔΑ͏ʹ͓ͯ͘͜͠ͱ͕ޙͰॏཁʹͳΔ ʢ୯ͳΔJOUFSQPMBUFETUSJOHʹ͠ͳ͍ཧ༝ʣ
  12. 42 var char2 = MasterData.GetCharacter(2); var address = ExampleResolver1.Resolve(char2); Debug.Log(address);

    // "Assets/Icons/Bar.png" ήʔϜͷϥϯλΠϜίʔυ ϚελʔσʔλΛಡΈࠐΉ
  13. 43 var char2 = MasterData.GetCharacter(2); var address = ExampleResolver1.Resolve(char2); Debug.Log(address);

    // "Assets/Icons/Bar.png" ήʔϜͷϥϯλΠϜίʔυ Ϛελʔσʔλ͔ΒΞηοτΞυϨεΛܭࢉ ΞηοτΞυϨεΛ΋ͱʹΞηοτΛಡΈࠐΉ
  14. 44 static IDictionary<Type, Func<string[]>> ResolverMap => new Dictionary<Type, Func<string[]>> {

    { typeof(ExampleResolver1), () => ExampleResolver1.GetList( 
 MasterData.GetAllCharacters()) }, // ... }; ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ
  15. 45 static IDictionary<Type, Func<string[]>> ResolverMap => new Dictionary<Type, Func<string[]>> {

    { typeof(ExampleResolver1), () => ExampleResolver1.GetList( 
 MasterData.GetAllCharacters()) }, // ... }; ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ ΞηοτΞυϨεͷܭࢉنଇ͸ͨ͘͞Μ 
 ͋ΔͷͰίϨΫγϣϯʹ·ͱΊΔΑ͏ʹ͢Δ
  16. 46 static IDictionary<Type, Func<string[]>> ResolverMap => new Dictionary<Type, Func<string[]>> {

    { typeof(ExampleResolver1), () => ExampleResolver1.GetList( 
 MasterData.GetAllCharacters()) }, // ... }; ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ ΞηοτΞυϨεͷܭࢉنଇʹ͢΂ͯͷ 
 ϚελʔσʔλΛೖྗͯ͠ɺࢀর͞ΕΔ 
 ͢΂ͯͷΞηοτΞυϨεΛܭࢉ͢Δؔ਺
  17. 47 static IDictionary<Type, Func<string[]>> ResolverMap => new Dictionary<Type, Func<string[]>> {

    { typeof(ExampleResolver1), () => ExampleResolver1.GetList( 
 MasterData.GetAllCharacters()) }, // ... }; ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ ͜ΕΛΞηοτΞυϨεͷܭࢉنଇ͝ͱʹॻ͘
  18. 48 static IDictionary<Type, Func<string[]>> ResolverMap => new Dictionary<Type, Func<string[]>> {

    { typeof(ExampleResolver1), () => ExampleResolver1.GetList( 
 MasterData.GetAllCharacters()) }, // ... }; ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ ͢ΔͱϚελʔσʔλ͔Βܭࢉ͞ΕΔ 
 ͢΂ͯͷΞηοτΞυϨεΛೖखͰ͖Δ
  19. 49 ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ [Test] public void AllAssetsExistAndReferenced() { var resolvers =

    TypeCache.GetTypesWithAttribute 
 <ResolverGeneratedAttribute>(); var addressesRead = new HashSet<string>( 
 resolvers.SelectMany(resolver => ResolverMap[resolver]())); var addressesExist = SomeAssetSystem.GetAllAssetAddresses(); SetAssert.AreEqual(addressesExist, addressesRead); }
  20. [Test] public void AllAssetsExistAndReferenced() { var resolvers = TypeCache.GetTypesWithAttribute 


    <ResolverGeneratedAttribute>(); var addressesRead = new HashSet<string>( 
 resolvers.SelectMany(resolver => ResolverMap[resolver]())); var addressesExist = SomeAssetSystem.GetAllAssetAddresses(); SetAssert.AreEqual(addressesExist, addressesRead); } 50 ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ ܭࢉ͞ΕͨΞηοτΞυϨεͱଘࡏ͢ΔΞηοτͷ 
 ΞηοτΞυϨεΛಥ͖߹Θͤͯ֬ೝ͢Δϝιου
  21. 51 [Test] public void AllAssetsExistAndReferenced() { var resolvers = TypeCache.GetTypesWithAttribute

    
 <ResolverGeneratedAttribute>(); var addressesRead = new HashSet<string>( 
 resolvers.SelectMany(resolver => ResolverMap[resolver]())); var addressesExist = SomeAssetSystem.GetAllAssetAddresses(); SetAssert.AreEqual(addressesExist, addressesRead); } ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ લड़ͷͱ͓Γίʔυੜ੒͞ΕͨΞηοτΞυϨεͷ 
 ܭࢉنଇʹ͸BUUSJCVUF͕͍͍ͭͯΔͷͰ͢΂ͯΛऔಘͰ͖Δ
  22. 52 [Test] public void AllAssetsExistAndReferenced() { var resolvers = TypeCache.GetTypesWithAttribute

    
 <ResolverGeneratedAttribute>(); var addressesRead = new HashSet<string>( 
 resolvers.SelectMany(resolver => ResolverMap[resolver]())); var addressesExist = SomeAssetSystem.GetAllAssetAddresses(); SetAssert.AreEqual(addressesExist, addressesRead); } ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ ͢΂ͯͷܭࢉنଇ͔Βࢀর͞Ε͏ΔΞηοτͷΞυϨεΛܭࢉ͢Δ ΋֬͠ೝ͔Β࿙Ε͍ͯΔܭࢉنଇ͕͋Δͱྫ֎ʹͳΔͷͰؾ෇͚Δ
  23. 53 [Test] public void AllAssetsExistAndReferenced() { var resolvers = TypeCache.GetTypesWithAttribute

    
 <ResolverGeneratedAttribute>(); var addressesRead = new HashSet<string>( 
 resolvers.SelectMany(resolver => ResolverMap[resolver]())); var addressesExist = SomeAssetSystem.GetAllAssetAddresses(); SetAssert.AreEqual(addressesExist, addressesRead); } ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ ͳΜΒ͔ͷΞηοτγεςϜ͔Β͢΂ͯͷΞηοτͷΞυϨεΛऔಘ͢Δ
  24. 54 [Test] public void AllAssetsExistAndReferenced() { var resolvers = TypeCache.GetTypesWithAttribute

    
 <ResolverGeneratedAttribute>(); var addressesRead = new HashSet<string>( 
 resolvers.SelectMany(resolver => ResolverMap[resolver]())); var addressesExist = SomeAssetSystem.GetAllAssetAddresses(); SetAssert.AreEqual(addressesExist, addressesRead); } ෆ੔߹Λ֬ೝ͢Δίʔυʢʣ ࢀর͞ΕΔΞηοτͷΞυϨεͷू߹ͱ 
 ଘࡏ͢ΔΞηοτͷΞυϨεͷू߹͕ 
 Ұக͠ͳ͚Ε͹ɺଘࡏ͠ͳ͍Ξηοτ΁ͷ 
 ࢀর΍ωλόϨΞηοτ͕͋ΔͱΘ͔Δ
  25. 55 "TTFU.BTUFS*OUFHSBUJPO5FTU "MM"TTFUT&YJTU"OE3FGFSFODFE "MM3FTPMWFST$PWFSFE 3VO"MM 3VO4FMFDUFE 3FSVO'BJMFE $MFBS3FTVMUT 1MBZ.PEF &EJU.PEF

      5FTU3VOOFS 5FTU3VOOFS <5FTU>ͷ͍ͭͨϝιου͸5FTU3VOOFS 
 ΢Οϯυ΢͔Β࣮ߦͰ͖Δ ⎋ /PUIJOH
  26. 56 "TTFU.BTUFS*OUFHSBUJPO5FTU "MM"TTFUT&YJTU"OE3FGFSFODFE "MM"TTFUT&YJTU"OE3FGFSFODFE T   "MMFMFNFOUTFYJTUPOCPUI4FUT "TTFUT4USFBNJOH"TTFUT#BS$POTU "TTFUT4USFBNJOH"TTFUT#B[

    3VO"MM 3VO4FMFDUFE 3FSVO'BJMFE $MFBS3FTVMUT 1MBZ.PEF &EJU.PEF   5FTU3VOOFS 5FTU3VOOFS ෆ੔߹͕ͳ͚Ε͹྘ͷ 
 νΣοΫϚʔΫ͕ͭ͘ ⎋ /PUIJOH
  27. "TTFU.BTUFS*OUFHSBUJPO5FTU "MM"TTFUT&YJTU"OE3FGFSFODFE "MM"TTFUT&YJTU"OE3FGFSFODFE T   .JTTJOHFMFNFOUT FYUSBFMFNFOUTPGBMMFMFNFOUT  NJTTJOH

    "TTFUT4USFBNJOH"TTFUT'PP4FDPOE  NJTTJOH "TTFUT4USFBNJOH"TTFUT'PP4FDPOE 3VO"MM 3VO4FMFDUFE 3FSVO'BJMFE $MFBS3FTVMUT 1MBZ.PEF &EJU.PEF /PUIJOH   5FTU3VOOFS 5FTU3VOOFS ෆ੔߹͕͋Ε͹੺ͷ 
 ΤϥʔϚʔΫ͕ͭ͘ ⎋ ෆ੔߹ͷৄࡉ͕Θ͔Δ 57