.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: 0px; }
.katex { font: 1.21em/1.2 KaTeX_Main, "Times New Roman", serif; text-indent: 0px; text-rendering: auto; }
.katex * { }
.katex .katex-mathml { position: absolute; clip: rect(1px, 1px, 1px, 1px); padding: 0px; border: 0px; 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: 0px; position: relative; }
.katex .vlist > span > span { display: inline-block; }
.katex .vlist > span > .pstrut { overflow: hidden; width: 0px; }
.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: 0px; 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: 0px; }
.katex .rlap > .inner, .katex .clap > .inner { left: 0px; }
.katex .clap > .inner > span { margin-left: -50%; margin-right: 50%; }
.katex .rule { display: inline-block; border: 0px 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: 0px; }
.katex .accent .accent-body { position: relative; }
.katex .overlay { display: block; }
.katex .mtable .vertical-separator { display: inline-block; margin: 0px -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: 0px; width: 50.2%; overflow: hidden; }
.katex .halfarrow-right { position: absolute; right: 0px; width: 50.2%; overflow: hidden; }
.katex .brace-left { position: absolute; left: 0px; width: 25.1%; overflow: hidden; }
.katex .brace-center { position: absolute; left: 25%; width: 50%; overflow: hidden; }
.katex .brace-right { position: absolute; right: 0px; width: 25.1%; overflow: hidden; }
.katex .x-arrow-pad { padding: 0px 0.5em; }
.katex .x-arrow, .katex .mover, .katex .munder { text-align: center; }
.katex .boxpad { padding: 0px 0.3em; }
.katex .fbox { box-sizing: border-box; border: 0.04em solid black; }
.katex .fcolorbox { box-sizing: border-box; border: 0.04em solid; }
.katex .cancel-pad { padding: 0px 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 { display: -webkit-box !important; }
.output_wrapper .hljs{color: rgb(169, 183, 198); background: rgb(40, 43, 46); display: block; overflow-x: auto; padding: 0.5em;}

.output_wrapper .hljs-params{color: rgb(255, 152, 35);}

.output_wrapper .hljs-number,.output_wrapper .hljs-literal,.output_wrapper .hljs-symbol,.output_wrapper .hljs-bullet{color: rgb(174, 135, 250);}

.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: rgb(248, 35, 117);}

.output_wrapper .hljs-variable,.output_wrapper .hljs-template-variable,.output_wrapper .hljs-link{color: rgb(98, 151, 85);}

.output_wrapper .hljs-comment,.output_wrapper .hljs-quote{color: rgb(128, 128, 128);}

.output_wrapper .hljs-meta{color: rgb(91, 218, 237);}

.output_wrapper .hljs-string,.output_wrapper .hljs-attribute,.output_wrapper .hljs-addition{color: rgb(238, 220, 112);}

.output_wrapper .hljs-attr,.output_wrapper .hljs-section,.output_wrapper .hljs-title,.output_wrapper .hljs-type{color: rgb(165, 218, 45);}

.output_wrapper .hljs-selector-class{color: rgb(165, 218, 45);}

.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: 0px; letter-spacing: 0px;}
.output_wrapper{font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; 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 center;}

.output_wrapper *{font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;}

.output_wrapper p{margin: 1.5em 0px;}

.output_wrapper h1,.output_wrapper h2,.output_wrapper h3,.output_wrapper h4,.output_wrapper h5,.output_wrapper h6{margin: 1.5em 0px; 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: 0px; letter-spacing: 0px;}

.output_wrapper .code_size_tight{line-height: 15px; font-size: 11px; font-weight: normal; word-spacing: -3px; letter-spacing: 0px;}

.output_wrapper pre code{font-family: Consolas, Inconsolata, Courier, monospace; border-radius: 0px;}

.output_wrapper blockquote{display: block; padding: 15px 15px 15px 1rem; font-size: 0.9em; margin: 1em 0px; color: rgb(129, 145, 152); border-left: 6px solid rgb(220, 230, 240); background: rgb(242, 247, 251); overflow: auto; overflow-wrap: normal; word-break: normal;}

