Skip to content

Commit 57cc6b4

Browse files
feat: adapt for mobiles
1 parent c2be7f6 commit 57cc6b4

File tree

6 files changed

+78
-13
lines changed

6 files changed

+78
-13
lines changed

games/running-ball-babylonjs/Readme.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
---
44

5-
### [[LIVE DEMO]: running-ball-babylonjs-by-user-of-github.netlify.app](https://running-ball-babylonjs-by-user-of-github.netlify.app/)
5+
### [[LIVE DEMO]: running-ball-babylonjs-by-user-of-github.netlify.app](https://running-ball-babylonjs-by-user-of-github.netlify.app/)
6+
7+
__Attention__: Adapted for desktop and mobile devices. On desktop use arrows or keys A/D to move left/right. On mobile just use taps (touches) to bottom-left or bottom-right part of the screen to translate the ball. On mobile ball is just translated to one of 3 roads, while on desktop it is moved via impulse.
68

79
---
810

games/running-ball-babylonjs/src/game/Game.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { GameStorageService, StoredDataType } from './game-services/storage.serv
55
import { GameUiElementsService } from './game-services/ui-elements.service';
66
import { GameConstants } from './constants';
77
import { GameSceneService } from './game-services/scene.service';
8+
import { isMobileDevice } from './utils';
89

