.output_wrapper pre code { font-family: Consolas, Inconsolata, Courier, monospace; display: block !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; overflow: auto !important }
.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) }

原文:How to Create a Simple Game in Unreal Engine 4
作者:Tommy Tran
译者:Shuchang Liu

在本篇教程中,你将学习制作一个第一人称动作游戏,学习如何随机生成并躲避障碍物。

如果你刚开始接触游戏,一个常常会收到的建议就是学习制作一款简单游戏,因为你能学会制作简单的游戏玩法,物件交互。

本篇教程中,我们将制作一个第一人称动作游戏,涉及以下知识点:

  • 持续移动玩家角色
  • 生成需要玩家躲避的障碍点
  • 随机生成不同样式的障碍点来丰富游戏性
  • 当玩家撞上障碍点后,显示重玩按钮

最后,我们将制作出如下图一样的小游戏:

请注意,教程涉及蓝图和UMG部分内容。如果你需要复习有关内容,请查看蓝图教程UI教程

注意:本篇教程只是Unreal Engine 4系列教程的其中一篇:

下载示例项目并解压。进入项目文件夹,双击InfiniteMatrix.uproject打开项目。

注意:如果你看到了项目是由较早的引擎版本创建的提示,这很正常(因为引擎经常更新版本)。你可以选择以拷贝副本的形式打开,也可以直接转换项目版本打开。

按下Play运行游戏尝试控制移动。你可以通过移动鼠标进行垂直,水平移动。

首先要做的是让玩家角色持续向前动起来。

Content Browser打开Blueprints文件夹并双击打开BP_Player

为了让玩家角色向前运动起来,我们需要给玩家位置每帧累加偏移值。

首先,创建变量定义玩家前移的速度。创建名为ForwardSpeedFloat变量,将默认值调成2000

接着,在Event Graph找到Event Tick节点,创建如下设置:

通过ForwardSpeedDelta Seconds相乘,就能获得帧率无关的运动位移。

注意:如果你不熟悉帧率无关的概念,请阅读蓝图教程

接着,我们需要将数值结果应用到角色上,让其沿指定轴运动。

为了让角色移动,创建AddActorWorldOffset节点。将Sweep选中框勾选为true

如果我们直接将Float节点与Delta Location输入引脚相连,Unreal会将其自动转换为Vector

然而,这样会导致Vector的x,y,z轴分量都设置成Float节点值。在教程例子中,玩家角色应该只沿着X轴移动。幸运地是,我们可以将Vector变量分解成三个Float组件。

确保AddActorWorldOffset节点的Delta Location引脚没有任何连接。右键点击Delta Location引脚,从弹出菜单中选择Split Struct Pin

最后,如下图进行连接:

让我们回顾下:

  1. 游戏每帧将ForwardSpeedDelta Seconds相乘得出帧率无关的位移值
  2. AddActorWorldOffset节点获取输入驱动角色沿X轴运动
  3. 确保勾选Sweep,这样角色移动过程中遇到障碍物就会停下来

点击Compile并回到主编辑器。角色就会沿着隧道穿行了。

与其手动放置一段段的隧道,我们接下来会创建蓝图用于自动生成隧道。

Content Browser跳转到Blueprints文件夹,创建以Actor为父类的Blueprint Class,命名为BP_TunnelSpawner并双击打开。

由于游戏过程会持续不断的生成隧道,我们可以创建一个相应的函数。在My Blueprint面板创建名为SpawnTunnel的新函数。该函数负责在指定位置创建隧道。

为了传递位置数据,这个函数需要一个输入参数。它在函数节点以输入引脚的方式存在。

在函数内部的入口节点,它又以输出引脚方式存在。

现在让我们来创建一个输入参数。确保界面处于SpawnTunnel函数的图标。选择入口节点,然后点击Details面板Inputs部分的+号。

将输入参数重命名为SpawnLocation,并将参数类型改为Vector

为了生成隧道,需要添加Spawn Actor From Class节点。点击Class引脚的下拉框,选择BP_Tunnel