.output_wrapper blockquote p{margin: 0px;}

.output_wrapper a{text-decoration: none; color: rgb(30, 107, 184); 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 0px; border-right: none; border-bottom: none; border-left: none; border-image: initial; border-top: 1px dashed rgb(165, 165, 165);}

.output_wrapper code{overflow-wrap: break-word; padding: 2px 4px; border-radius: 4px; margin: 0px 2px; color: rgb(233, 105, 0); background: rgb(248, 248, 248);}

.output_wrapper img{display: block; margin: 0px auto; max-width: 100%;}

.output_wrapper figcaption{margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;}

.output_wrapper table{display: table; width: 100%; text-align: left;}

.output_wrapper tbody{border: 0px;}

.output_wrapper table tr{border-width: 1px 0px 0px; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image: initial; border-top-style: solid; border-top-color: rgb(204, 204, 204); background-color: white;}

.output_wrapper table tr th,.output_wrapper table tr td{font-size: 1em; border: 1px solid rgb(204, 204, 204); padding: 0.5em 1em; text-align: left;}

.output_wrapper table tr th{font-weight: bold; background-color: rgb(240, 240, 240);}

.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: 0px 2px; padding: 1px 3px; color: rgb(255, 255, 255); background: rgb(102, 102, 102); font-size: 0.7em;}

.output_wrapper .task-list-list{list-style-type: none;}

.output_wrapper .task-list-list.checked{color: rgb(62, 62, 62);}

.output_wrapper .task-list-list.uncheck{color: rgb(191, 193, 191);}

.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 rgb(62, 62, 62); color: red;}

.output_wrapper .task-list-list .icon_uncheck::before{content: "x"; border: 2px solid rgb(191, 193, 191); color: rgb(191, 193, 191);}

.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-width: 1px 1px 1px 6px; border-style: solid; border-color: rgb(204, 204, 204) rgb(204, 204, 204) rgb(204, 204, 204) rgb(33, 152, 99);}

.output_wrapper pre code .linenum{padding-right: 20px; word-spacing: 0px;}

.output_wrapper .hljs{color: rgb(169, 183, 198); background: rgb(40, 43, 46); display: block; overflow-x: auto; padding: 0.5em;}

.output_wrapper .hljs-params{color: rgb(255, 152, 35);}

.output_wrapper .hljs-number,.output_wrapper .hljs-literal,.output_wrapper .hljs-symbol,.output_wrapper .hljs-bullet{color: rgb(174, 135, 250);}

.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: rgb(248, 35, 117);}

.output_wrapper .hljs-variable,.output_wrapper .hljs-template-variable,.output_wrapper .hljs-link{color: rgb(98, 151, 85);}

.output_wrapper .hljs-comment,.output_wrapper .hljs-quote{color: rgb(128, 128, 128);}

.output_wrapper .hljs-meta{color: rgb(91, 218, 237);}

.output_wrapper .hljs-string,.output_wrapper .hljs-attribute,.output_wrapper .hljs-addition{color: rgb(238, 220, 112);}

.output_wrapper .hljs-attr,.output_wrapper .hljs-section,.output_wrapper .hljs-title,.output_wrapper .hljs-type{color: rgb(165, 218, 45);}

.output_wrapper .hljs-selector-class{color: rgb(165, 218, 45);}

.output_wrapper .hljs-emphasis{font-style: italic;}

.output_wrapper .hljs-strong{font-weight: bold;}

.output_wrapper p{margin: 1.5em 0px;}

.output_wrapper h1,.output_wrapper h2,.output_wrapper h3,.output_wrapper h4,.output_wrapper h5,.output_wrapper h6{margin: 1.5em 0px; 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 rgb(62, 62, 62); margin-bottom: 50px;}

.output_wrapper h3 span{display: inline-block; padding: 10px 0px;}

