topical media & game development
mobile-query-three-plugins-ogsworkshop-examples-game085.htm / htm
<!--
Let's chat with each other
* add dom element on top
* hook it to the network code
* draw the text on top
-->
<!doctype html><title>Minimal tQuery Page</title>
<script src="../../../build/tquery-bundle-require.js"></script>
<!-- all network scripts -->
<script src="http://localhost:4000/socket.io/socket.io.js"></script>
<script src="http://localhost:4000/examples/client.js"></script>
<body><div style="position: absolute; font-size: 200%; right: 0; z-index: 1; text-align: right;">
<form action="javascript:void(0)" id='chatInputForm'>
<input size=100 type="text" placeholder='Chat here'/>
</form>
<select id="skinSelect">
<option value="char.png">char</option>
<option value="3djesus.png">3djesus</option>
<option value="agentsmith.png">agentsmith</option>
<option value="batman.png">batman</option>
<option value="god.png">god</option>
<option value="Joker.png">Joker</option>
<option value="Mario.png">Mario</option>
<option value="martialartist.png">martialartist</option>
<option value="robocop.png">robocop</option>
<option value="Sonicthehedgehog.png">Sonic the hedgehog</option>
<option value="Spiderman.png">Spiderman</option>
<option value="Superman.png">Superman</option>
<option value="theflash.png">theflash</option>
<option value="woody.png">woody</option>
<option value="Iron-Man-Minecraft-Skin.png">ironman</option>
</select>
<form action="javascript:void(0)" id='nicknameForm' style='display: inline-block'>
<input type="text"/>
</form>
</div><script>
require(['tquery.minecraft', 'tquery.skymap', 'tquery.grassground', 'tquery.keyboard'
, 'tquery.shadowmap'], function(){
// create world
var world = tQuery.createWorld().boilerplate({
fullscreen : false,
screenshot : false
}).start();
// enable shaddow in the renderer
world.tRenderer().shadowMapEnabled = true;
world.tRenderer().shadowMapSoft = true;
// add a skybox
tQuery.createSkymap('skybox').addTo(world);
// add a ground
var ground = tQuery.createGrassGround({
textureRepeatX : 20,
textureRepeatY : 20,
}).addTo(world)
.scale(80)
.receiveShadow(true)
ground.get(0).material.map.anisotropy = 16;
/////////////////////////////////////////////////////////////////
// lights //
/////////////////////////////////////////////////////////////////
var light = tQuery.createDirectionalLight().addTo(world)
.position(-1, 2, 3)
.color(0xffffff).intensity(4)
.castShadow(true)
.shadowDarkness(0.4)
.shadowMap(512*2,512*2)
.shadowCamera(8, -8, 8, -8, 0.1, 10)
.shadowCameraVisible(true)
// for light to follow character
world.loop().hook(function(){
if( !players[mySourceId] ) return;
var player = players[mySourceId];
var character = player.character;
var model = character.object3D('root');
light.get(0).target.position.copy(model.position());
var delta = tQuery.createVector3(-1,2,3);
var position = model.position().clone().addSelf(delta);
light.position(position)
});
/////////////////////////////////////////////////////////////////
// network //
/////////////////////////////////////////////////////////////////
// init the gameServer
var mySourceId = null;
var players = {};
// initiate connect with server
var serverUrl = 'http://localhost:4000';
var userInfo = {
nickName : 'Player-'+Math.floor(Math.random()*10000).toString(16),
skinBasename : 'char.png',
};
var gameServer = new SimpleMMOServer('public', userInfo, serverUrl);
// handle event
gameServer.addEventListener('connected', function(sourceId, usersInfo){
console.log('connected', arguments)
createMyPlayer(sourceId, gameServer.userInfo())
Object.keys(usersInfo).forEach(function(sourceId){
var userInfo = usersInfo[sourceId];
createPlayer(sourceId, userInfo)
});
});
gameServer.addEventListener('userJoin', function(data){
console.log('userJoin', arguments)
createPlayer(data.sourceId, data.userInfo);
});
gameServer.addEventListener('userLeft', function(data){
console.log('userLeft', data)
destroyPlayer(data.sourceId);
});
gameServer.addEventListener('clientEcho', function(data){
console.assert( players[data.sourceId] );
if( data.message.type === 'positionChange' ){
if( data.sourceId === mySourceId ) return;
var player = players[data.sourceId];
var mesh = player.character.object3D('root')
mesh.positionX( data.message.position.x )
mesh.positionY( data.message.position.y )
mesh.positionZ( data.message.position.z )
mesh.rotationX( data.message.rotation.x )
mesh.rotationY( data.message.rotation.y )
mesh.rotationZ( data.message.rotation.z )
}else if( data.message.type === 'chatText' ){
var player = players[data.sourceId];
var mesh = player.character.object3D('root')
var character = player.character;
// remove previous message if any
tQuery('.chatMessage', mesh).detach()
// add text
var text = tQuery.createSprite().addTo(mesh)
.translateY(1.4).scaleBy(1/200)
.addClass('chatMessage')
.map(buildChatBubble(data.message.text))
// remove the text after a while
setTimeout(function(){ text.detach(); }, 10*1000);
}else{
console.log('unknown echo message.type', message.type)
}
});
gameServer.addEventListener('userInfo', function(sourceId, curUserInfo, oldUserInfo){
//console.log('userInfo', arguments)
updatePlayer(sourceId, curUserInfo, oldUserInfo)
});
/////////////////////////////////////////////////////////////////
// handle player animation //
/////////////////////////////////////////////////////////////////
setInterval(function(){
Object.keys(players).forEach(function(sourceId){
var player = players[sourceId];
var mesh = player.character.object3D('root')
var velocity = mesh.position().clone().subSelf(player.prevPosition);
if( velocity.length() ){
player.bodyAnims.start('run')
}else{
player.bodyAnims.start('stand')
}
// update player.prevPosition/player.prevRotation
player.prevPosition.copy( mesh.position() )
player.prevRotation.copy( mesh.rotation() )
})
}, 1000/5); // Important to be less than framerate - thus you dont misdetect still
/////////////////////////////////////////////////////////////////
// UI //
/////////////////////////////////////////////////////////////////
document.body.setAttribute("tabIndex", "0"); // make body focusable
// handle nicknameForm
var nicknameForm = document.getElementById('nicknameForm')
nicknameForm.addEventListener('keydown', function(event){ event.stopPropagation(); });
// handle nick dom element
nicknameForm[0].value = gameServer.userInfo().nickName;
nicknameForm.addEventListener('submit', function(){
var myUserInfo = gameServer.userInfo();
// update myUserInfo
myUserInfo.nickName = nicknameForm[0].value;
// notify the server
gameServer.userInfo(myUserInfo);
// put back the focus on body
document.body.focus();
});
// handle skin dom element
var skinSelectEl = document.getElementById('skinSelect')
skinSelectEl.addEventListener('change', function(){
var myUserInfo = gameServer.userInfo();
// update myUserInfo
myUserInfo.skinBasename = skinSelectEl.value;
// notify the server
gameServer.userInfo(myUserInfo);
// put back the focus on body
document.body.focus();
});
// handle char input element
var chatInputForm = document.getElementById('chatInputForm')
chatInputForm.addEventListener('keydown', function(event){ event.stopPropagation(); });
chatInputForm.addEventListener('change', function(){
// send chat input to the server
gameServer.clientEcho({
type : 'chatText',
text : chatInputForm[0].value
});
// zero the chat input
chatInputForm[0].value = null;
// put back the focus on body
document.body.focus();
});
////////////////////////////////////////////////////////////////////////////
// //
////////////////////////////////////////////////////////////////////////////
function createMyPlayer(sourceId, userInfo){
console.assert(mySourceId === null)
mySourceId = sourceId;
createPlayer(mySourceId, userInfo)
var character = players[mySourceId].character;
// to enable a tracking camera
var cameraControls = tQuery.createCameraFpsControls({
trackedObject : character.object3D('root').get(0),
tCamera : world.tCamera()
});
world.setCameraControls(cameraControls)
//cameraControls.deltaCamera().position(0, 0.7, -0.07)
//////////////////////////////////////////////////////////////////
// user controls on keyboard //
//////////////////////////////////////////////////////////////////
tQuery.createMinecraftCharKeyboard2({
object3D : character.object3D('root').get(0),
lateralMove : 'rotationY',
//lateralMove : 'strafe'
});
// periodically send the position of the character
// - NOTE: not done on requestAnimationFrame as it has to be done even if page isnt visible
setInterval(function(){
var mesh = character.object3D('root');
var position = mesh.position();
var rotation = mesh.rotation();
gameServer.clientEcho({
type : 'positionChange',
position: { x : position.x, y : position.y, z : position.z },
rotation: { x : rotation.x, y : rotation.y, z : rotation.z },
});
}, 1000/60);
}
function createPlayer(sourceId, userInfo){
console.assert(players[sourceId] === undefined);
players[sourceId] = {}
// create the minecraft character
var skinUrl = '../../minecraft/examples/images/'+userInfo.skinBasename;
var character = tQuery.createMinecraftChar({
skinUrl : skinUrl
}).addTo(world);
tQuery('mesh', character.object3D('root')).castShadow(true)
// store it
players[sourceId].character = character;
// add a nickname cartouche
tQuery.createSprite().addTo(character.object3D('root')) .addClass('cartouche')
.scaleBy(1/200).positionY(1.1)
.map(buildNickCartouche(userInfo.nickName))
// init bodyAnims
var bodyAnims = new tQuery.MinecraftCharAnimations(character);
bodyAnims.start('run');
players[sourceId].bodyAnims = bodyAnims;
// init prevPosition/prevRotation to estimate velocity later
players[sourceId].prevPosition = tQuery.createVector3()
players[sourceId].prevRotation = tQuery.createVector3()
}
function destroyPlayer(sourceId){
var player = players[sourceId];
player.character.removeFrom(world)
player.bodyAnims.stop()
delete players[sourceId]
}
function updatePlayer(sourceId, curUserInfo, oldUserInfo){
console.assert(players[sourceId] !== undefined);
var otherInfo = players[sourceId];
var character = otherInfo.character;
// update the nickname cartouche if needed
if( !oldUserInfo || curUserInfo.nickName !== oldUserInfo.nickName ){
var texture = tQuery('.cartouche', character.object3D('root')).get(0).map;
texture.image = buildNickCartouche(curUserInfo.nickName)
texture.needsUpdate = true;
}
// update the skin if needed
if( !oldUserInfo || curUserInfo.skinBasename !== oldUserInfo.skinBasename ){
var skinUrl = '../../minecraft/examples/images/'+curUserInfo.skinBasename;
character.loadSkin(skinUrl)
}
}
function buildNickCartouche(text){
// create the canvas
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = 256;
canvas.height = 128;
// center the origin
context.translate( canvas.width/2, canvas.height/2 );
// measure text
var fontSize = 36;
context.font = "bolder "+fontSize+"px Verdana";
var fontH = fontSize;
var fontW = context.measureText(text).width;
// build the background
context.fillStyle = "rgba(0,0,255,0.3)";
var scale = 1.2;
context.fillRect(-fontW*scale/2,-fontH*scale/1.3,fontW*scale,fontH*scale)
// display the text
context.fillStyle = "rgba(0,0,0,0.7)";
context.fillText(text, -fontW/2, 0);
// return the canvas element
return canvas;
}
function buildChatBubble(text){
// create the canvas
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = 1024;
canvas.height = 512;
// center the origin
context.translate( canvas.width/2, canvas.height/2 );
// measure text
var fontSize = 24;
context.font = "bolder "+fontSize+"px Verdana";
var fontH = fontSize;
var fontW = context.measureText(text).width;
// build the background
context.fillStyle = "rgba(255,255,255,0.3)";
var scale = 1.2;
context.fillRect(-fontW*scale/2,-fontH*scale/1.3,fontW*scale,fontH*scale)
// display the text
context.fillStyle = "rgba(0,0,0,0.7)";
context.fillText(text, -fontW/2, 0);
// return the canvas element
return canvas;
}
})
</script></body>
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.