为了设置生成位置,右键点击Spawn Transform引脚,从弹出菜单选择Split Struct Pin。随后如图连接Spawn Actor From Class节点与Entry节点:

下面来测试下吧!

切换到Event Graph并找到Event BeginPlay节点。添加SpawnTunnel节点并与Event BeginPlay节点相连。

SpawnTunnel节点的Spawn Location设置为(2000, 0, 500)

现在当游戏启动时,隧道会生成在距离玩家角色比较远的地方。点击Compile并回到主编辑器。

首先,删除关卡场景里的BP_Tunnel。在World Outliner里左键点击BP_Tunnel,然后按Delete键删除隧道。

接着,在Content Browser里左键拖拽BP_TunnelSpawner至Viewport,这样就往关卡中添加了对应实例。

现在点击Play运行游戏,能够看到玩家角色上方会生成一个隧道。

测试完毕后,再次打开BP_TunnelSpawner。将SpawnTunnel节点的Spawn Location重置为(0, 0, 0)

随后,点击Compile并返回主编辑器。

教程下一部分,我们将编写BP_Tunnel蓝图。

BP_Tunnel蓝图需要干两件事。首先它需要检测何时需要生成一段新隧道。为了实现这点,我们需要创建一个触发区域。一旦触发,BP_Tunnel负责通知BP_TunnelSpawner生成一段新隧道。这样就能实现隧道绵延无尽的错觉。

其次它需要确定下一段隧道的生成点。BP_TunnelSpawner会根据这个点来生成隧道。

现在先看看怎么创建触发区域吧。

打开BP_Tunnel并来到Components面板,添加Box Collision组件,将其命名为TriggerZone

此时组件的碰撞区域还很小。留意Details面板的Shape部分设置,将Box Extent属性改为(32, 500, 500)

接着,Location属性设为(2532, 0, 0),将触发区域位置摆放到隧道网格末端。这意味着玩家角色触达隧道末端时就应该生成一段新隧道了。

现在,是时候创建生成点了。

为了标记生成点位置,我们可以使用Scene组件。Scene组件非常适合用于标记位置,因为该组件没有外观显示,只有一个Transform。同时该组件在Viewport又是可视的,方便我们知道生成点在哪。

没有选中任何东西的前提下,在Components面板添加Scene组件,并命名为SpawnPoint

隧道网格沿X轴总共2500单位长,所以生成点应该就在这,在Details面板设置Location参数为(2500, 0, 0)

接下来需要创建一个函数,负责在生成点生成隧道。

点击Compile并切换到BP_TunnelSpawner

新的BP_Tunnel永远应该在最远隧道的生成点生成。这样看起来,隧道就是无穷尽的。

由于最远隧道必定是最新生成的隧道,所以我们不难获得其引用。

打开SpawnTunnel图表,在Spawn Actor From Class节点的Return Value引脚右键点击,从弹出菜单中选择Promote to Variable并命名为NewestTunnel

现在,我们就获得了最远隧道的引用了。

接着,创建新函数并命名为SpawnTunnelAtSpawnPoint。创建如下图表:

这样设置就可以获得SpawnPoint组件的位置,并在该点生成一段新隧道。

为了让BP_Tunnel能通知调用到BP_TunnelSpawner,前者需要有后者的引用。否则,BP_TunnelSpawner就不知道何时生成隧道了。

点击Compile并关闭SpawnTunnelAtSpawnPoint图表,随后打开BP_Tunnel

添加新变量,命名为TunnelSpawner,将Variable Type类型设置为BP_TunnelSpawner\Object Reference

点击Compile并切换回BP_TunnelSpawner

打开SpawnTunnel图表添加对应节点:

现在,每个隧道都能获得BP_TunnelSpawner的引用。

接着,我们要让BP_TunnelSpawner能在玩家角色进入触发区域时生成新隧道。

点击Compile并切回到BP_Tunnel

在Components面板右键点击,在弹出菜单中选择Add Event\Add OnComponentBeginOverlap。Event Graph会新增下面节点:

这个节点会在Actor触碰触发区域时执行。

首先,我们要确认触碰触发区域Actor是玩家角色。

左键拖拽Other Actor引脚至空白处,从弹出菜单中选择Cast to BP_Player节点。

注意:由于新隧道会在另一段隧道的末端生成,它会触发执行隧道的TriggerZone节点,Cast to BP_Player节点能拦截其他Actor执行后续逻辑。

接着,在Cast to BP_Player节点后面添加如下节点:

让我们再回顾以上步骤:

  1. Actor触碰触发区域On Component Begin Overlap (TriggerZone)节点会触发执行
  2. Cast to BP_Player节点检测触碰Actor是否为玩家角色
  3. 如果是玩家,BP_TunnelSpawner会生成新隧道,生成位置在最后一段隧道的SpawnPoint组件位置。
  4. 由于前面那段隧道已经没用了,DestroyActor节点会将其销毁移除。

点击Compile,回到主编辑器并点击Play。一旦触达隧道末端,就会又有一段隧道生成。

至此,虽然游戏一直在生成隧道,看起来并不像穿行在无尽的隧道。我们可以通过在屏幕上多显示几条隧道来优化画面表现。后面再配合上障碍点的显示,玩家就不会留意到隧道的突然生成了。

首先,我们要创建函数用于生成一定数量的隧道。

打开BP_TunnelSpawner创建名为SpawnInitialTunnels的新函数。

为了生成一定数量的隧道,我们可以使用ForLoop节点。该节点会执行多次连接的节点。添加ForLoop节点并与入口节点相连。

为了使得ForLoop节点执行n次,我们需要将Last Index设置成n-1。

在这篇教程里,我们需要生成3段隧道,为了循环执行3次,将Last Index设置成2

注意:如果不显式设置First IndexLast Index字段,默认为0。

玩家应该在游戏开始时就处于隧道中,所以我们可以将第一段隧道生成在玩家位置。

为了判断第一段隧道是否已生成,我们可以检查NewestTunnel字段是否有值。如果没有值则说明第一段隧道还未生成,因为NewestTunnel字段是在游戏生成第一段隧道后设值的。

为了进行检查,在ForLoop节点后添加IsValid节点(带有问号图标)。

接着,获取NewestTunnel引用连接IsValid节点的Input Object引脚。

如果NewestTunnel没有值,执行Is Not Valid引脚,反之执行另一个引脚。

IsValid节点的Is Not Valid引脚分支添加如下节点:

这样就能在玩家位置生成一段隧道。

好了,接下来让我们来生成其他隧道。

添加SpawnTunnelAtSpawnPoint节点并与IsValid节点的Is Valid引脚连接。

下图就是最终图表:

小结:

  1. ForLoop节点会循环执行3次
  2. 第1次循环,它会在玩家位置生成隧道
  3. 后续循环中,它会在最新隧道的SpawnPoint处生成新隧道

接着,在Event Graph中删除SpawnTunnel节点,并在Event BeginPlay节点后添加SpawnInitialTunnels节点。

现在再运行游戏,就会生成了3段隧道。

点击Compile,回到主编辑器点击Play。隧道看起来长多了!

这个游戏目前为止还没有游戏难度,让我们来加上一些障碍点。

这些是我们创建障碍点会用上网格:

在Components面板双击打开BP_Tunnel。添加Static Mesh组件,命名为WallMesh

在Details面板将它的Static Mesh属性改为SM_Hole_01

再将Location属性改为(2470, 0, 0)。这样网格就会处于隧道末端位置。

为了让游戏更有趣些,最好让这面障碍墙转动起来。添加Float变量,命名为RotateSpeed,设置默认值30

切换到Event Graph找到Event Tick。进行如下设置:

这样WallMesh每帧都会转动一点点。

点击Compile并返回主编辑器。按下Play看看转动的障碍墙吧。

接着让我们再来添加一些随机性吧。

