Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/virtual joystick #10

Open
wants to merge 2 commits into
base: base-63
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"phaser": "^3.55.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-joystick-component": "^6.0.0",
JWS-aAI marked this conversation as resolved.
Show resolved Hide resolved
"react-redux": "^7.2.5",
"sass": "^1.42.1",
"styled-components": "^5.3.5",
Expand Down
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import WhiteboardDialog from './components/WhiteboardDialog'
import VideoConnectionDialog from './components/VideoConnectionDialog'
import Chat from './components/Chat'
import HelperButtonGroup from './components/HelperButtonGroup'
import MobileVirtualJoystick from './components/MobileVirtualJoystick'

const Backdrop = styled.div`
position: absolute;
Expand Down Expand Up @@ -39,6 +40,7 @@ function App() {
<Chat />
{/* Render VideoConnectionDialog if user is not connected to a webcam. */}
{!videoConnected && <VideoConnectionDialog />}
<MobileVirtualJoystick />
</>
)
}
Expand Down
27 changes: 23 additions & 4 deletions client/src/characters/MyPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import store from '../stores'
import { pushPlayerJoinedMessage } from '../stores/ChatStore'
import { ItemType } from '../../../types/Items'
import { NavKeys } from '../../../types/KeyboardState'
import { JoystickMovement } from '../components/MobileVirtualJoystick'

export default class MyPlayer extends Player {
private playContainerBody: Phaser.Physics.Arcade.Body
private chairOnSit?: Chair
public joystickMovement?: JoystickMovement
Comment on lines 20 to +21
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to see that you've added a new property joystickMovement to handle the virtual joystick input. However, please ensure that the JoystickMovement type is properly defined and imported, and that the handleJoystickMovement method is correctly updating the joystickMovement property.

constructor(
scene: Phaser.Scene,
x: number,
Expand All @@ -41,6 +43,10 @@ export default class MyPlayer extends Player {
phaserEvents.emit(Event.MY_PLAYER_TEXTURE_CHANGE, this.x, this.y, this.anims.currentAnim.key)
}

handleJoystickMovement(movement: JoystickMovement) {
this.joystickMovement = movement
}

update(
playerSelector: PlayerSelector,
cursors: NavKeys,
Expand Down Expand Up @@ -121,13 +127,26 @@ export default class MyPlayer extends Player {
const speed = 200
let vx = 0
let vy = 0
if (cursors.left?.isDown || cursors.A?.isDown) vx -= speed
if (cursors.right?.isDown || cursors.D?.isDown) vx += speed
if (cursors.up?.isDown || cursors.W?.isDown) {

let joystickLeft = false
let joystickRight = false
let joystickUp = false
let joystickDown = false

if (this.joystickMovement?.isMoving) {
joystickLeft = this.joystickMovement.direction.left
joystickRight = this.joystickMovement.direction.right
joystickUp = this.joystickMovement.direction.up
joystickDown = this.joystickMovement.direction.down
}

if (cursors.left?.isDown || cursors.A?.isDown || joystickLeft) vx -= speed
if (cursors.right?.isDown || cursors.D?.isDown || joystickRight) vx += speed
if (cursors.up?.isDown || cursors.W?.isDown || joystickUp) {
vy -= speed
this.setDepth(this.y) //change player.depth if player.y changes
}
if (cursors.down?.isDown || cursors.S?.isDown) {
if (cursors.down?.isDown || cursors.S?.isDown || joystickDown) {
vy += speed
this.setDepth(this.y) //change player.depth if player.y changes
}
Expand Down
18 changes: 14 additions & 4 deletions client/src/characters/PlayerSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,23 @@ export default class PlayerSelector extends Phaser.GameObjects.Zone {

// update player selection box position so that it's always in front of the player
const { x, y } = player
if (cursors.left?.isDown || cursors.A?.isDown) {
let joystickLeft = false
let joystickRight = false
Comment on lines +27 to +28
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The integration of joystick input into the PlayerSelector update logic is well done. Just ensure that the joystickMovement property is always in a valid state when accessed, and consider adding null checks or default values to prevent any potential runtime errors.

let joystickUp = false
let joystickDown = false
if (player.joystickMovement?.isMoving) {
joystickLeft = player.joystickMovement?.direction.left
joystickRight = player.joystickMovement?.direction.right
joystickUp = player.joystickMovement?.direction.up
joystickDown = player.joystickMovement?.direction.down
}
if (cursors.left?.isDown || cursors.A?.isDown || joystickLeft) {
this.setPosition(x - 32, y)
} else if (cursors.right?.isDown || cursors.D?.isDown) {
} else if (cursors.right?.isDown || cursors.D?.isDown || joystickRight) {
this.setPosition(x + 32, y)
} else if (cursors.up?.isDown || cursors.W?.isDown) {
} else if (cursors.up?.isDown || cursors.W?.isDown || joystickUp) {
this.setPosition(x, y - 32)
} else if (cursors.down?.isDown || cursors.S?.isDown) {
} else if (cursors.down?.isDown || cursors.S?.isDown || joystickDown) {
this.setPosition(x, y + 32)
}

Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import { MessageType, setFocused, setShowChat } from '../stores/ChatStore'

const Backdrop = styled.div`
position: fixed;
bottom: 0;
bottom: 60px;
left: 0;
Comment on lines +23 to 24
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusting the Backdrop styled component to accommodate the virtual joystick is a good approach. However, please verify that the new bottom and max-width values do not negatively impact the chat component's layout on different screen sizes or resolutions.

