Merge pull request #6724 from thoys/20736

Winter Smash up Target Practice game
This commit is contained in:
James B. Pollack 2015-12-23 10:34:20 -08:00
commit d8278459be
6 changed files with 503 additions and 32 deletions

View file

@ -0,0 +1,72 @@
//
// triggeredRecordingOnAC.js
// examples/acScripts
//
// Created by Thijs Wenker on 12/21/15.
// Copyright 2015 High Fidelity, Inc.
//
// This is the triggered rocording script used in the winterSmashUp target practice game.
// Change the CLIP_URL to your asset,
// the RECORDING_CHANNEL and RECORDING_CHANNEL_MESSAGE are used to trigger it i.e.:
// Messages.sendMessage("PlayBackOnAssignment", "BowShootingGameWelcome");
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const CLIP_URL = "atp:3fbe82f2153c443f12f9a2b14ce2d7fa2fff81977263746d9e0885ea5aabed62.hfr";
const RECORDING_CHANNEL = 'PlayBackOnAssignment';
const RECORDING_CHANNEL_MESSAGE = 'BowShootingGameWelcome'; // For each different assignment add a different message here.
const PLAY_FROM_CURRENT_LOCATION = true;
const USE_DISPLAY_NAME = true;
const USE_ATTACHMENTS = true;
const USE_AVATAR_MODEL = true;
const AUDIO_OFFSET = 0.0;
const STARTING_TIME = 0.0;
const COOLDOWN_PERIOD = 0; // The period in ms that no animations can be played after one has been played already
var isPlaying = false;
var isPlayable = true;
var playRecording = function() {
if (!isPlayable || isPlaying) {
return;
}
Agent.isAvatar = true;
Recording.setPlayFromCurrentLocation(PLAY_FROM_CURRENT_LOCATION);
Recording.setPlayerUseDisplayName(USE_DISPLAY_NAME);
Recording.setPlayerUseAttachments(USE_ATTACHMENTS);
Recording.setPlayerUseHeadModel(false);
Recording.setPlayerUseSkeletonModel(USE_AVATAR_MODEL);
Recording.setPlayerLoop(false);
Recording.setPlayerTime(STARTING_TIME);
Recording.setPlayerAudioOffset(AUDIO_OFFSET);
Recording.loadRecording(CLIP_URL);
Recording.startPlaying();
isPlaying = true;
isPlayable = false; // Set this true again after the cooldown period
};
Script.update.connect(function(deltaTime) {
if (isPlaying && !Recording.isPlaying()) {
print('Reached the end of the recording. Resetting.');
isPlaying = false;
Agent.isAvatar = false;
if (COOLDOWN_PERIOD === 0) {
isPlayable = true;
return;
}
Script.setTimeout(function () {
isPlayable;
}, COOLDOWN_PERIOD);
}
});
Messages.subscribe(RECORDING_CHANNEL);
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel === RECORDING_CHANNEL && message === RECORDING_CHANNEL_MESSAGE) {
playRecording();
}
});

View file

