changed game dynamics.
This commit is contained in:
parent
5b7d04bd39
commit
c9d03e832d
@ -1,20 +1,21 @@
|
|||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
import {
|
import {
|
||||||
AbstractMesh,
|
AbstractMesh,
|
||||||
Color3, DistanceConstraint, Engine, InstancedMesh, Mesh,
|
Color3, DistanceConstraint, Engine, InstancedMesh, LinesMesh, Mesh,
|
||||||
MeshBuilder,
|
MeshBuilder,
|
||||||
Observable,
|
Observable,
|
||||||
ParticleHelper,
|
ParticleHelper,
|
||||||
PhysicsAggregate,
|
PhysicsAggregate,
|
||||||
PhysicsMotionType,
|
PhysicsMotionType,
|
||||||
PhysicsShapeType, Sound,
|
PhysicsShapeType, PointsCloudSystem, Sound,
|
||||||
StandardMaterial, TransformNode,
|
StandardMaterial, TransformNode,
|
||||||
Vector3
|
Vector3
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import {Ship} from "./ship";
|
import {Ship} from "./ship";
|
||||||
import {ScoreEvent} from "./scoreEvent";
|
|
||||||
import {RockFactory} from "./starfield";
|
import {RockFactory} from "./starfield";
|
||||||
import Level from "./level";
|
import Level from "./level";
|
||||||
|
import {Scoreboard} from "./scoreboard";
|
||||||
|
|
||||||
export class Level1 implements Level {
|
export class Level1 implements Level {
|
||||||
private _ship: Ship;
|
private _ship: Ship;
|
||||||
@ -22,10 +23,11 @@ export class Level1 implements Level {
|
|||||||
private _initialized: boolean = false;
|
private _initialized: boolean = false;
|
||||||
private _startBase: AbstractMesh;
|
private _startBase: AbstractMesh;
|
||||||
private _endBase: AbstractMesh;
|
private _endBase: AbstractMesh;
|
||||||
public onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
|
private _scoreboard: Scoreboard;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._ship = new Ship();
|
this._ship = new Ship();
|
||||||
|
this._scoreboard = new Scoreboard();
|
||||||
const xr = DefaultScene.XR;
|
const xr = DefaultScene.XR;
|
||||||
xr.baseExperience.onInitialXRPoseSetObservable.add(() => {
|
xr.baseExperience.onInitialXRPoseSetObservable.add(() => {
|
||||||
xr.baseExperience.camera.parent = this._ship.transformNode;
|
xr.baseExperience.camera.parent = this._ship.transformNode;
|
||||||
@ -35,7 +37,6 @@ export class Level1 implements Level {
|
|||||||
this._ship.addController(controller);
|
this._ship.addController(controller);
|
||||||
});
|
});
|
||||||
this.createStartBase();
|
this.createStartBase();
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -55,6 +56,7 @@ export class Level1 implements Level {
|
|||||||
this._endBase.dispose();
|
this._endBase.dispose();
|
||||||
}
|
}
|
||||||
public async initialize() {
|
public async initialize() {
|
||||||
|
console.log('initialize');
|
||||||
if (this._initialized) {
|
if (this._initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -62,17 +64,37 @@ export class Level1 implements Level {
|
|||||||
ParticleHelper.BaseAssetsUrl = window.location.href;
|
ParticleHelper.BaseAssetsUrl = window.location.href;
|
||||||
this._ship.position = new Vector3(0, 1, 0);
|
this._ship.position = new Vector3(0, 1, 0);
|
||||||
await RockFactory.init();
|
await RockFactory.init();
|
||||||
const baseTransform = new TransformNode("baseTransform", DefaultScene.MainScene);
|
|
||||||
baseTransform.position = this._startBase.getAbsolutePosition();
|
|
||||||
|
|
||||||
for (let i = 0; i < 50; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
const dist = (Math.random() * 200) + 190;
|
const dist = (Math.random() * 50) + 190;
|
||||||
const rock = await RockFactory.createRock(i, new Vector3(Math.random() * 200 +50 * Math.sign(Math.random() -.5),200,200), Vector3.Random(1, 5))
|
const size = Vector3.Random(1,1.3).scale(Math.random() * 5 + 5)
|
||||||
|
|
||||||
|
const rock = await RockFactory.createRock(i, new Vector3(Math.random() * 200 +50 * Math.sign(Math.random() -.5),200,200),
|
||||||
|
size,
|
||||||
|
this._scoreboard.onScoreObservable);
|
||||||
const constraint = new DistanceConstraint(dist, DefaultScene.MainScene);
|
const constraint = new DistanceConstraint(dist, DefaultScene.MainScene);
|
||||||
//rock.physicsBody.addConstraint(this._endBase.physicsBody, constraint);
|
|
||||||
|
/*
|
||||||
|
const options: {updatable: boolean, points: Array<Vector3>, instance?: LinesMesh} =
|
||||||
|
{updatable: true, points: [rock.position, this._startBase.absolutePosition]}
|
||||||
|
|
||||||
|
let line = MeshBuilder.CreateLines("line", options , DefaultScene.MainScene);
|
||||||
|
|
||||||
|
line.color = new Color3(1, 0, 0);
|
||||||
|
DefaultScene.MainScene.onAfterRenderObservable.add(() => {
|
||||||
|
//const pos = rock.position;
|
||||||
|
options.points[0].copyFrom(rock.position);
|
||||||
|
options.instance = line;
|
||||||
|
line = MeshBuilder.CreateLines("lines", options);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
this._scoreboard.onScoreObservable.notifyObservers({
|
||||||
|
score: 0,
|
||||||
|
remaining: 1,
|
||||||
|
message: "Get Ready"
|
||||||
|
});
|
||||||
this._startBase.physicsBody.addConstraint(rock.physicsBody, constraint);
|
this._startBase.physicsBody.addConstraint(rock.physicsBody, constraint);
|
||||||
rock.physicsBody.applyForce(Vector3.Random(-1, 1).scale(50000000), rock.getAbsolutePosition());
|
rock.physicsBody.applyForce(Vector3.Random(-1, 1).scale(5000000), rock.position);
|
||||||
//rock.physicsBody.setAngularVelocity(Vector3.Random(-.5, .5));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
src/main.ts
30
src/main.ts
@ -1,4 +1,13 @@
|
|||||||
import {Engine, HavokPlugin, PhotoDome, Scene, Vector3, WebGPUEngine, WebXRDefaultExperience} from "@babylonjs/core";
|
import {
|
||||||
|
Color3,
|
||||||
|
Engine,
|
||||||
|
HavokPlugin,
|
||||||
|
PhotoDome,
|
||||||
|
Scene, StandardMaterial,
|
||||||
|
Vector3,
|
||||||
|
WebGPUEngine,
|
||||||
|
WebXRDefaultExperience
|
||||||
|
} from "@babylonjs/core";
|
||||||
import '@babylonjs/loaders';
|
import '@babylonjs/loaders';
|
||||||
import HavokPhysics from "@babylonjs/havok";
|
import HavokPhysics from "@babylonjs/havok";
|
||||||
|
|
||||||
@ -21,6 +30,10 @@ export class Main {
|
|||||||
private _gameState: GameState = GameState.DEMO;
|
private _gameState: GameState = GameState.DEMO;
|
||||||
constructor() {
|
constructor() {
|
||||||
this._loadingDiv = document.querySelector('#loadingDiv');
|
this._loadingDiv = document.querySelector('#loadingDiv');
|
||||||
|
if (!navigator.xr) {
|
||||||
|
this._loadingDiv.innerText = "This browser does not support WebXR";
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
|
||||||
document.querySelector('#startButton').addEventListener('click', () => {
|
document.querySelector('#startButton').addEventListener('click', () => {
|
||||||
@ -55,7 +68,18 @@ export class Main {
|
|||||||
this._currentLevel.getReadyObservable().add(() => {
|
this._currentLevel.getReadyObservable().add(() => {
|
||||||
|
|
||||||
});
|
});
|
||||||
const photoDome = new PhotoDome("testdome", '/8192.webp', {}, DefaultScene.MainScene);
|
|
||||||
|
const photoDome1 = new PhotoDome("testdome", '/8192.webp', {size: 1000}, DefaultScene.MainScene);
|
||||||
|
photoDome1.material.diffuseTexture.hasAlpha = true;
|
||||||
|
photoDome1.material.alpha = .3;
|
||||||
|
|
||||||
|
const photoDome2 = new PhotoDome("testdome", '/8192.webp', {size: 2000}, DefaultScene.MainScene);
|
||||||
|
photoDome2.rotation.y = Math.PI;
|
||||||
|
photoDome2.rotation.x = Math.PI/2;
|
||||||
|
DefaultScene.MainScene.onAfterRenderObservable.add(() => {
|
||||||
|
photoDome1.position = DefaultScene.MainScene.activeCamera.globalPosition;
|
||||||
|
photoDome2.position = DefaultScene.MainScene.activeCamera.globalPosition;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
private setLoadingMessage(message: string) {
|
private setLoadingMessage(message: string) {
|
||||||
this._loadingDiv.innerText = message;
|
this._loadingDiv.innerText = message;
|
||||||
@ -75,6 +99,7 @@ export class Main {
|
|||||||
}
|
}
|
||||||
DefaultScene.DemoScene = new Scene(engine);
|
DefaultScene.DemoScene = new Scene(engine);
|
||||||
DefaultScene.MainScene = new Scene(engine);
|
DefaultScene.MainScene = new Scene(engine);
|
||||||
|
DefaultScene.MainScene.ambientColor = new Color3(.2, .2, .2);
|
||||||
|
|
||||||
this.setLoadingMessage("Initializing Physics Engine..");
|
this.setLoadingMessage("Initializing Physics Engine..");
|
||||||
await this.setupPhysics();
|
await this.setupPhysics();
|
||||||
@ -98,6 +123,7 @@ export class Main {
|
|||||||
const havok = await HavokPhysics();
|
const havok = await HavokPhysics();
|
||||||
const havokPlugin = new HavokPlugin(true, havok);
|
const havokPlugin = new HavokPlugin(true, havok);
|
||||||
DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin);
|
DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin);
|
||||||
|
DefaultScene.MainScene.getPhysicsEngine().setSubTimeStep(5);
|
||||||
DefaultScene.MainScene.collisionsEnabled = true;
|
DefaultScene.MainScene.collisionsEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,40 +1,46 @@
|
|||||||
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock} from "@babylonjs/gui";
|
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock} from "@babylonjs/gui";
|
||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
import {
|
import {
|
||||||
AbstractMesh,
|
|
||||||
ActionManager,
|
|
||||||
Angle,
|
Angle,
|
||||||
ExecuteCodeAction,
|
|
||||||
MeshBuilder,
|
MeshBuilder,
|
||||||
Observable,
|
Observable, StandardMaterial,
|
||||||
TransformNode,
|
|
||||||
Vector3,
|
Vector3,
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import {ScoreEvent} from "./scoreEvent";
|
export type ScoreEvent = {
|
||||||
|
score: number,
|
||||||
|
message: string,
|
||||||
|
remaining: number,
|
||||||
|
timeRemaining? : number
|
||||||
|
}
|
||||||
export class Scoreboard {
|
export class Scoreboard {
|
||||||
private _score: number = 0;
|
private _score: number = 0;
|
||||||
|
private _remaining: number = 0;
|
||||||
|
private _timeRemaining: number = 61;
|
||||||
private _lastMessage: string = null;
|
private _lastMessage: string = null;
|
||||||
public onscoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
|
private _active = false;
|
||||||
|
private _done = false;
|
||||||
|
public readonly onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
|
||||||
constructor() {
|
constructor() {
|
||||||
DefaultScene.MainScene.onNewMeshAddedObservable.add((mesh) => {
|
DefaultScene.MainScene.onNewMeshAddedObservable.add((mesh) => {
|
||||||
if (mesh.id == 'RightUpperDisplay') {
|
if (mesh.id == 'RightUpperDisplay') {
|
||||||
window.setTimeout(() => {
|
this.initialize();
|
||||||
//mesh.material = null;
|
|
||||||
this.initialize();
|
|
||||||
},1000);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//this.initialize(camera);
|
}
|
||||||
|
public get done() {
|
||||||
|
return this._done;
|
||||||
|
}
|
||||||
|
public set done(value: boolean) {
|
||||||
|
this._done = value;
|
||||||
}
|
}
|
||||||
private initialize() {
|
private initialize() {
|
||||||
const scene = DefaultScene.MainScene;
|
const scene = DefaultScene.MainScene;
|
||||||
|
|
||||||
const parent = scene.getMeshById('RightUpperDisplay');
|
const parent = scene.getMeshById('RightUpperDisplay');
|
||||||
const scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
|
const scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
|
||||||
scoreboard.parent =parent;
|
const material = new StandardMaterial("scoreboard", scene);
|
||||||
|
|
||||||
|
scoreboard.parent =parent;
|
||||||
scoreboard.position.x = -.76;
|
scoreboard.position.x = -.76;
|
||||||
scoreboard.position.y = 4.19;
|
scoreboard.position.y = 4.19;
|
||||||
scoreboard.position.z = .53;
|
scoreboard.position.z = .53;
|
||||||
@ -42,43 +48,71 @@ export class Scoreboard {
|
|||||||
scoreboard.rotation.z = Math.PI;
|
scoreboard.rotation.z = Math.PI;
|
||||||
scoreboard.scaling = new Vector3(.3, .3, .3);
|
scoreboard.scaling = new Vector3(.3, .3, .3);
|
||||||
|
|
||||||
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard);
|
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard, 512, 512);
|
||||||
advancedTexture.background = "black";
|
advancedTexture.background = "black";
|
||||||
|
advancedTexture.hasAlpha = false;
|
||||||
const scoreText = this.createText();
|
const scoreText = this.createText();
|
||||||
advancedTexture.addControl(scoreText);
|
|
||||||
const fpsText = this.createText();
|
const fpsText = this.createText();
|
||||||
fpsText.top = '120px';
|
fpsText.text = "FPS: 60";
|
||||||
|
|
||||||
const hullText = this.createText();
|
const hullText = this.createText();
|
||||||
//hullText.top = '240px';
|
|
||||||
hullText.text = 'Hull: 100%';
|
hullText.text = 'Hull: 100%';
|
||||||
|
|
||||||
|
const remainingText = this.createText();
|
||||||
|
remainingText.text = 'Remaining: 0';
|
||||||
|
|
||||||
|
const timeRemainingText = this.createText();
|
||||||
|
timeRemainingText.text = 'Time: 2:00';
|
||||||
|
|
||||||
const panel = new StackPanel();
|
const panel = new StackPanel();
|
||||||
panel.isVertical = true;
|
panel.isVertical = true;
|
||||||
advancedTexture.addControl(fpsText);
|
panel.height = 1;
|
||||||
advancedTexture.addControl(scoreText);
|
panel.isVertical = true;
|
||||||
|
panel.addControl(scoreText);
|
||||||
|
panel.addControl(remainingText);
|
||||||
|
panel.addControl(fpsText);
|
||||||
|
panel.addControl(hullText);
|
||||||
|
panel.addControl(timeRemainingText);
|
||||||
|
|
||||||
advancedTexture.addControl(panel);
|
advancedTexture.addControl(panel);
|
||||||
|
|
||||||
scene.onAfterRenderObservable.add(() => {
|
let i = 0;
|
||||||
|
let lastSecond: number = Date.now();
|
||||||
|
const afterRender = scene.onAfterRenderObservable.add(() => {
|
||||||
scoreText.text = `Score: ${this.calculateScore()}`;
|
scoreText.text = `Score: ${this.calculateScore()}`;
|
||||||
if (this._lastMessage != null) {
|
remainingText.text = `Remaining: ${this._remaining}`;
|
||||||
fpsText.text = this._lastMessage;
|
const now = Date.now();
|
||||||
} else {
|
if (this._active && (Math.floor(lastSecond / 1000) < Math.floor(now/1000))) {
|
||||||
fpsText.text = '';
|
this._timeRemaining--;
|
||||||
|
if (this._timeRemaining <= 0) {
|
||||||
|
scene.onAfterRenderObservable.remove(afterRender);
|
||||||
|
}
|
||||||
|
lastSecond = now;
|
||||||
|
timeRemainingText.text = `Time: ${Math.floor(this._timeRemaining/60).toString().padStart(2,"0")}:${(this._timeRemaining%60).toString().padStart(2,"0")}`;
|
||||||
|
}
|
||||||
|
if (i++%60 == 0) {
|
||||||
|
fpsText.text = `FPS: ${Math.floor(scene.getEngine().getFps())}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.onscoreObservable.add((score) => {
|
this.onScoreObservable.add((score: ScoreEvent) => {
|
||||||
this._score += score.score;
|
this._score += score.score * this._timeRemaining;
|
||||||
|
this._remaining += score.remaining;
|
||||||
this._lastMessage = score.message;
|
this._lastMessage = score.message;
|
||||||
|
if (score.timeRemaining) {
|
||||||
|
this._timeRemaining = score.timeRemaining;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
this._active = true;
|
||||||
}
|
}
|
||||||
private createText(): TextBlock {
|
private createText(): TextBlock {
|
||||||
const text1 = new TextBlock();
|
const text1 = new TextBlock();
|
||||||
|
|
||||||
text1.color = "white";
|
text1.color = "white";
|
||||||
text1.fontSize = 90;
|
text1.fontSize = "60px";
|
||||||
|
text1.height = "80px";
|
||||||
text1.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
|
text1.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
|
||||||
text1.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
|
text1.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
|
||||||
return text1;
|
return text1;
|
||||||
}
|
}
|
||||||
private calculateScore() {
|
private calculateScore() {
|
||||||
|
|||||||
146
src/ship.ts
146
src/ship.ts
@ -2,9 +2,8 @@ import {
|
|||||||
AbstractMesh,
|
AbstractMesh,
|
||||||
Color3,
|
Color3,
|
||||||
DirectionalLight,
|
DirectionalLight,
|
||||||
Engine,
|
|
||||||
FreeCamera,
|
FreeCamera,
|
||||||
GlowLayer,
|
GlowLayer, InstancedMesh, Mesh,
|
||||||
MeshBuilder,
|
MeshBuilder,
|
||||||
Observable,
|
Observable,
|
||||||
PhysicsAggregate,
|
PhysicsAggregate,
|
||||||
@ -22,9 +21,7 @@ import {
|
|||||||
WebXRInputSource
|
WebXRInputSource
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
const MAX_FORWARD_THRUST = 40;
|
||||||
import {ShipEngine} from "./shipEngine";
|
|
||||||
import {Level1} from "./level1";
|
|
||||||
|
|
||||||
const controllerComponents = [
|
const controllerComponents = [
|
||||||
'a-button',
|
'a-button',
|
||||||
@ -49,6 +46,7 @@ type ControllerEvent = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ControllerStickMode {
|
enum ControllerStickMode {
|
||||||
|
BEGINNER,
|
||||||
ARCADE,
|
ARCADE,
|
||||||
REALISTIC
|
REALISTIC
|
||||||
}
|
}
|
||||||
@ -56,30 +54,31 @@ enum ControllerStickMode {
|
|||||||
export class Ship {
|
export class Ship {
|
||||||
private _ship: TransformNode;
|
private _ship: TransformNode;
|
||||||
private _controllerObservable: Observable<ControllerEvent> = new Observable<ControllerEvent>();
|
private _controllerObservable: Observable<ControllerEvent> = new Observable<ControllerEvent>();
|
||||||
public onReadyObservable: Observable<unknown> = new Observable<unknown>();
|
|
||||||
private _engine: ShipEngine;
|
|
||||||
private _ammoMaterial: StandardMaterial;
|
private _ammoMaterial: StandardMaterial;
|
||||||
private _forwardNode: TransformNode;
|
private _forwardNode: TransformNode;
|
||||||
private _rotationNode: TransformNode;
|
private _rotationNode: TransformNode;
|
||||||
private _onscore: Observable<number>;
|
|
||||||
private _ammo: Array<AbstractMesh> = [];
|
|
||||||
private _glowLayer: GlowLayer;
|
private _glowLayer: GlowLayer;
|
||||||
private _thrust: Sound;
|
private _primaryThrustVectorSound: Sound;
|
||||||
private _thrust2: Sound;
|
private _secondaryThrustVectorSound: Sound;
|
||||||
private _shot: Sound;
|
private _shot: Sound;
|
||||||
private _shooting: boolean = false;
|
private _shooting: boolean = false;
|
||||||
private _camera: FreeCamera;
|
private _camera: FreeCamera;
|
||||||
|
private _ammoBaseMesh: AbstractMesh;
|
||||||
constructor() {
|
private _controllerMode: ControllerStickMode;
|
||||||
|
private _active = false;
|
||||||
|
constructor(mode: ControllerStickMode = ControllerStickMode.BEGINNER) {
|
||||||
|
this._controllerMode = mode
|
||||||
this.setup();
|
this.setup();
|
||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
public set controllerMode(mode: ControllerStickMode) {
|
||||||
|
this._controllerMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
private shoot() {
|
private shoot() {
|
||||||
this._shot.play();
|
this._shot.play();
|
||||||
const ammo = MeshBuilder.CreateCapsule("bullet", {radius: .1, height: 2.5}, DefaultScene.MainScene);
|
const ammo = new InstancedMesh("ammo", this._ammoBaseMesh as Mesh);
|
||||||
ammo.parent = this._ship
|
ammo.parent = this._ship;
|
||||||
ammo.position.y = 2;
|
ammo.position.y = 2;
|
||||||
ammo.rotation.x = Math.PI / 2;
|
ammo.rotation.x = Math.PI / 2;
|
||||||
ammo.setParent(null);
|
ammo.setParent(null);
|
||||||
@ -87,17 +86,18 @@ export class Ship {
|
|||||||
mass: 1000,
|
mass: 1000,
|
||||||
restitution: 0
|
restitution: 0
|
||||||
}, DefaultScene.MainScene);
|
}, DefaultScene.MainScene);
|
||||||
|
ammoAggregate.body.setAngularDamping(1);
|
||||||
|
|
||||||
|
|
||||||
ammo.material = this._ammoMaterial;
|
|
||||||
ammoAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
ammoAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||||
|
|
||||||
ammoAggregate.body.setLinearVelocity(this._ship.forward.scale(200).add(this._ship.physicsBody.getLinearVelocity()));
|
ammoAggregate.body.setLinearVelocity(this._ship.forward.scale(10000))
|
||||||
|
//.add(this._ship.physicsBody.getLinearVelocity()));
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
ammoAggregate.dispose();
|
ammoAggregate.dispose();
|
||||||
ammo.dispose()
|
ammo.dispose()
|
||||||
}, 1500)
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
public set position(newPosition: Vector3) {
|
public set position(newPosition: Vector3) {
|
||||||
@ -113,11 +113,11 @@ export class Ship {
|
|||||||
this._ship = new TransformNode("ship", DefaultScene.MainScene);
|
this._ship = new TransformNode("ship", DefaultScene.MainScene);
|
||||||
this._glowLayer = new GlowLayer('bullets', DefaultScene.MainScene);
|
this._glowLayer = new GlowLayer('bullets', DefaultScene.MainScene);
|
||||||
this._glowLayer.intensity = 1;
|
this._glowLayer.intensity = 1;
|
||||||
this._thrust = new Sound("thrust", "/thrust5.mp3", DefaultScene.MainScene, null, {
|
this._primaryThrustVectorSound = new Sound("thrust", "/thrust5.mp3", DefaultScene.MainScene, null, {
|
||||||
loop: true,
|
loop: true,
|
||||||
autoplay: false
|
autoplay: false
|
||||||
});
|
});
|
||||||
this._thrust2 = new Sound("thrust2", "/thrust5.mp3", DefaultScene.MainScene, null, {
|
this._secondaryThrustVectorSound = new Sound("thrust2", "/thrust5.mp3", DefaultScene.MainScene, null, {
|
||||||
loop: true,
|
loop: true,
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
volume: .5
|
volume: .5
|
||||||
@ -126,6 +126,9 @@ export class Ship {
|
|||||||
{loop: false, autoplay: false, volume: .5});
|
{loop: false, autoplay: false, volume: .5});
|
||||||
this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
|
this._ammoMaterial = new StandardMaterial("ammoMaterial", DefaultScene.MainScene);
|
||||||
this._ammoMaterial.emissiveColor = new Color3(1, 1, 0);
|
this._ammoMaterial.emissiveColor = new Color3(1, 1, 0);
|
||||||
|
this._ammoBaseMesh = MeshBuilder.CreateCapsule("bullet", {radius: .1, height: 2.5}, DefaultScene.MainScene);
|
||||||
|
this._ammoBaseMesh.material = this._ammoMaterial;
|
||||||
|
|
||||||
const light = new DirectionalLight("light", new Vector3(.1, -1, 0), DefaultScene.MainScene);
|
const light = new DirectionalLight("light", new Vector3(.1, -1, 0), DefaultScene.MainScene);
|
||||||
|
|
||||||
const landingLight = new SpotLight("landingLight", new Vector3(0, 0, 0), new Vector3(0, -.5, .5), 1.5, .5, DefaultScene.MainScene);
|
const landingLight = new SpotLight("landingLight", new Vector3(0, 0, 0), new Vector3(0, -.5, .5), 1.5, .5, DefaultScene.MainScene);
|
||||||
@ -163,13 +166,17 @@ export class Ship {
|
|||||||
signtMaterial.emissiveColor = Color3.Yellow();
|
signtMaterial.emissiveColor = Color3.Yellow();
|
||||||
sight.material = signtMaterial;
|
sight.material = signtMaterial;
|
||||||
sight.position = new Vector3(0, 2, 125);
|
sight.position = new Vector3(0, 2, 125);
|
||||||
|
let i = Date.now();
|
||||||
window.setInterval(() => {
|
DefaultScene.MainScene.onBeforeRenderObservable.add(() => {
|
||||||
this.applyForce();
|
if (Date.now() - i > 50 && this._active == true) {
|
||||||
}, 50);
|
this.applyForce();
|
||||||
|
i = Date.now();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._active = true;
|
||||||
}
|
}
|
||||||
private async initialize() {
|
private async initialize() {
|
||||||
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "cockpit3.glb", DefaultScene.MainScene);
|
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "cockpit2.glb", DefaultScene.MainScene);
|
||||||
const shipMesh = importMesh.meshes[0];
|
const shipMesh = importMesh.meshes[0];
|
||||||
shipMesh.id = "shipMesh";
|
shipMesh.id = "shipMesh";
|
||||||
shipMesh.name = "shipMesh";
|
shipMesh.name = "shipMesh";
|
||||||
@ -177,7 +184,8 @@ export class Ship {
|
|||||||
shipMesh.rotation.y = Math.PI;
|
shipMesh.rotation.y = Math.PI;
|
||||||
shipMesh.position.y = 1;
|
shipMesh.position.y = 1;
|
||||||
shipMesh.position.z = -1;
|
shipMesh.position.z = -1;
|
||||||
DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .7;
|
|
||||||
|
DefaultScene.MainScene.getMaterialById('glass_mat.002').alpha = .4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -190,65 +198,41 @@ export class Ship {
|
|||||||
private _mouseDown = false;
|
private _mouseDown = false;
|
||||||
private _mousePos = new Vector2(0, 0);
|
private _mousePos = new Vector2(0, 0);
|
||||||
|
|
||||||
private scale(value: number) {
|
|
||||||
return value * .8;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public get transformNode() {
|
public get transformNode() {
|
||||||
return this._ship;
|
return this._ship;
|
||||||
}
|
}
|
||||||
|
|
||||||
private adjust(value: number, increment: number = .8): number {
|
|
||||||
if (Math.abs(value) < .001) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return value * increment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private applyForce() {
|
private applyForce() {
|
||||||
if (!this?._ship?.physicsBody) {
|
if (!this?._ship?.physicsBody) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const body = this._ship.physicsBody;
|
const body = this._ship.physicsBody;
|
||||||
if (Math.abs(this._forwardValue) > 40) {
|
//If we're moving over MAX_FORWARD_THRUST, we can't add any more thrust,
|
||||||
this._forwardValue = Math.sign(this._forwardValue) * 40;
|
//just continue at MAX_FORWARD_THRUST
|
||||||
|
if (Math.abs(this._forwardValue) > MAX_FORWARD_THRUST) {
|
||||||
|
this._forwardValue = Math.sign(this._forwardValue) * MAX_FORWARD_THRUST;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.abs(this._forwardValue) <= 40) {
|
//if forward thrust is under 40 we can apply more thrust
|
||||||
|
if (Math.abs(this._forwardValue) <= MAX_FORWARD_THRUST) {
|
||||||
if (Math.abs(this._leftStickVector.y) > .1) {
|
if (Math.abs(this._leftStickVector.y) > .1) {
|
||||||
if (!this._thrust.isPlaying) {
|
if (!this._primaryThrustVectorSound.isPlaying) {
|
||||||
this._thrust.play();
|
this._primaryThrustVectorSound.play();
|
||||||
}
|
}
|
||||||
this._thrust.setVolume(Math.abs(this._leftStickVector.y));
|
this._primaryThrustVectorSound.setVolume(Math.abs(this._leftStickVector.y));
|
||||||
this._forwardValue += this._leftStickVector.y * .8;
|
this._forwardValue += this._leftStickVector.y * .8;
|
||||||
} else {
|
} else {
|
||||||
if (this._thrust.isPlaying) {
|
if (this._primaryThrustVectorSound.isPlaying) {
|
||||||
this._thrust.pause();
|
this._primaryThrustVectorSound.pause();
|
||||||
}
|
}
|
||||||
this._forwardValue = this.adjust(this._forwardValue, .98);
|
this._forwardValue = decrementValue(this._forwardValue, .98);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.abs(this._leftStickVector.x) > .1) {
|
this._yawValue = adjustStickValue(this._leftStickVector.x, this._yawValue);
|
||||||
this._yawValue += this._leftStickVector.x * .03;
|
this._rollValue = adjustStickValue(this._rightStickVector.x, this._rollValue);
|
||||||
} else {
|
this._pitchValue = adjustStickValue(this._rightStickVector.y, this._pitchValue);
|
||||||
|
|
||||||
this._yawValue = this.adjust(this._yawValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(this._rightStickVector.x) > .1) {
|
|
||||||
this._rollValue += this._rightStickVector.x * .03;
|
|
||||||
} else {
|
|
||||||
this._rollValue = this.adjust(this._rollValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(this._rightStickVector.y) > .1) {
|
|
||||||
this._pitchValue += this._rightStickVector.y * .03;
|
|
||||||
} else {
|
|
||||||
this._pitchValue = this.adjust(this._pitchValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._forwardNode.position.z = this._forwardValue;
|
this._forwardNode.position.z = this._forwardValue;
|
||||||
this._rotationNode.position.y = this._yawValue;
|
this._rotationNode.position.y = this._yawValue;
|
||||||
@ -260,20 +244,18 @@ export class Ship {
|
|||||||
Math.abs(this._leftStickVector.x);
|
Math.abs(this._leftStickVector.x);
|
||||||
|
|
||||||
if (thrust2 > .01) {
|
if (thrust2 > .01) {
|
||||||
if (!this._thrust2.isPlaying) {
|
if (!this._secondaryThrustVectorSound.isPlaying) {
|
||||||
this._thrust2.play();
|
this._secondaryThrustVectorSound.play();
|
||||||
}
|
}
|
||||||
this._thrust2.setVolume(thrust2 * .4);
|
this._secondaryThrustVectorSound.setVolume(thrust2 * .4);
|
||||||
} else {
|
} else {
|
||||||
if (this._thrust2.isPlaying) {
|
if (this._secondaryThrustVectorSound.isPlaying) {
|
||||||
this._thrust2.pause();
|
this._secondaryThrustVectorSound.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.setAngularVelocity(this._rotationNode.absolutePosition.subtract(this._ship.absolutePosition));
|
body.setAngularVelocity(this._rotationNode.absolutePosition.subtract(this._ship.absolutePosition));
|
||||||
body.setLinearVelocity(this._forwardNode.absolutePosition.subtract(this._ship.absolutePosition).scale(-1));
|
body.setLinearVelocity(this._forwardNode.absolutePosition.subtract(this._ship.absolutePosition).scale(-1));
|
||||||
//this._engine.forwardback(this._forwardValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private controllerCallback = (controllerEvent: ControllerEvent) => {
|
private controllerCallback = (controllerEvent: ControllerEvent) => {
|
||||||
@ -349,7 +331,6 @@ export class Ship {
|
|||||||
case '1':
|
case '1':
|
||||||
this._camera.position.x = 15;
|
this._camera.position.x = 15;
|
||||||
this._camera.rotation.y = -Math.PI / 2;
|
this._camera.rotation.y = -Math.PI / 2;
|
||||||
console.log(1);
|
|
||||||
break;
|
break;
|
||||||
case ' ':
|
case ' ':
|
||||||
this.shoot();
|
this.shoot();
|
||||||
@ -386,14 +367,12 @@ export class Ship {
|
|||||||
if (controller.inputSource.handedness == "left") {
|
if (controller.inputSource.handedness == "left") {
|
||||||
this._leftInputSource = controller;
|
this._leftInputSource = controller;
|
||||||
this._leftInputSource.onMotionControllerInitObservable.add((motionController) => {
|
this._leftInputSource.onMotionControllerInitObservable.add((motionController) => {
|
||||||
console.log(motionController);
|
|
||||||
this.mapMotionController(motionController);
|
this.mapMotionController(motionController);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (controller.inputSource.handedness == "right") {
|
if (controller.inputSource.handedness == "right") {
|
||||||
this._rightInputSource = controller;
|
this._rightInputSource = controller;
|
||||||
this._rightInputSource.onMotionControllerInitObservable.add((motionController) => {
|
this._rightInputSource.onMotionControllerInitObservable.add((motionController) => {
|
||||||
console.log(motionController);
|
|
||||||
this.mapMotionController(motionController);
|
this.mapMotionController(motionController);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -435,3 +414,18 @@ export class Ship {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function decrementValue(value: number, increment: number = .8): number {
|
||||||
|
if (Math.abs(value) < .01) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return value * increment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustStickValue(stickVector: number, thrustValue: number): number {
|
||||||
|
if (Math.abs(stickVector) > .03) {
|
||||||
|
return thrustValue + (Math.pow(stickVector, 3) * .1);
|
||||||
|
} else {
|
||||||
|
return decrementValue(thrustValue, .85);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,69 +1,95 @@
|
|||||||
import {
|
import {
|
||||||
AbstractMesh, Color3, ISceneLoaderAsyncResult, MeshBuilder, ParticleHelper, ParticleSystem, ParticleSystemSet,
|
AbstractMesh,
|
||||||
PhysicsAggregate,
|
Color3, InstancedMesh,
|
||||||
|
Mesh,
|
||||||
|
MeshBuilder, Observable,
|
||||||
|
ParticleHelper,
|
||||||
|
ParticleSystem,
|
||||||
|
ParticleSystemSet,
|
||||||
|
PBRMaterial,
|
||||||
|
PhysicsAggregate, PhysicsBody,
|
||||||
PhysicsMotionType,
|
PhysicsMotionType,
|
||||||
PhysicsShapeType,
|
PhysicsShapeType,
|
||||||
SceneLoader, StandardMaterial,
|
SceneLoader,
|
||||||
Vector3
|
Vector3
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
|
import {ScoreEvent} from "./scoreboard";
|
||||||
let _particleData: any = null;
|
let _particleData: any = null;
|
||||||
|
export class Rock {
|
||||||
|
private _rockMesh: AbstractMesh;
|
||||||
|
constructor(mesh: AbstractMesh) {
|
||||||
|
this._rockMesh = mesh;
|
||||||
|
}
|
||||||
|
public get physicsBody(): PhysicsBody {
|
||||||
|
return this._rockMesh.physicsBody;
|
||||||
|
}
|
||||||
|
public get position(): Vector3 {
|
||||||
|
return this._rockMesh.getAbsolutePosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class RockFactory {
|
export class RockFactory {
|
||||||
private static _rockMesh: AbstractMesh;
|
private static _rockMesh: AbstractMesh;
|
||||||
private static _rockMaterial: StandardMaterial;
|
private static _rockMaterial: PBRMaterial;
|
||||||
|
private static _explosion: ParticleSystemSet;
|
||||||
public static async init() {
|
public static async init() {
|
||||||
|
|
||||||
|
if (!this._explosion) {
|
||||||
|
const set = await ParticleHelper.CreateAsync("explosion", DefaultScene.MainScene);
|
||||||
|
this._explosion = set.serialize(true);
|
||||||
|
set.dispose();
|
||||||
|
}
|
||||||
if (!this._rockMesh) {
|
if (!this._rockMesh) {
|
||||||
console.log('loading mesh');
|
console.log('loading mesh');
|
||||||
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "asteroid.glb", DefaultScene.MainScene);
|
const importMesh = await SceneLoader.ImportMeshAsync(null, "./", "asteroid2.glb", DefaultScene.MainScene);
|
||||||
this._rockMesh = importMesh.meshes[1].clone("asteroid", null, true);
|
this._rockMesh = importMesh.meshes[1].clone("asteroid", null, false);
|
||||||
this._rockMesh.setParent(null);
|
this._rockMesh.setParent(null);
|
||||||
this._rockMesh.setEnabled(false);
|
this._rockMesh.setEnabled(false);
|
||||||
|
|
||||||
//importMesh.meshes[1].dispose();
|
//importMesh.meshes[1].dispose();
|
||||||
console.log(importMesh.meshes);
|
console.log(importMesh.meshes);
|
||||||
if (!this._rockMaterial) {
|
if (!this._rockMaterial) {
|
||||||
this._rockMaterial = this._rockMesh.material.clone("asteroid") as StandardMaterial;
|
this._rockMaterial = this._rockMesh.material.clone("asteroid") as PBRMaterial;
|
||||||
this._rockMaterial.name = 'asteroid-material';
|
this._rockMaterial.name = 'asteroid-material';
|
||||||
this._rockMaterial.id = 'asteroid-material';
|
this._rockMaterial.id = 'asteroid-material';
|
||||||
|
const material = (this._rockMaterial as PBRMaterial)
|
||||||
|
//material.albedoTexture = null;
|
||||||
|
material.ambientColor = new Color3(.4, .4 ,.4);
|
||||||
|
//material.albedoColor = new Color3(1, 1, 1);
|
||||||
|
//material.emissiveColor = new Color3(1, 1, 1);
|
||||||
|
this._rockMesh.material = this._rockMaterial;
|
||||||
importMesh.meshes[1].dispose(false, true);
|
importMesh.meshes[1].dispose(false, true);
|
||||||
importMesh.meshes[0].dispose();
|
importMesh.meshes[0].dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static async createRock(i: number, position: Vector3, size: Vector3): Promise<AbstractMesh> {
|
public static async createRock(i: number, position: Vector3, size: Vector3,
|
||||||
const explosion = await ParticleHelper.CreateAsync("explosion", DefaultScene.MainScene);
|
score: Observable<ScoreEvent>): Promise<Rock> {
|
||||||
|
|
||||||
const rock = this._rockMesh.clone("asteroid-" + i, null, true);
|
const rock = new InstancedMesh("asteroid-" +i, this._rockMesh as Mesh);
|
||||||
|
|
||||||
//rock.material.dispose();
|
|
||||||
//rock.material = rockMaterial;
|
|
||||||
rock.scaling = size;
|
rock.scaling = size;
|
||||||
rock.position = position;
|
rock.position = position;
|
||||||
//rock.setParent(null);
|
//rock.material = this._rockMaterial;
|
||||||
|
|
||||||
rock.name = "asteroid-" + i;
|
rock.name = "asteroid-" + i;
|
||||||
rock.id = "asteroid-" + i;
|
rock.id = "asteroid-" + i;
|
||||||
rock.metadata = {type: 'asteroid'};
|
rock.metadata = {type: 'asteroid'};
|
||||||
rock.setEnabled(true);
|
rock.setEnabled(true);
|
||||||
const agg = new PhysicsAggregate(rock, PhysicsShapeType.CONVEX_HULL, {mass: 10000, restitution: .0001}, DefaultScene.MainScene);
|
const agg = new PhysicsAggregate(rock, PhysicsShapeType.CONVEX_HULL, {
|
||||||
|
mass: 10000,
|
||||||
|
restitution: .5,
|
||||||
|
}, DefaultScene.MainScene);
|
||||||
const body =agg.body;
|
const body =agg.body;
|
||||||
body.setLinearDamping(.001);
|
body.setLinearDamping(0);
|
||||||
//body.setAngularDamping(.00001);
|
|
||||||
body.setMotionType(PhysicsMotionType.DYNAMIC);
|
body.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||||
body.setCollisionCallbackEnabled(true);
|
body.setCollisionCallbackEnabled(true);
|
||||||
//rock.renderOutline = true;
|
|
||||||
//rock.outlineColor = Color3.Red();
|
|
||||||
//rock.outlineWidth = .02;
|
|
||||||
//rock.showBoundingBox = true;
|
|
||||||
|
|
||||||
//rock.renderOverlay = true;
|
|
||||||
|
|
||||||
body.getCollisionObservable().add((eventData) => {
|
body.getCollisionObservable().add((eventData) => {
|
||||||
if (eventData.type == 'COLLISION_STARTED') {
|
if (eventData.type == 'COLLISION_STARTED') {
|
||||||
if ( eventData.collidedAgainst.transformNode.id == 'bullet') {
|
if ( eventData.collidedAgainst.transformNode.id == 'ammo') {
|
||||||
|
score.notifyObservers({score: 1, remaining: -1, message: "Asteroid Destroyed"});
|
||||||
|
const explosion = ParticleSystemSet.Parse(this._explosion, DefaultScene.MainScene, false, 10);
|
||||||
const position = eventData.point;
|
const position = eventData.point;
|
||||||
// _explosion.emitterNode = position;
|
// _explosion.emitterNode = position;
|
||||||
|
|
||||||
@ -77,11 +103,11 @@ export class RockFactory {
|
|||||||
|
|
||||||
const ball = MeshBuilder.CreateBox("ball", {size: .01}, DefaultScene.MainScene);
|
const ball = MeshBuilder.CreateBox("ball", {size: .01}, DefaultScene.MainScene);
|
||||||
|
|
||||||
ball.scaling = new Vector3(.1, .1, .1);
|
ball.scaling = new Vector3(.4, .4, .4);
|
||||||
ball.position = position;
|
ball.position = position;
|
||||||
const material = new StandardMaterial("ball-material", DefaultScene.MainScene);
|
//const material = new StandardMaterial("ball-material", DefaultScene.MainScene);
|
||||||
material.emissiveColor = Color3.Yellow();
|
//material.emissiveColor = Color3.Yellow();
|
||||||
ball.material = material;
|
//ball.material = material;
|
||||||
|
|
||||||
explosion.start(ball);
|
explosion.start(ball);
|
||||||
|
|
||||||
@ -95,13 +121,13 @@ export class RockFactory {
|
|||||||
ball.dispose(false, true);
|
ball.dispose(false, true);
|
||||||
}
|
}
|
||||||
//ball.dispose();
|
//ball.dispose();
|
||||||
}, 2000);
|
}, 1500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//body.setAngularVelocity(new Vector3(Math.random(), Math.random(), Math.random()));
|
//body.setAngularVelocity(new Vector3(Math.random(), Math.random(), Math.random()));
|
||||||
// body.setLinearVelocity(Vector3.Random(-10, 10));
|
// body.setLinearVelocity(Vector3.Random(-10, 10));
|
||||||
return rock;
|
return new Rock(rock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user