Add help overlay, Fix joystick, Update comment (#45)

* add help overlay, fix joystick

* Remove icecandidate in connectionState

* UPgrade turn server

* Update README

* Update README

* Update readme
This commit is contained in:
giongto35 2019-05-27 19:31:07 +08:00 committed by GitHub
parent aa6dfcb7f8
commit da292a4e33
18 changed files with 176 additions and 30 deletions

4
README.md vendored
View file

@ -1,5 +1,5 @@
# POGO, Web-based Cloud Gaming Service
**SEA**: [http://cloud.webgame2d.com](http://cloud.webgame2d.com) | **US West**: (not hosted)| **US East**: (not hosted)| **Europe**: (not hosted)
**SEA**: [http://cloud.webgame2d.com](http://cloud.webgame2d.com) | **US West**: [http://usw.cloud.webgame2d.com](http://usw.cloud.webgame2d.com) | **US East**: [http://use.cloud.webgame2d.com](http://use.cloud.webgame2d.com) | **Europe**: [http://eu.cloud.webgame2d.com](http://eu.cloud.webgame2d.com) | **Instruction**: [Instruction](document/instruction/)
For the best gaming experience, please select the closest region to you.
**Video demo**: https://www.youtube.com/watch?v=koqWB1VKflo
@ -49,7 +49,7 @@ And run
* The scripts includes build the binary using Go module
## Documentation
[Design Doc](document/)
[Design Doc](document/designdoc/)
[Implentation Doc](document/implementation/)
## Follow up

View file

@ -7,14 +7,14 @@ Web-based Cloud Gaming Service contains multiple workers for gaming streaming an
Worker is responsible for streaming game to frontend
![worker](../img/worker.png)
- After Coordinator found the most appropriate server for the user, webRTC peer-to-peer handshake will be conducted. Coordinator will exchange the signature (WebRTC Session Remote Description) between two peers over Web Socket connection.
- After Coordinator matches most appropriate server to the user, webRTC peer-to-peer handshake will be conducted. Coordinator will exchange the signature (WebRTC Session Remote Description) between two peers over Web Socket connection.
- On worker, each user session will spawn a new room running a gaming emulator. Image stream and audio stream from emulator is captured and encoded to WebRTC streaming format. We applied Vp8 for Video compression and Opus for audio compression to ensure the smoothest experience. After finish encoded, these stream is then piped out to user and observers joining that room.
- On the other hand, input from users is sent to workers over WebRTC DataChannel. Game logic on the emulator will be updated based on the input stream.
- Game state is stored in cloud storage, so all workers can collaborate and keep the same understanding with each other. It allows user can continue from the saved state in the next time.
## Overlord
Overlord is in charge of picking the most suitable workers for a user. Every time a user connects to Overlord, it will collect all the metric from all workers, i.e free CPU resources and latency from worker to user. Overlord will decide the best candidate based on the metric and setup peer-to-peer connection between worker and user based on WebRTC protocol
Overlord is loadbalancer and coordinator, which is in charge of picking the most suitable workers for a user. Every time a user connects to Overlord, it will collect all the metric from all workers, i.e free CPU resources and latency from worker to user. Overlord will decide the best candidate based on the metric and setup peer-to-peer connection between worker and user based on WebRTC protocol
![Architecture](../img/overlord.png)

28
document/instruction/README.md vendored Normal file
View file

@ -0,0 +1,28 @@
# Web-based Cloud Gaming Service Game Instruction
The game can be played on Desktop, Mobile (Android only). You can plug joystick to play with the game.
Click question mark on the top left to see game instruction.
## Key map on Desktop
Game keymap follows
Arrow keys to move
H -> Show help
C -> Start
V -> Select
Z -> A
X -> B
S -> Save (Save state)
A -> Load (Load previous saved state)
W -> Share your running game to other or you can keep it to continue playing the next time. Multiple people can access the same game for multiplayer or observation.
F -> Full screen
Q -> Quit the current game and go to menu screen.**NOTE**: we are facing some issue with quit, so it's better to refresh the page.
## Mobile play
You can play the game on Android device. Make sure your Android has the version that support WebRTC. IOS doesn't support WebRTC streaming now.
The keys map are equivalent to Desktop. Press the button to fire input.
## Joystick
The game also accepts joystick, so you can try plug in one and experience. It will be very fun!

BIN
pogo vendored

Binary file not shown.

44
static/css/main.css vendored
View file

@ -1,9 +1,10 @@
body {
background-image: url('/static/img/500_F_241143639_RW6VdHbqYRM0yfNpDxDlWpj0uXWglAC2.jpg');
background-image: url('/static/img/background.jpg');
background-repeat: repeat;
}
#gamebody {
overflow: hidden;
display: block;
width: 556px;
height: 278px;
@ -21,6 +22,33 @@ body {
box-shadow: inset 0px 0px 2px 2px rgba(219, 222, 222, 1);
}
#help-overlay {
width: 100%;
height: 100%;
display: none;
position: absolute;
}
#help-overlay-background {
width: 100%;
height: 100%;
position: absolute;
background-color: gray;
opacity: 0.4;
}
#help-overlay-detail {
width: 100%;
height: 100%;
display: block;
position: absolute;
background-image: url('/static/img/help_overlay.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
#circle-pad-holder {
display: block;
@ -168,6 +196,18 @@ body {
box-shadow: 0px 0px 2px 2px rgba(219, 222, 222, 1);
}
#btn-help {
padding-top: 5px;
width: 20px;
height: 15px;
font-size: 10px;
top: 10px;
left: 15px;
background-color: lightskyblue;
}
#btn-load {
top: 30px;
left: 435px;
@ -352,7 +392,7 @@ body {
height: 240px;
/* background-color: gray; */
background-image: url('/static/img/pixel_waterfall_bg__by_isohei-d4xntof.gif');
background-image: url('/static/img/screen_background1.gif');
background-size: cover;
}

9
static/game.html vendored
View file

@ -20,7 +20,6 @@
<body>
<div id="gamebody">
<div id="circle-pad-holder">
<div id="btn-up" class="dpad" value="up"></div>
<div id="btn-down" class="dpad" value="down"></div>
@ -30,8 +29,7 @@
</div>
<div id="bottom-screen">
<video id="game-screen" autoplay=true
poster="/static/img/october_2nd___gameboy_poltergeist_by_wanyo-dbpdmnd.gif"></video>
<video id="game-screen" autoplay=true poster="/static/img/screen_loading.gif"></video>
<div id="menu-screen">
<div id="menu-container">
@ -66,6 +64,11 @@
<div id="noti-box" unselectable="on" class="unselectable">Oh my god</div>
<div id="help-overlay">
<div id="help-overlay-background"></div>
<div id="help-overlay-detail"></div>
</div>
<div id="btn-help" unselectable="on" class="btn unselectable" value="help">?</div>
</div>

View file

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 235 KiB

Before After
Before After

BIN
static/img/help_overlay.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 886 KiB

After

Width:  |  Height:  |  Size: 886 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

@ -2,6 +2,36 @@
Menu Controller
*/
function showHelpScreen() {
// show btn-save, btn-load
if (screenState === "menu") {
$("#btn-save").show();
$("#btn-load").show();
$("#menu-screen").hide();
} else {
$("#game-screen").hide();
}
// show help overlay
$("#help-overlay").show();
}
function hideHelpScreen() {
//
if (screenState === "menu") {
$("#btn-save").hide();
$("#btn-load").hide();
$("#menu-screen").show();
} else {
$("#game-screen").show();
}
// show help overlay
$("#help-overlay").hide();
}
function reloadGameMenu() {
log("Load game menu");
@ -143,6 +173,10 @@ function doButtonDown(name) {
} else if (screenState === "game") {
setKeyState(name, true);
}
if (name === "help") {
showHelpScreen();
}
}
@ -150,11 +184,32 @@ function doButtonUp(name) {
$(`#btn-${name}`).removeClass("pressed");
if (screenState === "menu") {
if (name === "up" || name === "down") {
stopGamePickerTimer();
} else if (name === "join" || name === "a" || name === "b" || name === "start" || name === "select") {
startGame();
//log("select game");
switch (name) {
case "up":
case "down":
stopGamePickerTimer();
break;
case "join":
case "a":
case "b":
case "start":
case "select":
startGame();
break;
case "quit":
popup("You are already in menu screen!");
break;
case "load":
popup("Lets play to load game!");
break;
case "save":
popup("Lets play to save game!");
break;
}
} else if (screenState === "game") {
setKeyState(name, false);
@ -184,6 +239,7 @@ function doButtonUp(name) {
openFullscreen(screen);
}
break;
case "quit":
stopGameInputTimer();
showMenuScreen();
@ -196,5 +252,9 @@ function doButtonUp(name) {
break;
}
}
if (name === "help") {
hideHelpScreen();
}
}

View file

@ -15,6 +15,10 @@
axis 0, 1 <--> second dpad
*/
/*
change full to help (temporary)
*/
let joystickMap;
let joystickState;
let joystickIdx;
@ -28,7 +32,7 @@ function checkJoystickAxisState(name, state) {
if (state === true) {
doButtonDown(name);
} else {
doButtonUp(button);
doButtonUp(name);
}
}
}
@ -70,7 +74,7 @@ function checkJoystickState() {
// we only capture the last plugged joystick
$(window).on("gamepadconnected", function (event) {
window.addEventListener("gamepadconnected", function (event) {
var gamepad = event.gamepad;
log(`Gamepad connected at index ${gamepad.index}: ${gamepad.id}. ${gamepad.buttons.length} buttons, ${gamepad.axes.length} axes.`);
@ -83,28 +87,29 @@ $(window).on("gamepadconnected", function (event) {
if (os === "android") {
// default of android is KeyMap1
joystickMap = { 2: "a", 0: "b", 3: "start", 4: "select", 10: "load", 11: "save", 8: "full", 9: "quit", 12: "up", 13: "down", 14: "left", 15: "right" };
joystickMap = { 2: "a", 0: "b", 3: "start", 4: "select", 10: "load", 11: "save", 8: "help", 9: "quit", 12: "up", 13: "down", 14: "left", 15: "right" };
} else {
// default of other OS is KeyMap2
joystickMap = { 0: "a", 1: "b", 2: "start", 3: "select", 8: "load", 9: "save", 6: "full", 7: "quit", 12: "up", 13: "down", 14: "left", 15: "right" };
joystickMap = { 0: "a", 1: "b", 2: "start", 3: "select", 8: "load", 9: "save", 6: "help", 7: "quit", 12: "up", 13: "down", 14: "left", 15: "right" };
}
if (os === "android" && (browser === "firefox" || browser === "uc")) { //KeyMap2
joystickMap = { 0: "a", 1: "b", 2: "start", 3: "select", 8: "load", 9: "save", 6: "full", 7: "quit", 12: "up", 13: "down", 14: "left", 15: "right" };
joystickMap = { 0: "a", 1: "b", 2: "start", 3: "select", 8: "load", 9: "save", 6: "help", 7: "quit", 12: "up", 13: "down", 14: "left", 15: "right" };
}
if (os === "win" && browser === "firefox") { //KeyMap3
joystickMap = { 1: "a", 2: "b", 0: "start", 3: "select", 8: "load", 9: "save", 6: "full", 7: "quit" };
joystickMap = { 1: "a", 2: "b", 0: "start", 3: "select", 8: "load", 9: "save", 6: "help", 7: "quit" };
}
if (os === "mac" && browser === "safari") { //KeyMap4
joystickMap = { 1: "a", 2: "b", 0: "start", 3: "select", 8: "load", 9: "save", 6: "full", 7: "quit", 14: "up", 15: "down", 16: "left", 17: "right" };
joystickMap = { 1: "a", 2: "b", 0: "start", 3: "select", 8: "load", 9: "save", 6: "help", 7: "quit", 14: "up", 15: "down", 16: "left", 17: "right" };
}
if (os === "mac" && browser === "firefox") { //KeyMap5
joystickMap = { 1: "a", 2: "b", 0: "start", 3: "select", 8: "load", 9: "save", 6: "full", 7: "quit", 14: "up", 15: "down", 16: "left", 17: "right" };
joystickMap = { 1: "a", 2: "b", 0: "start", 3: "select", 8: "load", 9: "save", 6: "help", 7: "quit", 14: "up", 15: "down", 16: "left", 17: "right" };
}
// reset state
joystickState = {
left: false,
@ -128,7 +133,7 @@ $(window).on("gamepadconnected", function (event) {
// disconnected event is triggered
$(window).on("gamepaddisconnected", (event) => {
window.addEventListener("gamepaddisconnected", (event) => {
clearInterval(joystickTimer);
log(`Gamepad disconnected at index ${e.gamepad.index}`);
});

View file

@ -19,6 +19,7 @@ const KEYBOARD_MAP = {
87: "join", // w
65: "load", // a
70: "full", // f
72: "help", // h
}
$("body").on("keyup", function (event) {

11
static/js/init.js vendored
View file

@ -1,8 +1,15 @@
// Window rerender / rotate screen if needed
function fixScreenLayout() {
var targetWidth = $(document).width() * 0.8;
var targetHeight = $(document).height() * 0.8;
var targetWidth = $(document).width() * 0.9;
var targetHeight = $(document).height() * 0.9;
// mobile == full screen
if (getOS() === "android") {
var targetWidth = $(document).width();
var targetHeight = $(document).height();
}
// Should have maximum box for desktop?
// targetWidth = 800; targetHeight = 600; // test on desktop

1
static/js/utils.js vendored
View file

@ -42,6 +42,7 @@ function getOS() {
var OSName = "unknown";
if (navigator.appVersion.indexOf("Win") !== -1) OSName = "win";
else if (navigator.appVersion.indexOf("Mac") !== -1) OSName = "mac";
else if (navigator.userAgent.indexOf("Linux") !== -1) OSName = "linux";
else if (navigator.userAgent.indexOf("Android") !== -1) OSName = "android";
return OSName;
}

11
static/js/ws.js vendored
View file

@ -97,11 +97,11 @@ function sendPing() {
function startWebRTC() {
// webrtc
pc = new RTCPeerConnection({iceServers: [{
urls: 'stun:159.65.141.209:3478',
urls: 'stun:stun-turn.webgame2d.com:3478',
username: "root",
credential: "root"
}, {
urls: "turn:159.65.141.209:3478",
urls: "turn:stun-turn.webgame2d.com:3478",
username: "root",
credential: "root"
}]})
@ -181,7 +181,6 @@ function startWebRTC() {
if (pc.iceConnectionState === "connected") {
gameReady = true
iceSuccess = true
conn.send(JSON.stringify({"id": "icecandidate", "data": e.candidate}));
}
else if (pc.iceConnectionState === "failed") {
gameReady = false
@ -231,11 +230,13 @@ function startWebRTC() {
function startGame() {
if (!iceSuccess) {
popup("Game cannot load. Please refresh")
popup("Game cannot load. Please refresh");
return;
}
// TODO: Add while loop
if (!gameReady) {
popup("Game is not ready yet. Please wait")
popup("Game is not ready yet. Please wait");
return;
}
log("Starting game screen");
screenState = "game";