@ -277,12 +277,12 @@ function MyController(hand) {
//for visualizations
this.overlayLine = null;
this.particleBeam = null;
//for lights
this.spotlight = null;
this.pointlight = null;
this.overlayLine = null;
this.ignoreIK = false;
this.offsetPosition = Vec3.ZERO;
this.offsetRotation = Quat.IDENTITY;
@ -387,7 +387,7 @@ function MyController(hand) {
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}
})
});
} else {
@ -444,7 +444,7 @@ function MyController(hand) {
this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan);
} else {
this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan);
}
}
};
this.handleDistantParticleBeam = function(handPosition, objectPosition, color) {
@ -532,12 +532,12 @@ function MyController(hand) {
Entities.editEntity(this.particleBeam, {
rotation: orientation,
position: position,
visible: true,
color: color,
visible: true,
color: color,
emitSpeed: speed,
speedSpread: spread,
lifespan: lifespan
})
})
};
@ -553,7 +553,7 @@ function MyController(hand) {
x: 1,
y: 0,
z: 0
});
});
return {
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
@ -933,7 +933,7 @@ function MyController(hand) {
}
if (USE_OVERLAY_LINES_FOR_SEARCHING === true) {
this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR);
this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR);
}
if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) {
@ -1160,13 +1160,13 @@ function MyController(hand) {
// if an object is "equipped" and has a spatialKey, use it.
this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false;
if (grabbableData.spatialKey.relativePosition) {
this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
} else {
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
}
if (grabbableData.spatialKey.relativeRotation) {
this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
} else {
this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
} else {
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
}
} else {
@ -1281,7 +1281,6 @@ function MyController(hand) {
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
Entities.callEntityMethod(this.grabbedEntity, "unequip");
this.endHandGrasp();
}
};
@ -1406,7 +1405,7 @@ function MyController(hand) {
}
if (USE_ENTITY_LINES_FOR_MOVING === true) {
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
}
Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger");
@ -1617,7 +1616,7 @@ Controller.enableMapping(MAPPING_NAME);
var handToDisable = 'none';
function update() {
if (handToDisable !== LEFT_HAND && handToDisable !== 'both') {
if (handToDisable !== LEFT_HAND && handToDisable!=='both') {
leftController.update();
}
if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') {
@ -1626,27 +1625,34 @@ function update() {
}
Messages.subscribe('Hifi-Hand-Disabler');
Messages.subscribe('Hifi-Hand-Grab');
handleHandDisablerMessages = function(channel, message, sender) {
handleHandMessages = function(channel, message, sender) {
if (sender === MyAvatar.sessionUUID) {
if (message === 'left') {
handToDisable = LEFT_HAND;
}
if (message === 'right') {
handToDisable = RIGHT_HAND;
}
if (message === 'both') {
handToDisable = 'both';
}
if (message === 'none') {
handToDisable = 'none';
if (channel === 'Hifi-Hand-Disabler') {
if (message === 'left') {
handToDisable = LEFT_HAND;
}
if (message === 'right') {
handToDisable = RIGHT_HAND;
}
if (message === 'both' || message === 'none') {
handToDisable = handToDisable;
}
} else if (channel === 'Hifi-Hand-Grab') {
try {
var data = JSON.parse(message);
var selectedController = (data.hand === 'left') ? leftController : rightController;
selectedController.release();
selectedController.setState(STATE_EQUIP);
selectedController.grabbedEntity = data.entityID;
} catch (e) { }
}
}
}
Messages.messageReceived.connect(handleHandDisablerMessages);
Messages.messageReceived.connect(handleHandMessages);
function cleanup() {
rightController.cleanup();

View file

@ -241,9 +241,9 @@
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
},
creatorSessionUUID: MyAvatar.sessionUUID
})
});
var makeArrowStick = function(entityA, entityB, collision) {

View file

@ -0,0 +1,73 @@
//
// shooterPlatform.js
// examples/winterSmashUp/targetPractice
//
// Created by Thijs Wenker on 12/21/15.
// Copyright 2015 High Fidelity, Inc.
//
// The Winter Smash Up Target Practice Game using a bow.
// This is the platform that spawns the bow and attaches it to the avatars hand,
// then de-rez the bow on leaving to prevent walking up to the targets with the bow.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this = this;
const GAME_CHANNEL = 'winterSmashUpGame';
const SCRIPT_URL = Script.resolvePath('../../toybox/bow/bow.js');
const MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/bow/new/bow-deadly.fbx";
const COLLISION_HULL_URL = "https://hifi-public.s3.amazonaws.com/models/bow/new/bow_collision_hull.obj";
const RIGHT_HAND = 1;
const LEFT_HAND = 0;
var bowEntity = undefined;
_this.enterEntity = function(entityID) {
print('entered bow game entity');
// Triggers a recording on an assignment client:
Messages.sendMessage('PlayBackOnAssignment', 'BowShootingGameExplaination');
bowEntity = Entities.addEntity({
name: 'Hifi-Bow-For-Game',
type: 'Model',
modelURL: MODEL_URL,
position: MyAvatar.position,
dimensions: {x: 0.04, y: 1.3, z: 0.21},
collisionsWillMove: true,
gravity: {x: 0, y: 0, z: 0},
shapeType: 'compound',
compoundShapeURL: COLLISION_HULL_URL,
script: SCRIPT_URL,
userData: JSON.stringify({
grabbableKey: {
invertSolidWhileHeld: true,
spatialKey: {
leftRelativePosition: {
x: 0.05,
y: 0.06,
z: -0.05
},
rightRelativePosition: {
x: -0.05,
y: 0.06,
z: -0.05
},
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
}
}
})
});
Messages.sendMessage('Hifi-Hand-Grab', JSON.stringify({hand: 'left', entityID: bowEntity}));
};
_this.leaveEntity = function(entityID) {
if (bowEntity !== undefined) {
Entities.deleteEntity(bowEntity);
bowEntity = undefined;
}
};
});

