.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的一套可视化脚本系统,通过蓝图可以快速制作游戏原型。你不再需要一行行的编写代码,取而代之的是可视化操作:拖拽节点,在UI里设置节点参数,给节点进行连线。
除了作为一款非常便捷的原型工具,蓝图也降低了非开发人员制作游戏逻辑的门槛。
在本篇教程,你将使用蓝图来:
- 设置垂直视角摄像机
- 创建具备基本移动的玩家控制器角色
- 设置玩家输入
- 创建可被角色触碰并收集的道具
起步入门
下载示例项目并解压。进入项目文件夹,双击BananaCollector.uproject打开项目。
你可以看到以下的场景。我们创建的角色会在场景内移动并收集道具。
为了方便起见,我已经给项目文件做了文件夹分类,如下图所示:
你可以点击红框处按钮来显示/隐藏侧边栏列表。
创建玩家
点击进入Content Browser界面的Blueprints文件夹。点击Add New按钮并选择Blueprint Class。
由于玩家需要能够获取输入,Pawn类比较适合。在弹出窗口选择创建Pawn,将其命名为BP_Player。
关联摄像机
摄像机是玩家观察游戏世界的方式。我们要创建一个垂直视角观察玩家角色的摄像机。
在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事件每帧调用,那同等时间内运动节点执行的次数更多。导致的结果就是高配机器角色移动得更快,反之亦然。
为了避免这个问题,角色运动必须是帧率无关的。
帧率无关
帧率无关意味着不管帧率是多少,相同时间的游戏运行,会产生完全一致的结果。幸运地是,在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只认为方块的根组件可产生碰撞。由于方块的根组件并没有任何碰撞体,所以就穿透了其他物体。
所以,为了使用碰撞网格,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引擎的材质系统。