height: 400px;
width: 500px;
max-height: 50%;
max-width: 50%;
max-width: 100%;
`

const Wrapper = styled.div`
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/ComputerDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const Wrapper = styled.div`

.close {
position: absolute;
top: 16px;
right: 16px;
top: 0px;
right: 0px;
}
Comment on lines +36 to 37
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please ensure that the new position of the close button in the Wrapper styled component does not overlap with any other UI elements or make it difficult for users to interact with the button.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PR-Review sorry can you clarify

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JWS-aAI Certainly! My comment was regarding the changes you made to the position of the close button within the Wrapper styled component in both ComputerDialog.tsx and WhiteboardDialog.tsx. You've changed the top and right properties to 0px, which moves the close button to the very top-right corner of its container.

Here's what you should check to clarify the impact of this change:

  1. Overlap with Other Elements: Ensure that the new position of the close button does not cause it to overlap with any other elements in the UI, such as headers, borders, or any other interactive components.

  2. Ease of Interaction: Confirm that the button is still easily clickable and that its new position does not hinder the user's ability to interact with it. For example, if the button is too close to the edge of the screen or other UI elements, it might be difficult for users to click on it, especially on touch devices.

  3. Consistent Styling: Check that the styling is consistent with the overall design of the application and that the button's new position looks intentional and well-integrated into the layout.

To address these points, you may need to test the UI on different devices and screen sizes, and possibly adjust padding or margins to ensure the button is both visually pleasing and functionally accessible.

`

Expand Down
12 changes: 11 additions & 1 deletion client/src/components/HelperButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import LightbulbIcon from '@mui/icons-material/Lightbulb'
import ArrowRightIcon from '@mui/icons-material/ArrowRight'
import GitHubIcon from '@mui/icons-material/GitHub'
import TwitterIcon from '@mui/icons-material/Twitter'
import VideogameAssetIcon from '@mui/icons-material/VideogameAsset'
import VideogameAssetOffIcon from '@mui/icons-material/VideogameAssetOff'

import { BackgroundMode } from '../../../types/BackgroundMode'
import { toggleBackgroundMode } from '../stores/UserStore'
import { setShowJoystick, toggleBackgroundMode } from '../stores/UserStore'
import { useAppSelector, useAppDispatch } from '../hooks'
import { getAvatarString, getColorByString } from '../util'