910
export class Game {
1011
private readonly movingVectorStraight = new Vector3(0, 0, GameConstants.StartSpeedOfMovingStraight);
@@ -49,7 +50,15 @@ export class Game {
4950
(element as HTMLButtonElement).onclick = this.restart.bind(this);
5051
}
5152

52-
window.addEventListener('keydown', (event) => {
53+
if (isMobileDevice()) {
54+
this.initControlsMobile();
55+
} else {
56+
this.initControlsDesktop();
57+
}
58+
}
59+
60+
private initControlsDesktop() {
61+
window.addEventListener('keydown', event => {
5362
if (this.gameStatus === GameStatus.GameOver && event.key === 'Enter') {
5463
this.restart();
5564
return;
@@ -67,7 +76,7 @@ export class Game {
6776
}
6877
});
6978

70-
window.addEventListener('keyup', (event) => {
79+
window.addEventListener('keyup', event => {
7180
if (this.gameStatus !== GameStatus.Playing) return;
7281

7382
switch (true) {
@@ -81,6 +90,34 @@ export class Game {
8190
});
8291
}
8392

93+
private initControlsMobile() {
94+
console.log('MOBILE');
95+
96+
97+
this.elementsRefs.canvas.addEventListener('touchstart', event => {
98+
if (this.gameStatus !== GameStatus.Playing) return;
99+
if (!(event.changedTouches[0].screenY > 0.3 * window.screen.height)) return;
100+
101+
this.stopBallMovingAside();
102+
});
103+
104+
this.elementsRefs.canvas.addEventListener('touchend', event => {
105+
if (this.gameStatus !== GameStatus.Playing) return;
106+
if (!(event.changedTouches[0].screenY > 0.3 * window.screen.height)) return;
107+
108+
const x = event.changedTouches[0].clientX;
109+
const screenWidth = this.elementsRefs.canvas.clientWidth;
110+
111+
if (x < screenWidth / 2) {
112+
console.log('left')
113+
this.sceneService.ball.translate(GameConstants.TranslateVectorLeft, GameConstants.TranslateVectorDistance);
114+
} else {
115+
console.log('right')
116+
this.sceneService.ball.translate(GameConstants.TranslateVectorRight, GameConstants.TranslateVectorDistance);
117+
}
118+
});
119+
}
120+
84121
private checkIfGameOver(): void {
85122
const ball = this.sceneService.ball;
86123
const walls = this.sceneService.walls;

games/running-ball-babylonjs/src/game/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ export const GameConstants = Object.freeze({
1515
StartSpeedOfMovingStraight: 6,
1616
MoveVectorLeft: new Vector3(-2, 0, 0),
1717
MoveVectorRight: new Vector3(2, 0, 0),
18+
TranslateVectorLeft: new Vector3(-1, 0, 0),
19+
TranslateVectorRight: new Vector3(1, 0, 0),
20+
TranslateVectorDistance: 2.6666, // SinglePlatformSize.width / 3
1821
BallRadius: 0.75
1922
});

games/running-ball-babylonjs/src/game/game-services/scene.service.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import {
1616
Texture,
1717
type Material,
1818
Color3,
19-
SceneLoader
19+
SceneLoader,
20+
ArcRotateCamera
2021
} from '@babylonjs/core';
2122
import '@babylonjs/loaders/glTF';
2223
//@ts-ignore
2324
import * as CANNON from 'cannon';
2425
import { WaterMaterial } from '@babylonjs/materials';
2526
import { GameConstants } from '../constants';
26-
import { notRepeatedRandomFreeSpacePositionGenerator } from '../utils';
27+
import { isMobileDevice, notRepeatedRandomFreeSpacePositionGenerator } from '../utils';
2728

2829
export class GameSceneService {
2930
public readonly platformMaterial: Material;
@@ -40,6 +41,8 @@ export class GameSceneService {
4041
public readonly light: PointLight;
4142
public readonly shadowGenerator: ShadowGenerator;
4243

44+
private readonly isMobile = isMobileDevice();
45+
4346
private platforms: Mesh[] = [];
4447
private _walls: Mesh[] = [];
4548
private _ball: Mesh = {} as Mesh;
@@ -134,19 +137,35 @@ export class GameSceneService {
134137
}
135138

136139
private configureCamera(): Camera {
137-
const camera = new FreeCamera('camera', new Vector3(-2, 5, -10), this.scene);
138-
camera.setTarget(GameConstants.ZeroVector);
140+
let camera: Camera;
141+
142+
if (this.isMobile) {
143+
camera = new ArcRotateCamera("arcCamera", Math.PI / 4, Math.PI / 3, 9, new Vector3(0, 0, 6), this.scene);
144+
camera.position = new Vector3(0, 7, -15)
145+
} else {
146+
camera = new FreeCamera('camera', new Vector3(-2, 5, -10), this.scene);
147+
(camera as FreeCamera).setTarget(GameConstants.ZeroVector);
148+
}
139149

140150
return camera;
141151
}
142152

143153
private updateCameraAndLight(): void {
144-
this.camera.position.z = this._ball.getAbsolutePosition().z - 12;
145-
this.camera.position.y = this._ball.getAbsolutePosition().y + 6;
154+
const ballPosition = this._ball.getAbsolutePosition();
155+
156+
if (this.isMobile) {
157+
(this.camera as ArcRotateCamera).target.z = ballPosition.z + 5;
158+
(this.camera as ArcRotateCamera).target.y = ballPosition.y;
159+
this.camera.position.z = ballPosition.z - 20;
160+
this.camera.position.y = ballPosition.y + 7;
161+
} else {
162+
this.camera.position.z = this._ball.getAbsolutePosition().z - 12;
163+
this.camera.position.y = this._ball.getAbsolutePosition().y + 6;
164+
}
146165

147-
this.light.position.z = this._ball.getAbsolutePosition().z + 10;
148-
this.light.position.y = this._ball.getAbsolutePosition().y + 10;
149-
this.light.position.z = this._ball.getAbsolutePosition().z + 10;
166+
this.light.position.z = ballPosition.z + 10;
167+
this.light.position.y = ballPosition.y + 10;
168+
this.light.position.z = ballPosition.z + 10;
150169
}
151170

152171
private configureSky() {

games/running-ball-babylonjs/src/game/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ export const notRepeatedRandomFreeSpacePositionGenerator = (freeSpace: number):
1414
return randomFreePlaceIndexInRow;
1515
};
1616
};
17+
18+
export const isMobileDevice = (): boolean => {
19+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
20+
}

games/running-ball-babylonjs/src/style.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ body {
1616

1717
.app {
1818
width: 100%;
19-
height: 100vh;
19+
height: 100dvh;
2020
position: relative;
2121
}
2222

0 commit comments

Comments
 (0)