View file

@ -0,0 +1,93 @@
//
// startTargetPractice.js
// examples/winterSmashUp/targetPractice
//
// Created by Thijs Wenker on 12/21/15.
// Copyright 2015 High Fidelity, Inc.
//
// The Winter Smash Up Target Practice Game using a bow.
// This script starts the game, when the entity that contains the script gets shot.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this = this;
var waitForScriptLoad = false;
const MAX_GAME_TIME = 60; //seconds
const SCRIPT_URL = 'http://s3.amazonaws.com/hifi-public/scripts/winterSmashUp/targetPractice/targetPracticeGame.js';
const GAME_CHANNEL = 'winterSmashUpGame';
var isScriptRunning = function(script) {
script = script.toLowerCase().trim();
var runningScripts = ScriptDiscoveryService.getRunning();
for (i in runningScripts) {
if (runningScripts[i].url.toLowerCase().trim() == script) {
return true;
}
}
return false;
};
var sendStartSignal = function() {
Messages.sendMessage(GAME_CHANNEL, JSON.stringify({
action: 'start',
gameEntityID: _this.entityID,
playerSessionUUID: MyAvatar.sessionUUID
}));
}
var startGame = function() {
//TODO: check here if someone is already playing this game instance by verifying the userData for playerSessionID
// and startTime with a maximum timeout of X seconds (30?)
if (!isScriptRunning(SCRIPT_URL)) {
// Loads the script for the player if this isn't yet loaded
Script.load(SCRIPT_URL);
waitForScriptLoad = true;
return;
}
sendStartSignal();
};
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == GAME_CHANNEL) {
var data = JSON.parse(message);
switch (data.action) {
case 'scriptLoaded':
if (waitForPing) {
sendStartSignal();
waitForPing = false;
}
break;
}
}
});
_this.preload = function(entityID) {
_this.entityID = entityID;
};
_this.collisionWithEntity = function(entityA, entityB, collisionInfo) {
if (entityA == _this.entityID) {
try {
var data = JSON.parse(Entities.getEntityProperties(entityB).userData);
if (data.creatorSessionUUID === MyAvatar.sessionUUID) {
print('attempting to startGame by collisionWithEntity.');
startGame();
}
} catch(e) {
}
}
};
_this.onShot = function(forceDirection) {
print('attempting to startGame by onShot.');
startGame();
};
Messages.subscribe(GAME_CHANNEL);
});

View file