.output_wrapper h3 span::first-letter,.output_wrapper h3 .firstletter{color: rgb(255, 255, 255); padding: 10px 15px; margin-right: 20px; background: rgb(62, 62, 62);}
-->

原文:Unreal Engine 4 Tutorial: Artificial Intelligence
作者:Tommy Tran
译者:Shuchang Liu

在本篇教程中,你将学习如何使用行为树和AI感知来创建一个能四处走动,攻击敌人的简单AI。

在视频游戏中,人工智能(AI)通常指的是拥有自主决策行为的非玩家角色。AI可以是看到玩家然后进行攻击的简单角色,也可以是即时策略(RTS)游戏里的强大对手。

在Unreal引擎里,我们可以通过行为树创建AI。行为树是一个决定AI做哪种行为的实时决策系统。比如,如果AI有战斗和逃跑两种行为。你可以创建行为树,让AI在高于50%血量时进行战斗,低于50%血量时逃跑。

在本篇教程中,你将学习到:

  • 创建AI实体用于控制角色单位
  • 创建并使用行为树和黑板
  • 使用AI感知让角色单位获得视野
  • 创建行为让角色单位四处走动并攻击敌人

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

起步入门

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

按下Play运行游戏,在围栏内点击左键生成蘑菇小人。

在本例中,我们将创建一个能四处走动的AI,当其他蘑菇小人进入AI的视野时,AI会追逐对方并进行攻击。

要创建一个AI角色,我们需要三个元素:

  1. 身体:这个是角色的物理表现,在本例中,蘑菇小人就是身体
  2. 灵魂:这个是控制角色行为的实体,既能是玩家本身,也可以是AI
  3. 大脑: AI进行决策行为的逻辑,我们可以用C++代码,蓝图或者是行为树来实现逻辑。

现在我们已经有了身体,接着要搞来灵魂和大脑。首先,我们要创建控制器作为灵魂。

什么是控制器?

控制器是一个能控制角色单位的非物理Actor。这里所说的“控制”,具体指的是什么意思呢?

对于玩家而言,控制指的是能通过按键操控角色单位。控制器获取玩家输入,并将输入直接传给角色。当然,控制器也可以获取输入进行处理,然后再告诉角色单位做哪个行为。

对于AI来说,角色单位就是由控制器或“大脑”(取决于实现方式)来通知其做什么行为的。

为了用AI控制蘑菇小人,我们需要创建一类特殊的控制器——AI控制器

创建AI控制器

打开Characters\Muffin\AI目录并创建Blueprint Class,选中AIController作为父类并命名为AIC_Muffin

接着,我们需要让蘑菇小人使用这个AI控制器,打开Characters\Muffin\Blueprints并双击打开BP_Muffin

默认情况下,Details面板会显示蓝图的默认设置,如果没有显示,就点击Toolbar的Class Defaults

在Details面板找到Pawn设置,将AI Controller Class设为AIC_Muffin,这样当蘑菇小人生成时,就会对应生成一个AI控制器实例。

由于我们要动态生成蘑菇小人,Auto Possess AI要设成Spawned。这样当蘑菇小人生成时,AIC_Muffin就会自动控制BP_Muffin

点击Compile并关闭BP_Muffin

现在,我们要来创建决策蘑菇小人行为的逻辑,就要用上行为树

创建行为树

打开Characters\Muffin\AI目录,并选择Add New\Artificial Intelligence\Behavior Tree,将其命名为BT_Muffin并打开。

行为树编辑器

行为树编辑器包含3个新面板:

  1. Behavior Tree:这个图表面板用于创建行为树节点
  2. Details:展示选中节点的参数
  3. Blackboard:展示黑板的所有键值(后续讲解)和其对应数值。只有在游戏运行时才会有显示

像蓝图一样,行为树也是由节点构成的。行为树有4类节点,前两种分别是任务(tasks)组合(composites)节点。

什么是任务和组合节点?

