Unreal Engine 4 系列教程 Part 2:蓝图教程
.output_wrapper a:hover { text-decoration: underline; color: rgba(0, 96, 100, 1) }
.output_wrapper figcaption { margin-top: 10px; text-align: center; color: rgba(153, 153, 153, 1); font-size: 0.7em }
.output_wrapper pre code .linenum { padding-right: 20px; word-spacing: 0 }
.task-list-list { list-style-type: none }
.task-list-list.checked { color: rgba(62, 62, 62, 1) }
.task-list-list.uncheck { color: rgba(191, 193, 191, 1) }
.task-list-list .icon_uncheck, .task-list-list .icon_check { display: inline-block; vertical-align: middle; margin-right: 10px }
.task-list-list .icon_check::before { content: “√”; border: 2px solid rgba(62, 62, 62, 1); color: rgba(255, 0, 0, 1) }
.task-list-list .icon_uncheck::before { content: “x”; border: 2px solid rgba(191, 193, 191, 1); color: rgba(191, 193, 191, 1) }
.task-list-list .icon_check::before, .task-list-list .icon_uncheck::before { padding: 2px 8px 2px 5px; border-radius: 5px }
@font-face { font-family: KaTeX_AMS; src: url(“fonts/KaTeX_AMS-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_AMS-Regular.woff”) format(“woff”), url(“fonts/KaTeX_AMS-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Caligraphic; src: url(“fonts/KaTeX_Caligraphic-Bold.woff2”) format(“woff2”), url(“fonts/KaTeX_Caligraphic-Bold.woff”) format(“woff”), url(“fonts/KaTeX_Caligraphic-Bold.ttf”) format(“truetype”); font-weight: bold; font-style: normal }
@font-face { font-family: KaTeX_Caligraphic; src: url(“fonts/KaTeX_Caligraphic-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Caligraphic-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Caligraphic-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Fraktur; src: url(“fonts/KaTeX_Fraktur-Bold.woff2”) format(“woff2”), url(“fonts/KaTeX_Fraktur-Bold.woff”) format(“woff”), url(“fonts/KaTeX_Fraktur-Bold.ttf”) format(“truetype”); font-weight: bold; font-style: normal }
@font-face { font-family: KaTeX_Fraktur; src: url(“fonts/KaTeX_Fraktur-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Fraktur-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Fraktur-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Main; src: url(“fonts/KaTeX_Main-Bold.woff2”) format(“woff2”), url(“fonts/KaTeX_Main-Bold.woff”) format(“woff”), url(“fonts/KaTeX_Main-Bold.ttf”) format(“truetype”); font-weight: bold; font-style: normal }
@font-face { font-family: KaTeX_Main; src: url(“fonts/KaTeX_Main-BoldItalic.woff2”) format(“woff2”), url(“fonts/KaTeX_Main-BoldItalic.woff”) format(“woff”), url(“fonts/KaTeX_Main-BoldItalic.ttf”) format(“truetype”); font-weight: bold; font-style: italic }
@font-face { font-family: KaTeX_Main; src: url(“fonts/KaTeX_Main-Italic.woff2”) format(“woff2”), url(“fonts/KaTeX_Main-Italic.woff”) format(“woff”), url(“fonts/KaTeX_Main-Italic.ttf”) format(“truetype”); font-weight: normal; font-style: italic }
@font-face { font-family: KaTeX_Main; src: url(“fonts/KaTeX_Main-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Main-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Main-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Math; src: url(“fonts/KaTeX_Math-BoldItalic.woff2”) format(“woff2”), url(“fonts/KaTeX_Math-BoldItalic.woff”) format(“woff”), url(“fonts/KaTeX_Math-BoldItalic.ttf”) format(“truetype”); font-weight: bold; font-style: italic }
@font-face { font-family: KaTeX_Math; src: url(“fonts/KaTeX_Math-Italic.woff2”) format(“woff2”), url(“fonts/KaTeX_Math-Italic.woff”) format(“woff”), url(“fonts/KaTeX_Math-Italic.ttf”) format(“truetype”); font-weight: normal; font-style: italic }
@font-face { font-family: KaTeX_SansSerif; src: url(“fonts/KaTeX_SansSerif-Bold.woff2”) format(“woff2”), url(“fonts/KaTeX_SansSerif-Bold.woff”) format(“woff”), url(“fonts/KaTeX_SansSerif-Bold.ttf”) format(“truetype”); font-weight: bold; font-style: normal }
@font-face { font-family: KaTeX_SansSerif; src: url(“fonts/KaTeX_SansSerif-Italic.woff2”) format(“woff2”), url(“fonts/KaTeX_SansSerif-Italic.woff”) format(“woff”), url(“fonts/KaTeX_SansSerif-Italic.ttf”) format(“truetype”); font-weight: normal; font-style: italic }
@font-face { font-family: KaTeX_SansSerif; src: url(“fonts/KaTeX_SansSerif-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_SansSerif-Regular.woff”) format(“woff”), url(“fonts/KaTeX_SansSerif-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Script; src: url(“fonts/KaTeX_Script-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Script-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Script-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Size1; src: url(“fonts/KaTeX_Size1-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Size1-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Size1-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Size2; src: url(“fonts/KaTeX_Size2-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Size2-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Size2-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Size3; src: url(“fonts/KaTeX_Size3-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Size3-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Size3-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Size4; src: url(“fonts/KaTeX_Size4-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Size4-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Size4-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@font-face { font-family: KaTeX_Typewriter; src: url(“fonts/KaTeX_Typewriter-Regular.woff2”) format(“woff2”), url(“fonts/KaTeX_Typewriter-Regular.woff”) format(“woff”), url(“fonts/KaTeX_Typewriter-Regular.ttf”) format(“truetype”); font-weight: normal; font-style: normal }
@media screen { .katex .mtable .vertical-separator { min-width: 1px } .katex .mfrac .frac-line, .katex .overline .overline-line, .katex .underline .underline-line, .katex .hline, .katex .hdashline, .katex .rule { min-height: 1px } }
.katex-display { display: block; margin: 1em 0; text-align: center }
.katex-display>.katex { display: block; text-align: center; white-space: nowrap }
.katex-display>.katex>.katex-html { display: block }
.katex-display>.katex>.katex-html>.tag { position: absolute; right: 0 }
.katex { font: 1.21em / 1.2 KaTeX_Main, “Times New Roman”, serif; text-indent: 0; text-rendering: auto }
.katex * { }
.katex .katex-mathml { position: absolute; clip: rect(1px, 1px, 1px, 1px); padding: 0; border: 0; height: 1px; width: 1px; overflow: hidden }
.katex .katex-html { }
.katex .katex-html>.newline { display: block }
.katex .base { position: relative; display: inline-block; white-space: nowrap; width: min-content }
.katex .strut { display: inline-block }
.katex .textbf { font-weight: bold }
.katex .textit { font-style: italic }
.katex .textrm { font-family: KaTeX_Main }
.katex .textsf { font-family: KaTeX_SansSerif }
.katex .texttt { font-family: KaTeX_Typewriter }
.katex .mathit { font-family: KaTeX_Math; font-style: italic }
.katex .mathrm { font-style: normal }
.katex .mathbf { font-family: KaTeX_Main; font-weight: bold }
.katex .boldsymbol { font-family: KaTeX_Math; font-weight: bold; font-style: italic }
.katex .amsrm { font-family: KaTeX_AMS }
.katex .mathbb, .katex .textbb { font-family: KaTeX_AMS }
.katex .mathcal { font-family: KaTeX_Caligraphic }
.katex .mathfrak, .katex .textfrak { font-family: KaTeX_Fraktur }
.katex .mathtt { font-family: KaTeX_Typewriter }
.katex .mathscr, .katex .textscr { font-family: KaTeX_Script }
.katex .mathsf, .katex .textsf { font-family: KaTeX_SansSerif }
.katex .mainit { font-family: KaTeX_Main; font-style: italic }
.katex .mainrm { font-family: KaTeX_Main; font-style: normal }
.katex .vlist-t { display: inline-table; table-layout: fixed }
.katex .vlist-r { display: table-row }
.katex .vlist { display: table-cell; vertical-align: bottom; position: relative }
.katex .vlist>span { display: block; height: 0; position: relative }
.katex .vlist>span>span { display: inline-block }
.katex .vlist>span>.pstrut { overflow: hidden; width: 0 }
.katex .vlist-t2 { margin-right: -2px }
.katex .vlist-s { display: table-cell; vertical-align: bottom; font-size: 1px; width: 2px; min-width: 2px }
.katex .msupsub { text-align: left }
.katex .mfrac>span>span { text-align: center }
.katex .mfrac .frac-line { display: inline-block; width: 100%; border-bottom-style: solid }
.katex .mspace { display: inline-block }
.katex .llap, .katex .rlap, .katex .clap { width: 0; position: relative }
.katex .llap>.inner, .katex .rlap>.inner, .katex .clap>.inner { position: absolute }
.katex .llap>.fix, .katex .rlap>.fix, .katex .clap>.fix { display: inline-block }
.katex .llap>.inner { right: 0 }
.katex .rlap>.inner, .katex .clap>.inner { left: 0 }
.katex .clap>.inner>span { margin-left: -50%; margin-right: 50% }
.katex .rule { display: inline-block; border: 0 solid; position: relative }
.katex .overline .overline-line, .katex .underline .underline-line, .katex .hline { display: inline-block; width: 100%; border-bottom-style: solid }
.katex .hdashline { display: inline-block; width: 100%; border-bottom-style: dashed }
.katex .sqrt>.root { margin-left: 0.277778em; margin-right: -0.555556em }
.katex .sizing, .katex .fontsize-ensurer { display: inline-block }
.katex .sizing.reset-size1.size1, .katex .fontsize-ensurer.reset-size1.size1 { font-size: 1em }
.katex .sizing.reset-size1.size2, .katex .fontsize-ensurer.reset-size1.size2 { font-size: 1.2em }
.katex .sizing.reset-size1.size3, .katex .fontsize-ensurer.reset-size1.size3 { font-size: 1.4em }
.katex .sizing.reset-size1.size4, .katex .fontsize-ensurer.reset-size1.size4 { font-size: 1.6em }
.katex .sizing.reset-size1.size5, .katex .fontsize-ensurer.reset-size1.size5 { font-size: 1.8em }
.katex .sizing.reset-size1.size6, .katex .fontsize-ensurer.reset-size1.size6 { font-size: 2em }
.katex .sizing.reset-size1.size7, .katex .fontsize-ensurer.reset-size1.size7 { font-size: 2.4em }
.katex .sizing.reset-size1.size8, .katex .fontsize-ensurer.reset-size1.size8 { font-size: 2.88em }
.katex .sizing.reset-size1.size9, .katex .fontsize-ensurer.reset-size1.size9 { font-size: 3.456em }
.katex .sizing.reset-size1.size10, .katex .fontsize-ensurer.reset-size1.size10 { font-size: 4.148em }
.katex .sizing.reset-size1.size11, .katex .fontsize-ensurer.reset-size1.size11 { font-size: 4.976em }
.katex .sizing.reset-size2.size1, .katex .fontsize-ensurer.reset-size2.size1 { font-size: 0.833333em }
.katex .sizing.reset-size2.size2, .katex .fontsize-ensurer.reset-size2.size2 { font-size: 1em }
.katex .sizing.reset-size2.size3, .katex .fontsize-ensurer.reset-size2.size3 { font-size: 1.16667em }
.katex .sizing.reset-size2.size4, .katex .fontsize-ensurer.reset-size2.size4 { font-size: 1.33333em }
.katex .sizing.reset-size2.size5, .katex .fontsize-ensurer.reset-size2.size5 { font-size: 1.5em }
.katex .sizing.reset-size2.size6, .katex .fontsize-ensurer.reset-size2.size6 { font-size: 1.66667em }
.katex .sizing.reset-size2.size7, .katex .fontsize-ensurer.reset-size2.size7 { font-size: 2em }
.katex .sizing.reset-size2.size8, .katex .fontsize-ensurer.reset-size2.size8 { font-size: 2.4em }
.katex .sizing.reset-size2.size9, .katex .fontsize-ensurer.reset-size2.size9 { font-size: 2.88em }
.katex .sizing.reset-size2.size10, .katex .fontsize-ensurer.reset-size2.size10 { font-size: 3.45667em }
.katex .sizing.reset-size2.size11, .katex .fontsize-ensurer.reset-size2.size11 { font-size: 4.14667em }
.katex .sizing.reset-size3.size1, .katex .fontsize-ensurer.reset-size3.size1 { font-size: 0.714286em }
.katex .sizing.reset-size3.size2, .katex .fontsize-ensurer.reset-size3.size2 { font-size: 0.857143em }
.katex .sizing.reset-size3.size3, .katex .fontsize-ensurer.reset-size3.size3 { font-size: 1em }
.katex .sizing.reset-size3.size4, .katex .fontsize-ensurer.reset-size3.size4 { font-size: 1.14286em }
.katex .sizing.reset-size3.size5, .katex .fontsize-ensurer.reset-size3.size5 { font-size: 1.28571em }
.katex .sizing.reset-size3.size6, .katex .fontsize-ensurer.reset-size3.size6 { font-size: 1.42857em }
.katex .sizing.reset-size3.size7, .katex .fontsize-ensurer.reset-size3.size7 { font-size: 1.71429em }
.katex .sizing.reset-size3.size8, .katex .fontsize-ensurer.reset-size3.size8 { font-size: 2.05714em }
.katex .sizing.reset-size3.size9, .katex .fontsize-ensurer.reset-size3.size9 { font-size: 2.46857em }
.katex .sizing.reset-size3.size10, .katex .fontsize-ensurer.reset-size3.size10 { font-size: 2.96286em }
.katex .sizing.reset-size3.size11, .katex .fontsize-ensurer.reset-size3.size11 { font-size: 3.55429em }
.katex .sizing.reset-size4.size1, .katex .fontsize-ensurer.reset-size4.size1 { font-size: 0.625em }
.katex .sizing.reset-size4.size2, .katex .fontsize-ensurer.reset-size4.size2 { font-size: 0.75em }
.katex .sizing.reset-size4.size3, .katex .fontsize-ensurer.reset-size4.size3 { font-size: 0.875em }
.katex .sizing.reset-size4.size4, .katex .fontsize-ensurer.reset-size4.size4 { font-size: 1em }
.katex .sizing.reset-size4.size5, .katex .fontsize-ensurer.reset-size4.size5 { font-size: 1.125em }
.katex .sizing.reset-size4.size6, .katex .fontsize-ensurer.reset-size4.size6 { font-size: 1.25em }
.katex .sizing.reset-size4.size7, .katex .fontsize-ensurer.reset-size4.size7 { font-size: 1.5em }
.katex .sizing.reset-size4.size8, .katex .fontsize-ensurer.reset-size4.size8 { font-size: 1.8em }
.katex .sizing.reset-size4.size9, .katex .fontsize-ensurer.reset-size4.size9 { font-size: 2.16em }
.katex .sizing.reset-size4.size10, .katex .fontsize-ensurer.reset-size4.size10 { font-size: 2.5925em }
.katex .sizing.reset-size4.size11, .katex .fontsize-ensurer.reset-size4.size11 { font-size: 3.11em }
.katex .sizing.reset-size5.size1, .katex .fontsize-ensurer.reset-size5.size1 { font-size: 0.555556em }
.katex .sizing.reset-size5.size2, .katex .fontsize-ensurer.reset-size5.size2 { font-size: 0.666667em }
.katex .sizing.reset-size5.size3, .katex .fontsize-ensurer.reset-size5.size3 { font-size: 0.777778em }
.katex .sizing.reset-size5.size4, .katex .fontsize-ensurer.reset-size5.size4 { font-size: 0.888889em }
.katex .sizing.reset-size5.size5, .katex .fontsize-ensurer.reset-size5.size5 { font-size: 1em }
.katex .sizing.reset-size5.size6, .katex .fontsize-ensurer.reset-size5.size6 { font-size: 1.11111em }
.katex .sizing.reset-size5.size7, .katex .fontsize-ensurer.reset-size5.size7 { font-size: 1.33333em }
.katex .sizing.reset-size5.size8, .katex .fontsize-ensurer.reset-size5.size8 { font-size: 1.6em }
.katex .sizing.reset-size5.size9, .katex .fontsize-ensurer.reset-size5.size9 { font-size: 1.92em }
.katex .sizing.reset-size5.size10, .katex .fontsize-ensurer.reset-size5.size10 { font-size: 2.30444em }
.katex .sizing.reset-size5.size11, .katex .fontsize-ensurer.reset-size5.size11 { font-size: 2.76444em }
.katex .sizing.reset-size6.size1, .katex .fontsize-ensurer.reset-size6.size1 { font-size: 0.5em }
.katex .sizing.reset-size6.size2, .katex .fontsize-ensurer.reset-size6.size2 { font-size: 0.6em }
.katex .sizing.reset-size6.size3, .katex .fontsize-ensurer.reset-size6.size3 { font-size: 0.7em }
.katex .sizing.reset-size6.size4, .katex .fontsize-ensurer.reset-size6.size4 { font-size: 0.8em }
.katex .sizing.reset-size6.size5, .katex .fontsize-ensurer.reset-size6.size5 { font-size: 0.9em }
.katex .sizing.reset-size6.size6, .katex .fontsize-ensurer.reset-size6.size6 { font-size: 1em }
.katex .sizing.reset-size6.size7, .katex .fontsize-ensurer.reset-size6.size7 { font-size: 1.2em }
.katex .sizing.reset-size6.size8, .katex .fontsize-ensurer.reset-size6.size8 { font-size: 1.44em }
.katex .sizing.reset-size6.size9, .katex .fontsize-ensurer.reset-size6.size9 { font-size: 1.728em }
.katex .sizing.reset-size6.size10, .katex .fontsize-ensurer.reset-size6.size10 { font-size: 2.074em }
.katex .sizing.reset-size6.size11, .katex .fontsize-ensurer.reset-size6.size11 { font-size: 2.488em }
.katex .sizing.reset-size7.size1, .katex .fontsize-ensurer.reset-size7.size1 { font-size: 0.416667em }
.katex .sizing.reset-size7.size2, .katex .fontsize-ensurer.reset-size7.size2 { font-size: 0.5em }
.katex .sizing.reset-size7.size3, .katex .fontsize-ensurer.reset-size7.size3 { font-size: 0.583333em }
.katex .sizing.reset-size7.size4, .katex .fontsize-ensurer.reset-size7.size4 { font-size: 0.666667em }
.katex .sizing.reset-size7.size5, .katex .fontsize-ensurer.reset-size7.size5 { font-size: 0.75em }
.katex .sizing.reset-size7.size6, .katex .fontsize-ensurer.reset-size7.size6 { font-size: 0.833333em }
.katex .sizing.reset-size7.size7, .katex .fontsize-ensurer.reset-size7.size7 { font-size: 1em }
.katex .sizing.reset-size7.size8, .katex .fontsize-ensurer.reset-size7.size8 { font-size: 1.2em }
.katex .sizing.reset-size7.size9, .katex .fontsize-ensurer.reset-size7.size9 { font-size: 1.44em }
.katex .sizing.reset-size7.size10, .katex .fontsize-ensurer.reset-size7.size10 { font-size: 1.72833em }
.katex .sizing.reset-size7.size11, .katex .fontsize-ensurer.reset-size7.size11 { font-size: 2.07333em }
.katex .sizing.reset-size8.size1, .katex .fontsize-ensurer.reset-size8.size1 { font-size: 0.347222em }
.katex .sizing.reset-size8.size2, .katex .fontsize-ensurer.reset-size8.size2 { font-size: 0.416667em }
.katex .sizing.reset-size8.size3, .katex .fontsize-ensurer.reset-size8.size3 { font-size: 0.486111em }
.katex .sizing.reset-size8.size4, .katex .fontsize-ensurer.reset-size8.size4 { font-size: 0.555556em }
.katex .sizing.reset-size8.size5, .katex .fontsize-ensurer.reset-size8.size5 { font-size: 0.625em }
.katex .sizing.reset-size8.size6, .katex .fontsize-ensurer.reset-size8.size6 { font-size: 0.694444em }
.katex .sizing.reset-size8.size7, .katex .fontsize-ensurer.reset-size8.size7 { font-size: 0.833333em }
.katex .sizing.reset-size8.size8, .katex .fontsize-ensurer.reset-size8.size8 { font-size: 1em }
.katex .sizing.reset-size8.size9, .katex .fontsize-ensurer.reset-size8.size9 { font-size: 1.2em }
.katex .sizing.reset-size8.size10, .katex .fontsize-ensurer.reset-size8.size10 { font-size: 1.44028em }
.katex .sizing.reset-size8.size11, .katex .fontsize-ensurer.reset-size8.size11 { font-size: 1.72778em }
.katex .sizing.reset-size9.size1, .katex .fontsize-ensurer.reset-size9.size1 { font-size: 0.289352em }
.katex .sizing.reset-size9.size2, .katex .fontsize-ensurer.reset-size9.size2 { font-size: 0.347222em }
.katex .sizing.reset-size9.size3, .katex .fontsize-ensurer.reset-size9.size3 { font-size: 0.405093em }
.katex .sizing.reset-size9.size4, .katex .fontsize-ensurer.reset-size9.size4 { font-size: 0.462963em }
.katex .sizing.reset-size9.size5, .katex .fontsize-ensurer.reset-size9.size5 { font-size: 0.520833em }
.katex .sizing.reset-size9.size6, .katex .fontsize-ensurer.reset-size9.size6 { font-size: 0.578704em }
.katex .sizing.reset-size9.size7, .katex .fontsize-ensurer.reset-size9.size7 { font-size: 0.694444em }
.katex .sizing.reset-size9.size8, .katex .fontsize-ensurer.reset-size9.size8 { font-size: 0.833333em }
.katex .sizing.reset-size9.size9, .katex .fontsize-ensurer.reset-size9.size9 { font-size: 1em }
.katex .sizing.reset-size9.size10, .katex .fontsize-ensurer.reset-size9.size10 { font-size: 1.20023em }
.katex .sizing.reset-size9.size11, .katex .fontsize-ensurer.reset-size9.size11 { font-size: 1.43981em }
.katex .sizing.reset-size10.size1, .katex .fontsize-ensurer.reset-size10.size1 { font-size: 0.24108em }
.katex .sizing.reset-size10.size2, .katex .fontsize-ensurer.reset-size10.size2 { font-size: 0.289296em }
.katex .sizing.reset-size10.size3, .katex .fontsize-ensurer.reset-size10.size3 { font-size: 0.337512em }
.katex .sizing.reset-size10.size4, .katex .fontsize-ensurer.reset-size10.size4 { font-size: 0.385728em }
.katex .sizing.reset-size10.size5, .katex .fontsize-ensurer.reset-size10.size5 { font-size: 0.433944em }
.katex .sizing.reset-size10.size6, .katex .fontsize-ensurer.reset-size10.size6 { font-size: 0.48216em }
.katex .sizing.reset-size10.size7, .katex .fontsize-ensurer.reset-size10.size7 { font-size: 0.578592em }
.katex .sizing.reset-size10.size8, .katex .fontsize-ensurer.reset-size10.size8 { font-size: 0.694311em }
.katex .sizing.reset-size10.size9, .katex .fontsize-ensurer.reset-size10.size9 { font-size: 0.833173em }
.katex .sizing.reset-size10.size10, .katex .fontsize-ensurer.reset-size10.size10 { font-size: 1em }
.katex .sizing.reset-size10.size11, .katex .fontsize-ensurer.reset-size10.size11 { font-size: 1.19961em }
.katex .sizing.reset-size11.size1, .katex .fontsize-ensurer.reset-size11.size1 { font-size: 0.200965em }
.katex .sizing.reset-size11.size2, .katex .fontsize-ensurer.reset-size11.size2 { font-size: 0.241158em }
.katex .sizing.reset-size11.size3, .katex .fontsize-ensurer.reset-size11.size3 { font-size: 0.28135em }
.katex .sizing.reset-size11.size4, .katex .fontsize-ensurer.reset-size11.size4 { font-size: 0.321543em }
.katex .sizing.reset-size11.size5, .katex .fontsize-ensurer.reset-size11.size5 { font-size: 0.361736em }
.katex .sizing.reset-size11.size6, .katex .fontsize-ensurer.reset-size11.size6 { font-size: 0.401929em }
.katex .sizing.reset-size11.size7, .katex .fontsize-ensurer.reset-size11.size7 { font-size: 0.482315em }
.katex .sizing.reset-size11.size8, .katex .fontsize-ensurer.reset-size11.size8 { font-size: 0.578778em }
.katex .sizing.reset-size11.size9, .katex .fontsize-ensurer.reset-size11.size9 { font-size: 0.694534em }
.katex .sizing.reset-size11.size10, .katex .fontsize-ensurer.reset-size11.size10 { font-size: 0.833601em }
.katex .sizing.reset-size11.size11, .katex .fontsize-ensurer.reset-size11.size11 { font-size: 1em }
.katex .delimsizing.size1 { font-family: KaTeX_Size1 }
.katex .delimsizing.size2 { font-family: KaTeX_Size2 }
.katex .delimsizing.size3 { font-family: KaTeX_Size3 }
.katex .delimsizing.size4 { font-family: KaTeX_Size4 }
.katex .delimsizing.mult .delim-size1>span { font-family: KaTeX_Size1 }
.katex .delimsizing.mult .delim-size4>span { font-family: KaTeX_Size4 }
.katex .nulldelimiter { display: inline-block; width: 0.12em }
.katex .delimcenter { position: relative }
.katex .op-symbol { position: relative }
.katex .op-symbol.small-op { font-family: KaTeX_Size1 }
.katex .op-symbol.large-op { font-family: KaTeX_Size2 }
.katex .op-limits>.vlist-t { text-align: center }
.katex .accent>.vlist-t { text-align: center }
.katex .accent .accent-body:not(.accent-full) { width: 0 }
.katex .accent .accent-body { position: relative }
.katex .overlay { display: block }
.katex .mtable .vertical-separator { display: inline-block; margin: 0 -0.025em; border-right: 0.05em solid }
.katex .mtable .vs-dashed { border-right: 0.05em dashed }
.katex .mtable .arraycolsep { display: inline-block }
.katex .mtable .col-align-c>.vlist-t { text-align: center }
.katex .mtable .col-align-l>.vlist-t { text-align: left }
.katex .mtable .col-align-r>.vlist-t { text-align: right }
.katex .svg-align { text-align: left }
.katex svg, .screenShotTempCanvas { display: block; position: absolute; width: 100%; height: inherit; fill: currentColor; stroke: currentColor; fill-rule: nonzero; fill-opacity: 1; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0; stroke-opacity: 1 }
.katex svg path { stroke: none }
.katex .stretchy { width: 100%; display: block; position: relative; overflow: hidden }
.katex .stretchy::before, .katex .stretchy::after { content: “” }
.katex .hide-tail { width: 100%; position: relative; overflow: hidden }
.katex .halfarrow-left { position: absolute; left: 0; width: 50.2%; overflow: hidden }
.katex .halfarrow-right { position: absolute; right: 0; width: 50.2%; overflow: hidden }
.katex .brace-left { position: absolute; left: 0; width: 25.1%; overflow: hidden }
.katex .brace-center { position: absolute; left: 25%; width: 50%; overflow: hidden }
.katex .brace-right { position: absolute; right: 0; width: 25.1%; overflow: hidden }
.katex .x-arrow-pad { padding: 0 0.5em }
.katex .x-arrow, .katex .mover, .katex .munder { text-align: center }
.katex .boxpad { padding: 0 0.3em }
.katex .fbox { box-sizing: border-box; border: 0.04em solid rgba(0, 0, 0, 1) }
.katex .fcolorbox { box-sizing: border-box; border: 0.04em solid }
.katex .cancel-pad { padding: 0 0.2em }
.katex .cancel-lap { margin-left: -0.2em; margin-right: -0.2em }
.katex .sout { border-bottom-style: solid; border-bottom-width: 0.08em }
.output_wrapper pre code { }
.output_wrapper .hljs { color: rgba(169, 183, 198, 1); background: rgba(40, 43, 46, 1); display: block; overflow-x: auto; padding: 0.5em }
.output_wrapper .hljs-params { color: rgba(255, 152, 35, 1) }
.output_wrapper .hljs-number, .output_wrapper .hljs-literal, .output_wrapper .hljs-symbol, .output_wrapper .hljs-bullet { color: rgba(174, 135, 250, 1) }
.output_wrapper .hljs-function, .output_wrapper .hljs-built_in, .output_wrapper .hljs-name, .output_wrapper .hljs-keyword, .output_wrapper .hljs-selector-tag, .output_wrapper .hljs-deletion { color: rgba(248, 35, 117, 1) }
.output_wrapper .hljs-variable, .output_wrapper .hljs-template-variable, .output_wrapper .hljs-link { color: rgba(98, 151, 85, 1) }
.output_wrapper .hljs-comment, .output_wrapper .hljs-quote { color: rgba(128, 128, 128, 1) }
.output_wrapper .hljs-meta { color: rgba(91, 218, 237, 1) }
.output_wrapper .hljs-string, .output_wrapper .hljs-attribute, .output_wrapper .hljs-addition { color: rgba(238, 220, 112, 1) }
.output_wrapper .hljs-attr, .output_wrapper .hljs-section, .output_wrapper .hljs-title, .output_wrapper .hljs-type { color: rgba(165, 218, 45, 1) }
.output_wrapper .hljs-selector-class { color: rgba(165, 218, 45, 1) }
.output_wrapper .hljs-emphasis { font-style: italic }
.output_wrapper .hljs-strong { font-weight: bold }
.output_wrapper pre code { line-height: 18px; font-size: 14px; font-weight: normal; word-spacing: 0; letter-spacing: 0 }
.output_wrapper { font-size: 16px; color: rgba(62, 62, 62, 1); line-height: 1.6; word-spacing: 0; letter-spacing: 0; font-family: “Helvetica Neue”, Helvetica, “Hiragino Sans GB”, “Microsoft YaHei”, Arial, sans-serif; background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.05) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.05) 3%, rgba(0, 0, 0, 0) 3%); background-size: 20px 20px; background-position: center }
.output_wrapper * { font-size: inherit; color: inherit; line-height: inherit; margin: 0; padding: 0 }
.output_wrapper p { margin: 1.5em 0 }
.output_wrapper h1, .output_wrapper h2, .output_wrapper h3, .output_wrapper h4, .output_wrapper h5, .output_wrapper h6 { margin: 1.5em 0; font-weight: bold }
.output_wrapper h1 { font-size: 1.6em }
.output_wrapper h2 { font-size: 1.4em }
.output_wrapper h3 { font-size: 1.3em }
.output_wrapper h4 { font-size: 1.2em }
.output_wrapper h5 { font-size: 1em }
.output_wrapper h6 { font-size: 1em }
.output_wrapper ul, .output_wrapper ol { padding-left: 32px }
.output_wrapper ul { list-style-type: disc }
.output_wrapper ol { list-style-type: decimal }
.output_wrapper li * { }
.output_wrapper li { margin-bottom: 0.5em }
.output_wrapper .code_size_default { line-height: 18px; font-size: 14px; font-weight: normal; word-spacing: 0; letter-spacing: 0 }
.output_wrapper .code_size_tight { line-height: 15px; font-size: 11px; font-weight: normal; word-spacing: -3px; letter-spacing: 0 }
.output_wrapper pre code { font-family: Consolas, Inconsolata, Courier, monospace; border-radius: 0 }
.output_wrapper blockquote { display: block; padding: 15px 15px 15px 1rem; font-size: 0.9em; margin: 1em 0; color: rgba(129, 145, 152, 1); border-left: 6px solid rgba(220, 230, 240, 1); background: rgba(242, 247, 251, 1); overflow: auto; overflow-wrap: normal; word-break: normal }
.output_wrapper blockquote p { margin: 0 }
.output_wrapper a { text-decoration: none; color: rgba(30, 107, 184, 1); overflow-wrap: break-word }
.output_wrapper strong { font-weight: bold }
.output_wrapper em { font-style: italic }
.output_wrapper del { font-style: italic }
.output_wrapper strong em { font-weight: bold }
.output_wrapper hr { height: 1px; margin: 1.5rem 0; border-right: none; border-bottom: none; border-left: none; border-top: 1px dashed rgba(165, 165, 165, 1) }
.output_wrapper code { overflow-wrap: break-word; padding: 2px 4px; border-radius: 4px; margin: 0 2px; color: rgba(233, 105, 0, 1); background: rgba(248, 248, 248, 1) }
.output_wrapper img { display: block; margin: 0 auto; max-width: 100% }
.output_wrapper figcaption { margin-top: 10px; text-align: center; color: rgba(153, 153, 153, 1); font-size: 0.7em }
.output_wrapper table { display: table; width: 100%; text-align: left }
.output_wrapper tbody { border: 0 }
.output_wrapper table tr { border-top: 1px solid rgba(204, 204, 204, 1); border-right: 0; border-bottom: 0; border-left: 0; background-color: rgba(255, 255, 255, 1) }
.output_wrapper table tr th, .output_wrapper table tr td { font-size: 1em; border: 1px solid rgba(204, 204, 204, 1); padding: 0.5em 1em; text-align: left }
.output_wrapper table tr th { font-weight: bold; background-color: rgba(240, 240, 240, 1) }
.output_wrapper .katex-display { font-size: 1.22em }
.output_wrapper .katex { padding: 8px 3px }
.output_wrapper .katex-display>.katex { display: inline-block; text-align: center; padding: 3px }
.output_wrapper .katex img { display: inline-block; vertical-align: middle }
.output_wrapper a[href^=”#”] sup { vertical-align: super; margin: 0 2px; padding: 1px 3px; color: rgba(255, 255, 255, 1); background: rgba(102, 102, 102, 1); font-size: 0.7em }
.output_wrapper .task-list-list { list-style-type: none }
.output_wrapper .task-list-list.checked { color: rgba(62, 62, 62, 1) }
.output_wrapper .task-list-list.uncheck { color: rgba(191, 193, 191, 1) }
.output_wrapper .task-list-list .icon_uncheck, .output_wrapper .task-list-list .icon_check { display: inline-block; vertical-align: middle; margin-right: 10px }
.output_wrapper .task-list-list .icon_check::before { content: “√”; border: 2px solid rgba(62, 62, 62, 1); color: rgba(255, 0, 0, 1) }
.output_wrapper .task-list-list .icon_uncheck::before { content: “x”; border: 2px solid rgba(191, 193, 191, 1); color: rgba(191, 193, 191, 1) }
.output_wrapper .task-list-list .icon_check::before, .output_wrapper .task-list-list .icon_uncheck::before { padding: 2px 8px 2px 5px; border-radius: 5px }
.output_wrapper .toc { margin-left: 25px }
.output_wrapper .toc_item { display: block }
.output_wrapper .toc_left { margin-left: 25px }
.output_wrapper pre code { border-radius: 3px; border-top: 1px solid rgba(204, 204, 204, 1); border-right: 1px solid rgba(204, 204, 204, 1); border-bottom: 1px solid rgba(204, 204, 204, 1); border-left: 6px solid rgba(33, 152, 99, 1) }
.output_wrapper pre code .linenum { padding-right: 20px; word-spacing: 0 }
.output_wrapper .hljs { color: rgba(169, 183, 198, 1); background: rgba(40, 43, 46, 1); display: block; overflow-x: auto; padding: 0.5em }
.output_wrapper .hljs-params { color: rgba(255, 152, 35, 1) }
.output_wrapper .hljs-number, .output_wrapper .hljs-literal, .output_wrapper .hljs-symbol, .output_wrapper .hljs-bullet { color: rgba(174, 135, 250, 1) }
.output_wrapper .hljs-function, .output_wrapper .hljs-built_in, .output_wrapper .hljs-name, .output_wrapper .hljs-keyword, .output_wrapper .hljs-selector-tag, .output_wrapper .hljs-deletion { color: rgba(248, 35, 117, 1) }
.output_wrapper .hljs-variable, .output_wrapper .hljs-template-variable, .output_wrapper .hljs-link { color: rgba(98, 151, 85, 1) }
.output_wrapper .hljs-comment, .output_wrapper .hljs-quote { color: rgba(128, 128, 128, 1) }
.output_wrapper .hljs-meta { color: rgba(91, 218, 237, 1) }
.output_wrapper .hljs-string, .output_wrapper .hljs-attribute, .output_wrapper .hljs-addition { color: rgba(238, 220, 112, 1) }
.output_wrapper .hljs-attr, .output_wrapper .hljs-section, .output_wrapper .hljs-title, .output_wrapper .hljs-type { color: rgba(165, 218, 45, 1) }
.output_wrapper .hljs-selector-class { color: rgba(165, 218, 45, 1) }
.output_wrapper .hljs-emphasis { font-style: italic }
.output_wrapper .hljs-strong { font-weight: bold }
.output_wrapper p { margin: 1.5em 0 }
.output_wrapper h1, .output_wrapper h2, .output_wrapper h3, .output_wrapper h4, .output_wrapper h5, .output_wrapper h6 { margin: 1.5em 0; font-weight: bold }
.output_wrapper h1 { font-size: 1.6em }
.output_wrapper h2 { font-size: 1.4em }
.output_wrapper h3 { font-size: 1.3em }
.output_wrapper h4 { font-size: 1.2em }
.output_wrapper h5 { font-size: 1em }
.output_wrapper h6 { font-size: 1em }
.output_wrapper h3 { border-bottom: 2px solid rgba(62, 62, 62, 1); margin-bottom: 50px }
.output_wrapper h3 span { display: inline-block; padding: 10px 0 }
.output_wrapper h3 span::first-letter, .output_wrapper h3 .firstletter { color: rgba(255, 255, 255, 1); padding: 10px 15px; margin-right: 20px; background: rgba(62, 62, 62, 1) }
原文:Unreal Engine 4 Blueprints Tutorial
作者:Tommy Tran
译者:Shuchang Liu
在本篇教程里,你将学会如何用蓝图系统创建玩家角色,设置输入,并编写角色通过触碰,收集道具的游戏逻辑。
蓝图是Unreal Engine 4的一套可视化脚本系统,通过蓝图可以快速制作游戏原型。你不再需要一行行的编写代码,取而代之的是可视化操作:拖拽节点,在UI里设置节点参数,给节点进行连线。
除了作为一款非常便捷的原型工具,蓝图也降低了非开发人员制作游戏逻辑的门槛。
在本篇教程,你将使用蓝图来:
- 设置垂直视角摄像机
- 创建具备基本移动的玩家控制器角色
- 设置玩家输入
- 创建可被角色触碰并收集的道具
注意:本篇教程假定你已了解Unreal Engine 4的基本操作界面。你也应该熟悉基本的蓝图概念,比如组件和节点。如果你需要复习以上内容,点击入门教程。
注意:本篇教程只是Unreal Engine 4系列教程的其中一篇:
起步入门
下载示例项目并解压。进入项目文件夹,双击BananaCollector.uproject打开项目。
注意:如果你看到了项目是由较早的引擎版本创建的提示,这很正常(因为引擎经常更新版本)。你可以选择以拷贝副本的形式打开,也可以直接转换项目版本打开。
你可以看到以下的场景。我们创建的角色会在场景内移动并收集道具。
为了方便起见,我已经给项目文件做了文件夹分类,如下图所示:
你可以点击红框处按钮来显示/隐藏侧边栏列表。
创建玩家
点击进入Content Browser界面的Blueprints文件夹。点击Add New按钮并选择Blueprint Class。
由于玩家需要能够获取输入,Pawn类比较适合。在弹出窗口选择创建Pawn,将其命名为BP_Player。
注意:选择Character类创建也可以,它还默认引入了移动组件。不过,既然我们想要自己动手实现移动,Pawn类已经足够了。
关联摄像机
摄像机是玩家观察游戏世界的方式。我们要创建一个垂直视角观察玩家角色的摄像机。
在Content Browser 双击BP_Player打开蓝图编辑器。
为了创建摄像机,在Components面板点击Add Component并选择Camera。
为了让摄像机视角自顶向下,我们需要先把它放在玩家角色上方。选中摄像机组件,再切换到Viewport页签。
按下W键激活移动操作杆,将摄像机移动到(-1100, 0, 2000)。或者,你可以在Location字段输入坐标点来移动摄像机。
如果摄像机被移出界面外,按下F键就可以重新定位看到摄像机。
接着,按下E键激活旋转操作杆,将摄像机沿Y轴旋转-60度。
玩家表示
我们用一个红色方块来表示玩家,所以需要使用Static Mesh组件进行展示。
首先,在Components面板左键点击空白处取消选中Camera组件。否则,新加的组件会成为摄像机的子节点。
点击Add Component并选择Static Mesh。
为了展示红色方块,选择Static Mesh组件,随后在Details页签Static Mesh属性点击下拉框,选择SM_Cube。
你在关卡场景里应该就能看到如下画面(如果看不到,你可以在Viewport窗口按下F键聚焦画面)
现在,是时候在关卡里动态生成玩家了。点击Compile按钮并回到主编辑器。
生成玩家
为了让玩家能够控制角色,你需要明确两件事:
- 玩家所要控制的Pawn类
- 角色应该在哪里生成
你可以通过创建Game Mode类(游戏模式类)来实现第一点。
创建游戏模式
游戏模式是一个类,用于控制玩家进入游戏的方式。比如,在多人游戏里,可以使用游戏模式,决定每个玩家在哪里生成。更重要的是,游戏模式决定了玩家使用哪个角色。
在Content Browser里点击进入Blueprints文件夹。点击Add New按钮并选择Blueprint Class。
从弹出窗口里选择Game Mode Base,并命名为GM_Tutorial。
现在,你需要指定哪个Pawn作为默认类。双击GM_Tutorial打开蓝图编辑器。
留意Details面板的Classes部分。点击Default Pawn Class属性的下拉框,并选择BP_Player。
其次,关卡还需要知道当前使用哪个游戏模式。你可以在World Settings里指明这一点。点击Compile并关闭蓝图编辑器。
每个关卡都有自己的设置。你可以通过Window\World Settings查看设置。同样地,也可以在Toolbar选择Settings\World Settings进行查看。
Details页签旁边就会出现World Settings页签。点击该页签的GameMode Override字段,选择GM_Tutorial。
现在可以看到Game Mode类已经修改成GM_Tutorial。
最后,我们还需要指定玩家的生成位置。通过在关卡里放置Player Start(玩家出生点)即可。
放置玩家出生点
在生成玩家的过程中,游戏模式会寻找关卡中是否有玩家出生点。如果有,游戏模式就会在该点生成玩家。
为了放置玩家出生点,在Modes面板搜索Player Start。左键拖拽 Player Start至Viewport面板。
你可以随意放置玩家出生点。放好后,点击Toolbar的Play按钮。玩家就会在出生点生成。
如果想要退出游戏,点击Toolbar的Stop按钮或按下Esc键皆可。如果你看不到鼠标指针,按下Shift+F1。
如果玩家不能控制移动,那其实算不上游戏,对吧?我们的下个任务就是设置输入控制。
设置输入
将某个按键指定成触发某个行为,称之为按键绑定。
在Unreal里,你可以通过按键触发event(事件)的方式来实现按键绑定。事件节点是一类当接收到特定行为时,触发执行的节点(比如在这个例子里,当你按下绑定按键时,触发执行某个事件节点)。当某个事件被触发时,任何跟事件节点连接的节点就会被执行。
这种按键绑定的方式非常有好处,因为这意味着你不需要硬编码按键对应的逻辑。
比如,你绑定了鼠标左键并命名为Shoot事件。任何有射击能力的Actor都可以使用Shoot事件,用于检测玩家何时按下了鼠标左键。如果你想修改射击对应绑定的按键,只要在输入设置里修改即可。
如果以硬编码方式实现,你需要遍历每个Actor,分别修改射击对应绑定的按键。
轴映射和操作映射
在Edit\Project Settings里可以看到输入设置。点击Engine部分的Input选项进入设置界面。
Bindings就是用于设置输入的部分。
Unreal提供了两类按键绑定方法:
- Action Mapping(操作映射):这类绑定只有两种状态:按下或者没按下。行为只会在按下或释放按键的时候触发。这类绑定适用于没有中间状态的行为,比如枪械射击。
- Axis Mapping(轴映射):这类绑定输出一个称之为Axis Value(下文有更多介绍)的数字。每帧都会触发对应的轴事件。这类绑定通常用于摇杆或者鼠标。
在这篇教程里,我们会使用Axis Mapping。
创建移动映射
首先,你需要创建两组Axis Mapping。这样就可以实现多按键触发同一个事件。
为了创建一组新的Axis Mapping,点击Axis Mappings右侧的+号。创建两组Axis Mapping,分别命名为MoveForward和MoveRight。
MoveForward负责前后移动。MoveRight负责左右移动。
我们需要把移动映射到四个按键上:W,A,S和D。现在只有两个插槽用于映射按键。通过点击每组映射文本框右侧的+号,添加映射插槽。
接着点击每组插槽的下拉框,将W和S键映射到MoveForward,A和D键映射到MoveRight。
接着,我们需要设置Scale字段。
Axis Value和Input Scale
在你设置Scale字段前,我们需要了解它是怎么跟axis values搭配起作用的。
Axis Value的输出数值取决于输入类型和使用方式。按钮点击会输出1。摇杆根据推动方向和力度,输出-1~1之间的数值。
你可以使用Axis Value控制角色的移动速度。比如,如果你把摇杆推到了最边上,Axis Value是1。如果你只推一半,Axis Value就是0.5。
将Axis Value跟某个速度变量进行相乘,你就能够调整摇杆的速度。
你也可以通过Axis Value判断出方向。如果你将角色速度与一个Axis Value正数相乘,得到的就是正数偏移。角色速度与一个Axis Value负数相乘,得到的就是负数偏移。将偏移值与角色位置相加,就能知道角色往哪个方向移动了。
由于键盘按键只能输出0或1的Axis Value,你可以使用Scale来将其转换成负数。通过Axis Value和Scale值相乘就可以做到这点。
如果正数(Axis Value)和负数(Scale)相乘,按键输出就是负数。
因此我们点击修改按键S和A的Scale字段文本为-1。
接着,有趣的部分来了:让角色动起来!关闭项目设置面板,双击BP_Player打开蓝图编辑器。
角色移动
首先,你需要获取移动映射的事件。在事件图表空白处点击右键打开节点列表。从弹出菜单中搜索MoveForward。添加处于Axis Events下的MoveForward节点。注意我们要添加的是Axis Events下的红色节点,而非Axis Values下的绿色节点。
添加MoveRight节点的步骤同上。
现在,我们需要设置下MoveForward节点。
使用变量
为了实现移动,我们需要指定角色的运动速度。最简单的方法是通过变量保存移动速度。
要新建变量,我们点击My Blueprint页签Variable部分的+号。
选中新建的变量,观察Details页签。将变量名重命名为MaxSpeed。随后,点击Variable Type的下拉框选择Float,将变量类型修改为Float。
接着,需要给变量设置默认值。在这之前,先点击Toolbar上的Compile按钮。
保持变量选中,在Details页签的Default Value区域,将MaxSpeed的默认值设为10。
接着,将MaxSpeed变量从My Blueprint页签拖拽至Event Graph。并选择弹出菜单的Get。
我们现在需要将MaxSpeed与Axis Value相乘来得出最终移动速度和方向。添加float * float节点,并连接Axis Value和MaxSpeed。
获取角色朝向
为了向前移动,首先需要知道角色的朝向。幸运地是,Unreal里有这样的节点。我们需要添加Get Actor Forward Vector节点。
接着,添加Add Movement Input节点。该节点通过获取方向和数值,将其转换成移动偏移值。我们按下图连接各个节点:
白线代表节点执行顺序。换言之,玩家进行操作,触发相应事件并执行InputAxis MoveForward节点,随后执行Add Movement Input节点。
Add Movement Input节点需要如下输入:
- Target:默认为self,在这里就是玩家角色自己(红色方块)。
- World Direction:目标移动方向,在这里就是玩家角色的朝向。
- Scale Value:玩家移动距离,在这里为Max Speed * Axis Value(值范围为-1~1)。
MoveRight事件的设置步骤同上,除了要把Get Actor Forward Vector节点替换为Get Actor Right Vector节点。试着不对照前面的操作指引,自己动手看看!
添加移动偏移
为了让角色真正动起来,我们还需要将Add Movement Input节点计算得出的偏移值,累加到角色当前位置上。
基本上我们的策略就是让游戏角色每帧移动一点点,所以我们需要在Event Tick事件添加运动节点,每帧进行调用。
现在看看Event Graph有没有Event Tick节点,它应该处于灰化状态,如果没有就创建对应节点。
为了获取偏移值,创建Consume Movement Input Vector节点。要累加偏移值到玩家角色上,还需要添加AddActorLocalOffset节点。随后如下图连接节点:
这样意味着在游戏每帧,你会获取角色的移动输入,并将其累加在角色当前位置上。
点击Compile,回到主编辑器并点击Play。你就能操控红色方块四处移动了!
这里有个小问题。高配机器有可能运行帧率更高。由于Event Tick事件每帧调用,那同等时间内运动节点执行的次数更多。导致的结果就是高配机器角色移动得更快,反之亦然。
为了避免这个问题,角色运动必须是帧率无关的。
注意:我已经做了按键绑定用于展示帧率有关会产生的影响。按下数字键0将帧率调成60,按下数字键1将重置帧率。控制角色四处移动,看看不同帧率对移动速度的影响。
帧率无关
帧率无关意味着不管帧率是多少,相同时间的游戏运行,会产生完全一致的结果。幸运地是,在Unreal里要做到这点非常容易。
退出游戏,并打开BP_Player。接着,观察Event Tick节点的Delta Seconds字段。
Delta Seconds为当前帧的Event Tick调用与上一帧Event Tick调用的时间间隔。通过将偏移值与Delta Seconds相乘,角色运动就是帧率无关的。
比如,你的角色最高速度是100。如果距离上一帧Event Tick已经过了1秒,则角色移动100个单元。如果只过了0.5秒,则角色运动50个单元。
如果角色运动是帧率相关的,那就会产生不过帧率高低,每帧固定移动100单元的结果。
为了将偏移值与Delta Seconds相乘,添加vector * float节点。之后,如下图进行连线:
由于每帧间隔时间很短,角色移动起来会很慢,所以可以将MaxSpeed的默认值调大至600。
至此恭喜你,成功解锁了帧率无关成就!
你可能注意到了,方块现在会穿透其他物体。为了解决这个问题,我们需要用上碰撞检测。
角色碰撞
为了能够互相碰撞,角色需要一个代表其碰撞区域(通常称之为碰撞体)的东西。你可以使用以下任一种:
- Collision mesh(碰撞网格):这个通常在网格导入时就会生成(如果你勾选了允许)。用户也可以用3D软件创建自定义碰撞网格。红色方块导入时就自动生成了碰撞网格。
- Collision component(碰撞组件):一般由三种形状:盒子,胶囊体和球体。你可以通过Components面板进行添加。通常用于简单碰撞。
下面就是一个人物和其碰撞体的例子。
当一个碰撞体碰到了另个碰撞体,碰撞就产生了。
设置碰撞
你可能会奇怪,方块有碰撞体,却没有检测到碰撞。当你移动方块时,Unreal只认为方块的根组件可产生碰撞。由于方块的根组件并没有任何碰撞体,所以就穿透了其他物体。
注意:一个root组件没有碰撞体的角色,同样可以挡住其他角色。但如果你移动这个角色,它是不会与任何物体产生碰撞的。
所以,为了使用碰撞网格,StaticMesh必须作为根组件存在。我们通过在Components面板左键拖拽StaticMesh至DefaultSceneRoot。释放鼠标,StaticMesh现在就成了根组件了。
最后还要做一件事情。切换到Event Graph页签,将AddActorLocalOffset节点的Sweep输入勾选为true。
简单来讲,AddActorLocalOffset会将角色从旧位置瞬移到新位置上。Sweep可以确保旧位置和新位置之间的物体都能与角色进行碰撞检测。
回到主编辑器并点击Play。现在方块能与关卡场景产生碰撞了!
创建道具
任何东西都能成为供玩家触碰拾取的道具。这里我们使用BP_Banana作为道具。
为了检测方块是否碰到了道具,我们需要一个在产生碰撞时,触发执行的事件节点。我们可以使用碰撞响应来生成事件。
碰撞响应也决定了角色之间的碰撞后行为。有以下三类碰撞响应:Ignore,Overlap和Block。以下是它们互相之间的作用结果:
虽然可以使用Overlap或Block的任一种,本篇教程只展示如何使用Overlap。
设置碰撞响应
退出游戏并打开BP_Banana。选择StaticMesh组件并观察Details面板,Collision部分可以设置碰撞响应。
如图所示,大部分设置都是灰置的。为了编辑它们,左键点击Collision Presets的下拉框,选择Custom选项。
现在,你需要指定道具和方块之间的碰撞响应。
组件有一项属性称之为对象类型。对象类型是一种给角色进行简单分类的方法。你可以在这里了解更多关于对象类型的内容。
由于方块的类型是WorldDynamic,我们需要修改该类型对象的碰撞响应。在Collision Responses设置下,左键点击WorldDynamic中间的勾选框,将碰撞响应改成Overlap。
碰撞处理
为了获取碰撞情况并进行处理,我们需要用到overlap事件。在Components面板右键点击StaticMesh。从弹出菜单中,选择Add Event\Add OnComponentBeginOverlap。
Event Graph界面就会出现OnComponentBeginOverlap (StaticMesh)节点。
最后,添加DestroyActor节点,并连接OnComponentBeginOverlap (StaticMesh)节点。顾名思义,该节点会将目标角色从游戏中移除。由于我们没有显式指定Target,所以它会销毁调用该节点的角色。
放置道具
关闭蓝图编辑器,并确保Content Browser打开了Blueprints文件夹。
通过左键拖拽 BP_Banana至Viewport界面来在关卡放置香蕉道具。
点击Play按钮,开始收集香蕉吧!
后续学习
你可以在这里下载完整项目。
你现在距离成为Unreal Engine专家又近了一步了。希望这篇教程没有难倒你。
如果你还想继续学习Unreal Engine 4引擎,点击下篇教程,我会进一步讲解Unreal Engine 4引擎的材质系统。