Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
[New UI] Unity Build in React (#616)
Browse files Browse the repository at this point in the history
* Add react-unity-webgl package as a dependency

* Introduce new unity module to the component

* Update the README

* Move GameView to be a container

* Add some extra functionality local to the file for now

* Add utilities and a API with JSON settings. Resolves #612

* More functionality to connect two systems

* More Redux changes for success & connection

* Refactor method name of the view to not include 'view'

* Add initial state & send information across to Unity WebGL

* Add socketIO dependency to generated HTML

* Call Unity functions as redux actions now

* Remove irrelevant comments now

* Serialise boolean SSL flag for Unity

* More changes to how communication is conducted

* Handle errors

* Add lunch json for VS code to git ignore

* Emit success event in the epic

* Remove middlewares

* New feature introduced for Game

* Remove old game reducer and move to its own. Resolves #619

Same as below

* Separate epics for each event refactoring

* Remove older epic

* Update .gitignore launch json path
Ignore all .vscode files


Update .gitignore

* Remove a TODO comment

* Fix existing tests to suit new code

* Merge branch 'UI_new_design' into add_unity_to_react

* Regenerate yarn.lock

* Revert the refactoring, can be done in a different PR

* Add epic tests for the happy path

* Remove unwanted comment and add EOF lines

* Change reducer name, forgot to change beforehand

* Add reducer test for all current cases

* More function tests

* Some testing changes as requested in PR by @mrniket

* Change imports to one liner using aliases

* Remove redundant alias for default import

* Refactoring of setGameURL successful, rest not done

* Refactoring of setGameURL fixed

* Finish refactoring

* Merge remote-tracking branch 'origin/UI_new_design' into add_unity_to_react

* GameView minor esLint changes

* Merge remote-tracking branch 'origin/UI_new_design' into add_unity_to_react

* Fix indentation and other eslint issues in unity

* Certain eslint fixes

* More eslint

* Fix minor SSL bug

* Add extra developer settings to the parcel bundler

* Initial connectionParams naming changes


More naming changes


More naming changes again


connectionParams naming change done

* Name change in the view to parameters

* utilities called game_helpers now

* Add another shallow theme test to the layout

* Test passing, still unhappy with marble completion

* Use action creators instead of explicit objects

* Add css to Game Page testing

* Change the djangoBundler check environment before setting vars

* Update tests without theme to use shallow instead

* Export unity as a object containing all the calls instead

* Bring id of current avatar view to be called game_id again

* Add naming change to game_helpers as requested

* Fix tests to be correct now

* Make aimmo compatible with Django 1.9 (#642)

* Make aimmo compatible with Django 1.9

* Using patch version of Django 1.9 (#643)

* Using patch version of Django 1.9

* Merge remote-tracking branch 'origin/master' into add_unity_to_react

* Simply the API for api.js, put error in payload of fail actions

* Add test for emit unity failure
  • Loading branch information
OlafSzmidt authored and mrniket committed Jun 27, 2018
1 parent 61e080a commit dd2df80
Show file tree
Hide file tree
Showing 36 changed files with 985 additions and 79 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,6 @@ node_modules

# Vagrant
.vagrant/**

# VSCode
game_frontend/\.vscode/
1 change: 1 addition & 0 deletions aimmo_runner/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def run(use_minikube, use_vagrant=False, server_wait=True, capture_output=False,
run_command(['python', _MANAGE_PY, 'migrate', '--noinput'], capture_output=capture_output)
run_command(['python', _MANAGE_PY, 'collectstatic', '--noinput'], capture_output=capture_output)

django.setup()
create_superuser_if_missing(username='admin', password='admin')

if use_minikube:
Expand Down
2 changes: 1 addition & 1 deletion game_frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ yarn

### Standalone

It's possible to run the frontend by itself. To do so, run the command below in this folder:
It's possible to run the frontend by itself. To do so, run the command below in this folder. **Note:** the Unity WebGL build will not show in standalone mode and you will need to run the project with Django.

```
parcel index.html
Expand Down
6 changes: 5 additions & 1 deletion game_frontend/djangoBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ const options = {
outDir,
outFile: 'index.html',
publicUrl: './',
watch: process.env.NODE_ENV !== 'production'
watch: process.env.NODE_ENV !== 'production',
minify: process.env.NODE_ENV === 'production',
target: 'browser',
cache: process.env.NODE_ENV === 'production'
}

const templateFolder = Path.resolve(Path.join(__dirname, '../players/templates/players'))
Expand All @@ -21,6 +24,7 @@ bundler.on('bundled', (bundle) => {
let entryPointHTML = shell.cat(bundle.name).stdout
entryPointHTML = '{% load static %}\n' + entryPointHTML
entryPointHTML = entryPointHTML.replace(/(<script src=")(.*\.js)("><\/script>)/g, '$1{% static "react/$2" %}$3')
entryPointHTML += '<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.4/socket.io.min.js"></script>'
fs.writeFile(`${templateFolder}/game_ide.html`, entryPointHTML, (error) => {
if (error) { return console.log(error) }
console.log('game_ide.html django template generated sucessfully')
Expand Down
1 change: 1 addition & 0 deletions game_frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"react-ace": "^6.1.0",
"react-dom": "^16.2.0",
"react-redux": "^5.0.7",
"react-unity-webgl": "^6.5.0",
"redux": "^3.7.2",
"redux-observable": "^0.18.0",
"redux-devtools-extension": "^2.13.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
exports[`<Game /> renders correctly 1`] = `
<React.Fragment>
<styled.nav />
<styled.div />
<Connect(GameView) />
</React.Fragment>
`;
2 changes: 1 addition & 1 deletion game_frontend/src/components/Game/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react'
import GameMenu from 'components/GameMenu'
import GameView from 'components/GameView'
import GameView from 'containers/GameView'

export default class Game extends Component {
render () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,17 @@ exports[`<GamePage /> matches snapshot 1`] = `
<Game />
</styled.div>
`;

exports[`<GamePageLayout /> matches snapshot 1`] = `
.c0 {
display: grid;
grid-template: 80px 1fr 150px / 1fr 1fr;
grid-template-areas: "ide-menu game-menu" "ide-editor game-view" "ide-console game-view";
width: 100vw;
height: 100vh;
}
<div
className="c0"
/>
`;
6 changes: 3 additions & 3 deletions game_frontend/src/components/GamePage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from 'styled-components'
import IDE from 'components/IDE'
import Game from 'components/Game'

const GamePageContainer = styled.div`
export const GamePageLayout = styled.div`
display: grid
grid-template: 80px 1fr 150px / 1fr 1fr
grid-template-areas: "ide-menu game-menu"
Expand All @@ -16,10 +16,10 @@ const GamePageContainer = styled.div`
export default class GamePage extends Component {
render () {
return (
<GamePageContainer>
<GamePageLayout>
<IDE />
<Game />
</GamePageContainer>
</GamePageLayout>
)
}
}
15 changes: 12 additions & 3 deletions game_frontend/src/components/GamePage/index.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
/* eslint-env jest */
import React from 'react'
import GamePage, { GamePageLayout } from 'components/GamePage'
import createShallowWithTheme from 'testHelpers/createShallow'
import { shallow } from 'enzyme'
import GamePage from 'components/GamePage'

describe('<GamePage />', () => {
it('matches snapshot', () => {
const component = shallow(<GamePage />)
const tree = shallow(<GamePage />)

expect(component).toMatchSnapshot()
expect(tree).toMatchSnapshot()
})
})

describe('<GamePageLayout />', () => {
it('matches snapshot', () => {
const tree = createShallowWithTheme(<GamePageLayout />)

expect(tree).toMatchSnapshot()
})
})

This file was deleted.

8 changes: 0 additions & 8 deletions game_frontend/src/components/GameView/index.js

This file was deleted.

11 changes: 0 additions & 11 deletions game_frontend/src/components/GameView/index.test.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<GameView /> matches snapshot 1`] = `
<styled.div>
<Unity
loader="/static/unity/Build/UnityLoader.js"
src="/static/unity/Build/unity.json"
/>
</styled.div>
`;

exports[`<GameViewLayout /> matches snapshot 1`] = `
.c0 {
grid-area: game-view;
}
<div
className="c0"
/>
`;
76 changes: 76 additions & 0 deletions game_frontend/src/containers/GameView/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import styled from 'styled-components'
import React, { Component } from 'react'
import Unity, { RegisterExternalListener } from 'react-unity-webgl'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { actions } from 'features/Game'

export const GameViewLayout = styled.div`
grid-area: game-view
`

export class GameView extends Component {
constructor (props) {
super(props)

this.props.getConnectionParameters()

RegisterExternalListener('SendAllConnect', this.sendAllConnect.bind(this))
}

sendAllConnect () {
this.props.setGameURL(this.props.gameURL)
this.props.setGamePath(this.props.gamePath)
this.props.setGamePort(this.props.gamePort)
this.props.setGameSSL(this.serialisedSSLFlag())
this.props.establishGameConnection()
}

serialisedSSLFlag () {
let boolString = this.props.gameSSL.toString()

return boolString.charAt(0).toUpperCase() + boolString.slice(1)
}

render () {
return (
<GameViewLayout>
<Unity
src='/static/unity/Build/unity.json'
loader='/static/unity/Build/UnityLoader.js'
/>
</GameViewLayout>
)
}
}

GameView.propTypes = {
gameURL: PropTypes.string,
gamePath: PropTypes.string,
gamePort: PropTypes.number,
gameSSL: PropTypes.bool,
getConnectionParameters: PropTypes.func,
setGameURL: PropTypes.func,
setGamePath: PropTypes.func,
setGamePort: PropTypes.func,
setGameSSL: PropTypes.func,
establishGameConnection: PropTypes.func
}

const mapStateToProps = state => ({
gameURL: state.game.connectionParameters.game_url_base,
gamePath: state.game.connectionParameters.game_url_path,
gamePort: state.game.connectionParameters.game_url_port,
gameSSL: state.game.connectionParameters.game_ssl_flag
})

const mapDispatchToProps = {
getConnectionParameters: actions.getConnectionParametersRequest,
setGameURL: actions.setGameURL,
setGamePath: actions.setGamePath,
setGamePort: actions.setGamePort,
setGameSSL: actions.setGameSSL,
establishGameConnection: actions.establishGameConnection
}

export default connect(mapStateToProps, mapDispatchToProps)(GameView)
72 changes: 72 additions & 0 deletions game_frontend/src/containers/GameView/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* eslint-env jest */
import React from 'react'
import { GameView, GameViewLayout } from 'containers/GameView'
import { shallow } from 'enzyme/build/index'

describe('<GameView />', () => {
it('matches snapshot', () => {
const props = {
gameURL: 'test',
gamePath: '/test',
gamePort: 8000,
gameSSL: false,
getConnectionParameters: jest.fn(),
setGameURL: jest.fn(),
setGamePath: jest.fn(),
setGamePort: jest.fn(),
setGameSSL: jest.fn(),
establishGameConnection: jest.fn()
}

const component = shallow(<GameView {...props} />)
expect(component).toMatchSnapshot()
})

it('serialisedSSLFlag function returns correctly', () => {
const props = {
getConnectionParameters: jest.fn(),
gameSSL: false
}

const flagReturned = shallow(<GameView {...props} />).instance().serialisedSSLFlag()
expect(flagReturned).toBe('False')
})

it('sendAllConnect function calls all action dispatchers', () => {
const setGameURL = jest.fn()
const setGamePath = jest.fn()
const setGamePort = jest.fn()
const setGameSSL = jest.fn()
const establishGameConnection = jest.fn()

const props = {
gameURL: 'test',
gamePath: '/test',
gamePort: 8000,
gameSSL: false,
getConnectionParameters: jest.fn(),
setGameURL,
setGamePath,
setGamePort,
setGameSSL,
establishGameConnection
}

const wrapper = shallow(<GameView {...props} />)

wrapper.instance().sendAllConnect()

expect(setGameURL.mock.calls.length).toBe(1)
expect(setGamePath.mock.calls.length).toBe(1)
expect(setGamePort.mock.calls.length).toBe(1)
expect(setGameSSL.mock.calls.length).toBe(1)
expect(establishGameConnection.mock.calls.length).toBe(1)
})
})

describe('<GameViewLayout />', () => {
it('matches snapshot', () => {
const tree = shallow(<GameViewLayout />)
expect(tree).toMatchSnapshot()
})
})
9 changes: 9 additions & 0 deletions game_frontend/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ const initialState = {
},
editor: {
code: ''
},
game: {
connectionParameters: {
id: 1,
game_url_base: '',
game_url_path: '',
game_url_port: 0,
game_ssl_flag: false
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion game_frontend/src/redux/api/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import post from './post'
import get from './get'
import unity from './unity'

export default { get, post }
export default {
get,
post,
unity
}
30 changes: 30 additions & 0 deletions game_frontend/src/redux/api/unity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { UnityEvent } from 'react-unity-webgl'
import { Observable } from 'rxjs'
import { map, catchError } from 'rxjs/operators'

const sendExternalEvent = communicator => action$ =>
action$.mergeMap(action =>
Observable.of(action).pipe(
communicator,
map(event => action.payload.successAction),
catchError(error => Observable.of(action.payload.failAction(error)))
)
)

const emitToUnity = action$ =>
action$.map(
action => {
let unityEvent = new UnityEvent('World Controller', action.payload.unityEvent)

if (unityEvent.canEmit()) {
unityEvent.emit(action.payload.unityData)
} else {
throw new Error('Cannot emit the function!')
}
}
)

export default {
sendExternalEvent,
emitToUnity
}
Loading

0 comments on commit dd2df80

Please sign in to comment.