顾名思义,任务节点负责完成具体任务,可以是表现一套连招这样的复杂任务,也可以是原地等待这样的简单任务。

要完成多个任务,我们就要用上组合节点。一个行为树由许多分支(行为)组成。每个分支的根节点,都是一个组合节点。不同类型的组合节点,执行其子节点的方式也各不相同。

比如,我们有一组如下序列的行为:

要按顺序执行每个行为,我们就要用上Sequence组合节点,因为Sequence节点能够从左至右的执行子节点,图表看起来是这样的:

注意:从组合节点衍生出来的节点可以称为子树(subtree)。通常来说,这些节点就统称为一个行为。比如,SequenceMove To EnemyRotate Towards EnemyAttack就统称为“攻击敌人”行为。

如果Sequence的任意节点执行失败,整个Sequence节点就会停止执行。

比如,如果角色无法移动到敌人身边,Move To Enemy节点就执行失败了,这样Rotate Towards EnemyAttack节点也就无法继续执行了。反之,如果角色成功移动到敌人边上,就能执行随后两个节点。

后续我们还会学习Selector组合节点,不过现在先让我们用Sequence节点实现角色随机移动到某个位置并原地停留。

随机移动位置

首先,创建Sequence节点并与Root节点相连。

接着,我们需要让角色移动起来,创建MoveTo节点与Sequence节点相连,这个节点可以驱动角色移动到特定位置或Actor。

随后,创建Wait节点与Sequence节点相连,确保将其放置在MoveTo节点右边,放置顺序非常重要,因为子节点是按照从左到右的顺序执行的。

注意:你可以通过每个节点右上角的数字确认其执行顺序。数字越小执行顺序越高。

恭喜你,你刚刚创建了你的第一个行为!它将会驱动角色移动到指定位置并原地停留数秒。

为了让角色移动,我们还需要指定要移动的位置。由于MoveTo节点只接受由黑板提供的数值,我们要先创建一个黑板。

创建黑板

黑板是一个单纯用来存放变量(键值)的资源。我们可以将其理解为AI的内存。

虽然黑板不是必须使用的,但它确实为我们读取,存取数据提供了极大便利,这么说的原因是很多行为树节点只接受黑板键值作为参数输入。

要创建一个黑板,我们在Content Browser选择新建Add New\Artificial Intelligence\Blackboard,将其命名为BB_Muffin并打开。

黑板编辑器

黑板编辑器由2个面板组成:

  1. Blackboard:展示所有键值列表
  2. Blackboard Details:展示所选键值的参数

现在,我们要创建一个键值用于存放目标位置。

创建目标位置键值

由于是3D空间里的一个位置点,我们需要用Vector来进行存储。点击New Key并选择Vector,将其命名为TargetLocation

接着,我们需要随机生成一个位置并将其存在黑板里,我们就需要用到第三种类型的行为树节点:服务(service)节点。

什么是服务节点?

服务节点类似于任务节点,用于完成一些事情。然而,不同于操控角色做特定行为,服务节点用于执行检查或更新黑板操作。

服务并不是独立节点,而是依附于任务节点或者组合节点。这样使得行为树更加简洁易于组织,不会横生太多节点。如果我们用任务节点来实现,效果如下图所示:

如果用服务节点来实现,则如下图所示:

现在,让我们来创建一个生成随机位置的服务吧。

创建服务

回到BT_Muffin并点击New Service

这样就会新建一个服务并自动打开,我们回到Content Browser将其重命名为BTService_SetRandomLocation

服务应当且仅当在角色准备移动时才执行,因此我们要将它附着在MoveTo节点上。

打开BT_Muffin右键点击MoveTo节点,从弹出菜单选择Add Service\BTService Set Random Location

现在,当MoveTo激活执行时,BTService_SetRandomLocation也会跟着激活执行。

接着,我们需要随机生成目标点位置。

生成随机位置

打开BTService_SetRandomLocation

