弊社における3大バグの一角であるマスターデータとアセットの不整合を早期発見するライブラリの仕組みを紹介します。クラッシュや見た目の問題を引き起こす存在しないアセットへの参照や、データ解析によるネタバレを招くネタバレアセットの混入にお困りの方はぜひ聞いてみてください。
https://meetup.unity3d.jp/jp/events/1387
ଘࡏ͠ͳ͍Ξηοτͷࢀরͱ ະެ։ΞηοτͰͷωλόϨʹ Ͳ͏ཱ͔ͪ͏͔$"VOJUZ,VOJXBL %F/"$P -UE
View Slide
,VOJXBLVOJUZNFUBDIFDLΛ࡞ͬͨਓɻήʔϜ։ൃ͔Βۤ͠ΈΛऔΓআ͖͍ͨɻͰ੩తղੳثΛ։ൃ͍ͯ͠Δɻ2
͍͑ͨ͜ͱ3
ΞηοτͱϚελʔσʔλͷෆ߹ͷ༧ʹ Ϛελʔσʔλ͔Βࢀর͞ΕΔͯ͢ͷ ΞηοτΛѲͰ͖ΔΑ͏ʹ͢ΔͱΑ͍4
༻ޠͷఆٛ5
ϚελʔσʔλήʔϜͷৼΔ͍ΛܾΊΔύϥϝʔλͷ͏ͪɺ શϢʔβʔͰڞ༗͞Ε͍ͯͯมಈ͠ͳ͍ͷɻ දͰදݱ͞ΕΔ͜ͱ͕ଟ͍ɻ6
ΩϟϥΫλʔ*% ໊લ )1 ΞΠίϯ 'PP 'PP #BS" #BS #BS# #BSϚελʔσʔλͷྫ
ΞηοτlΞηοτͱɺήʔϜΞϓϦέʔγϣϯΛ࡞͢ΔͨΊʹɺ 6OJUZϓϩδΣΫτͰ༻͢ΔΞΠςϜͷ͜ͱɻΞηοτɺ %ϞσϧɺςΫενϟɺεϓϥΠτɺαϯυΤϑΣΫτɺ ԻָͳͲɺϓϩδΣΫτͷࢹ֮త·ͨௌ֮తཁૉΛද͠·͢z8https://docs.unity3d.com/ja/current/Manual/AssetWorkflow.html
ΞηοτΞυϨεΞηοτΛҰҙʹಛఆ͢Δจࣈྻɻ"EESFTTBCMFTͷ ΞυϨεͰྑ͍͠ɺ"EESFTTBCMFTͱಉ༷ͷ ΞηοτγεςϜ͕ൃߦ͢ΔΞυϨεͰΑ͍ɻ9
ฐࣾͷήʔϜ͕Ϛελʔσʔλ͔Β ࢀর͞ΕΔΞηοτΛಡΈࠐΉ·ͰͷྲྀΕ10
11 ϚελʔσʔλʹΞηοτͷ ෦จࣈྻ͕هࡌ͞Ε͍ͯΔ ෦จࣈྻʹ৭ʑҾ͚ͬͯ ΞηοτΞυϨε͕͢Δ ͜ͷΞηοτΞυϨεΛͱʹ Ξηοτ͕ಡΈࠐ·ΕΔΩϟϥΫλʔ*% ʜ ΞΠίϯ ʜ 'PP ʜ #BS ʜ #BSΩϟϥΫλʔ*%ͷ ΞΠίϯͷΞηοτ ΞυϨεΛੜ"TTFUT*DPOT#BSQOH
12ΩϟϥΫλʔ*% ʜ ΞΠίϯ ʜ 'PP ʜ #BS ʜ #BS"TTFUT*DPOT'PPQOH"TTFUT*DPOT#BSQOH"TTFUT*DPOT#BSQOHΞηοτΞυϨε
ฐࣾͷ๊͍͑ͯͨ՝13
w Ϛελʔσʔλʹଘࡏ͠ͳ͍ΞηοτΛ ࢀর͍ͯ͠Δͷ͕͋Δw Ϛελʔσʔλ͔Βࢀর͞Εͯ ͍ͳ͍Ξηοτ͕ଘࡏ͢Δ14ϚελʔσʔλͱΞηοτͷෆ߹
w Ϛελʔσʔλʹଘࡏ͠ͳ͍ΞηοτΛ ࢀর͍ͯ͠Δͷ͕͋Δw Ϛελʔσʔλ͔Βࢀর͞Εͯ ͍ͳ͍Ξηοτ͕ଘࡏ͢Δ15ϚελʔσʔλͱΞηοτͷෆ߹ήʔϜ͕Ϋϥογϡ͢Δɺ͋Δ͍ҙਤ͠ͳ͍ දݱʢςΫενϟͷܽଛʣʹͳͬͯ͠·͏
w Ϛελʔσʔλʹଘࡏ͠ͳ͍ΞηοτΛ ࢀর͍ͯ͠Δͷ͕͋Δw Ϛελʔσʔλ͔Βࢀর͞Εͯ ͍ͳ͍Ξηοτ͕ଘࡏ͢Δ16ϚελʔσʔλͱΞηοτͷෆ߹ެ։͖͢Ͱͳ͍Ξηοτ͕Ξηοτόϯυϧʹ ؚ·Εͯ͠·͍ɺղੳ͞ΕͯωλόϨͯ͠͠·͏
w Ϛελʔσʔλʹଘࡏ͠ͳ͍ΞηοτΛ ࢀর͍ͯ͠Δͷ͕͋Δw Ϛελʔσʔλ͔Βࢀর͞Εͯ ͍ͳ͍Ξηοτ͕ଘࡏ͢Δ17ϚελʔσʔλͱΞηοτͷෆ߹
2ͳͥෆ߹͕ى͜Δͷ͔18
"ΞηοτͱϚελʔσʔλฒྻʹ։ൃ͞Ε͍ͯΔ͔Β19Ϛελʔ͍ͬͨ͡ΞηοτͰ͖ͨϚελʔ͍ͬͨ͡ΞηοτͰ͖ͨ ΞηοτͰ͖ͨϚελʔ͍ͬͨ͡
ෆ߹Λ͙ͨΊʹw ଘࡏ͠ͳ͍ΞηοτΛࢀর͍ͯ͠Δ ϚελʔσʔλΛݟ͚ͭΒΕΔΑ͏ʹ͢Δw Ϛελʔσʔλ͔Βࢀর͞Ε͍ͯͳ͍ ΞηοτΛݟ͚ͭΒΕΔΑ͏ʹ͢Δ20
ฐࣾͷͱͬͨΞϓϩʔν21
22"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 $MFBS3FTVMUT1MBZ.PEF &EJU.PEF&YBNQMF ⎋5FTU3VOOFS5FTU3VOOFS
ෆ߹Λݟ͚ͭΔͨΊʹඞཁͳͷ23ࢀর͞Ε͏ΔΞηοτͯ͢ͷ ΞηοτΞυϨεଘࡏ͢ΔΞηοτͯ͢ͷ ΞηοτΞυϨεൺֱ͢Δͱ ෆ߹͕Θ͔Δ
ඞཁͳͷʹඞཁͳͷ24ϚελʔσʔλશମΛಘΔ"1*Ϛελʔσʔλ͔ΒಘΒΕΔΞηοτ ΞυϨεશͯΛܭࢉ͢Δ"1*͋ΔΞηοτ͔Βࢀর͞Ε͏Δ ͯ͢ͷΞηοτΛܭࢉ͢Δ"1*ଘࡏ͢ΔΞηοτ͔ΒΞηοτ ΞυϨεΛٯࢉ͢Δ"1*ࢀর͞Ε͏ΔΞηοτͯ͢ͷ ΞηοτΞυϨεଘࡏ͢ΔΞηοτͯ͢ͷ ΞηοτΞυϨεൺֱ͢Δͱ ෆ߹͕Θ͔Δ
ඞཁͳͷʹඞཁͳͷ25ϚελʔσʔλશମΛಘΔ"1*Ϛελʔσʔλ͔ΒಘΒΕΔΞηοτ ΞυϨεશͯΛܭࢉ͢Δ"1*͋ΔΞηοτ͔Βࢀর͞Ε͏Δ
࣮ݱͨ͜͠ͱ26
Ϛελʔσʔλ͔ΒಘΒΕΔͯ͢ͷ ΞηοτΞυϨεΛܭࢉ͢Δ"1*Λ ࣮͢Δ͜ͱʹͨ͠ͷͷʜ27
ϥϯλΠϜίʔυͷ͞·͟·ͳͱ͜Ζʹ ΞηοτΞυϨεͷܭࢉॲཧ͕ॻ͔Ε͍ͯͯࠈ28
ؤுͬͯΓ͖͕ͬͨͱͯͭΒ͍ʜ29
ϥϯλΠϜίʔυͷ͞·͟·ͳͱ͜Ζʹ ΞηοτΞυϨεͷܭࢉॲཧ͕ೖΒͳ͍Α͏ʹ ڽूͤ͞ΔϥΠϒϥϦΛ։ൃ͢Δ͜ͱʹͨ͠30
࣮ͨ͠ϥΠϒϥϦΛ ར༻ͨ͠ͱ͖ͷྲྀΕ31
32ΩϟϥΫλʔ*% ʜ ΞΠίϯ ʜ 'PP ʜ #BS ʜ #BS"TTFUT*DPOT'PPQOH"TTFUT*DPOT#BSQOH"TTFUT*DPOT#BSQOHΞηοτΞυϨε৽ͨʹΞηοτ͕Ճ͞ΕͨͷͰΞηοτΞυϨεΛ৽ઃ͍ͨ͠
33"TTFUT*DPOTJDPO/BNFQOHΞηοτΞυϨεΛղͯ͠ߟ͑Δݻఆจࣈྻ ݻఆจࣈྻύϥϝʔλ
ղ͞ΕͨΞηοτΞυϨε͔ΒܭࢉنଇΛ4DSJQUBCMF0CKFDUͱͯ͠࡞͢Δ34"TTFUT*DPOTύϥϝʔλQOHܭࢉنଇ
35*OTQFDUPSJ&YBNQMF3FTPMWFS 3BX3FTPMWFS4FFE1BSBNT0QFOʜʜ{} ʮܕύϥϝʔλ DPEFʯʹʮJOUʯʮ'PP#BSʯͷΑ͏ͳίʔυʹॻ͘ͷͱಉ͡ه๏Ͱೖྗ͍ͯͩ͘͠͞Ϋϥε໊ &YBNQMF3FTPMWFSϥϕϧύϥϝʔλએݴύϥϝʔλΞηϯϒϦܕNTDPSMJC4ZTUFN4USJOH*OTQFDUPSͰΆͪΆͪຒΊΔύϥϝʔλ
36ηάϝϯτηάϝϯτηάϝϯτछผݻఆจࣈྻ4UBUJDηάϝϯτηάϝϯτछผύϥϝʔλࢀর"TTFUT*DPOT1BSBNFUFSύϥϝʔλ4ZTUFN4USJOHϑΥʔϚοτ 5P4USJOH ͰจࣈྻԽηάϝϯτηάϝϯτछผݻఆจࣈྻ4UBUJDQOH$ϑΝΠϧΛ࡞ݻఆจࣈྻύϥϝʔλݻఆจࣈྻ
37ηάϝϯτηάϝϯτηάϝϯτछผݻఆจࣈྻ4UBUJDηάϝϯτηάϝϯτछผύϥϝʔλࢀর"TTFUT*DPOT1BSBNFUFSύϥϝʔλ4ZTUFN4USJOHϑΥʔϚοτ 5P4USJOH ͰจࣈྻԽηάϝϯτηάϝϯτछผݻఆจࣈྻ4UBUJDQOH$ϑΝΠϧΛ࡞ຒΊऴΘͬͨΒίʔυੜ
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();ੜ͞Εͨίʔυ
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();ੜ͞ΕͨίʔυύϥϝʔλΛ༩͑ͯΞηοτΞυϨεΛܭࢉݻఆจࣈྻ෦ύϥϝʔλ෦ݻఆจࣈྻ෦
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ʹ͠ͳ͍ཧ༝ʣ
41var char2 = MasterData.GetCharacter(2);var address = ExampleResolver1.Resolve(char2);Debug.Log(address); // "Assets/Icons/Bar.png"ήʔϜͷϥϯλΠϜίʔυ
42var char2 = MasterData.GetCharacter(2);var address = ExampleResolver1.Resolve(char2);Debug.Log(address); // "Assets/Icons/Bar.png"ήʔϜͷϥϯλΠϜίʔυϚελʔσʔλΛಡΈࠐΉ
43var char2 = MasterData.GetCharacter(2);var address = ExampleResolver1.Resolve(char2);Debug.Log(address); // "Assets/Icons/Bar.png"ήʔϜͷϥϯλΠϜίʔυϚελʔσʔλ͔ΒΞηοτΞυϨεΛܭࢉΞηοτΞυϨεΛͱʹΞηοτΛಡΈࠐΉ
44static IDictionary>ResolverMap => new Dictionary>{{typeof(ExampleResolver1),() => ExampleResolver1.GetList( MasterData.GetAllCharacters())},// ...};ෆ߹Λ֬ೝ͢Δίʔυʢʣ
45static IDictionary>ResolverMap => new Dictionary>{{typeof(ExampleResolver1),() => ExampleResolver1.GetList( MasterData.GetAllCharacters())},// ...};ෆ߹Λ֬ೝ͢ΔίʔυʢʣΞηοτΞυϨεͷܭࢉنଇͨ͘͞Μ ͋ΔͷͰίϨΫγϣϯʹ·ͱΊΔΑ͏ʹ͢Δ
46static IDictionary>ResolverMap => new Dictionary>{{typeof(ExampleResolver1),() => ExampleResolver1.GetList( MasterData.GetAllCharacters())},// ...};ෆ߹Λ֬ೝ͢ΔίʔυʢʣΞηοτΞυϨεͷܭࢉنଇʹͯ͢ͷ ϚελʔσʔλΛೖྗͯ͠ɺࢀর͞ΕΔ ͯ͢ͷΞηοτΞυϨεΛܭࢉ͢Δؔ
47static IDictionary>ResolverMap => new Dictionary>{{typeof(ExampleResolver1),() => ExampleResolver1.GetList( MasterData.GetAllCharacters())},// ...};ෆ߹Λ֬ೝ͢Δίʔυʢʣ͜ΕΛΞηοτΞυϨεͷܭࢉنଇ͝ͱʹॻ͘
48static IDictionary>ResolverMap => new Dictionary>{{typeof(ExampleResolver1),() => ExampleResolver1.GetList( MasterData.GetAllCharacters())},// ...};ෆ߹Λ֬ೝ͢Δίʔυʢʣ͢ΔͱϚελʔσʔλ͔Βܭࢉ͞ΕΔ ͯ͢ͷΞηοτΞυϨεΛೖखͰ͖Δ
49ෆ߹Λ֬ೝ͢Δίʔυʢʣ[Test]public void AllAssetsExistAndReferenced(){var resolvers = TypeCache.GetTypesWithAttribute ();var addressesRead = new HashSet( resolvers.SelectMany(resolver => ResolverMap[resolver]()));var addressesExist = SomeAssetSystem.GetAllAssetAddresses();SetAssert.AreEqual(addressesExist, addressesRead);}
[Test]public void AllAssetsExistAndReferenced(){var resolvers = TypeCache.GetTypesWithAttribute ();var addressesRead = new HashSet( resolvers.SelectMany(resolver => ResolverMap[resolver]()));var addressesExist = SomeAssetSystem.GetAllAssetAddresses();SetAssert.AreEqual(addressesExist, addressesRead);}50ෆ߹Λ֬ೝ͢Δίʔυʢʣܭࢉ͞ΕͨΞηοτΞυϨεͱଘࡏ͢ΔΞηοτͷ ΞηοτΞυϨεΛಥ͖߹Θͤͯ֬ೝ͢Δϝιου
51[Test]public void AllAssetsExistAndReferenced(){var resolvers = TypeCache.GetTypesWithAttribute ();var addressesRead = new HashSet( resolvers.SelectMany(resolver => ResolverMap[resolver]()));var addressesExist = SomeAssetSystem.GetAllAssetAddresses();SetAssert.AreEqual(addressesExist, addressesRead);}ෆ߹Λ֬ೝ͢Δίʔυʢʣલड़ͷͱ͓Γίʔυੜ͞ΕͨΞηοτΞυϨεͷ ܭࢉنଇʹBUUSJCVUF͕͍͍ͭͯΔͷͰͯ͢ΛऔಘͰ͖Δ
52[Test]public void AllAssetsExistAndReferenced(){var resolvers = TypeCache.GetTypesWithAttribute ();var addressesRead = new HashSet( resolvers.SelectMany(resolver => ResolverMap[resolver]()));var addressesExist = SomeAssetSystem.GetAllAssetAddresses();SetAssert.AreEqual(addressesExist, addressesRead);}ෆ߹Λ֬ೝ͢Δίʔυʢʣͯ͢ͷܭࢉنଇ͔Βࢀর͞Ε͏ΔΞηοτͷΞυϨεΛܭࢉ͢Δ֬͠ೝ͔Β࿙Ε͍ͯΔܭࢉنଇ͕͋Δͱྫ֎ʹͳΔͷͰؾ͚Δ
53[Test]public void AllAssetsExistAndReferenced(){var resolvers = TypeCache.GetTypesWithAttribute ();var addressesRead = new HashSet( resolvers.SelectMany(resolver => ResolverMap[resolver]()));var addressesExist = SomeAssetSystem.GetAllAssetAddresses();SetAssert.AreEqual(addressesExist, addressesRead);}ෆ߹Λ֬ೝ͢ΔίʔυʢʣͳΜΒ͔ͷΞηοτγεςϜ͔Βͯ͢ͷΞηοτͷΞυϨεΛऔಘ͢Δ
54[Test]public void AllAssetsExistAndReferenced(){var resolvers = TypeCache.GetTypesWithAttribute ();var addressesRead = new HashSet( resolvers.SelectMany(resolver => ResolverMap[resolver]()));var addressesExist = SomeAssetSystem.GetAllAssetAddresses();SetAssert.AreEqual(addressesExist, addressesRead);}ෆ߹Λ֬ೝ͢Δίʔυʢʣࢀর͞ΕΔΞηοτͷΞυϨεͷू߹ͱ ଘࡏ͢ΔΞηοτͷΞυϨεͷू߹͕ Ұக͠ͳ͚Εɺଘࡏ͠ͳ͍Ξηοτͷ ࢀরωλόϨΞηοτ͕͋ΔͱΘ͔Δ
55"TTFU.BTUFS*OUFHSBUJPO5FTU"MM"TTFUT&YJTU"OE3FGFSFODFE"MM3FTPMWFST$PWFSFE3VO"MM 3VO4FMFDUFE 3FSVO'BJMFE $MFBS3FTVMUT1MBZ.PEF &EJU.PEF 5FTU3VOOFS5FTU3VOOFS<5FTU>ͷ͍ͭͨϝιου5FTU3VOOFS Οϯυ͔Β࣮ߦͰ͖Δ⎋/PUIJOH
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 $MFBS3FTVMUT1MBZ.PEF &EJU.PEF 5FTU3VOOFS5FTU3VOOFSෆ߹͕ͳ͚Εͷ νΣοΫϚʔΫ͕ͭ͘⎋/PUIJOH
"TTFU.BTUFS*OUFHSBUJPO5FTU"MM"TTFUT&YJTU"OE3FGFSFODFE"MM"TTFUT&YJTU"OE3FGFSFODFE T.JTTJOHFMFNFOUT FYUSBFMFNFOUTPGBMMFMFNFOUT NJTTJOH "TTFUT4USFBNJOH"TTFUT'PP4FDPOE NJTTJOH "TTFUT4USFBNJOH"TTFUT'PP4FDPOE3VO"MM 3VO4FMFDUFE 3FSVO'BJMFE $MFBS3FTVMUT1MBZ.PEF &EJU.PEF/PUIJOH 5FTU3VOOFS5FTU3VOOFSෆ߹͕͋Εͷ ΤϥʔϚʔΫ͕ͭ͘⎋ෆ߹ͷৄࡉ͕Θ͔Δ57
6OJUZ5FTU'SBNFXPSLͷςετ$-*͔Β࣮ߦͰ͖ΔͷͰෆ߹ͷ ֬ೝࣗಈఆظ࣮ߦ͕͓͢͢Ί58
ಋೖޙͷޮՌ59
ࠓճઆ໌ͨ͠ΈͰͷޮՌ·ͩະܭଌͨͩ͠ݩͱͳͬͨ·ͩચ࿅͞Ε͍ͯͳ͍࣌ͷ ࣮ΞηοτͱϚελʔσʔλͷෆ߹Λ ܶతʹݮΒͨ͠ʢʹͳΒͳ͔ͬͨʣ60
·ͱΊ61
·ͱΊ62ΞηοτͱϚελʔσʔλͷෆ߹ͷ༧ʹ Ϛελʔσʔλ͔Βࢀর͞ΕΔͯ͢ͷ ΞηοτΛѲͰ͖ΔΑ͏ʹ͢ΔͱΑ͍
એ63ࠓճհͨ͠ΞηοτͱϚελʔσʔλͷ ෆ߹ͷ֬ೝ੩తݕࠪͱݺΕΔͷͰ͢ɻ%F/"ήʔϜͷ੩తݕࠪʹྗΛೖΕ͍ͯ·͢ɻ ڵຯ͕͋ͬͨΒͥͻ͓͕͚͍ͩ͘͞ʂhttps://twitter.com/orga_chem