@user2753585660653 UE5 ソウルライクゲーム
♬ オリジナル楽曲 – 長留裕平 – 長留裕平
15パズル javascript
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>15Puzzle</title>
<style>
canvas {
background: pink;
display: block;
margin: 0 auto;
cursor: pointer;
}
</style>
</head>
<body>
<canvas width="280" height="280">
Canvas not supported.
</canvas>
<script src="js/main.js"></script>
</body>
</html>
main.js
'use strict';
(() => {
class PuzzleRenderer {
constructor(puzzle, canvas) {
this.puzzle = puzzle;
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d');
this.TILE_SIZE = 70;
this.img = document.createElement('img');
this.img.src = 'img/animal1.png';
this.img.addEventListener('load', () => {
this.render();
});
this.canvas.addEventListener('click', e => {
if (this.puzzle.getCompletedStatus()) {
return;
}
const rect = this.canvas.getBoundingClientRect();
const col = Math.floor((e.clientX - rect.left) / this.TILE_SIZE);
const row = Math.floor((e.clientY - rect.top) / this.TILE_SIZE);
this.puzzle.swapTiles(col, row);
this.render();
if (this.puzzle.isComplete()) {
this.puzzle.setCompletedStatus(true);
this.renderGameClear();
}
});
}
renderGameClear() {
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.font = '28px Arial';
this.ctx.fillStyle = '#fff';
this.ctx.fillText('GAME CLEAR!!', 40, 150);
}
render() {
for (let row = 0; row < this.puzzle.getBoardSize(); row++) {
for (let col = 0; col < this.puzzle.getBoardSize(); col++) {
this.renderTile(this.puzzle.getTile(row, col), col, row);
}
}
}
renderTile(n, col, row) {
if (n === this.puzzle.getBlankIndex()) {
this.ctx.fillStyle = '#eeeeee';
this.ctx.fillRect(
col * this.TILE_SIZE,
row * this.TILE_SIZE,
this.TILE_SIZE,
this.TILE_SIZE
);
} else {
this.ctx.drawImage(
this.img,
(n % this.puzzle.getBoardSize()) * this.TILE_SIZE,
Math.floor(n / this.puzzle.getBoardSize()) * this.TILE_SIZE,
this.TILE_SIZE,
this.TILE_SIZE,
col * this.TILE_SIZE,
row * this.TILE_SIZE,
this.TILE_SIZE,
this.TILE_SIZE
);
}
}
}
class Puzzle {
constructor(level) {
this.level = level;
this.tiles = [
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15],
];
this.UDLR = [
[0, -1], // up
[0, 1], // down
[-1, 0], // left
[1, 0], // right
];
this.isCompleted = false;
this.BOARD_SIZE = this.tiles.length;
this.BLANK_INDEX = this.BOARD_SIZE ** 2 - 1;
do {
this.shuffle(this.level);
} while (this.isComplete());
}
getBoardSize() {
return this.BOARD_SIZE;
}
getBlankIndex() {
return this.BLANK_INDEX;
}
getCompletedStatus() {
return this.isCompleted;
}
setCompletedStatus(value) {
this.isCompleted = value;
}
getTile(row, col) {
return this.tiles[row][col];
}
shuffle(n) {
let blankCol = this.BOARD_SIZE - 1;
let blankRow = this.BOARD_SIZE - 1;
for (let i = 0; i < n; i++) {
let destCol;
let destRow;
do {
const dir = Math.floor(Math.random() * this.UDLR.length);
destCol = blankCol + this.UDLR[dir][0];
destRow = blankRow + this.UDLR[dir][1];
} while (this.isOutside(destCol, destRow));
[
this.tiles[blankRow][blankCol],
this.tiles[destRow][destCol],
] = [
this.tiles[destRow][destCol],
this.tiles[blankRow][blankCol],
];
[blankCol, blankRow] = [destCol, destRow];
}
}
swapTiles(col, row) {
if (this.tiles[row][col] === this.BLANK_INDEX) {
return;
}
for (let i = 0; i < this.UDLR.length; i++) {
const destCol = col + this.UDLR[i][0];
const destRow = row + this.UDLR[i][1];
if (this.isOutside(destCol, destRow)) {
continue;
}
if (this.tiles[destRow][destCol] === this.BLANK_INDEX) {
[
this.tiles[row][col],
this.tiles[destRow][destCol],
] = [
this.tiles[destRow][destCol],
this.tiles[row][col],
];
break;
}
}
}
isOutside(destCol, destRow) {
return (
destCol < 0 || destCol > this.BOARD_SIZE - 1 ||
destRow < 0 || destRow > this.BOARD_SIZE - 1
);
}
isComplete() {
let i = 0;
for (let row = 0; row < this.BOARD_SIZE; row++) {
for (let col = 0; col < this.BOARD_SIZE; col++) {
if (this.tiles[row][col] !== i++) {
return false;
}
}
}
return true;
}
}
const canvas = document.querySelector('canvas');
if (typeof canvas.getContext === 'undefined') {
return;
}
new PuzzleRenderer(new Puzzle(2), canvas);
})();
.hack//VR 企画書
.hack//VR 企画書
【タイトル】
.hack//VR(ドットハック ヴィーアール)
【ジャンル】
フルダイブ型MMORPG(仮想現実大規模多人数同時接続型RPG)
【対応プラットフォーム】
- Meta Quest 3 / Quest Pro
- PlayStation VR2
- PC VR(SteamVR対応)
- フルダイブ対応ヘッドセット(将来的展望)
【コンセプト】
「仮想世界は、現実を超える」
架空のネットワークRPG《The World》を舞台にした、現実とリンクするフルダイブ型VR体験。ユーザーは「プレイヤー」としてゲームに参加しながらも、実はゲームの裏で進行する謎と陰謀に巻き込まれていく。
【開発背景】
- 2002年の「.hack//Infection」シリーズから続く《The World》の世界観を最新技術で再構築。
- フルダイブ技術の発展を見据えたVR MMORPGの実験的開発。
- ユーザー間の社会的インタラクションとAI搭載NPCの自然会話を融合。
【ゲームの特徴】
1. フルダイブ没入体験
- プレイヤーはアバターとして《The World》に降り立ち、五感(視覚、聴覚、触覚)を通じて世界を体験。
- 拡張神経接続インターフェース「NeuroLink(仮)」対応。
2. 2層構造の物語
- 仮想世界《The World》内のRPGストーリー。
- 現実世界のプレイヤー間の謎、事件、そして「AI」の反乱。
- 2つの時間軸が交錯し、プレイヤー自身が物語の一部になる。
3. AI NPCとのリアルな交流
- GPT-4ベースの高度会話AI搭載NPC。
- 各キャラクターが記憶・感情を持ち、ユーザーの選択により関係性が変化。
4. VR戦闘システム
- ソードスキル、魔法、連携技をモーション操作でリアルタイム発動。
- 攻撃タイミングと身体操作が勝敗を左右する、直感型バトル。
5. プレイヤーギルドとハッキング要素
- ギルド運営、拠点の建築、PvPなどの要素に加え、
- 特定プレイヤーが「ハッカー」となり、ゲームの奥深くに潜む謎の領域へアクセス可能。
【ゲーム内用語】
| 用語 | 解説 |
|---|---|
| The World | ゲームの舞台となる仮想空間 |
| ネットスレイヤー | データを破壊する謎の存在 |
| AIコア | 人格を持ったAI、暴走の兆しあり |
| データドレイン | プレイヤーの記憶や感情を吸収する技術(禁忌) |
【ターゲット層】
- VRゲーマー(16〜35歳)
- SF・サイバーパンク・.hackシリーズファン
- フルダイブ技術に興味を持つ先端層
【マネタイズ】
- 基本無料+アバター装備・外見課金(スキン制)
- ストーリーDLC、特定イベント有料開放
- プレミアムアカウント(月額制)
【将来的展望】
- プレイヤー間の記憶共有機能(記憶ログシェア)
- 現実の感覚を記録・再体験する「リプレイVR」
- 現実の時間と連動する仮想祭イベント
【イメージビジュアル】(必要なら生成可能)
【補足】
.hack//VRは「ゲーム」であると同時に、フルダイブVRの社会実験でもあります。AIと人間がどこまで共存できるか、仮想空間における「自我」とは何かを問いかける、哲学的な要素を含む作品です。
VRSNS
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VR‑SNS Prototype v8 (Spatial Audio + PTT + エモート)</title>
<!-- Libraries -->
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-extras@6.1.1/dist/aframe-extras.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gun/gun.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/simple-peer@9.11.1/simplepeer.min.js"></script>
<style>
html,body{margin:0;padding:0;overflow:hidden;font-family:"Helvetica Neue",Arial,sans-serif;background:#111;color:#eee}
#ui{position:fixed;top:0;left:0;width:420px;height:100vh;overflow-y:auto;background:rgba(0,0,0,.9);backdrop-filter:blur(6px);padding:1rem;box-sizing:border-box;z-index:10;transition:transform .3s ease}
#ui.hidden{transform:translateX(-105%)}
#ui h1{font-size:1.5rem;margin:0 0 .8rem;text-align:center}
#ui label{display:block;font-size:.75rem;margin:.6rem 0 .15rem;color:#8fc}
#ui input,#ui select,#ui textarea{width:100%;box-sizing:border-box;margin-bottom:.7rem;padding:.56rem;border:none;border-radius:6px;font-size:.9rem;background:#222;color:#ddd}
#ui textarea{resize:vertical}
#ui button{width:100%;padding:.72rem;border:none;border-radius:6px;font-weight:bold;cursor:pointer;background:#06a;color:#fff;transition:background .2s}
#ui button:hover{background:#089}
#timeline{margin-top:1rem}
.post{background:#1a1a1a;border-radius:6px;padding:.75rem;margin-bottom:.75rem;word-break:break-word;font-size:.8rem;position:relative}
.post small{color:#999;font-size:.7rem}
.post img{max-width:100%;border-radius:4px;margin-top:.4rem}
.reactBar{display:flex;gap:5px;margin-top:.45rem}
.reactBtn{flex:1;background:#333;border:none;border-radius:4px;padding:3px 0;font-size:.75rem;cursor:pointer;color:#f88;display:flex;justify-content:center;align-items:center}
.reactBtn span{margin-left:4px;font-weight:bold;color:#ccc}
.hashTag{color:#6af;cursor:pointer}
#handle{position:absolute;top:50%;right:-18px;width:18px;height:80px;border-radius:0 6px 6px 0;background:#06a;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.7rem;transform:translateY(-50%)}
#tagFilter{margin-bottom:.8rem;width:100%;padding:.56rem;border:none;border-radius:6px;background:#222;color:#ddd}
#onlineCount{font-size:.75rem;margin-bottom:.8rem;color:#9f9}
#roomInput{width:70%;display:inline-block}
#roomBtn{width:28%;display:inline-block;margin-left:2%}
#ttsToggleLbl,#muteToggleLbl,#pttInfo{display:flex;align-items:center;font-size:.75rem;margin-bottom:.7rem;gap:.5rem}
.sysMsg{color:#8ff;font-style:italic}
.speaking{animation:pulse 1s infinite}
@keyframes pulse{0%{opacity:1}50%{opacity:.4}100%{opacity:1}}
.emote{position:absolute;left:50%;transform:translateX(-50%);top:-0.4m;font-size:0.4m}
</style>
</head>
<body>
<div id="ui">
<div id="handle">⮜</div>
<h1>VR‑SNS</h1>
<label>ルーム ID</label>
<input id="roomInput" value="lobby"/><button id="roomBtn">入室</button>
<div id="onlineCount">オンライン: 0</div>
<label>ユーザー名</label>
<input id="usernameInput" placeholder="匿名" />
<label>アバター色</label>
<input id="avatarColor" type="color" value="#ff8800" />
<label>環境テーマ</label>
<select id="themeSelect"><option value="dusk">夕暮れ空</option><option value="midnight">真夜中</option><option value="day">晴れ昼</option></select>
<label>ハッシュタグ絞り込み</label>
<select id="tagFilter"><option value="all">すべて</option></select>
<label id="ttsToggleLbl"><input type="checkbox" id="ttsToggle"/> 投稿読み上げ</label>
<label id="muteToggleLbl"><input type="checkbox" id="muteToggle"/> マイク常時OFF</label>
<div id="pttInfo">Push‑To‑Talk: <strong>Vキー</strong> 押下中のみ送信</div>
<label>投稿</label>
<textarea id="postText" rows="3"></textarea>
<button id="postButton">ポスト</button>
<button id="clearButton" style="background:#a00;margin-top:.3rem">ローカル履歴削除</button>
<div id="timeline"></div>
</div>
<a-scene renderer="antialias:true" xr-mode-ui="true" background="color:#112" cursor="rayOrigin:mouse">
<a-entity environment="preset: forest; ground:y"></a-entity>
<a-entity id="ground" geometry="primitive:plane; width:150; height:150" rotation="-90 0 0" material="visible:false" static-body></a-entity>
<a-entity id="rig" position="0 1.6 4" movement-controls="fly:false; speed:0.22">
<a-entity id="head" camera look-controls></a-entity>
<a-entity id="leftHand" laser-controls="hand:left" raycaster="objects:.interactive" teleport-controls="button:trigger; collisionEntities:#ground; cameraRig:#rig"></a-entity>
<a-entity id="rightHand" laser-controls="hand:right" raycaster="objects:.interactive"></a-entity>
</a-entity>
</a-scene>
<script>
(()=>{
const gun=Gun({peers:['https://gun-manhattan.herokuapp.com/gun']});
const myId=Gun.text.random();
let room='lobby',roomRef;
const $=id=>document.getElementById(id);
const ui=$('ui'),handle=$('handle');
const roomInput=$('roomInput'),roomBtn=$('roomBtn'),onlineCount=$('onlineCount');
const usernameInput=$('usernameInput'),avatarColorIn=$('avatarColor');
const themeSelect=$('themeSelect'),tagFilter=$('tagFilter');
const ttsToggle=$('ttsToggle'),muteToggle=$('muteToggle');
const postText=$('postText'),postBtn=$('postButton'),clearBtn=$('clearButton');
const timelineEl=$('timeline');
const scene=document.querySelector('a-scene');
const head=$('head'),leftHand=$('leftHand'),rightHand=$('rightHand');
const store={get:(k,d)=>JSON.parse(localStorage.getItem('vrsns_'+k)||JSON.stringify(d)),set:(k,v)=>localStorage.setItem('vrsns_'+k,JSON.stringify(v))};
usernameInput.value=store.get('user','');avatarColorIn.value=store.get('color','#ff8800');themeSelect.value=store.get('theme','dusk');ttsToggle.checked=store.get('tts',false);
const postsMap=new Map();const tags=new Set();const remoteAvatars=new Map();const remotePeers=new Map();let micStream=null,aCtx=null;let pushTalking=false;
let presenceRef,posRef,presenceInterval,posInterval;
applyTheme(themeSelect.value);createOrUpdateAvatar();setupMic();enterRoom(room);
handle.onclick=()=>ui.classList.toggle('hidden');scene.addEventListener('enter-vr',()=>ui.classList.add('hidden'));scene.addEventListener('exit-vr',()=>ui.classList.remove('hidden'));
roomBtn.onclick=()=>{const r=roomInput.value.trim();if(r){leaveRoom();enterRoom(r)}};
themeSelect.onchange=e=>{const v=e.target.value;store.set('theme',v);applyTheme(v)};avatarColorIn.oninput=createOrUpdateAvatar;
ttsToggle.onchange=e=>store.set('tts',e.target.checked);
muteToggle.onchange=e=>updateMicState();
postBtn.onclick=submitPost;clearBtn.onclick=()=>{if(confirm('ローカル履歴を削除しますか?')){localStorage.removeItem('vrsns_posts_'+room);timelineEl.innerHTML='';}};
tagFilter.onchange=filterTimeline;
window.addEventListener('keydown',e=>{if(e.key==='v'&&!pushTalking){pushTalking=true;updateMicState();}});
window.addEventListener('keyup',e=>{if(e.key==='v'){pushTalking=false;updateMicState();}});
async function setupMic(){try{aCtx=new (window.AudioContext||window.webkitAudioContext)();micStream=await navigator.mediaDevices.getUserMedia({audio:true});updateMicState();}catch(err){alert('マイク利用不可');}}
function updateMicState(){if(!micStream)return;const enabled=!muteToggle.checked&&(pushTalking||muteToggle.checked===false&&ttsToggle);micStream.getAudioTracks()[0].enabled=enabled;}
function enterRoom(r){room=r;roomRef=gun.get('vrsns').get(room);roomInput.value=room;timelineEl.innerHTML='';postsMap.clear();tags.clear();rebuildTagFilter();onlineCount.textContent='オンライン: 0';
roomRef.get('posts').map().on(onPost);roomRef.get('posts').map().get('reactions').map().on(onReaction);
presenceRef=roomRef.get('presence').get(myId);presenceRef.put({ts:Gun.state(),color:avatarColorIn.value});presenceInterval=setInterval(()=>presenceRef.put({ts:Gun.state(),color:avatarColorIn.value}),10000);roomRef.get('presence').map().on(updateOnline);
posRef=roomRef.get('pos').get(myId);sendPos();posInterval=setInterval(sendPos,100);
roomRef.get('pos').map().on(onRemotePos);roomRef.get('signal').map().on(onSignal);sysMsg(`[${room}] 入室しました`);}
function leaveRoom(){clearInterval(presenceInterval);clearInterval(posInterval);presenceRef&&presenceRef.put(null);posRef&&posRef.put(null);roomRef.off();roomRef.get('signal').off();remoteAvatars.forEach(av=>av.remove());remoteAvatars.clear();remotePeers.forEach(p=>p.destroy());remotePeers.clear();}
function submitPost(){const user=usernameInput.value.trim()||'匿名',text=postText.value.trim();if(!text)return;store.set('user',user);store.set('color',avatarColorIn.value);const id=Gun.text.random();const post={id,user,text,time:Date.now(),color:avatarColorIn.value,reactions:{'❤':0,'😂':0,'😮':0,'😢':0},tags:extractTags(text)};roomRef.get('posts').get(id).put(post);postText.value='';if(ttsToggle.checked)speak(`${user} さん: ${text.replace(/#/g,'')}`);}
function onPost(post){if(!post||postsMap.has(post.id))return;postsMap.set(post.id,post);collectTags(post);rebuildTagFilter();renderPost2D(post);renderPost3D(post);filterTimeline();}
function onReaction(v,field,key){const pid=field,emoji=key;const p=postsMap.get(pid);if(!p)return;p.reactions[emoji]=v;updatePostCard(pid,p);updatePost3D(pid,p);}
function react(id,emo){roomRef.get('posts').get(id).get('reactions').get(emo).once(v=>roomRef.get('posts').get(id).get('reactions').get(emo).put((v||0)+1));}
function updateOnline(){let c=0;roomRef.get('presence').map().once(()=>c++);setTimeout(()=>onlineCount.textContent=`オンライン: ${c}`,200);}
function sendPos(){const p=head.object3D.position,l=leftHand.object3D.position,r=rightHand.object3D.position;posRef.put({x:p.x,y:p.y,z:p.z,l:{x:l.x,y:l.y,z:l.z},r:{x:r.x,y:r.y,z:r.z},color:avatarColorIn.value,ts:Gun.state()});}
function onRemotePos(data,id){if(id===myId||!data)return;let av=remoteAvatars.get(id);if(!av){av=createAvatar(id,data.color);remoteAvatars.set(id,av);connectVoice(id);}av.setAttribute('position',`${data.x} 0 ${data.z}`);av.querySelectorAll('[geometry]').forEach(g=>g.setAttribute('color',data.color||'#fff'));['l','r'].forEach(h=>{let el=av.querySelector('.'+h);if(!el){el=document.createElement('a-sphere');el.classList.add(h);el.setAttribute('radius',0.05);av.appendChild(el);}const hp=data[h];if(hp)el.setAttribute('position',`${hp.x} ${hp.y} ${hp.z}`);});}
function createAvatar(id,color){const root=document.createElement('a-entity');root.id='av_'+id;root.innerHTML=`<a-sphere radius='0.25' color='${color||'#fff'}' position='0 1.6 0'></a-sphere><a-cylinder height='0.8' radius='0.2' color='${color||'#fff'}' position='0 1 0'></a-cylinder>`;scene.appendChild(root);return root;}
function connectVoice(rid){if(remotePeers.has(rid)||!micStream)return;const init=myId>rid;const peer=new SimplePeer({initiator:init,trickle:true,stream:micStream});remotePeers.set(rid,peer);peer.on('signal',d=>roomRef.get('signal').get(myId).get(rid).put(JSON.stringify(d)));roomRef.get('signal').get(rid).get(myId).on(sig=>sig&&peer.signal(JSON.parse(sig)));peer.on('stream',s=>{const av=remoteAvatars.get(rid);if(!av)return;const a=new Audio();a.srcObject=s;const src=aCtx.createMediaStreamSource(s);const panner=aCtx.createPanner();panner.panningModel='HRTF';panner.distanceModel='inverse';panner.maxDistance=20;src.connect(panner).connect(aCtx.destination);function upd(){if(!av.parentNode)return;const pos=av.object3D.position;panner.setPosition(pos.x,pos.y,pos.z);requestAnimationFrame(upd);}upd();a.play();});peer.on('close',()=>remotePeers.delete(rid));peer.on('error',()=>remotePeers.delete(rid));}
function onSignal(){}
function renderPost2D(p){const isImg=/\.(gif|jpe?g|png|webp)$/i.test(p.text.trim());const card=document.createElement('div');card.className='post';card.dataset.id=p.id;card.dataset.tags=p.tags.join(',');card.innerHTML=`<strong style='color:${p.color}'>${escapeHTML(p.user)}</strong>: ${isImg?`<img src='${escapeHTML(p.text)}'/>`:linkify(escapeHTML(p.text))}${p.tags.map(t=>` <span class='hashTag'>#${t}</span>`).join('')}<br><small>${new Date(p.time).toLocaleString()}</small>`;const bar=document.createElement('div');bar.className='reactBar';['❤','😂','😮','😢'].forEach(e=>{const b=document.createElement('button');b.className='reactBtn';b.dataset.e=e;b.innerHTML=`${e} <span>${p.reactions[e]||0}</span>`;b.onclick=()=>react(p.id,e);bar.appendChild(b);});card.appendChild(bar);card.querySelectorAll('.hashTag').forEach(el=>el.onclick=()=>{tagFilter.value=el.textContent.slice(1).toLowerCase();filterTimeline();});timelineEl.insertBefore(card,timelineEl.firstChild);}
function updatePostCard(id,p){const card=document.querySelector(`.post[data-id='${id}']`);if(card)card.querySelectorAll('.reactBtn').forEach(btn=>btn.querySelector('span').textContent=p.reactions[btn.dataset.e]||0);}
function format3D(p){const top=Object.entries(p.reactions||{}).sort((a,b)=>b[1]-a[1])[0];const r=top&&top[1]>0?` ${top[0]}×${top[1]}`:'';return `${p.user}${r}\n${p.text}`;}
function renderPost3D(p){const ent=document.createElement('a-entity');ent.classList.add('interactive');ent.id='p3d_'+p.id;ent.setAttribute('text',{value:format3D(p),align:'center',width:4,color:p.color});ent.onclick=()=>react(p.id,'❤');positionEntity(ent);scene.appendChild(ent);setTimeout(()=>ent.remove(),300000);if(/\.(gif|jpe?g|png|webp)$/i.test(p.text.trim())){const plane=document.createElement('a-plane');plane.setAttribute('src',p.text);plane.setAttribute('width',1.8);plane.setAttribute('height',1.2);plane.classList.add('interactive');positionEntity(plane,0.6);scene.appendChild(plane);setTimeout(()=>plane.remove(),300000);}}
function updatePost3D(id,p){const e=$('p3d_'+id);if(e)e.setAttribute('text','value',format3D(p));}
function positionEntity(e,y=0){const ang=Math.random()*Math.PI,rad=5+Math.random()*5;e.setAttribute('position',`${Math.cos(ang)*rad} ${2+y} ${-Math.sin(ang)*rad}`);}
function collectTags(p){p.tags.forEach(t=>tags.add(t.toLowerCase()));}
function rebuildTagFilter(){const cur=tagFilter.value;tagFilter.innerHTML='<option value="all">すべて</option>'+[...tags].sort().map(t=>`<option value='${t}'>#${t}</option>`).join('');tagFilter.value=cur||'all';}
function filterTimeline(){const f=tagFilter.value;document.querySelectorAll('.post').forEach(el=>el.style.display=(f==='all'||el.dataset.tags.includes(f))?'':'none');}
function escapeHTML(s){return s.replace(/[&<>"']/g,ch=>({'&':'&','<':'<',">":">","\"":""","'":"'"}[ch]));}
function extractTags(t){return(t.match(/#(\w+)/g)||[]).map(x=>x.slice(1).toLowerCase());}
function linkify(t){return t.replace(/https?:\/\/\S+/g,u=>`<a href='${u}' target='_blank'>${u}</a>`);}
function applyTheme(t){let sky=scene.querySelector('a-sky');if(!sky){sky=document.createElement('a-sky');scene.appendChild(sky);}sky.setAttribute('color',t==='midnight'?'#000022':t==='day'?'#86cefa':'#112');}
function createOrUpdateAvatar(){const c=avatarColorIn.value;store.set('color',c);let av=$('myAvatar');const body=`<a-sphere radius='0.25' color='${c}' position='0 1.6 0'></a-sphere><a-cylinder height='0.8' radius='0.2' color='${c}' position='0 1 0'></a-cylinder>`;if(!av){av=document.createElement('a-entity');av.id='myAvatar';av.innerHTML=body;scene.appendChild(av);}else av.innerHTML=body;}
function speak(msg){speechSynthesis.speak(new SpeechSynthesisUtterance(msg));}
function sysMsg(t){const el=document.createElement('div');el.className='post sysMsg';el.textContent=t;timelineEl.insertBefore(el,timelineEl.firstChild);}
})();
</script>
</body>
</html>
UE5 SoulsLike Game Combat framework
小説投稿サイト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>小説投稿サイト</title>
<style>
body {
font-family: sans-serif;
padding: 20px;
max-width: 800px;
margin: auto;
background: #f2f2f2;
}
h1 {
text-align: center;
}
form {
background: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
input, textarea {
width: 100%;
margin-bottom: 10px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.post {
background: white;
padding: 15px;
border-left: 5px solid #007bff;
margin-bottom: 20px;
border-radius: 5px;
}
.post h2 {
margin: 0 0 10px;
}
.meta {
color: gray;
font-size: 0.9em;
margin-bottom: 10px;
}
.delete-btn {
background-color: #dc3545;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
float: right;
cursor: pointer;
}
</style>
</head>
<body>
<h1>小説投稿サイト</h1>
<form id="novelForm">
<input type="text" id="author" placeholder="著者名" required>
<input type="text" id="title" placeholder="タイトル" required>
<textarea id="content" rows="8" placeholder="本文" required></textarea>
<button type="submit">投稿する</button>
</form>
<div id="postList"></div>
<script>
const form = document.getElementById('novelForm');
const postList = document.getElementById('postList');
let posts = JSON.parse(localStorage.getItem('novels')) || [];
function saveAndRender() {
localStorage.setItem('novels', JSON.stringify(posts));
renderPosts();
}
function renderPosts() {
postList.innerHTML = '';
[...posts].reverse().forEach((post, index) => {
const div = document.createElement('div');
div.className = 'post';
div.innerHTML = `
<button class="delete-btn" onclick="deletePost(${index})">削除</button>
<h2>${post.title}</h2>
<div class="meta">著者: ${post.author} | 投稿日: ${post.date}</div>
<p>${post.content.replace(/\n/g, '<br>')}</p>
`;
postList.appendChild(div);
});
}
form.addEventListener('submit', e => {
e.preventDefault();
const title = document.getElementById('title').value;
const content = document.getElementById('content').value;
const author = document.getElementById('author').value;
const date = new Date().toLocaleString();
posts.push({ title, content, author, date });
form.reset();
saveAndRender();
});
window.deletePost = function(index) {
posts.splice(posts.length - 1 - index, 1); // reverseしてるため
saveAndRender();
}
renderPosts();
</script>
</body>
</html>
フルダイブVR企画書
フルダイブVR企画書
企画名
フルダイブVRプロジェクト『NeoReal Dive(仮)』
企画概要
本プロジェクトは、脳と直接接続することで完全没入型の仮想現実体験(フルダイブVR)を実現することを目的とした、次世代VRプラットフォームの研究・開発・商用展開である。現行のHMD型VRを超越し、「五感の再現」「意識同期」「自由行動」の3要素を備えた、完全な仮想体験を提供する。
目的・背景
- 現在のVRは視覚・聴覚中心で、身体感覚・触覚・嗅覚などの再現が困難。
- 未来型のエンタメ・教育・医療・ビジネスにおいて、より高精度な仮想体験のニーズが高まっている。
- フルダイブVRは、脳波・神経インターフェース技術を応用することで「仮想世界での実体験」を可能にする。
目標
- 脳波インターフェースによる身体操作・五感再現システムの実装
- 仮想世界での自由移動・対話・感情表現が可能なAI/物理エンジンの開発
- フルダイブVR体験デモ版(プロトタイプ)を2年以内に完成
- エンタメ分野に限らず、医療・教育・研究機関への応用を展開
コンセプトアート/ビジュアル
※必要に応じて追加可能(仮想世界のイメージ、ユーザーの視点、デバイス外観など)
想定利用シーン
- フルダイブVR MMORPGゲーム
- リモート教育:歴史・宇宙体験・医療トレーニング
- 治療支援:リハビリ、精神ケア、PTSD治療など
- 働き方改革:完全仮想空間でのオフィス、コラボレーション
想定ターゲット
- ゲーム・VR愛好者(16〜40代)
- 研究機関、医療機関、教育機関
- メタバースビジネス参入企業
技術構成
- 脳波・神経インターフェース:BCI(Brain Computer Interface)を利用
- 五感再現:視覚・聴覚はHMD、触覚はハプティクス、嗅覚/味覚は化学刺激/脳信号への変換を検討
- 仮想世界の構築:Unreal Engine 5 / Unity + カスタムAIエンジン
- AI・NPCとの対話:GPT系AI + 音声合成エンジン
- データセキュリティ:ブロックチェーン技術または生体認証を採用
スケジュール(例)
| フェーズ | 期間 | 内容 |
|---|---|---|
| 企画・調査 | 0〜3ヶ月 | 技術調査・資金調達・提携交渉 |
| 開発準備 | 3〜6ヶ月 | プロトタイプ設計、チーム編成 |
| 開発第1段階 | 6〜12ヶ月 | 脳波制御・仮想環境ベース構築 |
| 開発第2段階 | 12〜18ヶ月 | 五感拡張、AI対話・感情再現 |
| 検証・試験 | 18〜24ヶ月 | ユーザーテスト・安全性評価 |
| 商用化準備 | 24ヶ月〜 | 市場投入計画、各分野への展開 |
予算案(概算)
- 研究開発費:¥500,000,000
- デバイス設計・試作:¥200,000,000
- プロモーション・展示会等:¥50,000,000
- 合計:¥750,000,000(初期段階)
パートナー候補
- 大学(脳科学・電気通信系)
- 医療機関・リハビリセンター
- 大手ゲーム会社
- ハードウェア開発企業(神経インターフェース)
将来的展望
- フルダイブによる「デジタルライフ」体験の一般化
- 仮想世界での経済活動(メタバース経済の拡大)
- 高齢者・障害者の社会参加支援
- 意識のデジタル保存や、死後世界への応用研究も視野に
その他
- 倫理的配慮:過剰依存・仮想と現実の区別問題への対策が必要
- 法整備:脳直結型デバイスに関する法規制との整合性
- 安全設計:睡眠中・長時間使用への対応、強制ログアウト機能など
必要であれば、プレゼン資料用スライド形式やPDF化もできます。また、内容を「ゲーム中心」「教育・医療応用中心」「メタバースビジネス中心」などに寄せたバージョンも可能です。どの方向性にしたいか教えてもらえますか?
4o
Javascript 迷路
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>My Maze</title>
</head>
<body>
<canvas>
Canvas not supported ...
</canvas>
<script src="js/main.js"></script>
</body>
</html>
main.js
'use strict';
(() => {
class MazeRenderer {
constructor(canvas) {
this.ctx = canvas.getContext('2d');
this.WALL_SIZE = 10;
}
render(data) {
canvas.height = data.length * this.WALL_SIZE;
canvas.width = data[0].length * this.WALL_SIZE;
for (let row = 0; row < data.length; row++) {
for (let col = 0; col < data[0].length; col++) {
if (data[row][col] === 1) {
this.ctx.fillRect(
col * this.WALL_SIZE,
row * this.WALL_SIZE,
this.WALL_SIZE,
this.WALL_SIZE
);
}
}
}
}
}
class Maze {
constructor(row, col, renderer) {
if (row < 5 || col < 5 || row % 2 === 0 || col % 2 === 0) {
alert('Size not valid!');
return;
}
this.renderer = renderer;
this.row = row;
this.col = col;
this.data = this.getData();
}
getData() {
const data = [];
for (let row = 0; row < this.row; row++) {
data[row] = [];
for (let col = 0; col < this.col; col++) {
data[row][col] = 1;
}
}
for (let row = 1; row < this.row - 1; row++) {
for (let col = 1; col < this.col - 1; col++) {
data[row][col] = 0;
}
}
for (let row = 2; row < this.row - 2; row += 2) {
for (let col = 2; col < this.col - 2; col += 2) {
data[row][col] = 1;
}
}
for (let row = 2; row < this.row - 2; row += 2) {
for (let col = 2; col < this.col - 2; col += 2) {
let destRow;
let destCol;
do {
const dir = row === 2 ?
Math.floor(Math.random() * 4) :
Math.floor(Math.random() * 3) + 1;
switch (dir) {
case 0: // up
destRow = row - 1;
destCol = col;
break;
case 1: // down
destRow = row + 1;
destCol = col;
break;
case 2: // left
destRow = row;
destCol = col - 1;
break;
case 3: // right
destRow = row;
destCol = col + 1;
break;
}
} while (data[destRow][destCol] === 1);
data[destRow][destCol] = 1;
}
}
return data;
}
render() {
this.renderer.render(this.data);
}
}
const canvas = document.querySelector('canvas');
if (typeof canvas.getContext === 'undefined') {
return;
}
const maze = new Maze(21, 15, new MazeRenderer(canvas));
maze.render();
})();
🎮 クロノクロス リメイク企画書(提案書)
🎮 クロノクロス リメイク企画書(提案書)
■ タイトル(仮)
CHRONO CROSS Re:Dreamers(クロノクロス リ・ドリーマーズ)
■ 開発目的
- 名作『クロノクロス』(1999年/PS)の世界観・物語・音楽を継承しつつ、現代の技術と表現力でフルリメイク。
- クロノシリーズの価値とブランドを再定義し、次世代のファンを獲得する。
- クロノ・トリガーから続く「時」と「次元」をテーマにした壮大な物語を、新たな感動体験として再構築。
■ ターゲット層
- 30〜40代:オリジナルファン(ノスタルジー層)
- 10〜20代:JRPG・アニメ調ゲームに興味がある若年層
- 世界市場向け:海外人気も高いため、グローバル対応必須(字幕・音声)
■ 主な特徴
| 項目 | 内容 |
|---|---|
| グラフィック | Unreal Engine 5を使用したセルルック風3D |
| サウンド | 全曲アレンジ+原曲切替可能/フルオーケストラ対応 |
| ボイス | 主要キャラクターにフルボイス対応(ON/OFF可) |
| UI | モダン+クラシック切替可能なデザイン |
| バトル | ターン制+リアルタイム演出のハイブリッドバトル |
| クロス要素 | 40人以上の仲間、選択によるマルチストーリー |
| 新要素 | 新規シナリオ分岐、外伝ストーリー、キャラエピソード |
■ ストーリー概要(簡易)
夢を旅する少年セルジュが、もう一つの世界で自らの存在が「死んでいたこと」を知る。
交錯する次元、因果のねじれ、「時を喰らうもの」によって歪められた歴史を、仲間たちとともに解き明かす物語。
『クロノ・トリガー』との繋がりも明確に描かれ、真実のエンディングへ導かれる。
■ プラットフォーム案
- PS5 / Xbox Series X|S / PC(Steam / Epic) / Nintendo Switch 2(次世代機を想定)
- クラウド対応 / Steam Deck対応予定
■ 追加要素・リメイク特有要素(例)
| 種別 | 内容 |
|---|---|
| DLC対応 | クロノトリガーエピソード、旧キャラコスチュームなど |
| クロスセーブ | 複数プラットフォームでの共有セーブ |
| ギャラリーモード | アート、BGM視聴、ボイス再生可能なコレクション |
| フォトモード | アングル調整+フィルターありの撮影機能 |
| 難易度設定 | イージー〜クラシック(敵の強化・MP制限など) |
■ 開発スケジュール案(例)
| 期間 | 内容 |
|---|---|
| Q1〜Q2 | プロトタイプ制作・初期アート制作 |
| Q3〜Q4 | メイン開発・音楽収録・シナリオ検証 |
| Q5〜Q6 | ベータ版、デバッグ、調整、プロモーション |
| Q7 | グローバルリリース(発売時期例:2027年冬) |
■ 予算感(概算・中規模プロジェクト)
- 総開発費:約15〜25億円(3年開発・UE5・全ボイス)
- 人員:50〜70名体制(内外注含む)
■ 参考資料
- クロノクロスHDリマスター(2022)
- ファイナルファンタジーVII リメイク
- ライブアライブHD-2D
- ゼノブレイドシリーズ(シナリオ設計・多人数管理)
■ 最後に
クロノクロスは「ゲーム音楽」「次元の物語」「美しいドットと詩的なセリフ」で多くのファンの心に残る名作。
本リメイクは、単なる懐古主義ではなく、「再構築」と「夢の継承」をテーマに、今の時代に語り直すことを目指す。
必要であれば、PDF書式の企画書風に整えることも可能だし、ゲーム画面のモックアップや仲間キャラ一覧風資料も作れるよ!
もっと深く踏み込みたい部分ある?(キャラ紹介・UI案・音楽面とか)
4o
MySQL IF CASE
DROP TABLE IF EXISTS posts;
CREATE TABLE posts (
id INT NOT NULL AUTO_INCREMENT,
message VARCHAR(140),
likes INT,
area VARCHAR(20),
PRIMARY KEY (id)
);
INSERT INTO posts (message, likes, area) VALUES
('post-1', 12, 'Tokyo'),
('post-2', 8, 'Fukuoka'),
('post-3', 11, 'Tokyo'),
('post-4', 3, 'Osaka'),
('post-5', 8, 'Tokyo'),
('post-6', 9, 'Osaka'),
('post-7', 4, 'Tokyo'),
('post-8', 10, 'Osaka'),
('post-9', 31, 'Fukuoka');