Expand Down Expand Up @@ -106,6 +108,7 @@ const StyledFab = styled(Fab)<{ target?: string }>`
export default function HelperButtonGroup() {
const [showControlGuide, setShowControlGuide] = useState(false)
const [showRoomInfo, setShowRoomInfo] = useState(false)
const showJoystick = useAppSelector((state) => state.user.showJoystick)
Comment on lines 110 to +111
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding toggle buttons for the virtual joystick is a user-friendly feature. Please make sure that the tooltip text is clear and that the icons used are intuitive for the users. Also, verify that the state management for showJoystick is properly synchronized across the application to prevent any inconsistencies.

const backgroundMode = useAppSelector((state) => state.user.backgroundMode)
const roomJoined = useAppSelector((state) => state.room.roomJoined)
const roomId = useAppSelector((state) => state.room.roomId)
Expand All @@ -116,6 +119,13 @@ export default function HelperButtonGroup() {
return (
<Backdrop>
<div className="wrapper-group">
{roomJoined && (
<Tooltip title={showJoystick ? 'Disable virtual joystick' : 'Enable virtual joystick'}>
<StyledFab size="small" onClick={() => dispatch(setShowJoystick(!showJoystick))}>
{showJoystick ? <VideogameAssetOffIcon /> : <VideogameAssetIcon />}
</StyledFab>
</Tooltip>
)}
{showRoomInfo && (
<Wrapper>
<IconButton className="close" onClick={() => setShowRoomInfo(false)} size="small">
Expand Down
79 changes: 79 additions & 0 deletions client/src/components/Joystick.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Joystick } from 'react-joystick-component'
import React from 'react'

var AngleToDirections = function (angle, out) {
if (out === undefined) {
out = {}
} else if (out === true) {
out = globOut
}

out.left = false
out.right = false
out.up = false
out.down = false

angle = (angle + 360) % 360

if (angle > 22.5 && angle <= 67.5) {
out.down = true
out.right = true
} else if (angle > 67.5 && angle <= 112.5) {
out.down = true
} else if (angle > 112.5 && angle <= 157.5) {
out.down = true
out.left = true
} else if (angle > 157.5 && angle <= 202.5) {
out.left = true
} else if (angle > 202.5 && angle <= 247.5) {
out.left = true
out.up = true
} else if (angle > 247.5 && angle <= 292.5) {
out.up = true
} else if (angle > 292.5 && angle <= 337.5) {
out.up = true
out.right = true
} else {
out.right = true
}
return out
}

var globOut = {}

function JoystickItem(props) {
return (
<>
<Joystick
size={75}
baseColor="#4b4b4b70"
stickColor="#42eacb80"
stop={() => {
props.onDirectionChange({
isMoving: false,
direction: {
left: false,
right: false,
up: false,
down: false,
},
})
}}
move={(event) => {
const x1 = 0
const y1 = event.y
const x2 = event.x
const y2 = 0
var deltaX = x2 - x1 // distance between joystick and center
var deltaY = y2 - y1 // distance between joystick and center
var rad = Math.atan2(deltaY, deltaX) // In radians
var deg = (rad * 180) / Math.PI // In degrees
var direction = AngleToDirections(deg, true) // Convert degrees to direction
props.onDirectionChange({ isMoving: true, direction: direction })
}}
/>
</>
)
}

export default JoystickItem
73 changes: 73 additions & 0 deletions client/src/components/MobileVirtualJoystick.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useRef, useState, useEffect } from 'react'
import styled from 'styled-components'
import 'emoji-mart/css/emoji-mart.css'
import JoystickItem from './Joystick'

import phaserGame from '../PhaserGame'
import Game from '../scenes/Game'

import { useAppDispatch, useAppSelector } from '../hooks'

export interface JoystickMovement {
isMoving: boolean
direction: {
left: boolean
right: boolean
up: boolean
down: boolean
}
}

const Backdrop = styled.div`
position: fixed;
bottom: 100px;
right: 32px;
max-height: 50%;
max-width: 100%;
`

const Wrapper = styled.div`
position: relative;
height: 100%;
padding: 16px;
display: flex;
flex-direction: column;
`

const JoystickWrapper = styled.div`
margin-top: auto;
align-self: flex-end;
`

export default function MobileVirtualJoystick() {
const inputRef = useRef<HTMLInputElement>(null)

const showJoystick = useAppSelector((state) => state.user.showJoystick)
const focused = useAppSelector((state) => state.chat.focused)
const showChat = useAppSelector((state) => state.chat.showChat)
const game = phaserGame.scene.keys.game as Game

useEffect(() => {
if (focused) {
inputRef.current?.focus()
}
}, [focused])

useEffect(() => {}, [showJoystick, showChat])

const handleMovement = (movement: JoystickMovement) => {
game.myPlayer?.handleJoystickMovement(movement)
}

return (
<Backdrop>
<Wrapper>
{(!showChat || window.innerWidth > 650) && showJoystick && (
<JoystickWrapper>
<JoystickItem onDirectionChange={handleMovement}></JoystickItem>
</JoystickWrapper>
)}
</Wrapper>
</Backdrop>
)
}
9 changes: 6 additions & 3 deletions client/src/components/WhiteboardDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const Backdrop = styled.div`
height: 100vh;
overflow: hidden;
padding: 16px 180px 16px 16px;
width: 100%;
height: 100%;
`
const Wrapper = styled.div`
width: 100%;
Expand All @@ -25,19 +27,20 @@ const Wrapper = styled.div`
position: relative;
display: flex;
flex-direction: column;
min-width: max-content;
Comment on lines 29 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The adjustments to the Wrapper and WhiteboardWrapper styled components, including the close button's position and the margin-right value, should be carefully tested to ensure they do not disrupt the layout or usability of the whiteboard dialog on various screen sizes.


.close {
position: absolute;
top: 16px;
right: 16px;
top: 0px;
right: 0px;
}
`