在此我们不需要给每种不同样式的障碍都创建一个蓝图,我们只要随机选择WallMesh即可。

打开BP_Tunnel并创建新函数RandomizeWall。随后,如下图设置图表:

顾名思义,Set Static Mesh节点会将WallMesh设置为指定的网格。

想要创建一个网格列表,我们可以使用Select节点。

左键拖拽New Mesh引脚至空白处,从弹出菜单中选择Select节点。

Select节点允许你设置一个选项列表。Index节点决定Select节点输出哪个列表选项。

既然有4个可用墙面网格,我们需要再创建2个Option引脚。通过右键点击Select节点,从弹出菜单中选择Add Option Pin

接着,如下设置每个选项:

  • Option 0: SM_Hole_01
  • Option 1: SM_Hole_02
  • Option 2: SM_Hole_03
  • Option 3: SM_Hole_04

现在,再随机选中一个选项。

我们可以使用Random Integer in Range节点来获取一个随机数字,该节点会返回>=Min且<=Max的数值。

添加Random Integer in Range节点并与Select节点的Index引脚相连。

Max值设为3。这样就会随机返回以下4个数字中的一个:0,1,2,3。

为了增加一些随机性,我们再给WallMesh添加一个随机旋转值。在Set Static Mesh节点后添加如下图表:

这样WallMesh就会有一个0~360度的旋转偏移值。

下图是最终的图表:

小结:

  1. Select节点提供了一组网格列表
  2. 使用Random Integer in Range节点来随机挑选一个网格
  3. Set Static Mesh节点将随机网格设置给了WallMesh
  4. AddLocalRotation节点给WallMesh设置了一个随机的旋转偏移值

点击Compile并关闭RandomizeWall图表。

切换到BP_TunnelSpawner并打开SpawnTunnel图表。添加下图的高亮节点:

现在,只要创建隧道,都会跟着随机生成障碍墙网格。

关闭SpawnTunnel图表并点击Compile。返回主编辑器并按下Play体验不同的障碍墙!

如果你撞上了墙,应该会马上停下来。如果你控制移动并穿过了墙上的洞,就可以继续前行。

我们下一步要做的是让玩家角色在撞上墙后停止移动。

要控制玩家角色能不能前行,我们可以使用Boolean变量。该变量只有两种状态:truefalse

打开BP_Player并创建新Boolean变量,命名为IsDead

接着,找到Event Tick节点并创建Branch节点。

随后,获取IsDead引用并连接Branch节点的Condition引脚。

连接Event Tick节点与Branch节点。随后,连接Branch节点的False引脚与AddActorWorldOffset节点。

现在,只要IsDead设置为true,玩家角色就会停止移动。

接着,我们要在玩家撞到墙时设置IsDead变量。

点击Compile并切换到BP_Tunnel。在Components面板右键点击WallMesh,在弹出菜单中选择Add Event\Add OnComponentHit。Event Graph就会新增如下节点:

只要ActorWallMesh触碰,节点就会执行。

首先,我们需要检查碰撞Actor是否为玩家。

左键拖拽Other Actor引脚至空白处,从弹出菜单中选择Cast to BP_Player

接着,左键拖拽Cast to BP_Player节点的BP_Player引脚至空白处,从弹出菜单中选择Set Is Dead节点。

IsDead的勾选框勾选为true

点击Compile并回到主编辑器。点击Play尝试在游戏撞上墙。你会发现你还可以上下移动,但已经不再继续前行了。

下一部分教程,我们将要在玩家撞上墙时显示重玩按钮。

我们要显示的控件叫WBP_Restart。你可以在UI文件夹找到它。控件看起来是这个样子的:

为了控制控件的显隐,首先要获得它的引用。打开BP_Player并创建变量RestartWidget,将Variable Type改为WBP_Restart\Object Reference

接着,在Event Graph找到Event BeginPlay节点。

新增Create Widget节点并设置ClassWBP_Restart

随后,添加Set Restart Widget节点并如下图连接:

现在,当玩家生成时,会同时生成WBP_Restart实例。下一步是创建函数来显示它。

创建名为DisplayRestart的新函数,然后如下图设置其图表:

小结:

  1. Add to Viewport节点负责将RestartWidget显示在屏幕上
  2. Set Input Mode UI Only节点用于限制玩家只能对UI进行交互。这样可以避免玩家在死亡后还能移动
  3. 顾名思义,Set Show Mouse Cursor节点用于控制鼠标显示

要显示重玩按钮,我们所要做的事就是在玩家撞墙后,调用DisplayRestart函数。

关闭DisplayRestart图表并点击Compile

切换到BP_Tunnel并找到On Component Hit (WallMesh)节点。

在节点链的末端添加DisplayRestart节点。

点击Compile并关闭BP_Tunnel。回到主编辑器并点击Play。如果你撞上了墙,就会显示重玩按钮。

现在还剩最后一步工作,在玩家点击按钮后重置游戏。

重置游戏需要做两件事:

  1. 重置玩家,包括将重玩按钮从屏幕移除。
  2. 重新生成隧道,一如玩家在一开始游戏那样。

首先我们先来重置玩家。

打开BP_Player并创建新函数RestartGame,创建如下图表:

小结:

  1. Set Is Dead节点将IsDead设置为false,这样使得玩家可以继续前行
  2. Remove From Parent节点负责将RestartWidget从屏幕中移除
  3. Set Input Mode Game Only节点恢复游戏输入,使得玩家可以重新操控角色
  4. Set Show Mouse Cursor节点负责隐藏鼠标

然后,我们还需要重新生成隧道。

点击Compile并关闭BP_Player

打开BP_TunnelSpawner并跳转到SpawnInitialTunnels图表。

首先,在生成新隧道前,要先把此前生成的隧道全部移除掉。

入口节点后面添加Sequence节点。将Then 1引脚与ForLoop节点相连。

注意:Sequence节点用于序列执行它的输出引脚。善用Sequence节点有利于图表的组织,特别是一味滥用节点链的情况下,图表会变得非常长。

接着,创建如下节点:

这样就能把此前生成的隧道全部移除掉。

最后,将Sequence节点的Then 0引脚与Get All Actors of Class节点相连。这样就能确保在生成新隧道前,移除已有隧道。

看看最终的图表:

还剩最后一件事了,处理按钮点击。

点击Compile并关闭BP_TunnelSpawner

打开Content Browser的UI文件夹,双击打开WBP_Restart

选中RestartButton并打开Details面板,点击Events下的OnClicked右侧按钮

Unreal会创建On Clicked (RestartButton)节点。这个节点会在玩家点击RestartButton时触发执行。

如下图设置函数图表:

小结:

  1. Get Owning Player Pawn返回当前控制的玩家角色
  2. Cast to BP_Player检查角色类是否为BP_Player
  3. 如果是则调用RestartGame函数,该函数会重置角色并隐藏重玩按钮
  4. Get All Actors of ClassGet节点获取到BP_TunnelSpawner引用并调用SpawnInitialTunnels函数,移除已有隧道并创建新隧道。

注意:你可能奇怪我们要用Get All Actors Of Class节点,而不是直接获取BP_TunnelSpawner引用。主要原因是BP_TunnelWBP_Restart并没有任何引用关系。对于这样的一个简单游戏,用这种方式获得引用比较简单粗暴点。

点击Compile并关闭蓝图编辑器。按下Play测试下刚实现的重玩按钮吧!

你可以在这里下载完整项目。

现在你已经有了一个简单可玩的游戏,试着在此基础进一步完善丰富游戏。比如按玩家穿越了多少障碍点来进行计分。

你也可以尝试制作像Pong俄罗斯方块这样的经典游戏。这些游戏机制都很简单,但实现起来都有一定难度。

如果你还想继续学习引擎其他内容,点击下篇教程,讲解如何在游戏中利用蓝图实现角色动画。

 

版权声明:本文为leoin2012原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/leoin2012/p/11713493.html