为了监听获知服务何时触发执行,我们创建Event Receive Activation AI节点,这个节点会在服务父类(所附着的节点)激活时触发执行。

注意:另一个事件Event Receive Activation也有着相同的触发时机,两者区别在于Event Receive Activation AI事件额外提供了Controlled Pawn参数。

为了生成随机位置,添加如下高亮节点,确保将Radius设置为500

这样就能返回得到该角色500单位半径内的一个随机可达目标点。

注意:GetRandomPointInNavigableRadius节点使用了导航数据(称之为NavMesh)来判断一个点是否可达。在本例中,我已提前创建好了NavMesh。你可以通过在Viewport选中Show\Navigation观察NavMesh。


如果你想创建自己的NavMesh,请创建Nav Mesh Bounds Volume,缩放其大小为理想可达区域。

接下来,我们需要将位置数据存储到黑板里。有两种方式指定要存放的键值:

  1. 我们可以使用Make Literal Name节点指定键值名字
  2. 我们可以将变量暴露给行为树,这样就能在行为树里通过下拉列表选中变量

这里我们使用第二种方法。创建类型为Blackboard Key Selector的变量。将其命名为BlackboardKey并启用Instance Editable,这样行为树里的服务就会出现对应变量。

随后,创建如下高亮节点:

小结:

  1. Event Receive Activation AI节点会在其父类(本例中的MoveTo节点)激活时执行
  2. GetRandomPointInNavigableRadius节点返回角色500单位半径内的一个随机可达目标点
  3. Set Blackboard Value as Vector节点将一个黑板键值(BlackboardKey)数值设为随机位置点

点击Compile并关闭BTService_SetRandomLocation

接着,我们需要让行为树来使用这个黑板值。

使用黑板

打开BT_Muffin并确保没有选中任何东西。在Details面板的Behavior Tree设置处,将Blackboard Asset设为BB_Muffin

然后MoveToBTService_SetRandomLocation就会自动使用黑板的第一个键值,在本例中,就是TargetLocation

最后,我们需要让AI控制器来运行行为树。

运行行为树

打开AIC_Muffin并连接Run Behavior Tree节点与Event BeginPlay节点,将BTAsset设为BT_Muffin

这样当AIC_Controller生成时就会执行BT_Muffin

点击Compile并返回主编辑器,按下Play运行游戏,生成一些蘑菇小人,观察它们四处走动吧。

虽然设置很繁琐,我们还是搞定了!接着,我们要进一步设置AI控制器,让它可以在一定范围内感知敌人所在。要实现这点,就要使用AI感知(AI Perception)

设置AI感知

AI感知是一个可以添加给Actor的组件,通过它,我们可以给AI添加感官能力(如视觉和听觉)

打开AIC_Muffin并添加AIPerception

接着,我们要添加一个感官,由于我们想要蘑菇小人能够感知到其他小人靠近,我们给它加上视觉感官。

选中AIPerception并在Details面板的AI Perception设置处,给Senses Config添加新元素。

将元素0设置为AI Sight config并展开它。

对于视觉有3个主要设置:

  1. Sight Radius:蘑菇小人的最远视觉范围,将其设置为3000
  2. Lose Sight Radius:如果蘑菇小人已经看到了敌人,那敌人要逃离小人视野的距离,将其设置为3500
  3. Peripheral Vision Half Angle Degrees:决定蘑菇小人视野的角度,将其设置为45,蘑菇小人就会有90度的范围视角。

默认情况下,AI感知只检测敌人(被指定为不同队伍(team)的Actor)。然而,Actor默认是没有设置队伍的,如果Actor没有队伍,AI感知就会将其认为中立(neutral)角色。

截至目前,还没有方法能通过蓝图设置Actor的队伍,退而求其次,我们展开Detection by Affiliation设置,启用Detect Neutrals

