英语阅读理解的类型《混沌 In C++:是类型?还是函数调用

下载:0次 阅读:44次
下载:0次 阅读:42次
下载:0次 阅读:45次
下载:0次 阅读:29次
下载:0次 阅读:29次
下载:0次 阅读:22次
下载:0次 阅读:32次
下载:0次 阅读:28次
下载:0次 阅读:27次
下载:0次 阅读:35次
下载:0次 阅读:34次
下载:0次 阅读:33次
下载:0次 阅读:27次
下载:0次 阅读:20次
下载:0次 阅读:26次
下载:0次 阅读:30次
下载:0次 阅读:25次
下载:0次 阅读:111次
下载:0次 阅读:33次
下载:0次 阅读:40次
下载:0次 阅读:33次
下载:0次 阅读:120次
下载:0次 阅读:51次
下载:0次 阅读:46次
下载:0次 阅读:37次
下载:0次 阅读:37次
下载:0次 阅读:29次
下载:0次 阅读:47次
下载:0次 阅读:37次
下载:0次 阅读:39次
下载:0次 阅读:40次
下载:0次 阅读:35次
下载:0次 阅读:114次
下载:0次 阅读:53次
下载:0次 阅读:62次
下载:0次 阅读:76次
下载:0次 阅读:67次
下载:0次 阅读:64次
下载:0次 阅读:48次
下载:0次 阅读:48次
下载:0次 阅读:50次
下载:0次 阅读:0次
下载:0次 阅读:83次
下载:0次 阅读:510次
下载:0次 阅读:305次
下载:0次 阅读:85次
下载:0次 阅读:328次
下载:0次 阅读:158次
下载:0次 阅读:112次
下载:0次 阅读:263次
下载:0次 阅读:138次
下载:0次 阅读:88次
下载:0次 阅读:117次
下载:0次 阅读:129次
下载:0次 阅读:171次
下载:0次 阅读:113次
下载:0次 阅读:175次
下载:0次 阅读:155次
下载:1次 阅读:417次
下载:0次 阅读:169次
下载:4次 阅读:1650次
下载:24次 阅读:1490次
下载:24次 阅读:4956次
下载:0次 阅读:52次
下载:0次 阅读:233次
下载:0次 阅读:236次
下载:0次 阅读:217次
下载:0次 阅读:46次
下载:0次 阅读:79次
下载:0次 阅读:110次
下载:0次 阅读:57次
下载:0次 阅读:61次
下载:0次 阅读:87次
下载:0次 阅读:26次
下载:1次 阅读:280次
下载:0次 阅读:32次
下载:0次 阅读:263次
下载:0次 阅读:36次
下载:0次 阅读:16次
下载:0次 阅读:2次
下载:0次 阅读:31次
下载:0次 阅读:2次
下载:0次 阅读:125次
下载:0次 阅读:31次
下载:0次 阅读:58次
下载:0次 阅读:24次
下载:0次 阅读:109次
下载:0次 阅读:25次
下载:0次 阅读:35次
下载:0次 阅读:31次
下载:0次 阅读:33次
下载:0次 阅读:30次
下载:0次 阅读:29次
下载:0次 阅读:66次
下载:0次 阅读:22次
下载:0次 阅读:241次
下载:0次 阅读:34次
下载:0次 阅读:37次
下载:0次 阅读:81次
下载:0次 阅读:31次
下载:0次 阅读:265次
下载:0次 阅读:263次
下载:0次 阅读:33次
下载:0次 阅读:72次
下载:0次 阅读:190次
下载:0次 阅读:46次
下载:0次 阅读:42次
下载:0次 阅读:41次
下载:0次 阅读:11次
下载:0次 阅读:28次
下载:0次 阅读:31次
下载:0次 阅读:37次
下载:1次 阅读:322次
下载:0次 阅读:60次
下载:1次 阅读:57次
下载:0次 阅读:173次
下载:0次 阅读:323次
下载:0次 阅读:218次
下载:1次 阅读:126次
下载:0次 阅读:31次
下载:0次 阅读:46次
下载:0次 阅读:60次
下载:0次 阅读:131次
下载:0次 阅读:86次
下载:0次 阅读:234次
下载:0次 阅读:196次
下载:0次 阅读:142次
下载:0次 阅读:252次
下载:0次 阅读:54次
下载:0次 阅读:35次
下载:0次 阅读:40次
下载:0次 阅读:34次
下载:0次 阅读:71次
下载:0次 阅读:59次
下载:0次 阅读:49次
下载:0次 阅读:248次
下载:0次 阅读:181次
下载:0次 阅读:32次
下载:0次 阅读:44次
下载:0次 阅读:266次
下载:0次 阅读:107次
下载:0次 阅读:74次
下载:0次 阅读:74次
下载:0次 阅读:68次
下载:0次 阅读:59次
下载:0次 阅读:57次
下载:0次 阅读:29次
下载:0次 阅读:380次
下载:0次 阅读:31次
下载:1次 阅读:162次
下载:0次 阅读:130次
下载:0次 阅读:51次
下载:0次 阅读:77次
下载:0次 阅读:50次
下载:0次 阅读:51次
下载:1次 阅读:156次
下载:0次 阅读:74次
下载:0次 阅读:218次
下载:0次 阅读:401次
下载:0次 阅读:36次
下载:0次 阅读:117次
下载:0次 阅读:76次
下载:0次 阅读:113次
下载:0次 阅读:46次
下载:1次 阅读:50次
下载:6次 阅读:1473次
下载:0次 阅读:166次
下载:0次 阅读:49次
下载:0次 阅读:42次
下载:0次 阅读:36次
下载:0次 阅读:31次
下载:0次 阅读:34次
下载:0次 阅读:53次
下载:0次 阅读:42次
下载:0次 阅读:30次
下载:0次 阅读:40次
下载:0次 阅读:44次
下载:0次 阅读:46次
下载:0次 阅读:34次
下载:0次 阅读:36次
下载:0次 阅读:52次
下载:0次 阅读:62次
下载:7次 阅读:674次
下载:0次 阅读:103次
下载:1次 阅读:68次
下载:0次 阅读:57次
下载:1次 阅读:216次
下载:0次 阅读:230次
下载:0次 阅读:48次
下载:0次 阅读:205次
下载:0次 阅读:78次
下载:0次 阅读:191次
下载:0次 阅读:89次
下载:1次 阅读:217次
下载:0次 阅读:56次
下载:0次 阅读:260次
下载:0次 阅读:104次
下载:0次 阅读:92次
下载:0次 阅读:107次
下载:0次 阅读:62次
下载:0次 阅读:210次
下载:0次 阅读:36次
下载:0次 阅读:55次
下载:0次 阅读:717次
下载:0次 阅读:94次
下载:0次 阅读:56次
下载:0次 阅读:255次
下载:0次 阅读:22次
下载:0次 阅读:44次
下载:0次 阅读:26次
下载:0次 阅读:30次
下载:0次 阅读:18次
下载:0次 阅读:40次
下载:0次 阅读:27次
下载:0次 阅读:41次
下载:0次 阅读:74次
下载:0次 阅读:90次
下载:0次 阅读:245次
下载:0次 阅读:26次
下载:0次 阅读:29次
下载:0次 阅读:318次
下载:0次 阅读:37次
下载:0次 阅读:97次
下载:0次 阅读:38次
下载:1次 阅读:139次
下载:0次 阅读:17次
下载:0次 阅读:12次
下载:0次 阅读:95次
下载:0次 阅读:35次
下载:0次 阅读:36次
下载:0次 阅读:26次
下载:0次 阅读:314次
下载:0次 阅读:217次
下载:0次 阅读:36次
下载:0次 阅读:58次
下载:0次 阅读:46次
下载:0次 阅读:63次
下载:0次 阅读:206次
下载:0次 阅读:40次
下载:0次 阅读:46次
下载:0次 阅读:42次
下载:0次 阅读:42次
下载:0次 阅读:55次
下载:1次 阅读:54次
下载:0次 阅读:85次
下载:0次 阅读:73次
下载:0次 阅读:41次
下载:0次 阅读:54次
下载:0次 阅读:46次
下载:0次 阅读:58次
下载:0次 阅读:83次
下载:0次 阅读:199次
下载:0次 阅读:74次
下载:0次 阅读:79次
下载:1次 阅读:320次
下载:0次 阅读:68次
下载:0次 阅读:52次
下载:1次 阅读:271次
下载:0次 阅读:65次
下载:0次 阅读:59次
下载:0次 阅读:136次
下载:0次 阅读:60次
下载:0次 阅读:81次
下载:0次 阅读:64次
下载:0次 阅读:121次
下载:0次 阅读:55次
下载:0次 阅读:55次
下载:0次 阅读:56次
下载:0次 阅读:57次
下载:0次 阅读:86次
下载:0次 阅读:250次
下载:0次 阅读:64次
下载:0次 阅读:58次
下载:0次 阅读:55次
下载:0次 阅读:71次
下载:0次 阅读:83次
下载:2次 阅读:82次
下载:1次 阅读:232次
下载:0次 阅读:48次
下载:0次 阅读:51次
下载:0次 阅读:49次
下载:2次 阅读:64次
下载:0次 阅读:119次
下载:0次 阅读:315次
下载:1次 阅读:81次
下载:1次 阅读:93次
下载:0次 阅读:69次
下载:0次 阅读:63次
下载:0次 阅读:297次
下载:0次 阅读:218次
下载:0次 阅读:46次
下载:0次 阅读:86次
下载:0次 阅读:99次
下载:0次 阅读:65次
下载:0次 阅读:81次
下载:0次 阅读:72次
下载:0次 阅读:69次
下载:1次 阅读:90次
下载:0次 阅读:104次
下载:0次 阅读:383次
下载:1次 阅读:150次
下载:0次 阅读:313次
下载:0次 阅读:117次
下载:0次 阅读:280次
下载:0次 阅读:87次
下载:0次 阅读:60次
下载:0次 阅读:197次
下载:0次 阅读:245次
下载:0次 阅读:217次
下载:0次 阅读:201次
下载:0次 阅读:95次
下载:0次 阅读:59次
下载:0次 阅读:132次
下载:0次 阅读:279次
下载:0次 阅读:41次
下载:0次 阅读:317次
下载:0次 阅读:131次
下载:0次 阅读:33次
下载:0次 阅读:87次
下载:0次 阅读:373次
下载:0次 阅读:151次
下载:0次 阅读:65次
下载:0次 阅读:109次
下载:0次 阅读:53次
下载:0次 阅读:72次
下载:0次 阅读:54次
下载:0次 阅读:62次
下载:0次 阅读:35次
下载:0次 阅读:59次
下载:0次 阅读:45次
下载:0次 阅读:67次
下载:0次 阅读:456次
下载:0次 阅读:63次
下载:0次 阅读:79次
下载:0次 阅读:68次
下载:0次 阅读:43次
下载:0次 阅读:53次
下载:0次 阅读:38次
下载:0次 阅读:51次
下载:0次 阅读:36次
下载:0次 阅读:44次
下载:0次 阅读:46次
下载:0次 阅读:30次
下载:0次 阅读:37次
下载:0次 阅读:48次
下载:0次 阅读:45次
下载:0次 阅读:56次
下载:0次 阅读:22次
下载:0次 阅读:37次
下载:0次 阅读:249次
下载:0次 阅读:49次
下载:0次 阅读:39次
下载:0次 阅读:298次
下载:0次 阅读:117次
下载:0次 阅读:144次
下载:0次 阅读:13次
下载:0次 阅读:11次
下载:0次 阅读:22次
下载:0次 阅读:64次
下载:0次 阅读:13次
下载:0次 阅读:99次
下载:0次 阅读:19次
下载:0次 阅读:10次
下载:0次 阅读:10次
下载:0次 阅读:32次
下载:0次 阅读:277次
下载:0次 阅读:71次
下载:0次 阅读:49次
下载:0次 阅读:74次
下载:0次 阅读:70次
下载:0次 阅读:44次
下载:0次 阅读:171次
下载:0次 阅读:67次
下载:0次 阅读:136次
下载:0次 阅读:58次
下载:1次 阅读:110次
下载:0次 阅读:45次
下载:0次 阅读:145次
下载:0次 阅读:492次
下载:0次 阅读:83次
下载:0次 阅读:202次
下载:0次 阅读:102次
下载:0次 阅读:63次
下载:0次 阅读:93次
下载:0次 阅读:62次
下载:1次 阅读:760次
下载:0次 阅读:70次
下载:0次 阅读:68次
下载:0次 阅读:111次
下载:0次 阅读:173次
下载:0次 阅读:65次
下载:0次 阅读:158次
下载:0次 阅读:52次
下载:0次 阅读:107次
下载:0次 阅读:118次
下载:0次 阅读:64次
下载:0次 阅读:86次
下载:0次 阅读:83次
下载:0次 阅读:79次
下载:0次 阅读:114次
下载:0次 阅读:214次
下载:0次 阅读:109次
下载:0次 阅读:46次
下载:0次 阅读:84次
下载:0次 阅读:87次
下载:0次 阅读:66次
下载:0次 阅读:46次
下载:0次 阅读:41次
下载:0次 阅读:140次
下载:0次 阅读:42次
下载:0次 阅读:93次
下载:0次 阅读:77次
下载:0次 阅读:33次
下载:0次 阅读:48次
下载:0次 阅读:47次
下载:0次 阅读:47次
下载:0次 阅读:86次
下载:0次 阅读:36次
下载:0次 阅读:81次
下载:0次 阅读:76次
下载:0次 阅读:46次
下载:0次 阅读:38次
下载:0次 阅读:49次
下载:0次 阅读:159次
下载:0次 阅读:57次
下载:0次 阅读:60次
下载:0次 阅读:251次
下载:0次 阅读:717次
下载:0次 阅读:966次
下载:0次 阅读:986次
下载:1次 阅读:505次
下载:0次 阅读:447次
下载:1次 阅读:134次
下载:0次 阅读:492次
下载:0次 阅读:42次
下载:0次 阅读:68次
下载:0次 阅读:68次
下载:0次 阅读:42次
下载:0次 阅读:41次
下载:0次 阅读:215次
下载:1次 阅读:193次
下载:0次 阅读:539次
下载:0次 阅读:96次
下载:0次 阅读:52次
下载:0次 阅读:46次
下载:0次 阅读:48次
下载:0次 阅读:34次
下载:0次 阅读:118次
下载:0次 阅读:111次
下载:0次 阅读:79次
下载:0次 阅读:78次
下载:0次 阅读:2次
下载:0次 阅读:2次
下载:0次 阅读:1次
下载:0次 阅读:0次
下载:0次 阅读:1次
下载:0次 阅读:0次
下载:0次 阅读:0次
下载:0次 阅读:0次
下载:0次 阅读:0次
下载:0次 阅读:0次
下载:0次 阅读:1次
下载:0次 阅读:0次
下载:0次 阅读:0次
下载:0次 阅读:0次
下载:0次 阅读:0次
下载:0次 阅读:0次
下载:0次 阅读:116次
下载:0次 阅读:56次
下载:0次 阅读:248次
下载:0次 阅读:136次
下载:0次 阅读:99次
下载:0次 阅读:111次
下载:1次 阅读:230次
下载:0次 阅读:343次
下载:1次 阅读:75次
下载:0次 阅读:136次
下载:0次 阅读:148次
下载:0次 阅读:44次
下载:0次 阅读:45次
下载:0次 阅读:218次
下载:0次 阅读:472次
下载:0次 阅读:151次
下载:0次 阅读:148次
下载:0次 阅读:59次
下载:0次 阅读:78次
下载:0次 阅读:255次
下载:0次 阅读:50次
下载:0次 阅读:93次
下载:0次 阅读:72次
下载:0次 阅读:116次
下载:0次 阅读:81次
下载:0次 阅读:462次
下载:0次 阅读:133次
下载:0次 阅读:242次
下载:0次 阅读:89次
下载:0次 阅读:48次
下载:0次 阅读:11次
下载:0次 阅读:86次
下载:0次 阅读:33次
下载:0次 阅读:47次
下载:0次 阅读:28次
下载:0次 阅读:152次
下载:0次 阅读:77次
下载:0次 阅读:36次
下载:0次 阅读:57次
下载:0次 阅读:37次
下载:0次 阅读:68次
下载:0次 阅读:46次
下载:0次 阅读:204次
下载:0次 阅读:30次
下载:0次 阅读:12次
下载:0次 阅读:10次
下载:0次 阅读:7次
下载:0次 阅读:14次
下载:0次 阅读:11次
下载:0次 阅读:65次
下载:1次 阅读:4791次
下载:0次 阅读:79次
下载:0次 阅读:130次
下载:0次 阅读:53次
下载:0次 阅读:59次
下载:0次 阅读:46次
下载:0次 阅读:100次
下载:0次 阅读:89次
下载:0次 阅读:59次
下载:0次 阅读:55次
下载:0次 阅读:37次
下载:0次 阅读:119次
下载:0次 阅读:58次
下载:0次 阅读:50次
下载:0次 阅读:109次
下载:0次 阅读:82次
下载:0次 阅读:60次
下载:0次 阅读:60次
下载:0次 阅读:0次
下载:0次 阅读:27次
下载:0次 阅读:31次
下载:0次 阅读:54次
下载:0次 阅读:49次
下载:0次 阅读:45次
下载:0次 阅读:67次
下载:0次 阅读:49次
下载:0次 阅读:65次
下载:0次 阅读:89次
下载:0次 阅读:54次
下载:0次 阅读:46次
下载:0次 阅读:70次
下载:0次 阅读:39次
下载:0次 阅读:87次
下载:0次 阅读:105次
下载:0次 阅读:47次
下载:0次 阅读:68次
下载:0次 阅读:50次
下载:0次 阅读:206次
下载:0次 阅读:43次
下载:0次 阅读:123次
下载:0次 阅读:74次
下载:0次 阅读:53次
下载:0次 阅读:54次
下载:0次 阅读:62次
下载:0次 阅读:221次
下载:0次 阅读:39次
下载:0次 阅读:56次
下载:1次 阅读:755次
下载:0次 阅读:69次
下载:0次 阅读:48次
下载:0次 阅读:51次
下载:0次 阅读:28次
下载:0次 阅读:43次
下载:0次 阅读:30次
下载:0次 阅读:59次
下载:0次 阅读:39次
下载:0次 阅读:44次
下载:0次 阅读:37次
下载:0次 阅读:62次
下载:0次 阅读:36次
下载:0次 阅读:53次
下载:0次 阅读:33次
下载:0次 阅读:37次
下载:0次 阅读:36次
下载:0次 阅读:46次
下载:0次 阅读:58次
下载:0次 阅读:41次
下载:0次 阅读:91次
下载:0次 阅读:133次
下载:0次 阅读:65次
下载:0次 阅读:55次
下载:0次 阅读:37次
下载:0次 阅读:50次
下载:0次 阅读:153次
下载:0次 阅读:31次
下载:0次 阅读:44次
下载:0次 阅读:40次
下载:0次 阅读:70次
下载:0次 阅读:24次
下载:0次 阅读:35次
下载:0次 阅读:149次
下载:0次 阅读:41次
下载:0次 阅读:162次
下载:0次 阅读:39次
下载:0次 阅读:45次
下载:0次 阅读:39次
下载:0次 阅读:47次
下载:0次 阅读:36次
下载:0次 阅读:313次
下载:0次 阅读:60次
下载:0次 阅读:25次
下载:0次 阅读:78次
下载:0次 阅读:40次
下载:0次 阅读:500次
下载:0次 阅读:207次
下载:0次 阅读:209次
下载:0次 阅读:347次
下载:0次 阅读:57次
下载:0次 阅读:48次
下载:0次 阅读:114次
下载:0次 阅读:57次
下载:0次 阅读:58次
下载:0次 阅读:93次
下载:0次 阅读:110次
下载:0次 阅读:69次
下载:0次 阅读:53次
下载:0次 阅读:66次
下载:0次 阅读:68次
下载:0次 阅读:46次
下载:0次 阅读:68次
下载:0次 阅读:57次
下载:0次 阅读:86次
下载:0次 阅读:207次
下载:0次 阅读:100次
下载:0次 阅读:66次{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"title":"《InsideUE4》目录","author":"fjz13","content":"UE4无疑是非常优秀的世界上最顶尖的引擎之一,性能和效果都非常出众,编辑器工作流也非常的出色,更难得宝贵的是完全的开源让我们有机会去从中吸取营养,学习世界上第一流游戏引擎的架构思想。本系列教程《InsideUE4》,希望从最最底层的C++源码剖析,到最最上层的蓝图节点,力求解释清楚各个选项的内部运作机理。希望做到知其然,而更要知其所以然。UE4也是一个非常博大精深的引擎,分析透彻各个具体模块的运作机理无疑也是个艰巨的任务,因此书写周期不定,尽量周更。计划(顺序不定)GamePlay架构 UObject (当前正在写作中……) 加载启动 模块机制独立编辑器客户端服务器编译系统 链接第三方库GamePlugin反射 UObjectUBT,UHT蓝图系统 编译加载调用网络 加入,事件物理 碰撞处理,Overlap,Hit布料破坏UI Slate,UMG渲染 流程Viewport相机管理,CameraManager灯光,烘培材质PostProcess模块 输入事件骨骼动画,融合Matinee,Cinematics粒子系统音频AI,行为树,环境探测地形视频LogProfile本地化统计Paper2D资源管理 加载机制uasset文件分析Level Streaming导入打包C++ 字符串处理FStringDelegateGC序列化SlowTask多线程VR 配置,头显扩展 资源更新HotReload引用---------------------------------------------------------------------------------------------------------------------------知乎专栏:UE4深入学习QQ群:(非新手入门群,请先学习完官方文档和视频教程) 微信公众号:aboutue,关于UE的一切新闻资讯、技巧问答、文章发布,欢迎关注。个人原创,未经授权,谢绝转载!","updated":"T06:06:27.000Z","canComment":false,"commentPermission":"anyone","commentCount":25,"likeCount":227,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-8cc3d99bf536aeea01bfa5_r.jpg","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"游戏引擎"},{"url":"/topic/","id":"","name":"虚幻引擎"},{"url":"/topic/","id":"","name":"游戏开发"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","column":{"slug":"insideue4","name":"InsideUE4"},"sourceUrl":"","pageCommentsCount":25,"snapshotUrl":"","publishedTime":"T14:06:27+08:00","url":"/p/","summary":"","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentsCount":25,"likesCount":227},"":{"title":"《InsideUE4》开篇","author":"fjz13","content":"前言VR行业发展是越来越火热了,硬件设备上有HTC VIVE,Oculus rift,PS VR,各种魔镜;平台上有Steam VR,Gear VR,Google Daydream。而游戏引擎上则有两大阵营:Unreal Engine和Unity。Unity凭着先期的移动平台优势占领了一大部分移动平台的市场,所以目前上手机上的VR游戏也大部分是由Unity开发的。而PC平台上,Unreal Engine凭借着优异的性能,绚丽的渲染效果,源码开源的战略也抢占了目前大部分PC平台VR游戏的份额。参加一场ChinaJoy的VR游戏,会发现大部分也都是由UE4开发的。虽然UE4的授权分成费确实比Unity要昂贵一些,但也因为VR行业本身也还处以社会主义初级阶段,大家也都是在做Demo性质的产品,还没有形成非常客观的市场利润市场。所以盈利后的那些分成费在现阶段已经不太有所谓了。大名鼎鼎的的虚幻引擎,从1998开始,到我们知道的UE3,UDK,一直是高大上的3A游戏和端游的渲染器引擎。然后到2013年,UE4大刀阔斧的改革,干掉了UnrealScript,引进了Blueprint蓝图系统,直接让策划美术也可以拖线实现游戏逻辑。更大的改变的是竟然开源了,受益于社区的回馈,版本更新的速度更是丧心病狂。小版本更新几乎是一两个月就一版。在学习了Unity的Marketplace和插件系统后,更是如虎添翼,焕发了新的生命力。虽然官方一直非常努力的升级更新引擎,但UE4目前也存在了学习曲线陡峭,教程资源稀少的问题。笔者自己从事VR游戏开发,在学习UE4的过程中,基本上也只能硬啃官方文档,youtube上官方视频教程,还有一些寥寥的第三方的视频教程。而且更大的问题在于基本上所有的教程都是非常初级的,只是在教你怎么”用”这个引擎,所以一旦在使用过程中发现了问题,往往手足无措,不能高层建瓴的去解决问题。官方的文档虽然说已经挺详尽了,但大部分重点也只是在介绍表层的各种功能,对于引擎内部的结构和运作机理讳莫如深。如果把UE4当作Unity那样的一个黑盒子去用,在遇到Bug时也只能去各种试各种猜,那也无疑浪费了UE的一个大优势。UE4无疑是非常优秀的世界上最顶尖的引擎之一,性能和效果都非常出众,编辑器工作流也非常的出色,更难得宝贵的是完全的开源让我们有机会去从中吸取营养,学习世界上第一流游戏引擎的架构思想。源码面前,了无秘密 ——侯捷所以笔者决定开始该系列教程《Inside UE4》,从最最底层的C++源码剖析,到最最上层的蓝图节点,力求解释清楚各个选项的内部运作机理。希望做到知其然,而更要知其所以然。UE4也是一个非常博大精深的引擎,光源码下载下来也都有1~2G,分析透彻各个具体模块的运作机理无疑也是个艰巨的任务,但我们努力一分也至少有一分的收获,有一分的甜蜜。面向的读者:不满足于目前世面上教程深度的。已经大概知道了引擎功能并使用,但是仍然想要知道得更多的人。有一定的C++基础。UE4里的C++已经被Epic给魔改后又和C#厮混在一起,一方面得益于此,UE4里的C++实现了各种方便的功能,如反射,垃圾回收,编译系统等重量级的功能。一方面也加大了我们的阅读难度。所以需要你有良好的C++基础,至少看得懂各种C++模板,熟悉各种数据结构。有一点点的C#语言能力,在涉及UE4编译系统的时候,会谈到一些C#,还好不是很多,也还好C#作为一门非常优秀的语言非常易读,不过你要是已经掌握C#,那就更好了。了解3D游戏引擎的一些基础概念,如知道什么是材质,什么是骨骼动画融合等。所幸这些都是很容易知道的知识。有一些基本的图形学知识,知道Mesh,Shader,RenderTarget……等等一些基本的概念。本教程在开始某个专题的时候,会简单讲解一下背景知识,但它不会变成基础图形学教程。不适合的读者:希望通过该教程学习快速上手UE4引擎的人,不适合你。目前快速上手UE4的最佳途径依然是官方文档和视频教程。希望学习然后自己搭建具体游戏的,如FPS,VR游戏,样板间等。本系列教程不会教你从零开始搭建一个游戏示例,虽然会讲解VR的各种配置的内部机制原理。希望学习某个模块具体案例的,如用材质编辑器实现各种效果。本教程会透彻分析材质编辑器内部的实现机制,也会讲解各个材质节点的功能和原理,有时也会看需要通过一些非常直接简单的示例来讲解。但目标从来都是讲解原理,而不是实现结果。愿景和计划从C++源码层次上分析整个游戏引擎的架构。了解清楚各个模块之间是怎么协作的,如果有闲情雅致,也甚至会具体到谈一谈某个很小的点为何这么设计。如UE4里的Delegate,Pointers,TArray等。虽然源码剖析本来就是曲线陡峭的上升,但还是希望能尽量深入浅出的讲解,所以也会尽量结合实际的效果演示。因为UE4比较庞大,所以会逐渐的展开各个专题展开,在讲解一个专题的源码时,会暂时忽略其它跟它协作模块。虽然UE4也可以做移动平台的开发,但本教程还是主要专注于Windows的PC端游戏内容。计划是连载周更,虽说已经有预定的专题讲解列表计划,但并不妨碍你留言告知你最想了解的下一个专题。我会酌情改变优先级。本人也是才疏学浅,经验有限,如有错误纰漏之处,也请不吝赐教,共同学习进步,不胜感激。一些准备工作虽说官方已经提供了简便的launcher,但还是推荐自己自己Clone源码编译,也方便时不时的Debug和查看源码知道Why。而且有些时候其实是直接更改引擎源码来得更为方便便利的。 Clone下来之后先点 Setup.bat再点 GenerateProjectFiles.bat ,然后打开UE4.sln,按照默认选项DevelopmentEditor,等待最初半个小时的编译后,就可以开始源码之旅了。 引擎版本紧跟Github最新release,目前最新4.14 注意:因为UnrealEngine只是公开源码,但不是开源项目,依然是个私有项目。访问该Github地址,需要先链接你的Github到EpicGames的会员权限里,这个文档说明了步骤。本教程也会同时大量引用官方文档的内容,在官方文档简略的介绍的基础上,通过源码加深理解,再更加透彻的解释。有条件的话,还是建议自搭梯子,youtube的视频教程更新是最快的,而且也有高清。一块大容量的SSD,UE用source build的话,特别是想调试引擎的话,一个配置编译出来都得耗用个好几个G,一个项目的编译20~30G轻轻松松。其他的无关的话:之前开源过一款自研的,一个人毕竟精力有限,也无法开工各种编辑器工作流。所以Medusa引擎目前只是作为自己的一个试验场,未来也会专注于2D游戏的一些探索。关于Medusa游戏引擎的内部架构,其实想讲的也挺多,希望以后在UE4的相关介绍后,得空顺便讲一些其他游戏引擎的架构思想,和C++的一些奇技淫巧。上篇:下篇:---------------------------------------------------------------------------------------------------------------------------知乎专栏:UE4深入学习QQ群:(非新手入门群,请先学习完官方文档和视频教程) 微信公众号:aboutue,关于UE的一切新闻资讯、技巧问答、文章发布,欢迎关注。个人原创,未经授权,谢绝转载!","updated":"T06:12:10.000Z","canComment":false,"commentPermission":"anyone","commentCount":6,"likeCount":72,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T14:12:10+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-26dceafbf6f_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":6,"likesCount":72},"":{"title":"《InsideUE4》基础概念","author":"fjz13","content":"创建测试项目接上文的准备工作,双击生成的UE4Editor.exe,选择创建测试C++空项目Hello(以后的源码分析都会基于该最简单的项目) 项目文件结构VS项目和文件目录:可以看到,Config目录里带着3个最主要的配置,Editor,Engine,Game。代码方面自动生成了用于编译系统的3个.cs文件,C++代码方面生成了一个Hello \"Game Module\",和HelloGameMode。 文件目录:Binaries:存放编译生成的结果二进制文件。该目录可以gitignore,反正每次都会生成。Config:配置文件。Content:平常最常用到,所有的资源和蓝图等都放在该目录里。DerivedDataCache:“DDC”,存储着引擎针对平台特化后的资源版本。比如同一个图片,针对不同的平台有不同的适合格式,这个时候就可以在不动原始的uasset的基础上,比较轻易的再生成不同格式资源版本。gitignore。Intermediate:中间文件(gitignore),存放着一些临时生成的文件。有: Build的中间文件,.obj和预编译头等UHT预处理生成的.generated.h/.cpp文件VS.vcxproj项目文件,可通过.uproject文件生成编译生成的Shader文件。AssetRegistryCache:Asset Registry系统的缓存文件,Asset Registry可以简单理解为一个索引了所有uasset资源头信息的注册表。CachedAssetRegistry.bin文件也是如此。Saved:存储自动保存文件,其他配置文件,日志文件,引擎崩溃日志,硬件信息,烘培信息数据等。gitignoreSource:代码文件。编译类型很多人在使用UE4的时候,往往只是依照默认的DevelopmentEditor,但实际上编译选项是非常重要的。 UE4本身包含网络模式和编辑器,这意味着你的工程在部署的时候将包含Server和Client,而在开发的时候,也将有Editor和Stand-alone之分;同时你也可以单独选择是否为Engine和Game生成调试信息,接着你还可以选择是否在游戏里内嵌控制台等。依照每种编译配置包含两种关键字。第一种表明了引擎以及游戏项目的状态。第二个关键字表明正在编译的目标。组合的各种情况:所以为了我们的调试代码方便,我们选择DebugEditor来加载游戏项目,当需要最简化流程的时候用Debug来运行独立版本。命名约定客观来说,相比其他引擎的源码,UE4的源码还是非常清晰的,模块组织也比较明了。但阅读源码的学习曲线依然陡峭,我想有以下原因: 1. UE4包含的模块众多,拢共有几十个模块,虽然采用了Module架构来解耦,但难免还是要有依赖交叉的地方,在阅读的时候就很难理清各部分的关系。 2. UE4的功能优秀,作为业界顶尖的成熟游戏引擎,在一些具体的模块内部实现上就脱离了简单粗暴,而是采用了各种设计模式和权衡。同时也需要阅读的人有相关的业务知识。比如材质编辑器编译生成Shader的过程就需要读者拥有至少差不多的图形学知识。 3. 被魔改后的C++,UE4为了各平台的编译和其他考量(具体以后说到编译系统的时候再细讨论),对标准的C++和编译,进行了相当程度的改造,在UHT代码生成和各种宏的嵌套之后,读者就很难一下子看清背后的各种的机制了。但万丈高楼平地起,咱们也可以从最简单的一步步开始学起,直到了解掌握整个引擎的内部结构。 在阅读代码之前,就必须去了解一下,具体的自己去查看官网文档,下面是一些基本需要知道的: 模版类以T作为前缀,比如TArray,TMap,TSet UObject派生类都以U前缀 AActor派生类都以A前缀 SWidget派生类都以S前缀 抽象接口以I前缀 枚举以E开头 bool变量以b前缀,如bPendingDestruction 其他的大部分以F开头,如FString,FName typedef的以原型名前缀为准,如typedef TArray FArrayOfMyT 在编辑器里和C#里,类型名是去掉前缀过的 UHT在工作的时候需要你提供正确的前缀,所以虽然说是约定,但你也得必须遵守。(编译系统怎么用到那些前缀,后续再讨论)基础概念和其他的3D引擎一样,UE4也有其特有的描述游戏世界的概念。在UE4中,几乎所有的对象都继承于UObject(跟Java,C#一样),UObject为它们提供了基础的垃圾回收,反射,元数据,序列化等,相应的,就有各种\"UClass\"的派生们定义了属性和行为的数据。 跟Unity(GameObject-Component)有些像的是,UE4也采用了组件式的架构,但细品起来却又有些不一样。在UE中,3D世界是由Actors构建起来的,而Actor又拥有各种Component,之后又有各种Controller可以控制Actor(Pawn)的行为。Unity中的Prefab,在UE4中变成了BlueprintClass,其实Class的概念确实更加贴近C++的底层一些。 Unity中,你可以为一个GameObject添加一个ScriptComponent,然后继承MonoBehaviour来编写游戏逻辑。在UE4中,你也可以为一个Actor添加一个蓝图或者C++ Component,然后实现它来直接组织逻辑。 UE4也支持各种插件。 其他的下篇再一一细说。编译系统UE4支持众多平台,包括Windows,IOS,Android等,因此UE4为了方便你配置各个平台的参数和编译选项,简化编译流程,UE4实现了自己的一套编译系统,否则我们就得接受各个平台再单独配置一套项目之苦了。 这套工具的编译流程结果,简单来说,就是你在VS里的运行,背后会运行UE4的一些命令行工具来完成编译,其他最重要的两个组件: UnrealBuildTool(UBT,C#):UE4的自定义工具,来编译UE4的逐个模块并处理依赖等。我们编写的Target.cs,Build.cs都是为这个工具服务的。 UnrealHeaderTool (UHT,C++):UE4的C++代码解析生成工具,我们在代码里写的那些宏UCLASS等和#include \"*.generated.h\"都为UHT提供了信息来生成相应的C++反射代码。 一般来说,UBT会先调用UHT会先负责解析一遍C++代码,生成相应其他代码。然后开始调用平台特定的编译工具(VisualStudio,LLVM)来编译各个模块。最后启动Editor或者是Game. 更细的留待“编译系统”再细细讨论引用上篇:下篇:---------------------------------------------------------------------------------------------------------------------------知乎专栏:UE4深入学习QQ群:(非新手入门群,请先学习完官方文档和视频教程) 微信公众号:aboutue,关于UE的一切新闻资讯、技巧问答、文章发布,欢迎关注。个人原创,未经授权,谢绝转载!","updated":"T06:14:18.000Z","canComment":false,"commentPermission":"anyone","commentCount":4,"likeCount":33,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T14:14:18+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-fe1a582d2b5db2f400f3cff_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":4,"likesCount":33},"":{"title":"《InsideUE4》GamePlay架构(一)Actor和Component","author":"fjz13","content":"引言如果让你来制作一款3D游戏引擎,你会怎么设计其结构?尽管游戏的类型有很多种,市面上也有众多的3D游戏引擎,但绝大部分游戏引擎都得解决一个基本问题:抽象模拟一个3D游戏世界。根据基本的图形学知识,我们知道,为了展示这个世界,我们需要一个个带着“变换”的“游戏对象”,接着让它们父子嵌套以表现更复杂的结构。本质上,其他的物理模拟,游戏逻辑等功能组件,最终目的也只是为了操作这些“游戏对象”。 这件事,在Unity那里就直接成了“GameObject”和“Component”;在Cocos2dx那里是一个个的“CCNode”,操纵部分直接内嵌在了CCNode里面;在Medusa里是一个个“INode”和“IComponent”。 那么在UE4的眼中,它是怎么看待游戏的3D世界的?创世记UE创世,万物皆UObject,接着有Actor。UObject:起初,UE创世,有感于天地间C++原始之气一片混沌虚无,便撷取凝实一团C++之气,降下无边魔力,洒下秩序之光,便为这个世界生成了坚实的土壤UObject,并用UClass一一为此命名。 藉着UObject提供的元数据、反射生成、GC垃圾回收、序列化、编辑器可见,Class Default Object等,UE可以构建一个Object运行的世界。(后续会有一个大长篇深挖UObject)Actor:世界有了土壤之后,但还少了一些生动色彩,如果女娲造人一般,UE取一些UObject的泥巴,派生出了Actor。在UE眼中,整个世界从此了有了一个个生动的“演员”,众多的“演员”们,一起齐心协力为观众上演一场精彩的游戏。 脱胎自Object的Actor也多了一些本事:Replication(网络复制),Spawn(生生死死),Tick(有了心跳)。 Actor无疑是UE中最重要的角色之一,组织庞大,最常见的有StaticMeshActor, CameraActor和 PlayerStartActor等。Actor之间还可以互相“嵌套”,拥有相对的“父子”关系。思考:为何Actor不像GameObject一样自带Transform? 我们知道,如果一个对象需要在3D世界中表示,那么它必然要携带一个Transform matrix来表示其位置。关键在于,在UE看来,Actor并不只是3D中的“表示”,一些不在世界里展示的“不可见对象”也可以是Actor,如AInfo(派生类AWorldSetting,AGameMode,AGameSession,APlayerState,AGameState等),AHUD,APlayerCameraManager等,代表了这个世界的某种信息、状态、规则。你可以把这些看作都是一个个默默工作的灵体Actor。所以,Actor的概念在UE里其实不是某种具象化的3D世界里的对象,而是世界里的种种元素,用更泛化抽象的概念来看,小到一个个地上的石头,大到整个世界的运行规则,都是Actor. 当然,你也可以说即使带着Transform,把坐标设置为原点,然后不可见不就行了?这样其实当然也是可以,不过可能因为UE跟贴近C++一些的缘故,所以设计哲学上就更偏向于C++的哲学“不为你不需要的东西付代价”。一个Transform再加上附带的逆矩阵之类的表示,内存占用上其实也是挺可观的。要知道UE可是会抠门到连bool变量都要写成uint bPending:1;位域来节省一个字节的内存的。 换一个角度讲,如果把带Transform也当成一个Actor的额外能力可以自由装卸的话,那其实也可以自圆其说。经过了UE的权衡和考虑,把Transform封装进了SceneComponent,当作RootComponent。但在权衡到使用的便利性的时候,大部分Actor其实是有Transform的,我们会经常获取设置它的坐标,如果总是得先获取一下SceneComponent,然后再调用相应接口的话,那也太繁琐了。所以UE也为了我们直接提供了一些便利性的Actor方法,如(Get/Set)ActorLocation等,其实内部都是转发到RootComponent。/*~\n * Returns location of the RootComponent \n * this is a template for no other reason than to delay compilation until USceneComponent is defined\n */ \ntemplate&class T&\nstatic FORCEINLINE FVector GetActorLocation(const T* RootComponent)\n{\n
return (RootComponent != nullptr) ? RootComponent-&GetComponentLocation() : FVector(0.f,0.f,0.f);\n}\nbool AActor::SetActorLocation(const FVector& NewLocation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)\n{\n
if (RootComponent)\n
const FVector Delta = NewLocation - GetActorLocation();\n
return RootComponent-&MoveComponent(Delta, GetActorQuat(), bSweep, OutSweepHitResult, MOVECOMP_NoFlags, Teleport);\n
else if (OutSweepHitResult)\n
*OutSweepHitResult = FHitResult();\n
}\\n}\n\n同理,Actor能接收处理Input事件的能力,其实也是转发到内部的UInputComponent* InputC同样也提供了便利方法。Component世界纷繁复杂,光有一种Actor可不够,自然就需要有各种不同技能的Actor各司其职。在早期的远古时代,每个Actor拥有的技能都是与生俱有,只能父传子一代代的传下去。随着游戏世界的越来越绚丽,需要的技能变得越来越多和频繁改变,这样一组合,唯出身论的Actor数量们就开始爆炸了,而且一个个也越来越胖,最后连UE这样的神也管理不了了。终于,到了第4个纪元,UE窥得一丝隔壁平行宇宙Unity的天机。下定决心,让Actor们轻装上阵,只提供一些通用的基本生存能力,而把众多的“技能”抽象成了一个个“Component”并提供组装的接口,让Actor随用随组装,把自己武装成一个个专业能手。看见UActorComponent的U前缀,是不是想起了什么?没错,UActorComponent也是基础于UObject的一个子类,这意味着其实Component也是有UObject的那些通用功能的。(关于Actor和Component之间Tick的传递后续再细讨论)下面我们来细细看一下Actor和Component的关系: TSet&UActorComponent*& OwnedComponents 保存着这个Actor所拥有的所有Component,一般其中会有一个SceneComponent作为RootComponent。 TArray&UActorComponent*& InstanceComponents 保存着实例化的Components。实例化是个什么意思呢,就是你在蓝图里Details定义的Component,当这个Actor被实例化的时候,这些附属的Component也会被实例化。这其实很好理解,就像士兵手上拿着把武器,当我们拥有一队士兵的时候,自然就一一对应拥有了不同实例化的武器。但OwnedComponents里总是最全的。ReplicatedComponents,InstanceComponents可以看作一个预先的分类。一个Actor若想可以被放进Level里,就必须实例化USceneComponent* RootComponent。但如果你光看代码的话,OwnedComponents其实也是可以包容多个不同SceneComponent的,然后你可以动态获取不同的SceneComponent来当作RootComponent,只不过这种用法确实不太自然,而且也得非常小心维护不同状态,不推荐如此用。在我们的直觉印象里,一个封装过后的Actor应该是一个整体,它能被放进Level中,拥有变换,这一整个整体的概念更加符合自然意识,所以我想,这也是UE为何要在Actor里一一对应一个RootComponent的原因。再来说说Component下面的家族(为了阐明概念,只列出了最常见的):ActorComponent下面最重要的一个Component就非SceneComponent莫属了。SceneComponent提供了两大能力:一是Transform,二是SceneComponent的互相嵌套。 思考:为何ActorComponent不能互相嵌套?而在SceneComponent一级才提供嵌套? 首先,ActorComponent下面当然不是只有SceneComponent,一些UMovementComponent,AIComponent,或者是我们自己写的Component,都是会直接继承ActorComponent的。但很奇怪的是,ActorComponent却是不能嵌套的,在UE的观念里,好像只有带Transform的SceneComponent才有资格被嵌套,好像Component的互相嵌套必须和3D里的transform父子对应起来。 老实说,如果让我来设计Entity-Component模式,我很可能会为了通用性而在ActorComponent这一级直接提供嵌套,这样所有的Component就与生俱来拥有了组合其他Component的能力,灵活性大大提高。但游戏引擎的设计必然也经过了各种权衡,虽然说架构上显得并不那么的统一干净,但其实也大大减少了被误用的机会。实体组件模式推崇的“组合优于继承”的概念确实很强大,但其实同时也带来了一些问题,如Component之间如何互相依赖,如何互相通信,嵌套过深导致的接口便利损失和性能损耗,真正一个让你随便嵌套的组件模式可能会在使用上更容易出问题。 从功能上来说,UE更倾向于编写功能单一的Component(如UMovementComponent),而不是一个整合了其他Component的大管家Component(当然如果你偏要这么干,那UE也阻止不了你)。 而从游戏逻辑的实现来说,UE也是不推荐把游戏逻辑写在Component里面,所以你其实也没什么机会去写一个很复杂的Component.思考:Actor的SceneComponent哲学 很多其他游戏引擎,还有一种设计思路是“万物皆Node”。Node都带变换。比如说你要设计一辆汽车,一种方式是车身作为一个Node,4个轮子各为车身的子Node,然后移动父Node来前进。而在UE里,一种很可能的方式就变成,汽车是一个Actor,车身作为RootComponent,4个轮子都作为RootComponent的子SceneComponent。请读者们细细体会这二者的区别。两种方式都可以实现出优秀的游戏引擎,只是有些理念和侧重点不同。 从设计哲学上来说,其实你把万物看成是Node,或者是Component,并没有什么本质上的不同。看作Node的时候,Node你就要设计的比较轻量廉价,这样才能比较没有负担的创建多个,同理Component也是如此。Actor可以带多个SceneComponent来渲染多个Mesh实体,同样每个Node带一份Mesh再组合也可以实现出同样效果。 个人观点来说,关键的不同是在于你是怎么划分要操作的实体的粒度的。当看成是Node时,因为Node身上的一些通用功能(事件处理等),其实我们是期望着我们可以非常灵活的操作到任何一个细小的对象,我们希望整个世界的所有物体都有一些基本的功能(比如说被拾取),这有点完美主义者的思路。而注重现实的人就会觉得,整个游戏世界里,有相当大一部分对象其实是不那么动态的。比如车子,我关心的只是整体,而不是细小到每一个车轱辘。这种理念就会导成另外一种设计思路:把要操作的实体按照功能划分,而其他的就尽量只是最简单的表示。所以在UE里,其实是把5个薄薄的SceneComponent表示再用Actor功能的盒子装了起来,而在这个盒子内部你可以编写操作这5个对象的逻辑。换做是Node模式,想编写操作逻辑的话,一般就来说就会内化到父Node的内部,不免会有逻辑与表现掺杂之嫌,而如果Node要把逻辑再用组合分离开的话,其实也就转化成了某种ScriptComponent。思考:Actor之间的父子关系是怎么确定的?你应该已经注意到了Actor里面的TArray&AActor*& Children字段,所以你可能会期望看到Actor:AddChild之类的方法,很遗憾。在UE里,Actor之间的父子关系却是通过Component确定的。同一般的Parent:AddChild操作原语不同,UE里是通过Child:AttachToActor或Child:AttachToComponent来创建父子连接的。void AActor::AttachToActor(AActor* ParentActor, const FAttachmentTransformRules& AttachmentRules, FName SocketName)\n{\n
if (RootComponent && ParentActor)\n
USceneComponent* ParentDefaultAttachComponent = ParentActor-&GetDefaultAttachComponent();\n
if (ParentDefaultAttachComponent)\n
RootComponent-&AttachToComponent(ParentDefaultAttachComponent, AttachmentRules, SocketName);\n
}\n}\nvoid AActor::AttachToComponent(USceneComponent* Parent, const FAttachmentTransformRules& AttachmentRules, FName SocketName)\n{\n
if (RootComponent && Parent)\n
RootComponent-&AttachToComponent(Parent, AttachmentRules, SocketName);\n
}\n}\n3D世界里的“父子”关系,我们一般可能会认为就是3D世界里的变换的坐标空间“父子”关系,但如果再度扩展一下,如上所述,一个Actor可是可以带有多个SceneComponent的,这意味着一个Actor是可以带有多个Transform“锚点”的。创建父子时,你到底是要把当前Actor当作对方哪个SceneComponent的子?再进一步,如果你想更细控制到Attach到某个Mesh的某个Socket(关于Socket Slot,目前可以简单理解为一个虚拟插槽,提供变换锚点),你就更需要去寻找到特定的变换锚点,然后Attach的过程分别在Location,Roator,Scale上应用Rule来计算最后的位置。/** Rules for attaching components - needs to be kept synced to EDetachmentRule */\nUENUM()\nenum class EAttachmentRule : uint8\n{\n
/** Keeps current relative transform as the relative transform to the new parent. */\n
KeepRelative,\n
/** Automatically calculates the relative transform such that the attached component maintains the same world transform. */\n
KeepWorld,\n
/** Snaps transform to the attach point */\n
SnapToTarget,\n};\n所以Actor父子之间的“关系”其实隐含了许多数据,而这些数据都是在Component上提供的。Actor其实更像是一个容器,只提供了基本的创建销毁,网络复制,事件触发等一些逻辑性的功能,而把父子的关系维护都交给了具体的Component,所以更准确的说,其实是不同Actor的SceneComponent之间有父子关系,而Actor本身其实并不太关心。接下来的左侧派生链依次提供了物理,材质,网格最终合成了一个我们最普通常见的StaticMeshComponent。而右侧的ChildActorComponent则是提供了Component之下再叠加Actor的能力。聊一聊ChildActorComponent 同作为最常用到的Component之一,ChildActorComponent担负着Actor之间互相组合的胶水。这货在蓝图里静态存在的时候其实并不真正的创建Actor,而是在之后Component实例化的时候才真正创建。void UChildActorComponent::OnRegister()\n{\n
Super::OnRegister();\n
if (ChildActor)\n
if (ChildActor-&GetClass() != ChildActorClass)\n
DestroyChildActor();\n
CreateChildActor();\n
ChildActorName = ChildActor-&GetFName();\n
USceneComponent* ChildRoot = ChildActor-&GetRootComponent();\n
if (ChildRoot && ChildRoot-&GetAttachParent() != this)\n
// attach new actor to this component\n
// we can't attach in CreateChildActor since it has intermediate Mobility set up\n
// causing spam with inconsistent mobility set up\n
// so moving Attach to happen in Register\n
ChildRoot-&AttachToComponent(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale);\n
// Ensure the components replication is correctly initialized\n
SetIsReplicated(ChildActor-&GetIsReplicated());\n
else if (ChildActorClass)\n
CreateChildActor();\n
}\n}\nvoid UChildActorComponent::OnComponentCreated()\n{\n
Super::OnComponentCreated();\n
CreateChildActor();\n}\n这就导致了一个问题,当你把一个ActorClass拖进Level后,这个Actor实际是已经实例化了,你可以直接调整这个Actor的属性。但是你把它拖到另一个Actor Class里,它只会给你空空白白的ChildActorComponent的DetailsPanel,你想调整Actor的属性,就只能等生成了之后,用蓝图或代码去修改。这一点来说,其实还是挺不方便的,我个人觉得应该是还有优化的空间。修订4.14 Child Actor TemplatesUE终于听到了人民群众的呼声,在4.14里增加了Child Actor Templates来支持在子ChildActor的DetailsPannel里查看和修改属性。 后记花了这么多篇幅,才刚刚讲到Actor和Component这两个最基本的整体设计,而关于Actor,Component生命周期,Tick,事件传递等机制性的问题,还都没有展开。UE作为从1代至今4代,久经磨练的一款成熟引擎,GamePlay框架部分其实也就不到十个类,而这些类之间怎么组织,为啥这么设计,有什么权衡和考虑,我相信这里面其实是非常有讲究的。如果是UE的总架构师来讲解的话,肯定能有非常多的心得体会故事。而我们作为学习者,也应该尽量去体会琢磨它的用心,一方面磨练我们自己的架构设计能力,一方面也让我们更能掌握这个游戏的引擎。 从此篇开始,会循序渐进的探讨各个部分的结构设计,最后再从整体的框架上讨论该结构的优劣点。上篇:下篇:引用UE4.14---------------------------------------------------------------------------------------------------------------------------知乎专栏:UE4深入学习QQ群:(非新手入门群,请先学习完官方文档和视频教程) 微信公众号:aboutue,关于UE的一切新闻资讯、技巧问答、文章发布,欢迎关注。个人原创,未经授权,谢绝转载!","updated":"T06:07:30.000Z","canComment":false,"commentPermission":"anyone","commentCount":6,"likeCount":57,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T14:07:30+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-abf21ac2e3c_r.jpg","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":6,"likesCount":57},"":{"title":"《InsideUE4》GamePlay架构(二)Level和World","author":"fjz13","content":"引言上文谈到Actor和Component的关系,UE利用Actor的概念组成一片游戏对象森林,并利用Component组装扩展Actor的能力,让世界里拥有了形形色色的Actor们,拥有了自由表达3D世界的能力。 那么,这些Actor们,到底是怎么组织起来的呢?既然提到了世界,我们的直觉反应是采用一个\"World\"对象来包容所有的Actor们。但是当游戏的虚拟世界非常巨大时,这种方式就捉襟见肘了。首先,目前虽然PC的性能日益强大,但是依然内存也限制了不能一下子加载进所有的游戏资源;其次,因为玩家的活动和可见范围有限,为了最优性能,把即使是很远的跟玩家无关的对象也考虑进来也明显是不明智的。所以我们需要一种更细粒度的概念来划分世界。 不同的游戏引擎们,看待这个过程的角度和理念也不一样。Cocos2dx会认为游戏世界是由Scene组成的,Scene再由一个个Layer层叠表现,然后再有一个Director来导演整个游戏。Unity觉得世界也是由Scene组成的,然后一个Application来扮演上帝来LoadLevel,后来换成了SceneManager。其他的,有的会称为关卡(Level)或地图(map)等等。而UE中把这种拆分叫做关卡(Level),由一个或多个Level组成一个World。 不要觉得这种划分好像很随意,只是个名字不同而已。实际上一个游戏引擎的“世界观”关系到了一整串后续的内容组织,玩家的管理,世界的生成,变换和毁灭。游戏引擎内部的资源的加载释放也往往都是和这种划分(Level)绑定在一起的。Level在UE的世界中,我们之前已经有了空气(C++),土壤(UObject),物件(Actor)。而现在UE又施展神力创建了一片片大陆(Level),在这片大陆上(.map文件),Actor们秩序井然,各种地形拔地而起,植被繁茂,天空雾云缭绕,圣光普照,这也是玩家们降生开始精彩冒险的地方。 可以从ULevel的前缀U看出来Level(大陆)也确实是继承于UObject(土壤)的。那既然同属于Object下面的各Actor们都拥有了一定的智能能力(支持蓝图脚本),Level自然也得体现出大地的意志,所以默认带了一个土地公(ALevelScriptActor),允许我们在关卡里编写脚本,可以对本关卡里的所有Actor通过名字呼之则来,关卡蓝图实际上就代表着该片大陆上的运行规则。 在Level已经有了管理者之后,一开始大家都挺满意,但渐渐的就发现,好像各个Level需要的功能好像都差不多,都是修改一下光照,物理等一些属性。所以为了方便起见,UE便给每一个Level也都默认配了一个书记官(Info),他一一记录着本Level的各种规则属性,在UE需要的时候便负责相告。更重要的是,在Level需要有其他管理人员一起协助的时候,他也记录着“游戏模式”的名字来让UE可以指派。 前面我们说过,有一些Actor是不“显示”的(没有SceneComponent),是不能“摆放”到Level里的,但是它依然可以在关卡里出力。其中一个家族系列就是AInfo和其之类。今天我们只简单介绍一下跟Level直接相关的一位书记官:AWorldSettings。 其实虽然名字叫做WorldSettings,但其实只是跟Level相关,我猜可能是在上古时代,当时整个世界只有一块大陆,人们就以为当前的大陆就是整个世界,所以给这块大陆的设置就起名为WorldSettings,后来等技术进步了,发现必须有其他大陆了,这个名字已经用得太多反而不好改了,就只好遗留下来了。当然也有可能是因为当Level被添加进World后,这个Level的Settings如果是主PersistentLevel,那它就会被当作整个World的WorldSettings。 注意,Actors里也保存着AWorldSettings和ALevelScriptActor的指针,所以Actors实际上确实是保存了所有Actor。思考:为何AWorldSettings要放进在Actors[0]的位置?而ALevelScriptActor却不用?void ULevel::SortActorList()\n{\n
TArray&AActor*& NewA\n
TArray&AActor*& NewNetA\n
NewActors.Reserve(Actors.Num());\n
NewNetActors.Reserve(Actors.Num());\n
// The WorldSettings tries to stay at index 0\n
NewActors.Add(WorldSettings);\n
// Add non-net actors to the NewActors immediately, cache off the net actors to Append after\n
for (AActor* Actor : Actors)\n
if (Actor != nullptr && Actor != WorldSettings && !Actor-&IsPendingKill())\n
if (IsNetActor(Actor))\n
NewNetActors.Add(Actor);\n
NewActors.Add(Actor);\n
iFirstNetRelevantActor = NewActors.Num();\n
NewActors.Append(MoveTemp(NewNetActors));\n
Actors = MoveTemp(NewActors);
// Replace with sorted list.\n
// Add all network actors to the owning world\n
//[...]\n}\n实际上通过这一段代码可知,Actors们的排序依据是把那些“非网络”的Actor放在前面,而把“网络可复制”的Actor们放在后面,然后加一个起始索引标记iFirstNetRelevantActor,相当于为网络Actor划分了一个缓存,从而加速了网络复制时的检测速度。AWorldSettings因为都是静态的数据提供者,在游戏运行过程中也不会改变,不需要网络复制,所以也就可以一直放在前列,而如果再加个规则,一直放在第一个的话,也能同时把AWorldSettings和其他的前列Actor们再度区分开,在需要的时候也能加速判断。ALevelScriptActor因为是代表关卡蓝图,是允许携带“复制”变量函数的,所以也有可能被排序到后列。思考:既然ALevelScriptActor也继承于AActor,为何关卡蓝图不设计能添加Component? 观察到,平常我们在创建Actor的时候,我们蓝图界面是可以创建Component的。 那为什么在关卡蓝图里,却不能这么做(没有提供该界面功能)? 我虽然在图里标出了Level中拥有ModelComponents,但那其实只是针对BSP应用的一个子集。通过源码发现,其实UE自己也是在C++里往ALevelScriptActor添加UInputComponent来实现关卡蓝图可以响应事件。void ALevelScriptActor::PreInitializeComponents()\n{\n
if (UInputDelegateBinding::SupportsInputDelegate(GetClass()))\n
// create an InputComponent object so that the level script actor can bind key events\n
InputComponent = NewObject&UInputComponent&(this);\n
InputComponent-&RegisterComponent();\n
UInputDelegateBinding::BindInputDelegates(GetClass(), InputComponent);\n
Super::PreInitializeComponents();\n}其实既然ALevelScriptActor是个Actor,那意味着我们当然可以为它添加组件,实际上也确实可以这么做。比如你可以在关卡蓝图里这么干: 而如果你实际意识到关卡蓝图本身就是一个看不见的Actor,你就可以在上面用Actor的各种操作: 在关卡蓝图里的self其实也是个Actor!虽然一般这么干也没什么毛用。 那么好好想想,为啥UE要给你这么一个关卡蓝图界面呢? 在此,我也只能进行一番猜测,ALevelScriptActor作为一个特化的Actor,却把Components列表界面给隐藏了,说明UE其实是不希望我们去复杂化关卡构成的。 假设说UE开放了关卡Component,那么我们在创建组件时就必然要考虑一个问题:哪些是ActorComponent,哪些是LevelComponent,再怎么ALevelScriptActor本质是个Actor,但Level的概念还是要突出,ALevelScriptActor的Actor本质是要隐藏的。所以用户就会多一些心智负担,可能混淆。而如果像这样不开放,大家的思路就都转向先创建个Actor,然后再往之上添加component,思路会比较统一清晰。 再之,从游戏逻辑的组织上来说,Level其实更应该表现为一个Actor的容器。UE其实也是不鼓励在Level里编写太复杂的逻辑的。所以才接着会有了之后的GameMode,Controller那些真正的逻辑控制类(后续会再细讨论)。 所以游戏引擎也并不是说最大化的暴露一切功能给你就是最好的,有时候选择太多了反而容易出错。在这一点上,我觉得UE很好的保持了克制,为我们提供了一个优秀的清晰的不易出错的框架,同时也对高阶用户保留了灵活性。World终于,到了把大陆们(Level)拼装起来的时候了。可以用SubLevel的方式: 也支持WorldComposition的方式自动把项目里的所有Level都组合起来,并设置摆放位置: 具体摆放的操作和技巧并不是本文的重点。简单本质来说,就是一个World里有多个Level,这些Level在什么位置,是在一开始就加载进来,还是Streaming运行时加载。 UE里每个World支持一个PersistentLevel和多个其他Level: Persistent的意思是一开始就加载进World,Streaming是后续动态加载的意思。Levels里保存有所有的当前已经加载的Level,StreamingLevels保存整个World的Levels配置列表。PersistentLevel和CurrentLevel只是个快速引用。在编辑器里编辑的时候,CurrentLevel可以指向其他Level,但运行时CurrentLevel只能是指向PersistentLevel。思考:为何要有主PersistentLevel? 首先,World至少得有一个Level,就像你也得先出生在一块大陆上才可以继续谈起去探索别的新大陆。所以这块玩家出生的大陆就是主Level了。当然了,因为我们也可以同时配置别的Level一开始就加载进来,其实跟PersistentLevel是差不多等价的,但再考虑到另一问题:Levels拼接进World一起之后,各自有各自的worldsetting,那整个World的配置应该以谁的为主?AWorldSettings* UWorld::GetWorldSettings( bool bCheckStreamingPesistent, bool bChecked ) const\n{\n
checkSlow(IsInGameThread());\n
AWorldSettings* WorldSettings =\n
if (PersistentLevel)\n
WorldSettings = PersistentLevel-&GetWorldSettings(bChecked);\n
if( bCheckStreamingPesistent )\n
if( StreamingLevels.Num() & 0 &&\n
StreamingLevels[0] &&\n
StreamingLevels[0]-&IsA&ULevelStreamingPersistent&()) \n
ULevel* Level = StreamingLevels[0]-&GetLoadedLevel();\n
if (Level != nullptr)\n
WorldSettings = Level-&GetWorldSettings();\n
return WorldS\n}可以看出,World的Settings也是以PersistentLevel为主的,但这也并不以为着其他Level的Settings就完全没有作用了,本篇也无法一一列出所有配置选项来说明,简单来说,就是需要在整个世界范围内起作用的配置选项(比如VR的WorldToMeters,KillZ,WorldGravity其他大部分都是)就是需要从主PersistentLevel的配置中提取。而一些配置选项可以在单独Level中起作用的,比如在编辑Level时的光照质量配置就是一个个Level单独的,目前这种配置很少,但可能以后也会增加。在这里只是阐明一个为主其他为辅的Level配置系统。思考:Levels们的Actors和World有直接关系吗? 当别的Level被添加进当前World之后,我们能直接在WorldOutliner里看到其他Level的Actor们。 但这并不代表着World直接引用了Level里的Actor们。TActorIteratorBase(World的Actor迭代器)内部的实现也只是在遍历Levels来获得所有Actor。当然World为了更快速的操作Controllers和Pawn也都保存了引用。但Levels却共享着World的一个PhysicsScene,这也意味着Levels里的Actors的物理实体其实都是在World里的,这也好理解,毕竟物理的碰撞之类的当然要是全局的了。再说到导航,World在拼接Level的时候,也是会同时把两个Level的导航网格给“拼接”起来的。当然目前还不是深入细节的时候,现在只要从大局上明白World-Level-Actor的关系。思考:为什么要在Level里保存Actors,而不是把所有Map的Actors配置都生成在World一个总Actors里? 这肯定也是一种实现方式,好处是把整个World看成一个整体,所有的actors都从属于world,这样就不存在Level边界,可以更整体的处理Actors的作用范围和判定问题,实现上也少了拼接导航等步骤。当然坏处也是模糊了Level边界,这样在加载进一个Level之后,之后再动态释放,就需要再重新再从整体中抽离出部分来释放,这个筛选过程也会产生比较大的损耗。试着去理解UE的权衡,应该是尽量的把损耗平摊(这里是把Level加载释放的损耗尽量减小),才不会产生比较大的帧率波动,让玩家感觉到卡帧。总结Level作为Actor的容器,同时也划分了World,一方面支持了Level的动态加载,另一方面也允许了团队的实时协作,大家可以同时并行编辑不同的Level。一般而言,一个玩家从游戏开始到结束,UE会创造一个GameWorld给玩家并一直存在。玩家切换场景或关卡,也只是在这个World中加载释放不同的Level。既然Level拥有了管理者(LevelScriptActor),玩家可以编写特定关卡的逻辑,那么我们能否对World这种层次编写逻辑呢?答案是肯定的,不过本文篇幅有限,敬请期待下篇。上篇:下篇:UE4.14---------------------------------------------------------------------------------------------------------------------------知乎专栏:UE4深入学习QQ群:(非新手入门群,请先学习完官方文档和视频教程) 微信公众号:aboutue,关于UE的一切新闻资讯、技巧问答、文章发布,欢迎关注。个人原创,未经授权,谢绝转载!","updated":"T06:25:08.000Z","canComment":false,"commentPermission":"anyone","commentCount":8,"likeCount":36,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T14:25:08+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-0f7fea665f4a717d0ed1_r.jpg","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":8,"likesCount":36},"":{"title":"《InsideUE4》GamePlay架构(三)WorldContext,GameInstance,Engine","author":"fjz13","content":"引言前文提到说一个World管理多个Level,并负责它们的加载释放。那么,问题来了,一个游戏里是只有一个World吗?WorldContext答案是否定的,首先World就不是只有一种类型,比如编辑器本身就也是一个World,里面显示的游戏场景也是一个World,这两个World互相协作构成了我们的编辑体验。然后点播放的时候,引擎又可以生成新的类型World来让我们测试。简单来说,UE其实是一个平行宇宙世界观。 以下是一些世界类型:namespace EWorldType\n{\n\tenum Type\n\t{\n\t\tNone,\t\t// An untyped world, in most cases this will be the vestigial worlds of streamed in sub-levels\n\t\tGame,\t\t// The game world\n\t\tEditor,\t\t// A world being edited in the editor\n\t\tPIE,\t\t// A Play In Editor world\n\t\tPreview,\t// A preview world for an editor tool\n\t\tInactive\t// An editor world that was loaded but not currently being edited in the level editor\n\t};\n}而UE用来管理和跟踪这些World的工具就是WorldContext: FWorldContext保存着ThisCurrentWorld来指向当前的World。而当需要从一个World切换到另一个World的时候(比如说当点击播放时,就是从Preview切换到PIE),FWorldContext就用来保存切换过程信息和目标World上下文信息。所以一般在切换的时候,比如OpenLevel,也都会需要传FWorldContext的参数。一般就来说,对于独立运行的游戏,WorldContext只有唯一个。而对于编辑器模式,则是一个WorldContext给编辑器,一个WorldContext给PIE(Play In Editor)的World。一般来说我们不需要直接操作到这个类,引擎内部已经处理好各种World的协作。 不仅如此,同时FWorldContext还保存着World里Level切换的上下文:struct FWorldContext\n{\n
[...]\n\tTEnumAsByte&EWorldType::Type&\tWorldT\n\n\tFSeamlessTravelHandler SeamlessTravelH\n\n\tFName ContextH\n\n\t/** URL to travel to for pending client connect */\n\tFString TravelURL;\n\n\t/** TravelType for pending client connects */\n\tuint8 TravelT\n\n\t/** URL the last time we traveled */\n\tUPROPERTY()\n\tstruct FURL LastURL;\n\n\t/** last server we connected to (for \"reconnect\" command) */\n\tUPROPERTY()\n\tstruct FURL LastRemoteURL;\n\n}\n这里的TravelURL和TravelType就是负责设定下一个Level的目标和转换过程。// Traveling from server to server.\nUENUM()\nenum ETravelType\n{\n\t/** Absolute URL. */\n\tTRAVEL_Absolute,\n\t/** Partial (carry name, reset server). */\n\tTRAVEL_Partial,\n\t/** Relative URL. */\n\tTRAVEL_Relative,\n\tTRAVEL_MAX,\n};\n\nvoid UEngine::SetClientTravel( UWorld *InWorld, const TCHAR* NextURL, ETravelType InTravelType )\n{\n\tFWorldContext &Context = GetWorldContextFromWorldChecked(InWorld);\n\t// set TravelURL.
Will be processed safely on the next tick in UGameEngine::Tick().\n\tContext.TravelURL
= NextURL;\n\tContext.TravelType
= InTravelT\n
[...]\n}\n粗略的流程是UE在OpenLevel的时候, 先设置当前World的Context上的TravelURL,然后在UEngine::TickWorldTravel的时候判断TravelURL非空来真正执行Level的切换。具体的Level切换详细流程比较复杂,目前先从大局上理解整体结构。总而言之,WorldContext既负责World之间切换的上下文,也负责Level之间切换的操作信息。思考:为何Level的切换信息不放在World里? 因为UE有一个逻辑,一个World只有一个PersistentLevel(见上篇),而当我们OpenLevel一个PersistentLevel的时候,实际上引擎做的是先释放掉当前的World,然后再创建个新的World。所以如果我们把下一个Level的信息放在当前的World中,就不得不在释放当前World前又拷贝回来一遍了。 而LoadStreamLevel的时候,就只是在当前的World中载入对象了,所以其实就没有这个限制了。void UGameplayStatics::LoadStreamLevel(UObject* WorldContextObject, FName LevelName,bool bMakeVisibleAfterLoad,bool bShouldBlockOnLoad,FLatentActionInfo LatentInfo)\n{\n\tif (UWorld* World = GEngine-&GetWorldFromContextObject(WorldContextObject))\n\t{\n\t\tFLatentActionManager& LatentManager = World-&GetLatentActionManager();\n\t\tif (LatentManager.FindExistingAction&FStreamLevelAction&(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr)\n\t\t{\n\t\t\tFStreamLevelAction* NewAction = new FStreamLevelAction(true, LevelName, bMakeVisibleAfterLoad, bShouldBlockOnLoad, LatentInfo, World);\n\t\t\tLatentManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, NewAction);\n\t\t}\n\t}\n}\nWorld-&GetLatentActionManager()其实也算是保存在当前World里了。思考:为何World和Level的切换要放在下一帧再执行? 首先Level的加载显然是比较慢的,需要载入Map,相应的Mesh,Material……等等。所以这个操作就必须异步化,异步的话其实就剩下两种方式,一种是先记录下来信息之后再执行;一种是命令模式立马往队列里压个命令之后再执行。注意,因为OpenLevel还要相应在主线程生成相应Actor对象,所以有些部分还是要在主线程完成的。这两种模式其实都可以达成需求,前者更加简单明了,后者相对统一。UE也是个进化过来的引擎,也并不是所有的代码都完美无缺。猜想其实也是一开始这么简单就这么做了,后来也没有特别大的改动的动力就一直这样了。引擎最终比的是生产效率的提高,确实也不是代码有多优雅。GameInstance那么这些WorldContexts又是保存在哪里的呢?追根溯源: GameInstance里会保存着当前的WorldConext和其他整个游戏的信息。明白了GameInstance是比World更高的层次之后,我们也就能明白为何那些独立于Level的逻辑或数据要在GameInstance中存储了。 这一点其实也很好理解,大凡游戏引擎都会有一个Game的概念,不管是叫Application还是Director,它都是玩家能直接接触到的最根源的操作类。而UE的GameInstance因为继承于UObject,所以就拥有了动态创建的能力,所以我们可以通过指定GameInstanceClass来让UE创建使用我们自定义的GameInstance子类。所以不论是C++还是BP,我们通常会继承于GameInstance,然后在里面编写应用于整个游戏范围的逻辑。 因为经常有初学者会问到:我的Level切换了,变量数据就丟了,我应该把那些数据放在哪?再清晰直白一点,GameInstance就是你不管Level怎么切换,还是会一直存在的那个对象!Engine让我们继续再往上,终于得见UE大神: 此处UEngine分化出了两个子类:UGameEngine和UEditorEngine。众所周知,UE的编辑器也是UE用自己的引擎渲染出来的,采用的也是Slate那套UI框架。好处有很多,比如跨平台比较统一,UI框架可以复用一套控件库,Dogfood等等,此处不再细讲。所以本质上来说,UE的编辑器其实也是个游戏!我们是在编辑器这个游戏里面创造我们自己的另一个游戏。话虽如此,但比较编辑器和游戏还是有一定差别的,所以UE会在不同模式下根据编译环境而采用不同的具体Engine类,而在基类UEngine里通过一个WorldList保存了所有的World。Standlone Game:会使用UGameEngine来创建出唯一的一个GameWorld,因为也只有一个,所以为了方便起见,就直接保存了GameInstance指针。而对于编辑器来说,EditorWorld其实只是用来预览,所以并不拥有OwningGameInstance,而PlayWorld里的OwningGameInstance才是间接保存了GameInstance.目前来说,因为UE还不支持同时运行多个World(当前只能一个,但可以切换),所以GameInstance其实也是唯一的。提前说些题外话,虽然目前网络部分还没涉及到,但是当我们在Editor里进行MultiplePlayer的测试时,每一个Player Window里都是一个World。如果是DedicateServer模式,那DedicateServer也会是一个World。 最后实例化出来的UEngine实例用一个全局的GEngine变量来保存。至此,我们已经到了引擎的最根处://UnrealEngine\\Engine\\Source\\Runtime\\Engine\\Private\\UnrealEngine.cpp\nENGINE_API UEngine*\tGEngine = NULL;\nGEngine可以说是一切开始的地方了。翻看引擎源码,到处也可以看见从GEngine-&出来的引用。GamePlayStatics既然我们在引擎内部C++层次已经有了访问World操作Level的能力,那么在暴露出的蓝图系统里,UE为了我们的使用方便,也在Engine层次为我们提供了便利操作蓝图函数库。UCLASS ()\nclass UGameplayStatics : public UBlueprintFunctionLibrary \n我们在蓝图里见到的GetPlayerController、SpawActor和OpenLevel等都是来至于这个类的接口。这个类比较简单,相当于一个C++的静态类,只为蓝图暴露提供了一些静态方法。在想借鉴或者是查询某个功能的实现时,此处往往会是一个入口。总结从结构上而言,我们已经来到了最根源的地方。GEngine仿佛就是一棵大树的根,当我们拎起它的时候,也会带出整个游戏世界的各个对象。但目前这些对象:Object-&Actor+Component-&Level-&World-&WorldContext-&GameInstance-&Engine,确实已经足够表达UE游戏世界的各个部分。 那作为GamePlay部分而言,我们还有一个问题:UE是如何把在该对象结构上表达游戏逻辑的? 如果说:“程序=数据+算法”的话,那UE的GamePlay我们已经讨论完了数据部分,而下篇我们将开始讨论UE的游戏逻辑“算法”部分。上篇:下篇:UE4.14---------------------------------------------------------------------------------------------------------------------------知乎专栏:UE4深入学习QQ群:(非新手入门群,请先学习完官方文档和视频教程) 微信公众号:aboutue,关于UE的一切新闻资讯、技巧问答、文章发布,欢迎关注。个人原创,未经授权,谢绝转载!","updated":"T06:03:25.000Z","canComment":false,"commentPermission":"anyone","commentCount":8,"likeCount":32,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T14:03:25+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-a8de534fdd9dc42c233a5c2f5bfd2332_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":8,"likesCount":32},"":{"title":"《InsideUE4》GamePlay架构(四)Pawn","author":"fjz13","content":"我像是一颗棋 进退任由你决定 我不是你眼中唯一将领 却是不起眼的小兵引言欢迎来到GamePlay架构章节的下半部分! 在上一篇的内容里,我们谈到了UE的3D游戏世界是由Object-&Actor+Component-&Level-&World-&WorldContext-&GameInstance-&Engine来逐渐层层构建而成的。那么从这下半章节开始,我们就将要开始逐一分析,UE是如何在每一个对象层次上表达游戏逻辑的。和分析对象节点树一样,我们也将采用自底向上的方法,从最原始简单的对象开始。首先需要明确的是,本部分接下来要讲述的UE的GamePlay逻辑框架部分,只是讨论UE的设计思想和理念,并不是表示其在所有其他游戏引擎中是最优最完美的方案,同时当然也不是开发人员务必遵守的金科玉律,你依然可以也应该根据自己实际情况灵活变通。UE经过了很多权衡设计和历史进化,最后选择了该设计方案,一方面和对象层级相辅相成,另一方面也提供了足够的自由度可以供你腾挪。 实现一个游戏业务功能的方式有多种,你应该尽量妥善的权衡你当前的现实情况,考虑生产效率、维护性、功能实现、易理解、性能等等多种因素,然后选择你认为最恰当的方式。如果你当前在制作一个快速原型Demo,你大可以简单粗暴,我也不赞成时刻谨遵教条主义一定要分层拆分如何如何;而如果是面对一个正式的比较大型项目,随着规模的扩大,我们就得利用清晰的概念来帮助我们减轻心智负担。UE作为一个老牌的经历了十几年风风雨雨的游戏引擎,也当然有它的一套GamePlay哲学。我们选择了UE,接受了在UE的工作流之下工作,如果我们能比较好的理解它的概念和思想,就能更加的“顺”着它的思路,得心应手海阔任鱼跃。而如果我们“逆”着这个框架来搞自己的一套,一是不免有无法充分利用UE的嫌疑,二也是以UE的庞大和根深错节难免让你碰一头灰费力不讨好。Note1:虽然本部分会涉及到游戏的业务逻辑编写部分,但并不打算详细讨论AI(BehaviorTree,Navigation等)。AI也是一个很大的话题,值得专门开个大章节讨论,我们现在不应该委屈她。 Note2:本部分也不会细讨论输入事件的处理,游戏逻辑确实一大部分是由输入事件驱动起来的,不过我们此时只是简单聊一下概念,后续会有章节再细讨论输入事件的路由流程。 Note3:联机游戏的游戏逻辑自然也是非常重要的,但为了简化本章节的概念,所以网络联机的逻辑同步等也都不会涉及。留待后续网络章节再好好的阐述。ComponentActor可以说是由Component组成的,所以Component其实是我们对象树里最底层的员工了。在UE里,Component表达的是“功能”的概念。比如说你要实现一个可以响应的WASD移动的功能,或者是VR里抓取的功能,甚至是嵌套另一个Actor的功能,这些都是一个个组件。正确理解“功能”和“游戏业务逻辑”的区分是理解Component的关键要点。 所以我们在这一个层级上要编写的逻辑,是实现一个个“与特定游戏无关”的功能。理想情况下,等你的一个游戏完成,你那些已经实现完成的Components是可以无痛迁移到下一个游戏中用的。换言之,一旦你发现你在Component中含有游戏的业务逻辑代码,这就是所谓的“Bad Smell”了,要警惕游戏架构是否恰当,是否没有很清晰的概念划分。Actor如果说UE是一个大国家的话,那Actor无疑就是人口最大的民族了。StaticMeshActor,CameraActor……我们天天口里嚷嚷的也都是它。和Unity的Prefab对应的,在UE里我们用的最多的也是BlueprintActor了,我们也常常自定义我们的Actor子类来组装其他Component和Actor,然后再编写一些协作逻辑代码,就似乎完成了一个骁勇善战的特种兵,接下来就可以撒豆成兵般的往Level中扔了。 用的越广泛越多,往往错的也越多。似乎是受到了一种朴素的子承父业的精神感染,也或许是我们的面向对象编程都学得太好的缘故,我们都非常倾向于直接在Actor里堆砌逻辑。右键一个BlueprintActor,刚添加完Component,就立马撸起袖子来,Event、Function和Variable一个个罗列开来,噼里啪啦无不快活!但是且慢,这是最好的方式了吗?让我们一路带着这个问题,试着从UE角度去推演一下,重走一下Actor进化之路。在本章节旅程的终点,我保证,我们可以比较清楚的回答这个问题。其实所有的游戏引擎在构建完节点树之后,都会面临这么一个问题,我的游戏逻辑写在哪里? 有的原始的如Cocos2dx懒得想那么多,干脆就直接码在Node里面得了,所以你翻看Cocos2dx的源码你就会经常发现它的逻辑和表现往往是交杂在一起的,简单直接暴力美学,面向对象继承玩得溜。而面向组合阵营的领军Unity则干脆就把Component思想再应用极致一点,我的逻辑为什么不能也是一个组件?所以Unity里的ScriptComponent也是这种组合思想的体现,模型统一架构优雅,MonoBehavior立大功了!但是在一个Component(ScriptComponent)里去操作管理其他的Components,本身却其实并不是那么优雅,因为有些Component之上的协调管理的事务,从层次上来说,应该放在更高的一个概念上实现。UE在思考这个问题时,却是感觉有些理想主义,颇有些C++的理念,力求不为你不需要的东西付代价,宁愿有时候折衷,也想保住最优性能。UE的架构中也大量应用了各种继承,有些继承链也能拉得很长,同时一方面也吸纳了组合的优点,我们也能见到UE的源码中类的成员变量也是组合了好多其他对象。所以接下来的该介绍的就是UE综合应用这两种思想的设计产物。面向对象派生下来的Pawn和Character,支持组合的Controller们。Pawn那么第二个至关重要的的问题是,哪些Actor需要附加逻辑? 在游戏中,我们之所以会觉得一个角色生动,是因为它会响应我们的交互,并给出恰当的反应。而我们所谓的游戏业务逻辑,实际上编写的就是该如何对玩家的输入提供反馈。同样,一个Actor想要变得“生动”,就得有响应外部输入的能力,否则就只是自动运转麻木的机器人。但是在一个比较大型的3D游戏中,Actor有千千万万,然后并不是所有的Actor都需要和玩家互动,得宠的能直接面圣和玩家互动的Actor也是比较少的。我们经常都只是操作我们的“角色”,让“角色”和场景里的其他物体互动。比如FPS游戏里我们操作的主角或者是FlappyBird里的那只小鸟。所以从这一点上来看,UE中Actor就立马又可以划分出一个类别了,这些Actor们可谓是玩家们的宠儿,它们是玩家们的亲卫兵,对,它的名字就是Pawn! 同其他AInfo一样,UE也是从Actor中再派生出了APawn,并定义了3块基本的模板方法接口: 1. 可被Controller控制 2. PhysicsCollision表示 3. MovementInput的基本响应接口为了更好理解这个概念,让我们看一下用搜索引擎搜一下Pawn得到的图: 没错,Pawn的英文翻译过来可以是兵卒,所以如果把UE游戏看作是一场棋盘上的

我要回帖

更多关于 混沌 的文章

 

随机推荐