const WhiteboardWrapper = styled.div`
flex: 1;
border-radius: 25px;
overflow: hidden;
margin-right: 50px;
margin-right: 25px;

iframe {
width: 100%;
Expand Down
5 changes: 5 additions & 0 deletions client/src/stores/UserStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const userSlice = createSlice({
videoConnected: false,
loggedIn: false,
playerNameMap: new Map<string, string>(),
showJoystick: window.innerWidth < 650,
},
reducers: {
toggleBackgroundMode: (state) => {
Expand All @@ -43,6 +44,9 @@ export const userSlice = createSlice({
removePlayerNameMap: (state, action: PayloadAction<string>) => {
state.playerNameMap.delete(sanitizeId(action.payload))
},
setShowJoystick: (state, action: PayloadAction<boolean>) => {
Comment on lines 46 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addition of the showJoystick state and the setShowJoystick reducer is a good implementation for managing the joystick visibility. Please ensure that the initial state of showJoystick is set correctly for different screen sizes and that it responds appropriately to window resize events.

state.showJoystick = action.payload
},
},
})

Expand All @@ -53,6 +57,7 @@ export const {
setLoggedIn,
setPlayerNameMap,
removePlayerNameMap,
setShowJoystick,
} = userSlice.actions

export default userSlice.reducer
5 changes: 5 additions & 0 deletions client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,11 @@ react-is@^17.0.1, react-is@^17.0.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==

react-joystick-component@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/react-joystick-component/-/react-joystick-component-6.0.0.tgz#5768762a6c52de57215a74ef54438221afedf36b"
integrity sha512-kbhDrPbasELoQcUxG+YNqsjsZDV3xo0TBnGXjv/TekoLZHZmmj/X/Y85YfvEPzWos6C3YmOjQKc5gK59o+jI2w==

react-redux@^7.2.5:
version "7.2.8"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
Expand Down