点击Compile并回到主编辑器。按下Play运行游戏来生成蘑菇。按下 ‘ 键可以显示AI调试信息,按下小键盘的数字键4可以可视化AI感知组件。当蘑菇小人进入视野时,就会显示绿色球体。

接着,我们要让蘑菇小人往敌人的方向走去。要实现这点,行为树就要了解敌人的信息,我们通过在黑板存储敌人的引用来完成这件事。

创建敌人键值

打开BB_Muffin并添加类型为Object的键值,将其命名为Enemy

现在,我们还不能在MoveTo节点使用Enemy,因为其键值类型为Object,但MoveTo只接受VectorActor类型的键值。

为了解决这点,我们选中Enemy并展开Key Type,将Base Class设置为Actor。这样行为树就能将Enemy识别为Actor了。

关闭BB_Muffin,现在,我们要创建一个行为让AI向敌人走去。

朝敌人移动

打开BT_Muffin并断开SequenceRoot连接。我们可以通过按住Alt键点击连线来做到,并将移动子树移到一边。

接着,创建如下高亮节点,并将Blackboard Key设置为Enemy

这样角色就会朝Enemy走去。有时候,角色不会刚好面对着它的目标,所以我们还需要用上Rotate to face BB entry节点。

现在,我们需要在AI感知检测到其他蘑菇时,将其设置为Enemy的值。

设置敌人键值

打开AIC_Muffin并选中AIPerception组件,添加Perception Updated事件。

只要感官发生更新,这个事件就会触发执行。在本例中,当AI获得或丢失了某物体的视野,这个事件就会执行,并提供了其当前所能感知到的Actor列表。

添加如下高亮节点,并确保将Make Literal Name节点设置为Enemy

这样就可以判断AI目前有没有敌人对象,如果没有,我们就要给它设置一个敌人,因此添加如下节点:

小结:

  1. IsValid节点负责判断Enemy键值是否有值
  2. 如果还没设置,遍历当前所有检测到的Actor
  3. Cast To BP_Muffin节点负责检查Actor是否为蘑菇
  4. 如果是蘑菇,进一步判断是否已死亡
  5. 如果IsDead返回false,将蘑菇设置为新敌人,并退出循环

点击Compile并关闭AIC_Muffin,按下Play运行游戏并生成两个蘑菇小人,其中一个生成暴露在另一个面前,后者就会自动向前者走过去。

接着,你要创建一个自定义任务,让蘑菇小人可以表演攻击行为。

创建攻击任务

我们可以直接在Content Browser创建任务,而无须通过行为树编辑器。创建新的Blueprint Class类,并将BTTask_BlueprintBase作为其父类。

将新建类命名为BTTask_Attack并打开,添加Event Receive Execute AI节点,这个节点会在行为树激活BTTask_Attack时触发执行。

首先,你需要让蘑菇执行攻击行为。BP_Muffin包含一个IsAttacking变量,当变量设置为true时,蘑菇会执行一次攻击,因此我们添加如下高亮节点:

如果这个任务节点在这里就结束了,那行为树执行就会卡在这个节点上,因为行为树并不知道该节点已执行完毕了,所以我们要在节点链末端添加Finish Execute节点。

接着,启用Success,由于我们用的是Sequence,这样就能让BTTask_Attack的后续节点得以执行。

现在图表看起来应该是这样的:

小结:

  1. 当行为树激活BTTask_Attack节点时,Event Receive Execute AI节点就会一同触发执行。
  2. Cast To BP_Muffin节点会检查Controlled Pawn是否为BP_Muffin类型
  3. 如果是,则设置IsAttacking变量为true
  4. 通过Finish Execute节点退出当前节点,让行为树继续往下执行

点击Compile并关闭BTTask_Attack

现在,我们需要将BTTask_Attack节点添加到行为树中。

行为树添加攻击行为

打开BT_Muffin,随后,将BTTask_Attack节点添加到Sequence节点后面。

接着,将Wait节点添加到Sequence节点后面,并将Wait Time设置为2。确保蘑菇小人不会攻击个不停。