@ -0,0 +1,227 @@
//
// targetPracticeGame.js
// examples/winterSmashUp/targetPractice
//
// Created by Thijs Wenker on 12/21/15.
// Copyright 2015 High Fidelity, Inc.
//
// The Winter Smash Up Target Practice Game using a bow.
// This script runs on the client side (it is loaded through the platform trigger entity)
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const GAME_CHANNEL = 'winterSmashUpGame';
const SCORE_POST_URL = 'https://script.google.com/macros/s/AKfycbwZAMx6cMBx6-8NGEhR8ELUA-dldtpa_4P55z38Q4vYHW6kneg/exec';
const MODEL_URL = 'http://cdn.highfidelity.com/chris/production/winter/game/';
const MAX_GAME_TIME = 120; //seconds
const TARGET_CLOSE_OFFSET = 0.5;
const MILLISECS_IN_SEC = 1000;
const HIT_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Clay_Pigeon_02.L.wav';
const GRAVITY = -9.8;
const MESSAGE_WIDTH = 100;
const MESSAGE_HEIGHT = 50;
const TARGETS = [
{pitch: -1, yaw: -20, maxDistance: 17},
{pitch: -1, yaw: -15, maxDistance: 17},
{pitch: -1, yaw: -10, maxDistance: 17},
{pitch: -2, yaw: -5, maxDistance: 17},
{pitch: -1, yaw: 0, maxDistance: 17},
{pitch: 3, yaw: 10, maxDistance: 17},
{pitch: -1, yaw: 15, maxDistance: 17},
{pitch: -1, yaw: 20, maxDistance: 17},
{pitch: 2, yaw: 25, maxDistance: 17},
{pitch: 0, yaw: 30, maxDistance: 17}
];
var gameRunning = false;
var gameStartTime;
var gameEntityID;
var gameTimeoutTimer;
var targetEntities = [];
var hitSound = SoundCache.getSound(HIT_SOUND_URL);
var messageOverlay = undefined;
var messageExpire = 0;
var clearMessage = function() {
if (messageOverlay !== undefined) {
Overlays.deleteOverlay(messageOverlay);
messageOverlay = undefined;
}
};
var displayMessage = function(message, timeout) {
clearMessage();
messageExpire = Date.now() + timeout;
messageOverlay = Overlays.addOverlay('text', {
text: message,
x: (Window.innerWidth / 2) - (MESSAGE_WIDTH / 2),
y: (Window.innerHeight / 2) - (MESSAGE_HEIGHT / 2),
width: MESSAGE_WIDTH,
height: MESSAGE_HEIGHT,
alpha: 1,
backgroundAlpha: 0.0,
font: {size: 20}
});
};
var getStatsText = function() {
var timeLeft = MAX_GAME_TIME - ((Date.now() - gameStartTime) / MILLISECS_IN_SEC);
return 'Time remaining: ' + timeLeft.toFixed(1) + 's\nTargets hit: ' + (TARGETS.length - targetEntities.length) + '/' + TARGETS.length;
};
const timerOverlayWidth = 50;
var timerOverlay = Overlays.addOverlay('text', {
text: '',
x: Window.innerWidth / 2 - (timerOverlayWidth / 2),
y: 100,
width: timerOverlayWidth,
alpha: 1,
backgroundAlpha: 0.0,
visible: false,
font: {size: 20}
});
var cleanRemainingTargets = function() {
while (targetEntities.length > 0) {
Entities.deleteEntity(targetEntities.pop());
}
};
var createTarget = function(position, rotation, scale) {
var initialDimensions = {x: 1.8437, y: 0.1614, z: 1.8438};
var dimensions = Vec3.multiply(initialDimensions, scale);
return Entities.addEntity({
type: 'Model',
rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)),
lifetime: MAX_GAME_TIME,
modelURL: MODEL_URL + 'target.fbx',
shapeType: 'compound',
compoundShapeURL: MODEL_URL + 'targetCollision.obj',
dimensions: dimensions,
position: position
});
};
var createTargetInDirection = function(startPosition, startRotation, pitch, yaw, maxDistance, scale) {
var directionQuat = Quat.multiply(startRotation, Quat.fromPitchYawRollDegrees(pitch, yaw, 0.0));
var directionVec = Vec3.multiplyQbyV(directionQuat, Vec3.FRONT);
var intersection = Entities.findRayIntersection({direction: directionVec, origin: startPosition}, true);
var distance = maxDistance;
if (intersection.intersects && intersection.distance <= maxDistance) {
distance = intersection.distance - TARGET_CLOSE_OFFSET;
}
return createTarget(Vec3.sum(startPosition, Vec3.multiplyQbyV(directionQuat, Vec3.multiply(Vec3.FRONT, distance))), startRotation, scale);
};
var killTimer = function() {
if (gameTimeoutTimer !== undefined) {
Script.clearTimeout(gameTimeoutTimer);
gameTimeoutTimer = undefined;
}
};
var submitScore = function() {
gameRunning = false;
killTimer();
Overlays.editOverlay(timerOverlay, {visible: false});
if (!GlobalServices.username) {
displayMessage('Could not submit score, you are not logged in.', 5000);
return;
}
var timeItTook = Date.now() - gameStartTime;
var req = new XMLHttpRequest();
req.open('POST', SCORE_POST_URL, false);
req.send(JSON.stringify({
username: GlobalServices.username,
time: timeItTook / MILLISECS_IN_SEC
}));
displayMessage('Your score has been submitted!', 5000);
};
var onTargetHit = function(targetEntity, projectileEntity, collision) {
var targetIndex = targetEntities.indexOf(targetEntity);
if (targetIndex !== -1) {
try {
var data = JSON.parse(Entities.getEntityProperties(projectileEntity).userData);
if (data.creatorSessionUUID === MyAvatar.sessionUUID) {
this.audioInjector = Audio.playSound(hitSound, {
position: collision.contactPoint,
volume: 0.5
});
// Attach arrow to target for the nice effect
Entities.editEntity(projectileEntity, {
ignoreForCollisions: true,
parentID: targetEntity
});
Entities.editEntity(targetEntity, {
collisionsWillMove: true,
gravity: {x: 0, y: GRAVITY, z: 0},
velocity: {x: 0, y: -0.01, z: 0}
});
targetEntities.splice(targetIndex, 1);
if (targetEntities.length === 0) {
submitScore();
}
}
} catch(e) {
}
}
};
var startGame = function(entityID) {
cleanRemainingTargets();
killTimer();
gameEntityID = entityID;
targetEntities = [];
var parentEntity = Entities.getEntityProperties(gameEntityID);
for (var i in TARGETS) {
var target = TARGETS[i];
var targetEntity = createTargetInDirection(parentEntity.position, parentEntity.rotation, target.pitch, target.yaw, target.maxDistance, 0.67);
Script.addEventHandler(targetEntity, 'collisionWithEntity', onTargetHit);
targetEntities.push(targetEntity);
}
gameStartTime = Date.now();
gameTimeoutTimer = Script.setTimeout(function() {
cleanRemainingTargets();
Overlays.editOverlay(timerOverlay, {visible: false});
gameRunning = false;
displayMessage('Game timed out.', 5000);
}, MAX_GAME_TIME * MILLISECS_IN_SEC);
gameRunning = true;
Overlays.editOverlay(timerOverlay, {visible: true, text: getStatsText()});
displayMessage('Game started! GO GO GO!', 3000);
};
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == GAME_CHANNEL) {
var data = JSON.parse(message);
switch (data.action) {
case 'start':
if (data.playerSessionUUID === MyAvatar.sessionUUID) {
startGame(data.gameEntityID);
}
break;
}
}
});
Messages.subscribe(GAME_CHANNEL);
Messages.sendMessage(GAME_CHANNEL, JSON.stringify({action: 'scriptLoaded'}));
Script.update.connect(function(deltaTime) {
if (gameRunning) {
Overlays.editOverlay(timerOverlay, {text: getStatsText()});
}
if (messageOverlay !== undefined && Date.now() > messageExpire) {
clearMessage();
}
});
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(timerOverlay);
cleanRemainingTargets();
clearMessage();
});