diff --git a/Frontend/implementations/EpicGames/src/assets/css/showcase.css b/Frontend/implementations/EpicGames/src/assets/css/showcase.css new file mode 100644 index 00000000..cc7d5119 --- /dev/null +++ b/Frontend/implementations/EpicGames/src/assets/css/showcase.css @@ -0,0 +1,201 @@ + +:root { + --democolor0: rgba(15, 15, 15, 1); + --democolor1: #000000; + --democolor2: #FFFFFF; + --democolor3: #0585fe; + --democolor4: rgba(26, 26, 26, 1); + --democolor5: rgba(36, 36, 36, 1); + --democolor6: rgba(53, 53, 53, 1); + --democolor7: rgba(180, 180, 180, 1); +} + +body { + margin: 0px; + padding: 0px; + height: 100vh; + width: 100vw; + background-color: var(--democolor5); + font-family: verdana,sans-serif; + color: var(--democolor7); +} + +code { + background-color: var(--democolor6); +} + +.wrapper { + display: flex; + align-items: stretch; + height: 100%; + width: 100%; +} + +.spaced-row { + display: flex; + flex-direction: row; + justify-content: space-evenly; +} + +#infocontainer { + padding: 0.5em; + font-size: large; + min-height: 15vh; + max-height: 15vh; + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +#infoinstructions { + background-color: var(--democolor4); + padding: 0.5em; + font-size: medium; + flex-grow: 1; + overflow-y: auto; +} + +#content { + width: 100%; + flex-direction: column; + display: flex; + align-items: initial; + justify-content: center; +} + +#exampletitle { + padding: 1em; + display: flex; + flex-direction: column; + justify-content: center; + align-items: baseline; +} + +#sidebar { + min-width: 250px; + max-width: 250px; + background-color: var(--democolor4); + transition: all 0.3s; + font-size: small; + border-color: var(--democolor4); + border-style: solid; + border-width: 0.33em 0.33em 0em 0em; + align-content: flex-start; + display: flex; + flex-direction: column; +} + +#sidebar-tab-header { + width: 100%; +} + +#sidebar-header { + padding: 0.25rem 2rem 0.25rem 0.5rem; + background-color: var(--democolor5); + border-top: 1px solid var(--democolor3); + border-radius: 5px 5px 0px 0px; + margin-left: 1em; + width: -moz-fit-content; + width: fit-content; +} + +#sidebarContent { + max-height:100%; + overflow-y:auto; + background-color: var(--democolor5); + flex-grow: 1; + padding-left: 1em; + padding-right: 1em; +} + +#sidebar-example-selector { + background-color: var(--democolor5); + padding-top: 1em; + padding-bottom: 1em; + padding-left: 1em; +} + +#psdemotext { + font-size: large; +} + +#playercontainer { + background-color: var(--democolor0); + flex-grow: 1; + font-family: 'Montserrat', sans-serif; +} + +select { + font-size: large; + padding: 0.5em; + color: var(--democolor7); + background-color: var(--democolor0); + border: 2px solid var(--democolor6); + outline: none !important; + border-radius: 5px; +} + +select:hover { + color: var(--democolor2); +} + +a, a:hover, a:focus { + color: inherit; + text-decoration: none; + transition: all 0.3s; +} + +#sidebar ul.components { + padding: 20px 0; +} + +#sidebar ul p { + color: #fff; + padding: 10px; +} + +#sidebar ul li a { + padding: 10px; + font-size: 1.1em; + display: block; +} +#sidebar ul li a:hover { + color: #7386D5; + background: #fff; +} + +#sidebar ul li.active > a, a[aria-expanded="true"] { + color: #fff; + background: #212f44; + /*#f90;*/ +} +ul ul a { + font-size: 0.9em !important; + padding-left: 30px !important; + background: #354b6d; +} + +a[data-toggle="collapse"] { + position: relative; +} + +.dropdown-toggle::after { + display: block; + position: absolute; + top: 50%; + right: 20px; + transform: translateY(-50%); +} + +.characterBtn { + width: 100%; + cursor: pointer; +} + +.characterBtn:hover { + box-shadow: var(--democolor3) 0px 0px 0px 3px; +} + +.characterBtn:active { + box-shadow: var(--democolor5) 0px 0px 0px 3px; +} \ No newline at end of file diff --git a/Frontend/implementations/EpicGames/src/assets/images/Aurora.jpg b/Frontend/implementations/EpicGames/src/assets/images/Aurora.jpg new file mode 100644 index 00000000..3c7fe700 Binary files /dev/null and b/Frontend/implementations/EpicGames/src/assets/images/Aurora.jpg differ diff --git a/Frontend/implementations/EpicGames/src/assets/images/Crunch.jpg b/Frontend/implementations/EpicGames/src/assets/images/Crunch.jpg new file mode 100644 index 00000000..6984314a Binary files /dev/null and b/Frontend/implementations/EpicGames/src/assets/images/Crunch.jpg differ diff --git a/Frontend/implementations/EpicGames/src/player.ts b/Frontend/implementations/EpicGames/src/player.ts index 08823767..310907f1 100644 --- a/Frontend/implementations/EpicGames/src/player.ts +++ b/Frontend/implementations/EpicGames/src/player.ts @@ -1,24 +1,24 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; -import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2'; -const PixelStreamingApplicationStyles = - new PixelStreamingApplicationStyle(); -PixelStreamingApplicationStyles.applyStyleSheet(); - -document.body.onload = function() { - // Example of how to set the logger level - // Logger.SetLoggerVerbosity(10); - - // Create a config object - const config = new Config({ useUrlParams: true }); - - // Create a Native DOM delegate instance that implements the Delegate interface class - const stream = new PixelStreaming(config); - const application = new Application({ - stream, - onColorModeChanged: (isLightMode) => PixelStreamingApplicationStyles.setColorMode(isLightMode) - }); - // document.getElementById("centrebox").appendChild(application.rootElement); - document.body.appendChild(application.rootElement); -} +// Copyright Epic Games, Inc. All Rights Reserved. + +import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2'; +const PixelStreamingApplicationStyles = + new PixelStreamingApplicationStyle(); +PixelStreamingApplicationStyles.applyStyleSheet(); + +document.body.onload = function() { + // Example of how to set the logger level + // Logger.SetLoggerVerbosity(10); + + // Create a config object + const config = new Config({ useUrlParams: true }); + + // Create a Native DOM delegate instance that implements the Delegate interface class + const stream = new PixelStreaming(config); + const application = new Application({ + stream, + onColorModeChanged: (isLightMode) => PixelStreamingApplicationStyles.setColorMode(isLightMode) + }); + // document.getElementById("centrebox").appendChild(application.rootElement); + document.body.appendChild(application.rootElement); +} diff --git a/Frontend/implementations/EpicGames/src/showcase.html b/Frontend/implementations/EpicGames/src/showcase.html new file mode 100644 index 00000000..ed6142d4 --- /dev/null +++ b/Frontend/implementations/EpicGames/src/showcase.html @@ -0,0 +1,61 @@ + + + + + Pixel Streaming Showcase + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+ +
+
+ Information here +
+
+ +
+
+ + + diff --git a/Frontend/implementations/EpicGames/src/showcase.ts b/Frontend/implementations/EpicGames/src/showcase.ts new file mode 100644 index 00000000..c26889fe --- /dev/null +++ b/Frontend/implementations/EpicGames/src/showcase.ts @@ -0,0 +1,297 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2'; +export const PixelStreamingApplicationStyles = new PixelStreamingApplicationStyle(); +PixelStreamingApplicationStyles.applyStyleSheet(); + + +document.body.onload = function() { + // Example of how to set the logger level + // Logger.SetLoggerVerbosity(10); + + // Create a config object + const config = new Config({ useUrlParams: true }); + + // Create Pixel Streaming application + const stream = new PixelStreaming(config); + const application = new Application({ + stream, + onColorModeChanged: (isLightMode) => PixelStreamingApplicationStyles.setColorMode(isLightMode) + }); + document.getElementById("playercontainer").appendChild(application.rootElement); + + const showcase = new Showcase(stream); + + // Bind example selection to the onExampleChanged function + document.getElementById("exampleSelect").onchange = (event : Event) => { showcase.onExampleChanged(event); }; + + +} + +class Showcase { + + private _pixelStreaming : PixelStreaming; + private _infoElem : HTMLElement; + private _exampleSettingsElem : HTMLElement; + + constructor (pixelStreaming : PixelStreaming) { + this._pixelStreaming = pixelStreaming; + this._infoElem = document.getElementById("infoinstructions") as HTMLElement; + this._exampleSettingsElem = document.getElementById("sidebarContent") as HTMLElement; + this._createGettingStartedExample(); + } + + /** + * Event fired for when the selection drop down containing our showcase examples changes. + * @param event The change event. + */ + onExampleChanged(event : Event) : void { + + if(!event) { return; } + + const selectElement = event.target as HTMLSelectElement; + const exampleName = selectElement.value; + this._createExample(exampleName); + } + + private _createExample(exampleName : string) { + + // clear example elements + while (this._exampleSettingsElem.lastElementChild) { + this._exampleSettingsElem.removeChild(this._exampleSettingsElem.lastElementChild); + } + + // create the relevant example based on the string passed in + switch(exampleName) { + case "Send Data to UE": + this._createSendUEDataExample(); + break; + case "Getting Started": + this._createGettingStartedExample(); + break; + case "Send Commands to UE": + this._createUECommandExample(); + break; + default: + break; + } + } + + private _onCharacterClicked(characterName : string) { + this._pixelStreaming.emitUIInteraction({ Character: characterName }); + } + + private _onSkinClicked(skinIndex : number) { + this._pixelStreaming.emitUIInteraction({ Skin: skinIndex }); + } + + private _onResClicked(width : number, height : number) { + this._pixelStreaming.emitCommand({ Resolution: { Width: width, Height: height } }); + } + + private _createGettingStartedExample() { + this._infoElem.innerHTML = + ` +

Welcome to the Pixel Streaming demo showcase!

+

Getting Started

+
    +
  1. Run the Unreal Engine Pixel Streaming demo project with launch args for this server, example: -PixelStreamingUrl=ws://localhost:8888.
  2. +
  3. Click the "click to start" text on this page to start streaming.
  4. +
  5. Use the drop down to select an example.
  6. +
  7. Use control panel on the left to interact with the example.
  8. +
+ `; + } + + private _createSendUEDataExample() { + + this._infoElem.innerHTML = + ` +

Example: Sending data to Unreal Engine

+
    +
  1. Click the character portraits to change character.
  2. +
  3. Click the skins to change character skins.
  4. +
+

Under the hood these interactions use the WebRTC data channel to send a data payload that we interpret on the UE side and respond to appropriately.

+

In particular the function called to send custom data to Unreal Engine from the frontend is:

+ pixelstreaming.emitUIInteraction(data: object | string) + `; + + const characterSelectElem = document.createElement("div"); + this._exampleSettingsElem.appendChild(characterSelectElem); + + const sendDataTitle = document.createElement("h2"); + sendDataTitle.innerText = "Send data: "; + characterSelectElem.appendChild(sendDataTitle); + + const characterSelectTitle = document.createElement("p"); + characterSelectTitle.innerText = "Select a character: "; + characterSelectElem.appendChild(characterSelectTitle); + + // Make Aurora character + const auroraElem = document.createElement("div"); + characterSelectElem.appendChild(auroraElem); + const auroraImg = document.createElement("img"); + auroraImg.classList.add("characterBtn"); + auroraImg.src = "./images/Aurora.jpg"; + auroraImg.onclick = () => { this._onCharacterClicked("Aurora"); } + auroraElem.appendChild(auroraImg); + + // Make Crunch character + const crunchElem = document.createElement("div"); + characterSelectElem.appendChild(crunchElem); + const crunchImg = document.createElement("img"); + crunchImg.classList.add("characterBtn"); + crunchImg.src = "./images/Crunch.jpg"; + crunchImg.onclick = () => { this._onCharacterClicked("Crunch"); } + crunchElem.appendChild(crunchImg); + + // Make skin selection title + const skinSelectTitle = document.createElement("p"); + skinSelectTitle.innerText = "Select a skin: "; + this._exampleSettingsElem.appendChild(skinSelectTitle); + + // Make skin selection + const skinSelectElem = document.createElement("div"); + skinSelectElem.classList.add("spaced-row"); + this._exampleSettingsElem.appendChild(skinSelectElem); + + // Make skin selection buttons + + // Skin1 + const skin1Btn = document.createElement("button"); + skin1Btn.classList.add("btn-flat"); + skin1Btn.onclick = () => { this._onSkinClicked(0); } + skin1Btn.innerText = "Skin 1"; + skinSelectElem.appendChild(skin1Btn); + + // Skin2 + const skin2Btn = document.createElement("button"); + skin2Btn.classList.add("btn-flat"); + skin2Btn.onclick = () => { this._onSkinClicked(1); } + skin2Btn.innerText = "Skin 2"; + skinSelectElem.appendChild(skin2Btn); + + // Skin3 + const skin3Btn = document.createElement("button"); + skin3Btn.classList.add("btn-flat"); + skin3Btn.onclick = () => { this._onSkinClicked(2); } + skin3Btn.innerText = "Skin 3"; + skinSelectElem.appendChild(skin3Btn); + } + + private _createUECommandExample() { + + this._infoElem.innerHTML = + ` +

Example: Triggering Commands in Unreal Engine

+ +

Under the hood these interactions use the WebRTC data channel to send command messages that we interpret on the UE side to call specific UE functions.

+

There are a very select set of built-in commands such as changing resolution and change encoder QP, which can be triggered like so:

+ pixelStreaming.emitCommand({"Encoder.MinQP": 51,}) + +

However, you can bind your own custom commands in C++ using:

+ + // C++ side +
+ IPixelStreamingInputHandler::SetCommandHandler(const FString& CommandName, const TFunction& Handler) +
+ // JS side +
+ pixelstreaming.emitCommand({"MyCustomCommand": "MyCustomCommandParameter"}); +
+ +

Additionally you can also trigger Unreal Engine console commands like stat gpu if you launch Pixel Streaming with -AllowPixelStreamingCommands then calling:

+ pixelstreaming.emitConsoleCommand(command: string) + `; + + // Add a new element for containing elements for res changing feature + const changeResElem = document.createElement("div"); + this._exampleSettingsElem.appendChild(changeResElem); + + // Make res change title + const changeResTitle = document.createElement("h2"); + changeResTitle.innerText = "Send a custom command: "; + changeResElem.appendChild(changeResTitle); + + // Make change resolution text + const changeResText = document.createElement("p"); + changeResText.innerHTML = "Change resolution" + changeResElem.appendChild(changeResText); + + // Make res change button container + const changeResBtnContainer = document.createElement("div"); + changeResBtnContainer.classList.add("spaced-row"); + this._exampleSettingsElem.appendChild(changeResBtnContainer); + + // Make res change buttons + + // 720p + const res720pBtn = document.createElement("button"); + res720pBtn.classList.add("btn-flat"); + res720pBtn.onclick = () => { this._onResClicked(1280, 720); } + res720pBtn.innerText = "720p"; + changeResBtnContainer.appendChild(res720pBtn); + + // 1080p + const res1080pBtn = document.createElement("button"); + res1080pBtn.classList.add("btn-flat"); + res1080pBtn.onclick = () => { this._onResClicked(1920, 1080); } + res1080pBtn.innerText = "1080p"; + changeResBtnContainer.appendChild(res1080pBtn); + + // 1440p + const res1440pBtn = document.createElement("button"); + res1440pBtn.classList.add("btn-flat"); + res1440pBtn.onclick = () => { this._onResClicked(2560, 1440); } + res1440pBtn.innerText = "1440p"; + changeResBtnContainer.appendChild(res1440pBtn); + + // 4k + const res4kBtn = document.createElement("button"); + res4kBtn.classList.add("btn-flat"); + res4kBtn.onclick = () => { this._onResClicked(3840, 2160); } + res4kBtn.innerText = "4k"; + changeResBtnContainer.appendChild(res4kBtn); + + // Add a new element for containing elements for res changing feature + const consoleCommandElem = document.createElement("div"); + this._exampleSettingsElem.appendChild(consoleCommandElem); + + // Make console command title + const consoleCommandTitle = document.createElement("h2"); + consoleCommandTitle.innerText = "Send a console command: "; + consoleCommandElem.appendChild(consoleCommandTitle); + + // Text informing using about -AllowPixelStreamingCommands + const informPSCommandsText = document.createElement("p"); + informPSCommandsText.innerHTML = "(Requires UE side launched with -AllowPixelStreamingCommands)" + consoleCommandElem.appendChild(informPSCommandsText); + + // Make buttons for stat fps/stat gpu + const consoleCmdBtnsContainer = document.createElement("div"); + consoleCmdBtnsContainer.classList.add("spaced-row"); + this._exampleSettingsElem.appendChild(consoleCmdBtnsContainer); + + // stat fps + const statfpsBtn = document.createElement("button"); + statfpsBtn.classList.add("btn-flat"); + statfpsBtn.onclick = () => { this._pixelStreaming.emitConsoleCommand("stat fps"); } + statfpsBtn.innerText = "stat fps"; + consoleCmdBtnsContainer.appendChild(statfpsBtn); + + // stat pixelstreaming + const statgpuBtn = document.createElement("button"); + statgpuBtn.classList.add("btn-flat"); + statgpuBtn.onclick = () => { this._pixelStreaming.emitConsoleCommand("stat pixelstreaming"); } + statgpuBtn.innerText = "stat pixelstreaming"; + consoleCmdBtnsContainer.appendChild(statgpuBtn); + + } + +} + + diff --git a/Frontend/implementations/EpicGames/webpack.common.js b/Frontend/implementations/EpicGames/webpack.common.js index 0af7cf62..d5d0e0fe 100644 --- a/Frontend/implementations/EpicGames/webpack.common.js +++ b/Frontend/implementations/EpicGames/webpack.common.js @@ -44,12 +44,12 @@ module.exports = { } }, { - test: /\.(png|svg)$/i, + test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource', generator: { filename: 'images/[name][ext]' } - }, + } ], }, resolve: {