回到主编辑器点击Play运行游戏,像上次一样生成两个蘑菇小人。蘑菇小人会朝着敌人走去。随后,它会尝试攻击,然后休息两秒。当它发现另一个敌人时,又会重复以上行为。

在最后一部分,我们要将攻击和移动两颗子树合并在一起。

合并子树

为了合并子树,我们要用上Selector组合节点。类似于Sequence节点,它也是按从左向右的顺序执行的。然而,Selector节点会在子节点返回成功而非失败时停止执行。利用这个特性,就可以确保行为树每次只执行一颗子树。

打开BT_Muffin并在Root节点下创建Selector节点。随后,如下图连接两个子树:

这样同一时间只有一颗子树会得到执行,下面是每颗子树的执行情况:

  • 攻击: Selector节点会首先运行第一颗子树,如果所有任务都成功了,Sequence节点也会返回执行成功。Selector节点得知执行成功,就会停止执行后面的节点,这样就不会再执行移动节点。

  • 移动: Selector节点会尝试运行前面的攻击子树,如果Enemy还没有值,MoveTo节点就会执行失败,Sequence节点也就同样失败。由于第一个子树失败了,Selector节点就会执行后续这颗移动子树。

回到主编辑器,按下Play运行游戏,生成一些蘑菇小人试试看吧!

“等等,为什么图中这个蘑菇小人没有马上攻击另一只呢?”

在传统的行为树设计里,行为树每帧都会从根节点开始执行,意味着每帧更新,它都会尝试执行第一颗攻击子树,然后再执行第二颗移动子树,这也意味着当Enemy值发生变化时,行为树就会马上切换执行另一颗子树。

然而,Unreal的行为树并不是这样设计执行的。在Unreal里,行为树会继续执行上一帧选中的那颗子树。图中由于AI感知没有马上感知到另一只蘑菇小人的存在,行为树开始执行移动子树,于是行为树就只能乖乖等待移动子树执行完毕,才能重新评估确定执行攻击子树。

为了解决这个问题,我们需要用上最后一种类型节点:装饰(decorators)节点。

创建装饰节点

类似于服务节点,装饰节点也依附于任务或组合节点。通常而言,装饰节点用于做前置检查。如果检查结果为true,装饰节点就返回true,反之亦然。通过装饰节点,就能控制其依附节点是否能够执行。

装饰节点也有能力中止子树的运行,这意味着我们能实现一旦Enemy有设值,就立即中止移动子树。这样蘑菇小人就能在发现敌人的第一时间攻击敌人。

要实现中止功能,我们可以使用Blackboard装饰节点,这个节点只是简单地检查某个黑板键值是否有值。打开BT_Muffin,并在攻击子树的Sequence节点点击右键,从弹出菜单选中Add Decorator\Blackboard,这样Sequence节点就会添加上Blackboard节点。

接着,选中Blackboard装饰节点,并在Details面板将Blackboard Key设为Enemy

这样可以判断Enemy是否有值,如果没有值,节点返回失败,从而导致Sequence失败,从而让移动子树得到执行。

为了中止移动子树,我们需要用上Observer Aborts设置。

使用Observer Aborts

Observer Aborts能够实现所选中的黑板键值发生变化时,中止执行子树,这里分为两种类型的中止:

  1. Self: 该设置允许当Enemy值失效时,立即中止运行攻击子树,这种情况发生在攻击子树还未运行完毕,而Enemy又死亡的时候。
  2. Lower Priority:该设置允许当Enemy有值时,中止运行较低优先度的子树。由于移动子树放在攻击子树后面,它就是较低优先度子树。

我们将Observer Aborts设为Both,同时启用两种类型的中止。

现在,当AI已经没有敌人目标时,可以马上从攻击子树切换运行移动子树。同样的,当AI检测到敌人目标时,又能从移动子树切换运行攻击子树。

以下是完整的行为树图表:

攻击子树小结:

  1. Enemy有值,Selector开始运行攻击子树
  2. 一旦运行子树,角色开始朝敌人走去
  3. 随后,进行攻击
  4. 最后,角色原地停留2秒

移动子树小结:

  1. Enemy没有值,攻击子树运行失败时,Selector继续运行移动子树
  2. BTService_SetRandomLocation生成一个随机位置
  3. 角色朝指定位置移动
  4. 随后,角色原地停留5秒

关闭BT_Muffin并按下Play运行游戏,生成一些蘑菇小人进行一场你死我活的决斗吧!

后续学习

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

如你所见,制作简单AI还算一件不难的事。如果你想创建一个更加高级的AI,请查阅场景查询系统,这个系统允许AI收集场景数据并作出相应的反馈。

如果你还想继续学习引擎其他内容,点击下篇教程,将教你如何制作一个简单的第一人称射击游戏。

Unreal Engine 4 系列教程 Part 9:AI教程的更多相关文章

  1. Unreal Engine 4 系列教程 Part 1:入门

    原文:Unreal Engine 4 Tutorial for Beginners: Getting Started 作者:Tommy Tran 译者:Shuchang Liu 本篇教程将引导你安装U ...

  2. Unreal Engine 4 系列教程 Part 2:蓝图教程

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  3. Unreal Engine 4 系列教程 Part 3:材质教程

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  4. Unreal Engine 4 系列教程 Part 4:UI教程

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  5. Unreal Engine 4 系列教程 Part 5:制作简单游戏

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  6. Unreal Engine 4 系列教程 Part 10:制作简单FPS游戏

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  7. Unreal Engine 4 系列教程 Part 8:粒子系统教程

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  8. Unreal Engine 4 系列教程 Part 7:音频教程

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  9. Unreal Engine 4 系列教程 Part 6:动画教程

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

随机推荐

  1. Java连载50-import导入、访问控制权限修饰符

    一.import 1.import语句用来完成导入其他类,同一个包下的类不需要再导入 不在同一个包下需要手动导入. 2.import语法格式 import 类名: import 包名.*; //imp ...

  2. js scroll事件

    滚动改变头部颜色,当滚动到最顶端头部颜色还原 滚动前 滚动后 代码

  3. IT兄弟连 HTML5教程 HTML5的曲折发展过程 浏览器之间的大战

    播放电影和音乐要使用播放器,浏览网页就需要使用浏览器.浏览器虽然只是一个设备,并不是开发语言,但在Web开发中必不可少,因为浏览器要去解析HTML5.CSS3和JavaScript等语言用于显示网页, ...

  4. Python的条件锁与事件共享

    1:事件机制共享队列: 利用消息机制在两个队列中,通过传递消息,实现可以控制的生产者消费者问题要求:readthread读时,writethread不能写:writethread写时,readthre ...

  5. python 安装impala包

    一路安装就可以 .pip install six .pip install bit_array .pip install thriftpy .pip install thrift_sasl .pip ...

  6. XAF中多对多关系 (XPO)

    In this lesson, you will learn how to set relationships between business objects. For this purpose, ...

  7. Vuex细说

    vuex 1,什么是 vuex? vuex 是一个专门为 vue.js 应用程序 开发的状态管理模式+库 它充当应用程序中所有组件的集中存储(数据状态) ,其规则确保状态只能以可预测的方式进行变更 并 ...

  8. HTML颜色名称大全

    所有浏览器支持的颜色名称,所有现代浏览器都支持以下140种颜色名称(单击颜色名称或十六进制值,以将颜色视为背景颜色以及不同的文本颜色): 有关HTML颜色的完整概述,请访问我们的颜色教程. 颜色名称 ...

  9. Android多module下重复jar包问题

    版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/166 Android多module下重复jar包问题 An ...

  10. mysql常见错误代码解释

    mysql常见错误代码解释 原创 作者:bayaim 时间:2017-12-26 11:07:14 38  ---------------------------------------------- ...