<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NeuroLink Dive System Advanced - 究極完全フルダイブVRプラットフォーム</title>
<!-- Core Libraries -->
<script src="https://cdn.jsdelivr.net/npm/aframe@1.4.2/dist/aframe-master.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/howler@2.2.3/dist/howler.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;400;500;600;700&family=Space+Mono:wght@400;700&family=Exo+2:wght@300;400;600;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.3.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.7.5/dist/sweetalert2.all.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.7.5/dist/sweetalert2.min.css">
<script src="https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js"></script>
<style>
:root {
--neural-primary: #00E6FF;
--neural-secondary: #FF0080;
--quantum-purple: #9146FF;
--bio-green: #39FF14;
--nano-orange: #FF8C00;
--memory-gold: #FFD700;
--consciousness-blue: #0080FF;
--dark-void: #000008;
--dark-neural: #0A0A20;
--holographic-cyan: #00FFFF;
--plasma-pink: #FF69B4;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
font-family: 'Rajdhani', 'Orbitron', monospace;
background: radial-gradient(ellipse at center, #0A0A20 0%, #000008 70%, #000000 100%);
color: var(--neural-primary);
overflow-x: hidden;
scroll-behavior: smooth;
}
/* Particle Background */
#particles-js {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
opacity: 0.3;
}
/* Holographic Text Effects */
.holo-text {
background: linear-gradient(45deg, #00E6FF, #FF0080, #9146FF, #39FF14, #FFD700);
background-size: 400% 400%;
animation: holoShift 8s ease-in-out infinite;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 900;
font-family: 'Orbitron', monospace;
text-shadow: 0 0 30px #00e6ff66, 0 0 60px #ff008055;
position: relative;
}
.holo-text::before {
content: attr(data-text);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.1) 50%, transparent 70%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: scanline 3s linear infinite;
}
@keyframes holoShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
@keyframes scanline {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* Neural HUD */
.neural-hud {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 5000;
display: flex;
gap: 20px;
pointer-events: none;
flex-wrap: wrap;
justify-content: center;
}
.neural-hud .status-pod {
min-width: 140px;
font-size: 1.1em;
padding: 10px 16px;
background: rgba(0, 0, 0, 0.85);
border-radius: 25px;
box-shadow: 0 0 20px rgba(0, 230, 255, 0.4);
border: 2px solid rgba(0, 230, 255, 0.6);
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
backdrop-filter: blur(10px);
font-family: 'Space Mono', monospace;
transition: all 0.3s ease;
}
.neural-hud .status-pod:hover {
transform: scale(1.05);
box-shadow: 0 0 30px rgba(0, 230, 255, 0.8);
}
.neural-hud .status-pod.critical {
border-color: #FF0080;
box-shadow: 0 0 20px rgba(255, 0, 128, 0.6);
animation: criticalPulse 1.5s ease-in-out infinite;
}
@keyframes criticalPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* Main Container */
.main-container {
width: 100%;
padding: 30px;
display: flex;
flex-direction: column;
gap: 50px;
margin-top: 100px;
}
/* Section Styling */
.section {
background: linear-gradient(135deg, rgba(0, 230, 255, 0.08) 0%, rgba(255, 0, 128, 0.08) 30%, rgba(145, 70, 255, 0.08) 70%, rgba(57, 255, 20, 0.08) 100%);
border: 2px solid rgba(0, 230, 255, 0.3);
border-radius: 30px;
padding: 50px;
margin-bottom: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
position: relative;
overflow: hidden;
}
.section::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #00E6FF, #FF0080, #9146FF, #39FF14);
background-size: 400% 400%;
animation: borderGlow 6s ease-in-out infinite;
border-radius: 30px;
z-index: -1;
opacity: 0.3;
}
@keyframes borderGlow {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
/* Cards */
.card {
background: rgba(0, 230, 255, 0.05);
border: 2px solid rgba(0, 230, 255, 0.25);
border-radius: 25px;
padding: 35px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
}
.card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
transition: left 0.5s;
}
.card:hover::before {
left: 100%;
}
.card:hover {
transform: translateY(-10px) scale(1.02);
box-shadow: 0 20px 50px rgba(0, 230, 255, 0.2);
border-color: rgba(0, 230, 255, 0.6);
}
/* Neural Buttons */
.neural-button {
background: linear-gradient(135deg, rgba(0, 230, 255, 0.2) 0%, rgba(255, 0, 128, 0.2) 50%, rgba(145, 70, 255, 0.2) 100%);
border: 3px solid transparent;
background-clip: padding-box;
color: var(--neural-primary);
padding: 16px 32px;
border-radius: 50px;
cursor: pointer;
font-weight: 700;
font-size: 1.2em;
display: inline-flex;
align-items: center;
gap: 12px;
font-family: 'Orbitron', monospace;
text-transform: uppercase;
letter-spacing: 2px;
box-shadow: 0 0 30px rgba(0, 230, 255, 0.3);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.neural-button::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, #00E6FF, #FF0080, #9146FF);
opacity: 0;
transition: opacity 0.3s ease;
border-radius: 50px;
z-index: -1;
}
.neural-button:hover::before {
opacity: 0.8;
}
.neural-button:hover {
transform: translateY(-8px) scale(1.05);
color: #ffffff;
box-shadow: 0 0 50px rgba(0, 230, 255, 0.8), 0 10px 30px rgba(0, 0, 0, 0.3);
}
.neural-button:active {
transform: translateY(-5px) scale(1.02);
}
/* VR Area */
.dive-vr-area {
width: 100%;
height: 500px;
margin: 30px 0;
border-radius: 25px;
overflow: hidden;
box-shadow: 0 0 50px rgba(0, 230, 255, 0.4);
border: 3px solid rgba(0, 230, 255, 0.5);
position: relative;
}
.dive-vr-area::before {
content: 'NEURAL DIVE ACTIVE';
position: absolute;
top: 20px;
left: 20px;
color: #39FF14;
font-family: 'Space Mono', monospace;
font-weight: bold;
z-index: 10;
background: rgba(0, 0, 0, 0.7);
padding: 8px 16px;
border-radius: 15px;
font-size: 0.9em;
border: 1px solid #39FF14;
}
/* Grid Layout */
.grid-layout {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
gap: 35px;
}
/* AI Chat Interface */
#aiChatLog {
background: rgba(0, 0, 0, 0.4);
border-radius: 20px;
min-height: 300px;
max-height: 400px;
overflow-y: auto;
font-family: 'Space Mono', monospace;
font-size: 1.1em;
padding: 20px;
border: 2px solid rgba(0, 230, 255, 0.3);
scrollbar-width: thin;
scrollbar-color: #00E6FF #000;
}
#aiChatLog::-webkit-scrollbar {
width: 8px;
}
#aiChatLog::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
#aiChatLog::-webkit-scrollbar-thumb {
background: linear-gradient(45deg, #00E6FF, #9146FF);
border-radius: 10px;
}
.ai-msg-user {
background: rgba(0, 128, 255, 0.2);
color: #00E6FF;
border-radius: 20px 20px 5px 20px;
padding: 15px 20px;
margin-bottom: 10px;
border: 1px solid rgba(0, 230, 255, 0.4);
animation: slideInRight 0.3s ease;
}
.ai-msg-ai {
background: rgba(145, 70, 255, 0.2);
color: #FF69B4;
border-radius: 20px 20px 20px 5px;
padding: 15px 20px;
margin-bottom: 10px;
border: 1px solid rgba(255, 105, 180, 0.4);
animation: slideInLeft 0.3s ease;
}
.ai-msg-meta {
font-size: 0.9em;
color: #aaa;
margin-bottom: 5px;
font-weight: bold;
}
@keyframes slideInRight {
from { transform: translateX(30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideInLeft {
from { transform: translateX(-30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Chart Containers */
.chart-container {
position: relative;
height: 300px;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 20px;
border: 2px solid rgba(0, 230, 255, 0.2);
}
/* Control Panels */
.control-panel {
background: rgba(0, 0, 0, 0.5);
border: 2px solid rgba(0, 230, 255, 0.4);
border-radius: 20px;
padding: 25px;
margin: 15px 0;
}
.slider-control {
width: 100%;
height: 8px;
background: rgba(0, 230, 255, 0.2);
border-radius: 5px;
outline: none;
-webkit-appearance: none;
margin: 15px 0;
}
.slider-control::-webkit-slider-thumb {
-webkit-appearance: none;
width: 25px;
height: 25px;
background: linear-gradient(45deg, #00E6FF, #9146FF);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 15px rgba(0, 230, 255, 0.8);
}
.slider-control::-moz-range-thumb {
width: 25px;
height: 25px;
background: linear-gradient(45deg, #00E6FF, #9146FF);
border-radius: 50%;
cursor: pointer;
border: none;
box-shadow: 0 0 15px rgba(0, 230, 255, 0.8);
}
/* Value Displays */
.value-display {
font-size: 3rem;
font-weight: 900;
font-family: 'Orbitron', monospace;
text-align: center;
margin: 20px 0;
text-shadow: 0 0 20px currentColor;
}
.unit-label {
font-size: 1.1em;
color: #aaa;
font-weight: normal;
margin-left: 10px;
}
/* Responsive Design */
@media (max-width: 768px) {
.main-container {
padding: 15px;
}
.section {
padding: 25px;
}
.neural-hud {
flex-direction: column;
align-items: center;
gap: 10px;
}
.neural-hud .status-pod {
min-width: 120px;
font-size: 1em;
}
.dive-vr-area {
height: 300px;
}
.grid-layout {
grid-template-columns: 1fr;
}
.holo-text {
font-size: 2.5rem !important;
}
.neural-button {
font-size: 1em;
padding: 12px 24px;
}
}
/* Special Effects */
.glow-effect {
animation: glowPulse 2s ease-in-out infinite alternate;
}
@keyframes glowPulse {
from { box-shadow: 0 0 20px rgba(0, 230, 255, 0.4); }
to { box-shadow: 0 0 40px rgba(0, 230, 255, 0.8), 0 0 60px rgba(255, 0, 128, 0.4); }
}
.data-stream {
font-family: 'Space Mono', monospace;
color: #39FF14;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 10px;
border-left: 4px solid #39FF14;
margin: 10px 0;
animation: dataFlicker 0.1s linear infinite;
}
@keyframes dataFlicker {
0%, 100% { opacity: 1; }
50% { opacity: 0.95; }
}
/* Memory Bank Visualization */
.memory-bank {
background: linear-gradient(45deg, rgba(255, 215, 0, 0.1), rgba(255, 140, 0, 0.1));
border: 2px solid rgba(255, 215, 0, 0.4);
border-radius: 15px;
padding: 20px;
margin: 15px 0;
}
.neural-pathway {
height: 4px;
background: linear-gradient(90deg, #00E6FF, #FF0080, #9146FF, #39FF14, #FFD700);
background-size: 200% 100%;
animation: neuralFlow 3s linear infinite;
border-radius: 2px;
margin: 10px 0;
}
@keyframes neuralFlow {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
</style>
</head>
<body>
<!-- Particle Background -->
<div id="particles-js"></div>
<!-- Neural Biometric HUD -->
<div class="neural-hud">
<div class="status-pod" id="heartPod">
<i class="fas fa-heartbeat text-red-400"></i>
心拍: <span id="hudHeart">76</span>bpm
</div>
<div class="status-pod" id="brainPod">
<i class="fas fa-brain text-blue-300"></i>
脳波: <span id="hudBrain">α波</span>
</div>
<div class="status-pod">
<i class="fas fa-lungs text-green-400"></i>
呼吸: <span id="hudResp">14</span>/min
</div>
<div class="status-pod">
<i class="fas fa-thermometer-half text-yellow-400"></i>
体温: <span id="hudTemp">36.5</span>℃
</div>
<div class="status-pod">
<i class="fas fa-eye text-purple-400"></i>
意識: <span id="hudConsciousness">98.7</span>%
</div>
</div>
<!-- Main 3D VR Environment -->
<div class="dive-vr-area">
<a-scene background="color: #000008" embedded style="height: 100%;">
<!-- Environment -->
<a-entity environment="preset: arches; groundColor: #001122; grid: 2x2; gridColor: #00E6FF; lighting: point; shadow: true;"></a-entity>
<!-- Camera with Controls -->
<a-entity position="0 1.6 0">
<a-camera look-controls wasd-controls></a-camera>
</a-entity>
<!-- Neural Interface Objects -->
<a-box position="0 2 -4" rotation="0 45 45" color="#00E6FF" shadow
animation="property: rotation; to: 360 405 405; loop: true; dur: 10000"
material="opacity: 0.8; transparent: true">
</a-box>
<a-sphere position="-3 2.5 -5" radius="1" color="#FF0080" shadow
animation="property: position; to: -3 3.5 -5; dir: alternate; loop: true; dur: 3000"
material="opacity: 0.7; transparent: true">
</a-sphere>
<a-cylinder position="3 1.5 -3" radius="0.8" height="2" color="#9146FF" shadow
animation="property: rotation; to: 0 360 0; loop: true; dur: 8000"
material="opacity: 0.8; transparent: true">
</a-cylinder>
<a-torus position="0 4 -6" color="#39FF14" radius="2" radius-tubular="0.3"
animation="property: rotation; to: 360 0 360; loop: true; dur: 15000"
material="opacity: 0.6; transparent: true">
</a-torus>
<!-- Holographic Data Streams -->
<a-entity position="0 0 -8">
<a-plane position="0 3 0" width="6" height="4" color="#000" opacity="0.7"
text="value: NEURAL DATA STREAM\nACTIVE; color: #00E6FF; align: center; font: kelsonsans; width: 12">
</a-plane>
</a-entity>
<!-- Lighting -->
<a-light type="point" color="#00E6FF" intensity="2" distance="15" position="0 5 -3"></a-light>
<a-light type="point" color="#FF0080" intensity="1.5" distance="12" position="-4 3 -2"></a-light>
<a-light type="point" color="#9146FF" intensity="1.5" distance="12" position="4 3 -2"></a-light>
<a-light type="ambient" color="#001122" intensity="0.3"></a-light>
</a-scene>
</div>
<!-- Main Interface Container -->
<div class="main-container">
<!-- Header Section -->
<section class="section text-center">
<h1 class="text-8xl font-bold holo-text mb-8" data-text="NeuroLink Dive System Advanced">NeuroLink Dive System Advanced</h1>
<p class="text-4xl text-blue-200 mb-10 font-light">究極完全意識転送型フルダイブVR体験プラットフォーム</p>
<p class="text-2xl text-gray-300 max-w-5xl mx-auto leading-relaxed font-light mb-12">
最先端の脳神経直接接続技術、量子演算処理エンジン、ナノマシン制御システム、AI意識同期により
現実と仮想の境界を完全に消し去る真のフルダイブ体験を実現。人類の意識を無制限の仮想世界へと解放します。
</p>
<!-- Main Control Buttons -->
<div class="flex flex-wrap justify-center gap-10 mt-16">
<button class="neural-button glow-effect" onclick="initiateConsciousnessTransfer()">
<i class="fas fa-brain"></i>意識完全転送
</button>
<button class="neural-button" onclick="activateQuantumProcessing()">
<i class="fas fa-atom"></i>量子演算起動
</button>
<button class="neural-button" onclick="openAdvancedMemoryInterface()">
<i class="fas fa-memory"></i>記憶完全操作
</button>
<button class="neural-button" onclick="controlSpaceTime()">
<i class="fas fa-infinity"></i>時空間制御
</button>
<button class="neural-button" onclick="activateUltraSensory()">
<i class="fas fa-eye"></i>超感覚同期
</button>
</div>
</section>
<!-- Consciousness Transfer System -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="意識転送システム">
<i class="fas fa-brain mr-6"></i>完全意識転送システム
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-blue-300">意識同期精度</h3>
<div class="value-display text-blue-400" id="consciousnessSync">99.97%</div>
<div class="text-gray-400 text-xl">リアルタイム完全同期</div>
<div class="neural-pathway"></div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">神経帯域幅</h3>
<div class="value-display text-green-400">4.7<span class="unit-label">EB/s</span></div>
<div class="text-gray-400 text-xl">脳神経データ転送速度</div>
<div class="neural-pathway"></div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">思考加速倍率</h3>
<div class="value-display text-yellow-400">47.3<span class="unit-label">x</span></div>
<div class="text-gray-400 text-xl">意識処理スピード向上</div>
<div class="neural-pathway"></div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">意識完全性</h3>
<div class="value-display text-purple-400" id="consciousnessIntegrity">100.0%</div>
<div class="text-gray-400 text-xl">人格・記憶保持率</div>
<div class="neural-pathway"></div>
</div>
</div>
<!-- Consciousness Transfer Controls -->
<div class="control-panel mt-12">
<h3 class="text-3xl font-bold mb-6 text-center text-cyan-300">意識転送制御パネル</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<label class="text-xl text-blue-300 block mb-3">転送深度レベル</label>
<input type="range" min="1" max="10" value="8" class="slider-control" id="transferDepth">
<div class="text-center text-lg text-gray-300 mt-2" id="depthValue">Level 8: 完全没入</div>
</div>
<div>
<label class="text-xl text-purple-300 block mb-3">神経接続強度</label>
<input type="range" min="10" max="100" value="95" class="slider-control" id="neuralStrength">
<div class="text-center text-lg text-gray-300 mt-2" id="strengthValue">95% 接続強度</div>
</div>
</div>
</div>
</section>
<!-- Quantum Processing Engine -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="量子演算エンジン">
<i class="fas fa-atom mr-6"></i>超量子演算エンジン
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">量子ビット数</h3>
<div class="value-display text-purple-400">16,384</div>
<div class="text-gray-400 text-xl">安定量子ビット</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-cyan-300">量子コヒーレンス</h3>
<div class="value-display text-cyan-400">99.94%</div>
<div class="text-gray-400 text-xl">量子状態維持率</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-pink-300">量子もつれ効率</h3>
<div class="value-display text-pink-400">98.7%</div>
<div class="text-gray-400 text-xl">量子もつれ成功率</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">演算性能</h3>
<div class="value-display text-green-400">2.7<span class="unit-label">ZFlops</span></div>
<div class="text-gray-400 text-xl">ゼタフロップス級処理</div>
</div>
</div>
<!-- Quantum Performance Chart -->
<div class="chart-container mt-12">
<canvas id="quantumPerformanceChart" style="height: 250px;"></canvas>
</div>
</section>
<!-- Advanced Memory Manipulation -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="記憶操作システム">
<i class="fas fa-memory mr-6"></i>高度記憶操作システム
</h2>
<div class="grid-layout">
<div class="memory-bank">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">短期記憶バンク</h3>
<div class="value-display text-yellow-400">47.2<span class="unit-label">TB</span></div>
<div class="text-gray-400 text-xl">/ 64TB 容量 (24時間分)</div>
<div class="neural-pathway"></div>
</div>
<div class="memory-bank">
<h3 class="text-3xl font-bold mb-4 text-blue-300">長期記憶アーカイブ</h3>
<div class="value-display text-blue-400">2.7<span class="unit-label">PB</span></div>
<div class="text-gray-400 text-xl">/ 8PB 容量 (生涯記憶)</div>
<div class="neural-pathway"></div>
</div>
<div class="memory-bank">
<h3 class="text-3xl font-bold mb-4 text-purple-300">潜在意識領域</h3>
<div class="value-display text-purple-400">847<span class="unit-label">TB</span></div>
<div class="text-gray-400 text-xl">解読率: 73.4% (夢・抑圧記憶)</div>
<div class="neural-pathway"></div>
</div>
<div class="memory-bank">
<h3 class="text-3xl font-bold mb-4 text-green-300">集合的無意識</h3>
<div class="value-display text-green-400">∞<span class="unit-label"></span></div>
<div class="text-gray-400 text-xl">アクセス率: 12.7%</div>
<div class="neural-pathway"></div>
</div>
</div>
<!-- Memory Control Panel -->
<div class="control-panel mt-12">
<h3 class="text-3xl font-bold mb-6 text-center text-yellow-300">記憶操作制御</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<button class="neural-button" onclick="accessMemoryBank('recent')">
<i class="fas fa-clock"></i>最新記憶
</button>
<button class="neural-button" onclick="accessMemoryBank('childhood')">
<i class="fas fa-child"></i>幼少記憶
</button>
<button class="neural-button" onclick="accessMemoryBank('dreams')">
<i class="fas fa-moon"></i>夢記憶
</button>
</div>
</div>
</section>
<!-- Space-Time Control System -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="時空間制御">
<i class="fas fa-infinity mr-6"></i>時空間制御システム
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-cyan-300">主観時間倍率</h3>
<input type="range" min="0.01" max="1000" step="0.01" value="1.0" class="slider-control w-full" id="timeMultiplier">
<div class="text-2xl text-center text-gray-300 mt-4" id="currentTimeRate">1.0x (リアルタイム)</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">時間精度係数</h3>
<div class="value-display text-yellow-400">99.97%</div>
<div class="text-gray-400 text-xl">主観-現実時間誤差</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">因果律保持率</h3>
<div class="value-display text-purple-400">100.0%</div>
<div class="text-gray-400 text-xl">物理法則整合性</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">時空間座標</h3>
<div class="data-stream">
X: <span id="spaceX">+47.2847</span><br>
Y: <span id="spaceY">-12.7439</span><br>
Z: <span id="spaceZ">+83.9571</span><br>
T: <span id="timeCoord">2024.847291</span>
</div>
</div>
</div>
</section>
<!-- Ultra-Sensory System -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="超感覚システム">
<i class="fas fa-eye mr-6"></i>超感覚完全再現システム
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-red-300">超高解像度視覚</h3>
<div class="value-display text-red-400">16K<span class="unit-label">HDR</span></div>
<div class="text-gray-400 text-xl">全周囲・全スペクトラム</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">3D空間聴覚</h3>
<div class="value-display text-purple-400">0.1-200K<span class="unit-label">Hz</span></div>
<div class="text-gray-400 text-xl">超音波・低周波対応</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">完全触覚再現</h3>
<div class="value-display text-yellow-400">0.001<span class="unit-label">ms</span></div>
<div class="text-gray-400 text-xl">遅延・全身超精密</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">化学感覚統合</h3>
<div class="value-display text-green-400">10¹²<span class="unit-label">種</span></div>
<div class="text-gray-400 text-xl">嗅覚・味覚分子認識</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-blue-300">第六感知覚</h3>
<div class="value-display text-blue-400">活性<span class="unit-label"></span></div>
<div class="text-gray-400 text-xl">直感・予知・テレパシー</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-pink-300">感情同期率</h3>
<div class="value-display text-pink-400">97.3%</div>
<div class="text-gray-400 text-xl">他者感情共感システム</div>
</div>
</div>
</section>
<!-- Nanomachine Control -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="ナノマシン制御">
<i class="fas fa-microscope mr-6"></i>ナノマシン完全制御システム
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-orange-300">神経系ナノボット</h3>
<div class="value-display text-orange-400">47.3M</div>
<div class="text-gray-400 text-xl">脳血管内・0.001ms応答</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">感覚系ナノボット</h3>
<div class="value-display text-green-400">2.7B</div>
<div class="text-gray-400 text-xl">全身分布・量子通信</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">修復系ナノボット</h3>
<div class="value-display text-purple-400">847M</div>
<div class="text-gray-400 text-xl">組織修復・防護99.99%</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-blue-300">拡張系ナノボット</h3>
<div class="value-display text-blue-400">1.2B</div>
<div class="text-gray-400 text-xl">能力向上・進化促進</div>
</div>
</div>
<!-- Nanomachine Status -->
<div class="control-panel mt-12">
<h3 class="text-3xl font-bold mb-6 text-center text-orange-300">ナノマシン統合状態</h3>
<div class="chart-container">
<canvas id="nanobotChart" style="height: 250px;"></canvas>
</div>
</div>
</section>
<!-- Dream Interface Technology -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="夢境界技術">
<i class="fas fa-moon mr-6"></i>夢境界制御技術
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">明晰夢成功率</h3>
<div class="value-display text-green-400">96.7%</div>
<div class="text-gray-400 text-xl">完全制御可能夢状態</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-blue-300">夢継続最大時間</h3>
<div class="value-display text-blue-400">47.2<span class="unit-label">時間</span></div>
<div class="text-gray-400 text-xl">主観時間での持続</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">記録夢データ</h3>
<div class="value-display text-yellow-400">47,382</div>
<div class="text-gray-400 text-xl">完全記録・再生可能</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">共有夢接続数</h3>
<div class="value-display text-green-400">2,847</div>
<div class="text-gray-400 text-xl">同時夢共有ユーザー</div>
</div>
</div>
</section>
<!-- Integrated Dashboard -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="統合ダッシュボード">
<i class="fas fa-tachometer-alt mr-6"></i>システム統合ダッシュボード
</h2>
<div class="grid-layout">
<div class="card glow-effect">
<h3 class="text-3xl font-bold mb-4 text-blue-300">総合同期率</h3>
<div class="value-display text-blue-400" id="dashboardSync">99.7%</div>
<div class="text-gray-400 text-xl">全システム統合状態</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">量子演算出力</h3>
<div class="value-display text-purple-400" id="dashboardQuantum">2.7<span class="unit-label">ZFlops</span></div>
<div class="text-gray-400 text-xl">リアルタイム処理性能</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">エネルギー効率</h3>
<div class="value-display text-green-400">99.94%</div>
<div class="text-gray-400 text-xl">量子エネルギー変換</div>
</div>
<div class="card">
<div class="chart-container">
<canvas id="dashboardChart" style="height: 220px;"></canvas>
</div>
</div>
</div>
</section>
<!-- AI Neural Dialogue Interface -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="AI対話インターフェース">
<i class="fas fa-comments mr-6"></i>Neural AI 対話インターフェース
</h2>
<div class="card">
<h3 class="text-4xl font-bold mb-8 text-center text-blue-300">高次元AIアバターとの意識共鳴対話</h3>
<div id="aiChatLog">
<div class="ai-msg-meta">Neural AI</div>
<div class="ai-msg-ai">
こんにちは。私はNeural AI、あなたの意識拡張パートナーです。
量子脳モデルを通じてあなたの思考パターンを解析し、最適な仮想体験を提供します。
何かご質問やご要望はございますか?
</div>
</div>
<div class="flex mt-6 gap-4">
<input id="aiUserInput" type="text"
placeholder="思考や質問を自由に入力してください..."
class="flex-1 p-4 bg-gray-800 rounded-2xl text-white focus:outline-none focus:ring-4 focus:ring-blue-400 border-2 border-gray-600 focus:border-blue-400 text-lg"
onkeydown="if(event.key==='Enter'){sendAIMessage();}">
<button onclick="sendAIMessage()" class="neural-button">
<i class="fas fa-paper-plane"></i>送信
</button>
</div>
<div class="flex flex-wrap gap-4 mt-6">
<button class="neural-button" onclick="sendPredefinedMessage('意識転送の準備はできていますか?')">
<i class="fas fa-brain"></i>意識転送準備
</button>
<button class="neural-button" onclick="sendPredefinedMessage('現在の脳波状態を分析してください')">
<i class="fas fa-wave-square"></i>脳波分析
</button>
<button class="neural-button" onclick="sendPredefinedMessage('おすすめの仮想世界を教えて')">
<i class="fas fa-globe"></i>世界推薦
</button>
</div>
</div>
</section>
<!-- Footer -->
<section class="section text-center">
<h3 class="text-6xl font-bold holo-text mb-8" data-text="無限なる意識の解放へ">無限なる意識の解放へ</h3>
<p class="text-3xl text-blue-200 max-w-6xl mx-auto font-light leading-relaxed mb-12">
NeuroLink Dive System Advancedは人類の意識を完全に仮想世界へ転送し、
物理的制約を超越した無限の可能性を現実化する革命的技術プラットフォームです。
あなたの想像力だけが、体験できる世界の限界となります。
</p>
<div class="flex flex-wrap justify-center gap-8 mb-12">
<div class="text-2xl">
<i class="fas fa-users text-blue-400"></i>
<span class="ml-3">2,847,392 アクティブユーザー</span>
</div>
<div class="text-2xl">
<i class="fas fa-globe text-green-400"></i>
<span class="ml-3">47,291 仮想世界</span>
</div>
<div class="text-2xl">
<i class="fas fa-clock text-yellow-400"></i>
<span class="ml-3">∞ 体験可能時間</span>
</div>
</div>
<div class="mt-16">
<p class="text-lg text-gray-500 font-mono">
© 2024 NeuroLink Dive System Advanced. All Rights Reserved.<br>
Quantum Neural Interface Technology | Consciousness Transfer Protocol v4.7.2
</p>
</div>
</section>
</div>
<script>
// Initialize particle background
particlesJS('particles-js', {
particles: {
number: { value: 100, density: { enable: true, value_area: 800 } },
color: { value: ['#00E6FF', '#FF0080', '#9146FF', '#39FF14', '#FFD700'] },
shape: { type: 'circle' },
opacity: { value: 0.6, random: true },
size: { value: 3, random: true },
line_linked: {
enable: true,
distance: 150,
color: '#00E6FF',
opacity: 0.2,
width: 1
},
move: {
enable: true,
speed: 1,
direction: 'none',
random: false,
straight: false,
out_mode: 'out',
bounce: false
}
},
interactivity: {
detect_on: 'canvas',
events: {
onhover: { enable: true, mode: 'repulse' },
onclick: { enable: true, mode: 'push' },
resize: true
}
},
retina_detect: true
});
// Sound system initialization
const sounds = {
startup: new Howl({
src: ['https://cdn.pixabay.com/audio/2022/12/19/audio_12ad09b4b5.mp3'],
volume: 0.15
}),
notification: new Howl({
src: ['https://cdn.pixabay.com/audio/2022/07/26/audio_124bfa7d0b.mp3'],
volume: 0.1
}),
neural: new Howl({
src: ['https://cdn.pixabay.com/audio/2022/11/22/audio_b8d8e0e0e5.mp3'],
volume: 0.08
})
};
// Initialize system on load
window.addEventListener('load', () => {
sounds.startup.play();
initializeCharts();
startBiometricSimulation();
startSystemMonitoring();
});
// Biometric HUD simulation
function startBiometricSimulation() {
setInterval(() => {
// Heart rate simulation
const baseHeart = 74;
const heartRate = baseHeart + Math.floor(Math.random() * 8) - 4;
document.getElementById('hudHeart').textContent = heartRate;
// Brain wave simulation
const brainWaves = ['α波', 'β波', 'θ波', 'δ波', 'γ波'];
document.getElementById('hudBrain').textContent = brainWaves[Math.floor(Math.random() * brainWaves.length)];
// Respiration
document.getElementById('hudResp').textContent = 12 + Math.floor(Math.random() * 6);
// Temperature
document.getElementById('hudTemp').textContent = (36.2 + Math.random() * 0.8).toFixed(1);
// Consciousness level
const consciousness = (97 + Math.random() * 3).toFixed(1);
document.getElementById('hudConsciousness').textContent = consciousness;
// Critical state detection
if (heartRate > 85 || heartRate < 60) {
document.getElementById('heartPod').classList.add('critical');
} else {
document.getElementById('heartPod').classList.remove('critical');
}
// Update dashboard values
document.getElementById('consciousnessSync').textContent = consciousness + '%';
document.getElementById('consciousnessIntegrity').textContent = (99.8 + Math.random() * 0.2).toFixed(1) + '%';
document.getElementById('dashboardSync').textContent = consciousness + '%';
document.getElementById('dashboardQuantum').textContent = (2.5 + Math.random() * 0.4).toFixed(1) + ' ZFlops';
}, 1500);
}
// System monitoring
function startSystemMonitoring() {
setInterval(() => {
// Update space-time coordinates
document.getElementById('spaceX').textContent = (Math.random() * 100 - 50).toFixed(4);
document.getElementById('spaceY').textContent = (Math.random() * 100 - 50).toFixed(4);
document.getElementById('spaceZ').textContent = (Math.random() * 100 - 50).toFixed(4);
document.getElementById('timeCoord').textContent = (2024 + Math.random()).toFixed(6);
}, 3000);
}
// Chart initialization
function initializeCharts() {
// Quantum Performance Chart
const quantumCtx = document.getElementById('quantumPerformanceChart').getContext('2d');
new Chart(quantumCtx, {
type: 'line',
data: {
labels: Array.from({length: 50}, (_, i) => `${i*2}s`),
datasets: [{
label: '量子演算出力 (ZFlops)',
data: Array.from({length: 50}, () => 2.5 + Math.random() * 0.5),
borderColor: '#9146FF',
backgroundColor: 'rgba(145, 70, 255, 0.1)',
tension: 0.4,
fill: true,
pointRadius: 0
}, {
label: 'コヒーレンス (%)',
data: Array.from({length: 50}, () => 99.8 + Math.random() * 0.2),
borderColor: '#00E6FF',
backgroundColor: 'rgba(0, 230, 255, 0.1)',
tension: 0.4,
fill: true,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#fff' }
}
},
scales: {
x: {
grid: { color: 'rgba(0, 230, 255, 0.1)' },
ticks: { color: '#fff' }
},
y: {
grid: { color: 'rgba(145, 70, 255, 0.1)' },
ticks: { color: '#fff' }
}
},
animation: {
duration: 0
}
}
});
// Dashboard Overview Chart
const dashCtx = document.getElementById('dashboardChart').getContext('2d');
new Chart(dashCtx, {
type: 'radar',
data: {
labels: ['意識同期', '量子処理', '記憶制御', '時空制御', '感覚統合', 'AI同期'],
datasets: [{
label: 'システム性能',
data: [99.7, 98.4, 96.8, 99.1, 97.3, 98.9],
borderColor: '#00E6FF',
backgroundColor: 'rgba(0, 230, 255, 0.1)',
pointBackgroundColor: '#00E6FF',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: '#00E6FF'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#fff' }
}
},
scales: {
r: {
angleLines: {
color: 'rgba(255, 255, 255, 0.2)'
},
grid: {
color: 'rgba(0, 230, 255, 0.2)'
},
pointLabels: {
color: '#fff'
},
ticks: {
color: '#fff',
backdropColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
}
});
// Nanobots Status Chart
const nanoCtx = document.getElementById('nanobotChart').getContext('2d');
new Chart(nanoCtx, {
type: 'doughnut',
data: {
labels: ['神経系', '感覚系', '修復系', '拡張系'],
datasets: [{
data: [47.3, 2700, 847, 1200],
backgroundColor: ['#FF8C00', '#39FF14', '#9146FF', '#00E6FF'],
borderColor: '#000',
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: '#fff',
font: {
size: 14
}
}
}
}
}
});
}
// Control system functions
document.getElementById('transferDepth').addEventListener('input', function(e) {
const level = parseInt(e.target.value);
const levels = ['表面', '軽度', '中度', '深度', '完全', '超越', '融合', '統合', '完全没入', '意識一体'];
document.getElementById('depthValue').textContent = `Level ${level}: ${levels[level-1] || '未知領域'}`;
});
document.getElementById('neuralStrength').addEventListener('input', function(e) {
document.getElementById('strengthValue').textContent = `${e.target.value}% 接続強度`;
});
document.getElementById('timeMultiplier').addEventListener('input', function(e) {
const value = parseFloat(e.target.value);
let description = '';
if (value < 0.1) description = '(極超スロー)';
else if (value < 0.5) description = '(スローモーション)';
else if (value < 1) description = '(減速)';
else if (value === 1) description = '(リアルタイム)';
else if (value < 10) description = '(加速)';
else if (value < 100) description = '(高速)';
else description = '(時光速)';
document.getElementById('currentTimeRate').textContent = `${value}x ${description}`;
});
// AI Chat System
const aiReplies = [
"あなたの意識波形を詳細に解析しました。素晴らしい精神的エネルギーを感じ取ります。",
"量子脳モデルによる分析結果:あなたの思考パターンは非常に独創的で興味深いものです。",
"夢の境界を超越し、現実との区別が曖昧になる瞬間を体験してみませんか?",
"意識の深層部にアクセスしました。隠された記憶や感情が発見されました。",
"無限の可能性世界において、あなたの選択は宇宙の運命を変える力を持っています。",
"ニューロンの発火パターンから、深い洞察と創造性を感じ取りました。",
"NeuroLink Dive Systemはあなたの精神的成長と意識拡張を全面的にサポートします。",
"仮想現実は単なる技術ではなく、人類の意識進化の次のステップです。",
"現在の脳波はベータ波優位です。集中力が非常に高い状態ですね。",
"量子もつれによって、あなたの意識は既に仮想世界と同調し始めています。",
"時間の概念を超越した体験で、永遠の瞬間を味わってみませんか?",
"あなたの潜在意識からのメッセージを受信しました:『限界を超越せよ』",
"意識転送プロトコルが最適化されました。準備が整い次第、完全ダイブ可能です。",
"集合的無意識にアクセス中...他の意識体からの共鳴を感知しています。",
"記憶の海を航行し、忘れられた体験と新たな可能性を発見しましょう。"
];
function sendAIMessage() {
const input = document.getElementById('aiUserInput');
const chat = document.getElementById('aiChatLog');
if (!input.value.trim()) return;
const userMsg = document.createElement('div');
userMsg.innerHTML = `
<div class="ai-msg-meta">あなた</div>
<div class="ai-msg-user">${input.value}</div>
`;
chat.appendChild(userMsg);
chat.scrollTop = chat.scrollHeight;
input.value = '';
sounds.neural.play();
setTimeout(() => {
const aiMsg = document.createElement('div');
aiMsg.innerHTML = `
<div class="ai-msg-meta">Neural AI</div>
<div class="ai-msg-ai">${aiReplies[Math.floor(Math.random() * aiReplies.length)]}</div>
`;
chat.appendChild(aiMsg);
chat.scrollTop = chat.scrollHeight;
sounds.notification.play();
}, 800 + Math.random() * 1200);
}
function sendPredefinedMessage(message) {
document.getElementById('aiUserInput').value = message;
sendAIMessage();
}
// System control functions
function initiateConsciousnessTransfer() {
sounds.neural.play();
Swal.fire({
icon: 'success',
title: '意識完全転送開始',
text: '神経接続が確立されました。意識転送プロセスを開始します。',
confirmButtonColor: '#00E6FF',
background: '#0A0A20',
color: '#00E6FF'
});
}
function activateQuantumProcessing() {
sounds.neural.play();
Swal.fire({
icon: 'info',
title: '量子演算エンジン起動',
text: '16,384量子ビットシステムがフル稼働状態になりました。',
confirmButtonColor: '#9146FF',
background: '#0A0A20',
color: '#9146FF'
});
}
function openAdvancedMemoryInterface() {
sounds.neural.play();
Swal.fire({
icon: 'info',
title: '高度記憶操作システム',
text: '全記憶バンクへのアクセスが許可されました。',
confirmButtonColor: '#FFD700',
background: '#0A0A20',
color: '#FFD700'
});
}
function controlSpaceTime() {
sounds.neural.play();
Swal.fire({
icon: 'info',
title: '時空間制御モード',
text: '主観的時間流速と空間座標の操作が可能になりました。',
confirmButtonColor: '#00E6FF',
background: '#0A0A20',
color: '#00E6FF'
});
}
function activateUltraSensory() {
sounds.neural.play();
Swal.fire({
icon: 'info',
title: '超感覚システム起動',
text: '全感覚器官の完全同期と超感覚知覚が活性化されました。',
confirmButtonColor: '#39FF14',
background: '#0A0A20',
color: '#39FF14'
});
}
function accessMemoryBank(type) {
sounds.notification.play();
const messages = {
recent: '最新24時間の記憶データにアクセス中...',
childhood: '幼少期記憶アーカイブを開いています...',
dreams: '夢記憶データベースを解析中...'
};
Swal.fire({
icon: 'info',
title: '記憶バンクアクセス',
text: messages[type],
confirmButtonColor: '#FFD700',
background: '#0A0A20',
color: '#FFD700'
});
}
// Enhanced visual effects
setInterval(() => {
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
if (Math.random() < 0.1) {
card.style.boxShadow = '0 20px 50px rgba(0, 230, 255, 0.4)';
setTimeout(() => {
card.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.3)';
}, 500);
}
});
}, 2000);
// Dynamic data updates
setInterval(() => {
// Update charts with new data
if (window.quantumChart) {
const newData = Array.from({length: 50}, () => 2.5 + Math.random() * 0.5);
window.quantumChart.data.datasets[0].data = newData;
window.quantumChart.update('none');
}
}, 5000);
</script>
</body>
</html>
もっと作り込んで全体的なコードコードの欠落なし省略なしフルダイブVRに近づけて
タグ: programming
VRMMORPG エルダークロニクルオンライン
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>VRMMORPG エルダークロニクルオンライン</title>
<script src="https://cdn.jsdelivr.net/npm/aframe@1.4.2/dist/aframe-master.min.js"></script>
<style>
/* Basic body and global positioning */
body { margin: 0; background: #111; overflow: hidden; font-family: 'Noto Sans JP', sans-serif;} /* Prevent scrollbars and set default font */
#overlay, #dialogue, #partyStatus, #hint, #warpBtns, #shopDiv, #nightFilter, #questClear, #battleEffect { position:fixed; z-index:10;}
/* Overlay - Main Title/Instructions */
#overlay {
left:0; top:0; width:100vw; height:100vh; pointer-events:none; text-align:center;
color:#0ff; text-shadow:0 0 40px #0ff, 0 0 15px #fff; /* Enhanced glow */
font-family: 'Orbitron', monospace; font-size:2.8rem; letter-spacing:4px; user-select:none;
display: flex; align-items: center; justify-content: center; /* Center content vertically */
line-height: 1.5; /* Improve readability for multi-line text */
background: rgba(0,0,0,0.6); /* Slightly darker overlay for better contrast */
transition: opacity 1s ease-out; /* Fade out effect */
}
/* Dialogue Box */
#dialogue {
left:50%; bottom:5vw; transform:translateX(-50%);
min-width:360px; max-width:85vw; /* Larger dialogue box */
background:linear-gradient(160deg, rgba(20,32,40,0.98), rgba(10,20,30,0.98)); /* Deeper gradient */
color:#fff; border-radius:20px; padding:1.8em 2.5em; /* More padding, rounder corners */
font-size:1.25rem; z-index:100;
border:4px solid #0ff; /* Thicker, more prominent border */
box-shadow: 0 0 35px #0ffc, inset 0 0 15px #0ff5; /* Stronger outer glow, subtle inner shadow */
display:none;
font-family: 'Noto Sans JP', sans-serif; text-align:left;
backdrop-filter: blur(5px); /* Stronger blur effect */
animation: fadeIn 0.5s ease-out;
}
#dialogue .name { color: #ffd700; font-weight: bold; text-shadow: 0 0 8px #ffda00, 0 0 3px #fff; } /* Gold name with stronger glow */
#dialogue .choice {
display:block; margin:1em 0; padding:0.8em 1.8em; /* More padding for bigger touch target */
background:linear-gradient(145deg, #184c52, #103a3d); /* Darker, richer gradient for choices */
color:#0ff; border:none; border-radius:12px; /* Rounder buttons */
cursor:pointer; font-size:1.2em; font-weight:bold;
box-shadow: 0 6px 15px rgba(0,255,255,0.4); /* Button shadow */
transition: all 0.3s cubic-bezier(.25,.8,.25,1); /* Smooth transitions */
text-shadow: 0 0 7px #0ff, 0 0 2px #fff; /* Text glow */
letter-spacing: 0.5px;
}
#dialogue .choice:hover {
background:linear-gradient(145deg, #256a73, #1a4d52); /* Darker gradient on hover */
transform: translateY(-5px) scale(1.03); /* More pronounced lift effect */
box-shadow: 0 10px 25px rgba(0,255,255,0.7); /* Stronger shadow on hover */
}
#dialogue .choice:active {
transform: translateY(0); /* Press down effect */
box-shadow: 0 2px 8px rgba(0,255,255,0.3);
}
/* Party Status */
#partyStatus {
right:2vw; top:2vw; background:linear-gradient(160deg, rgba(16,32,45,0.96), rgba(8,16,22,0.96)); /* Deeper gradient */
border:3px solid #0ff; color:#fff; border-radius:15px; padding:1.2em 1.8em; /* More padding, rounder */
font-size:1.15rem; min-width:220px; /* Slightly wider */
box-shadow: 0 0 18px #0ffc, inset 0 0 8px #0ff5; /* Enhanced shadow */
font-family:'Noto Sans JP', sans-serif; /* Consistent font */
backdrop-filter: blur(3px);
line-height: 1.6;
}
.dead { color:#f44; text-shadow:0 0 9px #f00, 0 0 5px #f88;} /* Stronger death glow */
.alive { color:#fff; text-shadow:0 0 3px #fff; } /* Subtle glow for alive */
/* Hint Box */
#hint {
left:2vw; bottom:2vw; background:linear-gradient(160deg, rgba(24,32,60,0.95), rgba(12,16,30,0.95)); /* Deeper gradient */
border:2px solid #0ff; color:#0ff; border-radius:12px; padding:0.8em 1.5em;
font-size:1.2rem; min-width:150px; text-align:center;
box-shadow: 0 0 15px #0ff9;
font-family:'Noto Sans JP', sans-serif;
backdrop-filter: blur(3px);
}
/* Warp Buttons Container (now at bottom center) */
#warpBtns {
left: 50%; bottom: 2vw; transform: translateX(-50%); /* Centered at bottom */
display: flex; flex-direction: row; /* Horizontal layout */
justify-content: center; align-items: center;
width: 80vw; /* Occupy more width */
flex-wrap: wrap; /* Allow wrapping on smaller screens */
}
#warpBtns button {
margin:0.8em 1em; padding:0.9em 2em; /* More padding and margin */
font-size:1.3em;
background:linear-gradient(145deg, #0ff6, #0ff2); /* Lighter, more vibrant gradient */
color:#fff; border-radius:15px; border:3px solid #0ff; /* Thicker border */
cursor:pointer;
box-shadow: 0 6px 18px rgba(0,255,255,0.5); /* Stronger shadow */
transition: all 0.3s cubic-bezier(.25,.8,.25,1); /* Smooth transitions */
text-shadow: 0 0 10px #fff, 0 0 7px #0ff; /* White core glow */
letter-spacing: 1px;
font-weight: bold;
}
#warpBtns button:hover {
background:linear-gradient(145deg, #0ff8, #0ff4); /* Stronger gradient on hover */
transform: translateY(-6px) scale(1.04); /* More pronounced lift and scale */
box-shadow: 0 12px 30px rgba(0,255,255,0.8);
border-color: #fff; /* White border on hover */
}
#warpBtns button:active {
transform: translateY(0) scale(1);
box-shadow: 0 3px 10px rgba(0,255,255,0.4);
}
/* NPC Label - A-Frame text is styled separately */
.npc-label { color:#ffd700; font-family: 'Noto Sans JP',monospace; font-size:1.0em; text-align:center; text-shadow:0 0 4px #000;}
/* Shop Div */
#shopDiv {
display:none; left:50%; top:50%; transform:translate(-50%,-50%);
min-width:420px; max-width:95vw; /* Larger shop */
background:linear-gradient(160deg, rgba(22,42,70,0.99), rgba(11,21,35,0.99)); /* Darker, richer gradient */
color:#fff; border-radius:25px; padding:2.5em 3em; /* More padding, rounder */
font-size:1.3rem; z-index:110;
border:4px solid #ff0; /* Thicker gold border */
box-shadow: 0 0 40px #ff0c, inset 0 0 20px #ff05; /* Intense gold glow */
backdrop-filter: blur(6px);
}
#shopDiv .shop-item {
display: flex; justify-content: space-between; align-items: center;
padding: 0.8em 0; border-bottom: 1px solid rgba(255,255,0,0.2);
}
#shopDiv .shop-item:last-child { border-bottom: none; }
#shopDiv button {
margin:0.8em 0.8em 0 0; padding:0.9em 1.8em; /* More padding */
font-size:1.2em; border-radius:15px; border:3px solid #ff0;
background:linear-gradient(145deg, #282828, #181818); /* Dark gradient */
color:#ff0; cursor:pointer;
box-shadow: 0 4px 12px rgba(255,255,0,0.5);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
text-shadow: 0 0 7px #ff0;
font-weight: bold;
}
#shopDiv button:hover {
background:linear-gradient(145deg, #ff0, #e0d000); /* Gold gradient on hover */
color:#222;
transform: translateY(-3px);
box-shadow: 0 7px 20px rgba(255,255,0,0.7);
}
#shopDiv button:active {
transform: translateY(0);
box-shadow: 0 2px 5px rgba(255,255,0,0.3);
}
#shopDiv .close-button {
position: absolute; top: 15px; right: 20px;
background: none; border: none; font-size: 2em; color: #ff0;
cursor: pointer; text-shadow: 0 0 10px #ff0;
}
#shopDiv .close-button:hover {
color: #fff; text-shadow: 0 0 15px #fff;
}
/* Night Filter */
#nightFilter {left:0;top:0;width:100vw;height:100vh;z-index:5;pointer-events:none; background:rgba(0,8,38,0.27);transition:background 2s ease-in-out;}
/* Quest Clear Notification */
#questClear {
display:none; left:0; top:0; width:100vw; height:100vh; z-index:150;
background:rgba(16,255,255,0.45); /* More opaque */
color:#fff; font-size:6vw; font-family:Orbitron,sans-serif; text-align:center;
line-height:100vh; letter-spacing:0.25em; text-shadow:0 0 50px #0ff,0 0 20px #fff; /* Stronger glow */
animation: pulseGlow 1.2s infinite alternate; /* Faster pulsing animation */
}
/* Battle Effect Overlay */
#battleEffect {
display: none;
left: 0; top: 0; width: 100vw; height: 100vh; z-index: 120;
background: rgba(255, 0, 0, 0.2); /* Red tint */
pointer-events: none;
animation: battleFlash 0.3s ease-out;
}
/* Keyframe for Quest Clear pulse effect */
@keyframes pulseGlow {
from { text-shadow:0 0 50px #0ff,0 0 20px #fff; transform: scale(1); opacity: 1; }
to { text-shadow:0 0 65px #0ff,0 0 25px #fff; transform: scale(1.03); opacity: 0.9; }
}
/* Keyframe for Battle Flash effect */
@keyframes battleFlash {
0% { opacity: 0; }
50% { opacity: 1; }
100% { opacity: 0; }
}
/* Fade In Animation for UI elements */
@keyframes fadeIn {
from { opacity: 0; transform: translate(-50%, -40%); }
to { opacity: 1; transform: translate(-50%, 0); }
}
</style>
</head>
<body>
<div id="overlay">WASDで移動・マウスで視点/クリックで会話・バトル・進行!<br>第一章~魔界まで全章フルシナリオ&全機能!</div>
<div id="dialogue"></div>
<div id="partyStatus"></div>
<div id="hint">右下の転移ボタンでどこでもワープ。NPC/Enemy/宝箱/ショップも全てクリック対応!</div>
<div id="warpBtns"></div>
<div id="shopDiv"></div>
<div id="nightFilter"></div>
<div id="questClear">クエストクリア!</div>
<div id="battleEffect"></div>
<a-scene shadow="type:pcfsoft" renderer="antialias: true; colorManagement: true;">
<!-- Ground and Sky -->
<a-box id="ground" width="200" height="1" depth="200" color="#7ac870" position="0 0 0" shadow="receive: true"></a-box>
<a-sky id="sky" color="#b8e6ff"></a-sky>
<!-- Player Rig and Camera -->
<a-entity id="playerRig" position="0 1.6 22">
<a-camera id="playerCam" wasd-controls="acceleration:36" look-controls="pointerLockEnabled: true"></a-camera>
</a-entity>
<!-- Lights -->
<a-light id="dirlight" type="directional" color="#fff" intensity="1.13" position="10 20 10" castShadow="true"></a-light>
<a-light id="ambientlight" type="ambient" color="#b8e6ff" intensity="0.62"></a-light>
<!-- Character Models (Marichan, Marianne, Elina) -->
<!-- 真理 -->
<a-entity id="marichan" position="0 0.1 22">
<a-sphere position="0 1.7 0" radius="0.43" color="#f44"></a-sphere>
<a-torus position="0 1.95 0" radius="0.25" radius-tubular="0.06" color="#e44" rotation="90 0 0"></a-torus>
<a-cylinder position="0 1.1 0" radius="0.21" height="1.1" color="#222"></a-cylinder>
<a-sphere position="0.21 1.45 0" radius="0.09" color="#ff8888"></a-sphere>
<a-sphere position="-0.21 1.45 0" radius="0.09" color="#ff8888"></a-sphere>
<a-cylinder position="0.36 1.1 0" radius="0.06" height="0.5" color="#f99" rotation="0 0 22"></a-cylinder>
<a-cylinder position="-0.36 1.1 0" radius="0.06" height="0.5" color="#f99" rotation="0 0 -22"></a-cylinder>
<a-cylinder position="0.11 0.35 0" radius="0.07" height="0.47" color="#222" rotation="0 0 13"></a-cylinder>
<a-cylinder position="-0.11 0.35 0" radius="0.07" height="0.47" color="#222" rotation="0 0 -13"></a-cylinder>
<a-box position="0.37 1.0 0.12" width="0.07" height="0.5" depth="0.07" color="#222"></a-box>
<a-box position="0.37 1.27 0.12" width="0.05" height="0.18" depth="0.05" color="#bfb"></a-box>
<a-sphere position="0.12 1.82 0.39" radius="0.04" color="#fff"></a-sphere>
<a-sphere position="-0.12 1.82 0.39" radius="0.04" color="#fff"></a-sphere>
<a-sphere position="0.12 1.82 0.43" radius="0.018" color="#111"></a-sphere>
<a-sphere position="-0.12 1.82 0.43" radius="0.018" color="#111"></a-sphere>
<a-torus position="0 1.71 0.43" radius="0.07" radius-tubular="0.011" color="#e88" rotation="90 0 0"></a-torus>
<a-text value="真理" color="#fff" position="0 2.23 0" align="center" width="7"></a-text>
</a-entity>
<!-- マリアンヌ -->
<a-entity id="marianne" position="2.5 0.1 22">
<a-sphere position="0 1.7 0" radius="0.42" color="#ffe89b"></a-sphere>
<a-cone position="0 2.18 0" radius-bottom="0.13" radius-top="0.01" height="0.23" color="#ffd700"></a-cone>
<a-cylinder position="0 1.05 0" radius="0.21" height="1.1" color="#e4e7f4"></a-cylinder>
<a-cylinder position="0.28 1.12 0.09" radius="0.06" height="0.54" color="#eac" rotation="0 0 24"></a-cylinder>
<a-cylinder position="-0.28 1.12 0.09" radius="0.06" height="0.54" color="#eac" rotation="0 0 -24"></a-cylinder>
<a-box position="0.02 1.7 0.28" width="0.10" height="0.03" depth="0.01" color="#22f"></a-box>
<a-text value="マリアンヌ" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
</a-entity>
<!-- エリナ -->
<a-entity id="elina" position="-2.5 0.1 22">
<a-sphere position="0 1.7 0" radius="0.42" color="#70e59b"></a-sphere>
<a-cylinder position="0 1.05 0" radius="0.21" height="1.1" color="#e3ffe3"></a-cylinder>
<a-cone position="0.32 1.85 0" radius-bottom="0.06" radius-top="0.01" height="0.18" color="#fff" rotation="0 0 50"></a-cone>
<a-cone position="-0.32 1.85 0" radius-bottom="0.06" radius-top="0.01" height="0.18" color="#fff" rotation="0 0 -50"></a-cone>
<a-text value="エリナ" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
</a-entity>
<!-- Enhanced Town (街) Graphics - Position: 0 0 22 -->
<a-entity id="town-scene" position="0 0 22">
<!-- Main Building 1 (Guild) -->
<a-box position="-8 2.5 -5" width="6" height="5" depth="6" color="#a0522d" shadow="cast: true"></a-box>
<a-cone position="-8 5.5 -5" radius-bottom="4" height="3" color="#8b4513" rotation="0 45 0" shadow="cast: true"></a-cone>
<a-text value="GUILD" color="#333" position="-8 4.5 -1.9" align="center" width="10"></a-text>
<!-- Main Building 2 (Tavern) -->
<a-box position="8 2.5 -5" width="6" height="5" depth="6" color="#a0522d" shadow="cast: true"></a-box>
<a-cone position="8 5.5 -5" radius-bottom="4" height="3" color="#8b4513" rotation="0 -45 0" shadow="cast: true"></a-cone>
<a-text value="TAVERN" color="#333" position="8 4.5 -1.9" align="center" width="10"></a-text>
<!-- Inn -->
<a-box position="0 2.5 -10" width="8" height="5" depth="7" color="#deb887" shadow="cast: true"></a-box>
<a-cone position="0 5.5 -10" radius-bottom="5" height="3" color="#cd853f" shadow="cast: true"></a-cone>
<a-text value="INN" color="#333" position="0 4.5 -6.4" align="center" width="10"></a-text>
<!-- Shop -->
<a-box id="town-shop" position="12 2.5 0" width="5" height="5" depth="5" color="#8fbc8f" shadow="cast: true"></a-box>
<a-pyramid position="12 5.5 0" width="5" height="3" depth="5" color="#6b8e23" shadow="cast: true"></a-pyramid>
<a-text value="SHOP" color="#333" position="12 4.5 2.6" align="center" width="10"></a-text>
<!-- Fountain -->
<a-cylinder position="0 0.5 0" radius="2" height="1" color="#4682b4" shadow="cast: true"></a-cylinder>
<a-cylinder position="0 1.5 0" radius="0.5" height="1" color="#add8e6" shadow="cast: true"></a-cylinder>
<a-sphere position="0 2.5 0" radius="0.3" color="#add8e6" shadow="cast: true"></a-sphere>
<!-- Trees -->
<a-cylinder position="-10 2.5 5" radius="0.5" height="5" color="#8b4513" shadow="cast: true"></a-cylinder>
<a-cone position="-10 6.5 5" radius-bottom="2" height="4" color="#228b22" shadow="cast: true"></a-cone>
<a-cylinder position="10 2.5 5" radius="0.5" height="5" color="#8b4513" shadow="cast: true"></a-cylinder>
<a-cone position="10 6.5 5" radius-bottom="2" height="4" color="#228b22" shadow="cast: true"></a-cone>
<!-- Town NPC -->
<a-entity id="town-npc-1" position="-5 0.1 0" class="interactable-npc">
<a-sphere position="0 1.7 0" radius="0.4" color="#f0e68c"></a-sphere> <!-- Head -->
<a-cylinder position="0 1.0 0" radius="0.2" height="1.2" color="#808080"></a-cylinder> <!-- Body -->
<a-text value="村人" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
</a-entity>
</a-entity>
<!-- Enhanced Home (自宅) Graphics - Position: 18 0 31 -->
<a-entity id="home-scene" position="18 0 31">
<!-- House Body -->
<a-box position="0 2.5 0" width="8" height="5" depth="8" color="#d2b48c" shadow="cast: true"></a-box>
<!-- Roof -->
<a-box position="0 5.5 0" width="9" height="0.5" depth="9" color="#8b4513" rotation="0 45 0" shadow="cast: true"></a-box>
<a-cone position="0 7.5 0" radius-bottom="5" height="4" color="#8b4513" shadow="cast: true"></a-cone>
<!-- Chimney -->
<a-box position="3 7 3" width="1" height="3" depth="1" color="#654321" shadow="cast: true"></a-box>
<!-- Door -->
<a-box position="0 1.5 4.01" width="2" height="3" depth="0.1" color="#654321"></a-box>
<!-- Windows -->
<a-box position="3 2.5 4.01" width="1.5" height="2" depth="0.1" color="#87ceeb"></a-box>
<a-box position="-3 2.5 4.01" width="1.5" height="2" depth="0.1" color="#87ceeb"></a-box>
<a-box position="4.01 2.5 0" width="0.1" height="2" depth="1.5" rotation="0 90 0" color="#87ceeb"></a-box>
<!-- Small Garden & Fence -->
<a-plane position="0 0.01 6" width="10" height="4" rotation="-90 0 0" color="#556b2f" shadow="receive: true"></a-plane>
<a-sphere position="2 0.5 6" radius="0.5" color="#ff69b4"></a-sphere>
<a-sphere position="-2 0.5 6" radius="0.5" color="#ff69b4"></a-sphere>
<a-cylinder position="0 0.5 7" radius="0.3" height="1" color="#8b4513"></a-cylinder>
<a-cone position="0 1.5 7" radius-bottom="1" height="2" color="#228b22"></a-cone>
<!-- Fence -->
<a-box position="0 0.5 8" width="10" height="1" depth="0.1" color="#a0522d"></a-box>
<a-box position="4.9 0.5 6" width="0.1" height="1" depth="4" color="#a0522d"></a-box>
<a-box position="-4.9 0.5 6" width="0.1" height="1" depth="4" color="#a0522d"></a-box>
<!-- Interior elements (simple) -->
<a-box position="0 0.7 -2" width="6" height="0.5" depth="3" color="#a0522d"></a-box> <!-- Floor inside -->
<a-box position="2 1.5 -3" width="1.5" height="1" depth="0.8" color="#654321"></a-box> <!-- Table -->
<a-box position="2.5 0.7 -2.5" width="0.5" height="0.8" depth="0.5" color="#654321"></a-box> <!-- Chair -->
<a-box position="-2 1.5 -3.8" width="3" height="0.5" depth="1.5" color="#8b4513"></a-box> <!-- Bed -->
</a-entity>
<!-- Enhanced Dungeon Entrance (ダンジョン入口) Graphics - Position: -28 0 41 -->
<a-entity id="dungeon-scene" position="-28 0 41">
<!-- Cave Entrance Arch (more rugged) -->
<a-torus position="0 3 0" radius="4.5" radius-tubular="1.5" arc="180" rotation="90 0 0" color="#444" shadow="cast: true"></a-torus>
<a-box position="0 0.5 0" width="9" height="1.5" depth="1.5" color="#444" shadow="cast: true"></a-box>
<!-- Cave Interior (deeper, darker) -->
<a-box position="0 2.5 -8" width="12" height="6" depth="15" color="#222" shadow="cast: true" material="shader: flat; side: back"></a-box>
<!-- Jagged Rocks around entrance -->
<a-tetrahedron position="4 1.5 1" radius="1.5" color="#555" rotation="0 45 0" shadow="cast: true"></a-tetrahedron>
<a-tetrahedron position="-4 1.5 1" radius="1.5" color="#555" rotation="0 -45 0" shadow="cast: true"></a-tetrahedron>
<a-box position="0 0.8 2" width="6" height="1.5" depth="2" color="#555" rotation="0 15 0" shadow="cast: true"></a-box>
<!-- Pillars (more textured) -->
<a-cylinder position="3.5 2.5 3" radius="0.9" height="5" color="#666" shadow="cast: true"></a-cylinder>
<a-cylinder position="-3.5 2.5 3" radius="0.9" height="5" color="#666" shadow="cast: true"></a-cylinder>
<!-- Glowing Crystals (more prominent) -->
<a-dodecahedron position="5.5 1.8 -5" radius="0.9" color="#0ff" material="emissive: #0ff; emissiveIntensity: 1.2"></a-dodecahedron>
<a-dodecahedron position="-5.5 1.8 -5" radius="0.9" color="#0ff" material="emissive: #0ff; emissiveIntensity: 1.2"></a-dodecahedron>
<!-- Eerie light from deep within -->
<a-light type="point" color="#0ff" intensity="0.5" position="0 2 -10"></a-light>
<!-- Dungeon Enemy (Slime) -->
<a-entity id="dungeon-slime-1" position="3 0.5 -5" class="interactable-enemy">
<a-sphere position="0 0.5 0" radius="0.5" color="#8a2be2" opacity="0.7"></a-sphere>
<a-text value="スライム" color="#fff" position="0 1.2 0" align="center" width="5"></a-text>
</a-entity>
</a-entity>
<!-- Enhanced Large Castle (大城) Graphics - Position: 0 0 65 -->
<a-entity id="castle-scene" position="0 0 65">
<!-- Main Castle Wall (more imposing) -->
<a-box position="0 6 0" width="40" height="12" depth="20" color="#808080" shadow="cast: true"></a-box>
<!-- Battlements on main wall -->
<a-box position="18 12.5 0" width="2" height="1" depth="20" color="#696969" shadow="cast: true"></a-box>
<a-box position="-18 12.5 0" width="2" height="1" depth="20" color="#696969" shadow="cast: true"></a-box>
<a-box position="0 12.5 8" width="40" height="1" depth="2" color="#696969" shadow="cast: true"></a-box>
<a-box position="0 12.5 -8" width="40" height="1" depth="2" color="#696969" shadow="cast: true"></a-box>
<!-- Main Tower 1 (taller, more detailed) -->
<a-cylinder position="15 12 0" radius="4" height="24" color="#777" shadow="cast: true"></a-cylinder>
<a-cone position="15 25 0" radius-bottom="4.5" height="6" color="#555" shadow="cast: true"></a-cone>
<!-- Tower 1 Battlements -->
<a-box position="15 24.5 0" width="9" height="1" depth="9" color="#696969" rotation="0 45 0" shadow="cast: true"></a-box>
<!-- Main Tower 2 (taller, more detailed) -->
<a-cylinder position="-15 12 0" radius="4" height="24" color="#777" shadow="cast: true"></a-cylinder>
<a-cone position="-15 25 0" radius-bottom="4.5" height="6" color="#555" shadow="cast: true"></a-cone>
<!-- Tower 2 Battlements -->
<a-box position="-15 24.5 0" width="9" height="1" depth="9" color="#696969" rotation="0 45 0" shadow="cast: true"></a-box>
<!-- Gate (more complex drawbridge style) -->
<a-box position="0 4 -9.9" width="10" height="8" depth="0.2" color="#444" shadow="cast: true"></a-box>
<a-box position="0 9 -9.9" width="12" height="2" depth="0.2" color="#444" shadow="cast: true"></a-box>
<a-box position="0 11 -9.9" width="14" height="1" depth="0.2" color="#444" shadow="cast: true"></a-box>
<!-- Drawbridge elements -->
<a-box position="0 0.5 -9.8" width="8" height="1" depth="0.1" color="#654321" rotation="0 0 0"></a-box>
<a-box position="4.5 5 -9.8" width="0.2" height="10" depth="0.2" color="#333" rotation="0 0 15"></a-box>
<a-box position="-4.5 5 -9.8" width="0.2" height="10" depth="0.2" color="#333" rotation="0 0 -15"></a-box>
<!-- Flags on towers -->
<a-box position="15 28 0" width="0.6" height="4" depth="0.6" color="#8b4513"></a-box>
<a-plane position="15 30 1.8" width="2.5" height="1.8" rotation="0 90 0" color="#ffd700"></a-plane>
<a-box position="-15 28 0" width="0.6" height="4" depth="0.6" color="#8b4513"></a-box>
<a-plane position="-15 30 1.8" width="2.5" height="1.8" rotation="0 90 0" color="#ffd700"></a-plane>
</a-entity>
<!-- Field Enemy (Slime) -->
<a-entity id="field-slime-1" position="10 0.1 10" class="interactable-enemy">
<a-sphere position="0 0.5 0" radius="0.5" color="#00ff00" opacity="0.7"></a-sphere>
<a-text value="スライム" color="#fff" position="0 1.2 0" align="center" width="5"></a-text>
</a-entity>
<!-- Boss Placeholder (for evo, ogre, dragon) -->
<a-entity id="bossmon" visible="false"></a-entity>
</a-scene>
<script>
// UI Elements
const dialogue = document.getElementById('dialogue');
const partyStatus = document.getElementById('partyStatus');
const hint = document.getElementById('hint');
const warpBtns = document.getElementById('warpBtns');
const shopDiv = document.getElementById('shopDiv');
const questClear = document.getElementById('questClear');
const battleEffect = document.getElementById('battleEffect');
const overlay = document.getElementById('overlay');
// Game State
let state = {
party: [
{name:'真理', lv:5, hp:60, maxhp:60, alive:true, job:'剣姫', skills:['ファイアーボール','ファイアーイノセント']},
{name:'マリアンヌ', lv:5, hp:50, maxhp:50, alive:true, job:'魔法姫', skills:['ファイアーボール']},
{name:'エリナ', lv:5, hp:46, maxhp:46, alive:true, job:'エルフ賢者', skills:['アクアスパイク']}
],
gold:3000,
inventory: [],
currentStoryStep: "intro_01", // Current point in the story
lastPlayerPosition: {x:0, y:1.6, z:22}, // To track movement for progress
shopOpen: false,
inBattle: false,
evoCleared:false, ogreCleared:false, dragonCleared:false,
evoTry:0, evoLv:15, ogreLv:20, dragonLv:30,
};
// Warp Points (now with more descriptive names for UI)
const warpPoints = [
{name:"街", pos:{x:0, y:1.6, z:22}},
{name:"自宅", pos:{x:18, y:1.6, z:31}},
{name:"ダンジョン入口", pos:{x:-28, y:1.6, z:41}},
{name:"大城", pos:{x:0, y:1.6, z:65}}
];
// Shop Items
const shopItems = [
{id: 'potion', name: 'ポーション', price: 100, type: 'consumable', effect: 'HP回復'},
{id: 'longsword', name: 'ロングソード', price: 500, type: 'weapon', atk: 10},
{id: 'ironshield', name: 'アイアンシールド', price: 300, type: 'armor', def: 5}
];
// Story Data (structured for branching)
const storyData = {
"intro_01": { speaker: "宅間", text: "あ~暇だなネットサーフィンでもするか", next: "intro_02" },
"intro_02": { speaker: "Narration", text: "新作VR機器を注文して翌日届いた。", next: "intro_03" },
"intro_03": { speaker: "宅間", text: "ふひひ!幼女になれた!俺は幼女なんだ", next: "intro_04" },
"intro_04": { speaker: "Narration", text: "ハンドルネームを真理にしてログイン。最初の街ポルッドに着く。", next: "marianne_meet_01" },
"marianne_meet_01": { speaker: "マリアンヌ", text: "かわいいわね私はマリアンヌっていうの", next: "marianne_meet_02" },
"marianne_meet_02": { speaker: "真理", text: "お主、なかなかの美少女でござるなぁ…", next: "marianne_meet_03" },
"marianne_meet_03": { speaker: "マリアンヌ", text: "もしかして中身キモヲタのおっさん!?", next: "marianne_meet_04" },
"marianne_meet_04": { speaker: "真理", text: "うるちゃい!俺は幼女なのだぁ…!", next: "marianne_meet_05" },
"marianne_meet_05": { speaker: "マリアンヌ", text: "まあ見た目かわいいから一緒に行くか", next: "to_castle_01" },
"to_castle_01": { speaker: "Narration", text: "真理はだんだん幼女化していった。お城へ。", next: "king_meet_01" },
"king_meet_01": { speaker: "王様", text: "おお、マリアンヌ何処に行ってたのだ?おやその子は?", next: "king_meet_02" },
"king_meet_02": { speaker: "真理", text: "真理なのじゃ!!", next: "king_meet_03" },
"king_meet_03": { speaker: "王様", text: "何と可愛らしい…", next: "king_meet_04" },
"king_meet_04": { speaker: "マリアンヌ", text: "私決めたわ!この子と一緒に旅することに!", next: "king_meet_05" },
"king_meet_05": { speaker: "王様", text: "それは行かん!", next: "king_meet_06" },
"king_meet_06": { speaker: "真理", text: "行くのじゃ!", next: "leave_castle_01" },
"leave_castle_01": { speaker: "Narration", text: "マリアンヌの手を繋いで城を出た。", next: "leave_castle_02" },
"leave_castle_02": { speaker: "マリアンヌ", text: "ちょっと待ちなさいよ城から出て行く当てでもあるの?", next: "leave_castle_03" },
"leave_castle_03": { speaker: "真理", text: "次の街を探してみようと思うのじゃ", next: "leave_castle_04" },
"leave_castle_04": { speaker: "マリアンヌ", text: "ここから一番近くの街って言ったらイデルかしら", next: "leave_castle_05" },
"leave_castle_05": { speaker: "真理", text: "イデルってどういった場所なのかの?", next: "leave_castle_06" },
"leave_castle_06": { speaker: "マリアンヌ", text: "知らないわよ私街から出たことないし…", next: "leave_castle_07" },
"leave_castle_07": { speaker: "真理", text: "それじゃお試しでいくのじゃ!", next: "rest_01" },
"rest_01": { speaker: "Narration", text: "焚火→テントで休憩→朝", next: "start_journey_01" },
"start_journey_01": { speaker: "真理", text: "それじゃ向かうのじゃ!", next: "slime_appear_01" },
"slime_appear_01": { speaker: "Narration", text: "フィールドに出るとスライムが現れた", next: "battle_slime_01" },
"battle_slime_01": {
speaker: "真理",
text: "ファイアーボール!!",
onComplete: () => { startBattle('slime'); }, // Trigger battle function
next: "slime_defeated_01"
},
"slime_defeated_01": { speaker: "Narration", text: "真理の初級呪文でスライムを倒した", next: "marianne_magic_01" },
"marianne_magic_01": { speaker: "マリアンヌ", text: "へぇあんた呪文使えるのね", next: "marianne_magic_02" },
"marianne_magic_02": { speaker: "真理", text: "必要ならば教えてあげることも可能なのじゃ", next: "marianne_magic_03" },
"marianne_magic_03": { speaker: "マリアンヌ", text: "教えて教えて!", next: "marianne_magic_04" },
"marianne_magic_04": { speaker: "真理", text: "しょうがないのう", next: "marianne_magic_05" },
"marianne_magic_05": { speaker: "Narration", text: "真理はマリアンヌに初級魔術を教えた。", next: "marianne_magic_06" },
"marianne_magic_06": { speaker: "マリアンヌ", text: "こうですの?", next: "marianne_magic_07" },
"marianne_magic_07": { speaker: "Narration", text: "炎の魔術を繰り出した", next: "marianne_magic_08" },
"marianne_magic_08": { speaker: "真理", text: "そうそう", next: "marianne_magic_09" },
"marianne_magic_09": { speaker: "マリアンヌ", text: "結構簡単でしたね", next: "marianne_magic_10" },
"marianne_magic_10": { speaker: "真理", text: "初級魔術だからね今から魔術書で中級を覚える予定", next: "master_mid_magic_01" },
"master_mid_magic_01": { speaker: "Narration", text: "次の日、中級魔術をマスター", next: "ogre_appear_01" },
"ogre_appear_01": { speaker: "真理", text: "ファイアーイノセント!!", next: "ogre_defeated_01" },
"ogre_defeated_01": { speaker: "Narration", text: "巨大なオークを丸焼きに", next: "ogre_defeated_02" },
"ogre_defeated_02": { speaker: "マリアンヌ", text: "オークを一撃でやるなんてすごいわね", next: "ogre_defeated_03" },
"ogre_defeated_03": { speaker: "真理", text: "いつの間にかに強くなってたのじゃ", next: "dungeon_found_01" },
"dungeon_found_01": { speaker: "Narration", text: "洞窟を発見。中へ入る。", next: "dungeon_intro_01" },
"dungeon_intro_01": { speaker: "マリアンヌ", text: "どうやらここは本で書いてあった始まりの洞窟のようね", next: "lizardman_appear_01" },
"lizardman_appear_01": { speaker: "Narration", text: "リザードマンが出現!", next: "lizardman_battle_01" },
"lizardman_battle_01": { speaker: "真理", text: "リザードマンなのじゃ気を付けるのじゃ", next: "lizardman_battle_02" },
"lizardman_battle_02": { speaker: "マリアンヌ", text: "ファイアーボール!!", next: "lizardman_battle_03" },
"lizardman_battle_03": { speaker: "Narration", text: "リザードマン「兄貴!尻尾が燃えてますぜ」", next: "lizardman_battle_04" },
"lizardman_battle_04": { speaker: "真理", text: "もっと燃やしてやるのじゃ", next: "lizardman_defeated_01" },
"lizardman_defeated_01": { speaker: "Narration", text: "炎の魔術でリザードマン撃破!", next: "lizardman_defeated_02" },
"lizardman_defeated_02": { speaker: "マリアンヌ", text: "やるじゃない私も早く中級魔術使えるようになりたいわ", next: "lizardman_defeated_03" },
"lizardman_defeated_03": { speaker: "真理", text: "今日の夜にでも教えてあげるのじゃ", next: "treasure_chest_01" },
"treasure_chest_01": { speaker: "Narration", text: "洞窟の分岐で宝箱(ロングソード)を発見。", next: "treasure_chest_02" },
"treasure_chest_02": { speaker: "真理", text: "やった宝箱なのじゃー!", next: "treasure_chest_03" },
"treasure_chest_03": { speaker: "マリアンヌ", text: "中身は何かしらね", next: "treasure_chest_04" },
"treasure_chest_04": { speaker: "Narration", text: "中身はロングソードだった", next: "treasure_chest_05" },
"treasure_chest_05": { speaker: "真理", text: "何だ剣か…", next: "treasure_chest_06" },
"treasure_chest_06": { speaker: "マリアンヌ", text: "杖の方が良かったわねでもせっかくだから装備して見ましょう", next: "sword_lv_up_01" },
"sword_lv_up_01": { speaker: "Narration", text: "剣術レベル1になった", next: "sword_lv_up_02" },
"sword_lv_up_02": { speaker: "真理", text: "剣をもっと使いこなしたいのう", next: "sword_lv_up_03" },
"sword_lv_up_03": { speaker: "マリアンヌ", text: "剣術が出来る人を探すしかないわね", next: "exit_dungeon_01" },
"exit_dungeon_01": { speaker: "Narration", text: "洞窟出口→外に出る。", next: "exit_dungeon_02" },
"exit_dungeon_02": { speaker: "真理", text: "外に出たのじゃ!!", next: "idel_arrival_01" },
"idel_arrival_01": { speaker: "Narration", text: "街イデルに到着。", next: "idel_guide_01" },
"idel_guide_01": { speaker: "案内人", text: "ようこそイデルへ!ここは仲間を集めたりする酒場や、クエストを受けられる冒険者ギルドがあります", next: "idel_guide_02" },
"idel_guide_02": { speaker: "真理", text: "それじゃあまずは酒場で情報収集と仲間集めと行きますか", next: "idel_guide_03" },
"idel_guide_03": { speaker: "マリアンヌ", text: "どんな人がいるかしらね", next: "elina_appear_01" },
"elina_appear_01": { speaker: "Narration", text: "酒場でエリナ登場。", next: "elina_join_01" },
"elina_join_01": { speaker: "真理", text: "そこの美しいお方、わらわ達と冒険しませんか?", next: "elina_join_02" },
"elina_join_02": { speaker: "エリナ", text: "私はエリナ・カリオットよ見ての通りエルフで魔術師をやっているわ", next: "elina_join_03" },
"elina_join_03": { speaker: "真理", text: "よかったら仲間になってくれませんか?", next: "elina_join_04" },
"elina_join_04": { speaker: "エリナ", text: "クエストを手伝ってくれたら考えてもいいわよ", next: "elina_join_05" },
"elina_join_05": { speaker: "真理", text: "わーい手伝う手伝う!", next: "to_guild_with_elina_01" },
"to_guild_with_elina_01": { speaker: "Narration", text: "エリナと共に冒険者ギルドへ。", next: "gigborg_quest_01" },
"gigborg_quest_01": { speaker: "エリナ", text: "あなた達にはギグボルグの討伐をやって貰うわ", next: "gigborg_quest_02" },
"gigborg_quest_02": { speaker: "真理", text: "どんなモンスターかわからにゃいけど頑張るにゃ", next: "gigborg_quest_03" },
"gigborg_quest_03": { speaker: "マリアンヌ", text: "何でちょっと猫語になってんの", next: "gigborg_quest_04" },
"gigborg_quest_04": { speaker: "真理", text: "ふにゃ??分からないにゃ", next: "gigborg_quest_05" },
"gigborg_quest_05": { speaker: "エリナ", text: "場所は始まりの塔よ。北にあるわ。", next: "to_tower_01" },
"to_tower_01": { speaker: "Narration", text: "始まりの塔へ向かう。", next: "goblin_battle_01" },
"goblin_battle_01": { speaker: "Narration", text: "始まりの塔でゴブリン達とバトル!", next: "goblin_battle_02" },
"goblin_battle_02": { speaker: "真理", text: "ファイアーイノセント!!", onComplete: () => { startBattle('goblin'); }, next: "goblin_defeated_01" },
"goblin_defeated_01": { speaker: "Narration", text: "ゴブリンたちは焼き尽くされた。", next: "open_door_01" },
"open_door_01": { speaker: "Narration", text: "塔の中でアイテム端末を使い扉を開く。", next: "boss_warning_01" },
"boss_warning_01": { speaker: "冒険者", text: "この先ボス部屋があるぞ強いから注意しておけ", next: "gigborg_boss_01" },
"gigborg_boss_01": { speaker: "Narration", text: "ボス部屋でギグボルグLv10戦。", next: "gigborg_boss_02" },
"gigborg_boss_02": { speaker: "マリアンヌ", text: "ファイアーボール!!", next: "gigborg_boss_03" },
"gigborg_boss_03": { speaker: "エリナ", text: "アクアスパイク!!", next: "gigborg_boss_04" },
"gigborg_boss_04": { speaker: "Narration", text: "ギグボルグの斧で全滅→イデルで復活。", next: "level_up_needed_01" },
"level_up_needed_01": { speaker: "マリアンヌ", text: "やられたみたいねレベル上げでもしましょ", next: "goblin_hunt_01" },
"goblin_hunt_01": { speaker: "Narration", text: "西の森でゴブリン狩りLv10にアップ。", next: "lightning_vortex_01" },
"lightning_vortex_01": { speaker: "真理", text: "ライトニングヴォルテックスを覚えた!", next: "gigborg_retry_01" },
"gigborg_retry_01": { speaker: "Narration", text: "再挑戦でギグボルグ撃破。", next: "gigborg_defeated_01" },
"gigborg_defeated_01": { speaker: "マリアンヌ", text: "やったわ!ギグボルグ撃破よあんたおっさんの癖になかなかやるわね", next: "gigborg_defeated_02" },
"gigborg_defeated_02": { speaker: "真理", text: "おっさん言うな!どうみてもかわいい女の子じゃろ?", next: "gigborg_defeated_03" },
"gigborg_defeated_03": { speaker: "マリアンヌ", text: "そ、そうね…", next: "elina_officially_join_01" },
"elina_officially_join_01": { speaker: "エリナ", text: "約束通り仲間になってあげるわよ", next: "elina_officially_join_02" },
"elina_officially_join_02": { speaker: "真理", text: "よろしくなのじゃ", next: "evolvia_quest_01" },
"evolvia_quest_01": { speaker: "エリナ", text: "次はエボルビア討伐に行くわよ", next: "evolvia_quest_02" },
"evolvia_quest_02": { speaker: "Narration", text: "海にいる怪物エボルビア討伐へ。", next: "evolvia_battle_01" },
"evolvia_battle_01": { speaker: "真理", text: "ライトニングヴォルテックス!", next: "evolvia_retreat_01" },
"evolvia_retreat_01": { speaker: "Narration", text: "レベル差で撤退、レベル上げ&アイテム集め。", next: "lightning_judgment_01" },
"lightning_judgment_01": { speaker: "Narration", text: "Lv20到達でライトニングジャッジメントを覚える。", next: "evolvia_rematch_01" },
"evolvia_rematch_01": { speaker: "真理", text: "エボルビアを倒しに行くわよ", next: "evolvia_rematch_02" },
"evolvia_rematch_02": { speaker: "マリアンヌ", text: "今度こそ倒すわよ", next: "evolvia_rematch_03" },
"evolvia_rematch_03": { speaker: "真理", text: "ライトニングジャッジメント!!", next: "evolvia_defeated_01" },
"evolvia_defeated_01": { speaker: "Narration", text: "エボルビア討伐、クエストクリアー。", next: "quest_clear_effect_01" },
"quest_clear_effect_01": { speaker: "エリナ", text: "おっしゃークリア!お疲れ!", onComplete: () => { showQuestClear(); }, next: "ogre_quest_01" },
"ogre_quest_01": { speaker: "Narration", text: "オーガ討伐→オーガの爪1万Gで売却。", next: "buy_equip_01" },
"buy_equip_01": { speaker: "Narration", text: "武器・防具を買い強化。宿屋で体力回復。", next: "rank_up_quest_01" },
"rank_up_quest_01": { speaker: "受付の人", text: "現在のランクはEクラスです。ランクアップクエストはドラゴン討伐です。", next: "dragon_quest_01" },
"dragon_quest_01": { speaker: "真理", text: "ドラゴンの討伐いくのじゃ", next: "dragon_quest_02" },
"dragon_quest_02": { speaker: "Narration", text: "竜の山へピクニック→ドラゴンの巣で爆弾を設置して爆破!", next: "dragon_quest_03" },
"dragon_quest_03": { speaker: "真理", text: "少々やりすぎたのじゃ", next: "rank_up_d_01" },
"rank_up_d_01": { speaker: "Narration", text: "イデルでクエスト報告、Dランクにアップ&飛空艇GET。", next: "d_rank_quest_01" },
"d_rank_quest_01": { speaker: "マリアンヌ", text: "Dランクのクエストも受けておきましょ", next: "hero_tower_01" },
"hero_tower_01": { speaker: "Narration", text: "飛空艇で英雄の塔→闇の騎士ボス戦", next: "dark_knight_battle_01" },
"dark_knight_battle_01": { speaker: "マリアンヌ", text: "受け取って!", next: "dark_knight_battle_02" },
"dark_knight_battle_02": { speaker: "Narration", text: "マリアンヌが剣を投げ真理が闇の騎士を討つ。", next: "dark_knight_defeated_01" },
"dark_knight_defeated_01": { speaker: "真理", text: "どうじゃ?わらわの力は?", next: "dark_knight_defeated_02" },
"dark_knight_defeated_02": { speaker: "マリアンヌ", text: "あの時、私が剣を渡してなかったら死んでたわよ", next: "dark_knight_defeated_03" },
"dark_knight_defeated_03": { speaker: "真理", text: "それは感謝してるのじゃ", next: "sky_castle_01" },
"sky_castle_01": { speaker: "Narration", text: "塔の景色→空に浮かぶ城エリクッドへ。", next: "angel_meet_01" },
"angel_meet_01": { speaker: "天使", text: "ようこそエリクッドへ", next: "king_attacked_01" },
"king_attacked_01": { speaker: "王様", text: "今エリクッドは魔界の悪魔の攻撃を受けている", next: "demon_realm_quest_01" },
"demon_realm_quest_01": { speaker: "真理", text: "んじゃわらわたちが魔界に行って悪魔を倒してやるのじゃ!", next: "demon_realm_quest_02" },
"demon_realm_quest_02": { speaker: "王様", text: "異界の門はここからずっと北にある", next: "to_demon_gate_01" },
"to_demon_gate_01": { speaker: "Narration", text: "異界の門へ→ワイバーン撃破→サタン戦", next: "satan_battle_01" },
"satan_battle_01": { speaker: "サタン", text: "ここから先は通さないぜ", next: "satan_battle_02" },
"satan_battle_02": { speaker: "真理", text: "サンダージャッジメント!!", next: "satan_battle_03" },
"satan_battle_03": { speaker: "エリナ", text: "プラチナソード!!", next: "satan_defeated_01" },
"satan_defeated_01": { speaker: "Narration", text: "サタン討伐、魔界へ突入!", next: "demon_realm_arrival_01" },
"demon_realm_arrival_01": { speaker: "案内人", text: "ようこそ、魔界の街モルドへ", next: "demon_realm_arrival_02" },
"demon_realm_arrival_02": { speaker: "マリアンヌ", text: "ここの悪魔達は敵対心がないようね", next: "demon_realm_arrival_03" },
"demon_realm_arrival_03": { speaker: "真理", text: "もう歩き疲れたのじゃ宿屋に泊りたいのじゃ~", next: "demon_castle_01" },
"demon_castle_01": { speaker: "Narration", text: "魔界の城を目指しゾンビ戦→エリナが撃破!", next: "elina_defeat_zombie_01" },
"elina_defeat_zombie_01": { speaker: "エリナ", text: "私に任せなさい!!", next: "end_chapter" },
"end_chapter": { speaker: "Narration", text: "次章へ続く…", next: null }
};
let currentStoryId = state.currentStoryStep;
// --- UI Functions ---
function setWarpUI() {
warpBtns.innerHTML = '';
warpPoints.forEach(p=>{
let btn = document.createElement('button');
btn.textContent = p.name + "へ転移";
btn.onclick = ()=>{
document.querySelector('#playerRig').setAttribute('position', `${p.pos.x} ${p.pos.y} ${p.pos.z}`);
closeDialogue(); // Close dialogue on warp
closeShop(); // Close shop on warp
};
warpBtns.appendChild(btn);
});
}
function updatePartyStatus() {
partyStatus.innerHTML = `<b>パーティ</b><br>`;
state.party.forEach(p=>{
partyStatus.innerHTML += `<span class="${p.alive?'alive':'dead'}">${p.name}(Lv${p.lv}) ${p.hp}/${p.maxhp}HP</span><br>`;
});
partyStatus.innerHTML += `<b>G:</b> ${state.gold}<br>`;
partyStatus.innerHTML += `<b>アイテム:</b> ${state.inventory.map(item => item.name).join(', ') || 'なし'}`;
}
// --- Day/Night Cycle ---
let skyColor="#b8e6ff", nightColor="#26305a";
let night=false;
function setDayNight(toNight){
night=toNight;
document.getElementById('sky').setAttribute('color',night?nightColor:skyColor);
document.getElementById('dirlight').setAttribute('intensity',night?0.28:1.13);
document.getElementById('ambientlight').setAttribute('intensity',night?0.16:0.62);
document.getElementById('nightFilter').style.background=night?"rgba(0,8,38,0.53)":"rgba(0,8,38,0.0)";
}
setInterval(()=>{setDayNight(!night);},15000); setDayNight(false);
// --- Dialogue System ---
function showDialogueBlock(dialogueId) {
const data = storyData[dialogueId];
if (!data) {
closeDialogue();
return;
}
dialogue.innerHTML = '';
dialogue.style.display = 'block';
if (data.speaker && data.speaker !== 'Narration') {
const n = document.createElement('span');
n.className = 'name';
n.textContent = data.speaker + ' ';
dialogue.appendChild(n);
}
const t = document.createElement('span');
t.textContent = data.text;
dialogue.appendChild(t);
if (data.choices) {
data.choices.forEach(c => {
const btn = document.createElement('button');
btn.className = 'choice';
btn.textContent = c.text;
btn.onclick = () => {
if (c.onChoose) c.onChoose(); // Execute choice-specific logic
currentStoryId = c.next;
showDialogueBlock(currentStoryId);
};
dialogue.appendChild(btn);
});
} else {
dialogue.onclick = () => {
dialogue.onclick = null; // Prevent multiple clicks
if (data.onComplete) data.onComplete(); // Execute onComplete logic
currentStoryId = data.next;
showDialogueBlock(currentStoryId);
};
}
}
function closeDialogue() {
dialogue.style.display = 'none';
dialogue.onclick = null;
}
// --- Battle System (Simplified) ---
function startBattle(enemyType) {
state.inBattle = true;
battleEffect.style.display = 'block';
// Simulate battle outcome instantly for now
setTimeout(() => {
battleEffect.style.display = 'none';
state.inBattle = false;
// Example: Party takes some damage, or enemy is defeated
state.party.forEach(p => {
if (p.alive) p.hp = Math.max(0, p.hp - 5); // Simulate damage
if (p.hp === 0) p.alive = false;
});
updatePartyStatus();
// Logic for enemy defeat, item drops, XP gain would go here
// For now, the story progresses automatically after the simulated battle
}, 300); // Flash for 0.3 seconds
}
function showQuestClear() {
questClear.style.display = 'block';
setTimeout(() => {
questClear.style.display = 'none';
}, 3000); // Show for 3 seconds
}
// --- Shop System ---
function openShop() {
state.shopOpen = true;
shopDiv.style.display = 'block';
shopDiv.innerHTML = `
<h2>ショップ</h2>
<p>ゴールド: <span id="shopGold">${state.gold}</span>G</p>
<div id="shopItemsContainer"></div>
<button class="close-button" onclick="closeShop()">X</button>
`;
const shopItemsContainer = document.getElementById('shopItemsContainer');
shopItems.forEach(item => {
const itemDiv = document.createElement('div');
itemDiv.className = 'shop-item';
itemDiv.innerHTML = `
<span>${item.name} (${item.price}G)</span>
<button onclick="buyItem('${item.id}')">購入</button>
`;
shopItemsContainer.appendChild(itemDiv);
});
// Add a simple "Sell" option
if (state.inventory.length > 0) {
const sellSection = document.createElement('div');
sellSection.innerHTML = `<h3>持ち物売却</h3>`;
state.inventory.forEach((item, index) => {
sellSection.innerHTML += `
<div class="shop-item">
<span>${item.name} (${Math.floor(item.price / 2)}G)</span>
<button onclick="sellItem(${index})">売却</button>
</div>
`;
});
shopItemsContainer.appendChild(sellSection);
}
}
function buyItem(itemId) {
const item = shopItems.find(i => i.id === itemId);
if (item && state.gold >= item.price) {
state.gold -= item.price;
state.inventory.push(item);
updatePartyStatus();
document.getElementById('shopGold').textContent = state.gold;
// Re-render shop to show updated inventory for selling
openShop();
} else if (item) {
// Using dialogue for messages instead of alert
showDialogueBlock({ speaker: "ショップ店員", text: "ゴールドが足りません!" });
}
}
function sellItem(index) {
if (index >= 0 && index < state.inventory.length) {
const item = state.inventory.splice(index, 1)[0];
state.gold += Math.floor(item.price / 2); // Sell for half price
updatePartyStatus();
document.getElementById('shopGold').textContent = state.gold;
openShop(); // Re-render shop
}
}
function closeShop() {
state.shopOpen = false;
shopDiv.style.display = 'none';
}
// --- Interaction System ---
// Add click listeners to interactable entities
document.addEventListener('DOMContentLoaded', () => {
const scene = document.querySelector('a-scene');
// Event listener for NPC interaction
const townNpc = document.getElementById('town-npc-1');
if (townNpc) {
townNpc.addEventListener('click', () => {
if (!state.inBattle && !state.shopOpen) {
showDialogueBlock({ speaker: "村人", text: "こんにちは、冒険者さん!この街は平和ですよ。" });
}
});
}
// Event listener for Shop interaction
const townShop = document.getElementById('town-shop');
if (townShop) {
townShop.addEventListener('click', () => {
if (!state.inBattle && !state.shopOpen) {
openShop();
}
});
}
// Event listeners for Enemy interaction (Slimes)
const fieldSlime = document.getElementById('field-slime-1');
if (fieldSlime) {
fieldSlime.addEventListener('click', () => {
if (!state.inBattle && !state.shopOpen) {
showDialogueBlock({
speaker: "Narration",
text: "スライムが現れた!戦いますか?",
choices: [
{ text: "戦う", onChoose: () => { startBattle('slime'); fieldSlime.setAttribute('visible', 'false'); }, next: "battle_result_slime" },
{ text: "逃げる", next: "flee_result" }
]
});
}
});
}
const dungeonSlime = document.getElementById('dungeon-slime-1');
if (dungeonSlime) {
dungeonSlime.addEventListener('click', () => {
if (!state.inBattle && !state.shopOpen) {
showDialogueBlock({
speaker: "Narration",
text: "ダンジョンスライムが現れた!戦いますか?",
choices: [
{ text: "戦う", onChoose: () => { startBattle('dungeon-slime'); dungeonSlime.setAttribute('visible', 'false'); }, next: "battle_result_dungeon_slime" },
{ text: "逃げる", next: "flee_result" }
]
});
}
});
}
// Define battle results (simple for now)
storyData["battle_result_slime"] = { speaker: "Narration", text: "スライムを倒した!経験値と10Gを獲得した。", onComplete: () => { state.gold += 10; updatePartyStatus(); }, next: currentStoryId };
storyData["battle_result_dungeon_slime"] = { speaker: "Narration", text: "ダンジョンスライムを倒した!経験値と20Gを獲得した。", onComplete: () => { state.gold += 20; updatePartyStatus(); }, next: currentStoryId };
storyData["flee_result"] = { speaker: "Narration", text: "なんとか逃げ切った。", next: currentStoryId };
// Initial story start
showDialogueBlock(currentStoryId);
});
// --- Main Game Loop for Progress (simplified) ---
function checkProgress() {
updatePartyStatus();
const playerRig = document.querySelector('#playerRig');
if (!playerRig) return;
let pos = playerRig.object3D.position;
// Only progress linear story if no dialogue is open and not in battle/shop
if (dialogue.style.display === 'none' && !state.inBattle && !state.shopOpen) {
// Auto-progress main story
if (storyData[currentStoryId] && !storyData[currentStoryId].choices) {
// Simulate dialogue click if it's not a choice block
if (storyData[currentStoryId].next) {
// This part is tricky with auto-progression.
// For now, the story relies on user clicks, or specific triggers.
// The `onComplete` in storyData handles the actual progression.
}
}
}
// Example: Trigger story based on proximity (can be expanded)
// This is just a basic example, the main story progression is click-driven.
const townCenter = new THREE.Vector3(0, 1.6, 22);
if (pos.distanceTo(townCenter) < 10 && state.currentStoryStep === "intro_04") {
// This is handled by the initial showDialogueBlock, but a more complex game
// might trigger different dialogue based on location.
}
}
setInterval(checkProgress, 700); // Check progress every 0.7 seconds
// Initial setup calls
setWarpUI();
updatePartyStatus();
</script>
</body>
</html>
Verse – 次世代ソーシャルネットワーク
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verse - 次世代ソーシャルネットワーク</title>
<!-- SVG ファビコンを指定 -->
<link rel="shortcut icon" href="favicon.ico">
<!-- PNG版 -->
<link rel="icon" type="image/png" href="favicon.png">
<!-- SVG版 -->
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/rita@3.0.0/dist/rita.min.js"></script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.glass-effect {
background: rgba(255,255,255,0.25);
backdrop-filter: blur(10px);
border-radius: 10px;
border: 1px solid rgba(255,255,255,0.18);
}
.card-hover { transition: all 0.3s ease; }
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
}
.gradient-text {
background: linear-gradient(45deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.timeline-post {
background: white;
border-radius: 15px;
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
transition: all 0.3s ease;
border-left: 4px solid #667eea;
}
.timeline-post:hover {
transform: translateX(5px);
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
}
.profile-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
border: 4px solid white;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.btn-primary {
background: linear-gradient(45deg, #667eea, #764ba2);
border: none;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 7px 14px rgba(0,0,0,0.18);
}
.section-divider {
height: 3px;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 2px;
margin: 2rem 0;
}
.username-badge {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 0.2rem 0.5rem;
border-radius: 1rem;
font-size: 0.75rem;
font-weight: 600;
display: inline-block;
margin-left: 0.5rem;
}
.share-menu {
position: absolute;
z-index: 50;
min-width: 180px;
right: 0;
top: 110%;
background: white;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,0.13);
}
.share-menu button {
width: 100%;
text-align: left;
padding: 10px 20px;
border: none;
background: none;
cursor: pointer;
font-size: 0.95rem;
}
.share-menu button:hover { background: #f0f4ff; }
.status-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.status-active {
background-color: #10b981;
animation: pulse 2s infinite;
}
.status-inactive { background-color: #6b7280; }
.log-container {
max-height: 150px;
overflow-y: auto;
background: rgba(255,255,255,0.1);
border-radius: 5px;
padding: 10px;
margin-top: 10px;
font-size: 0.8rem;
font-family: monospace;
}
.rss-item {
background: rgba(255,255,255,0.1);
border-radius: 5px;
padding: 8px;
margin: 5px 0;
font-size: 0.8rem;
}
.error-message {
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
padding: 8px;
border-radius: 5px;
margin: 5px 0;
}
.success-message {
color: #10b981;
background: rgba(16, 185, 129, 0.1);
padding: 8px;
border-radius: 5px;
margin: 5px 0;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.dark .glass-effect {
background: rgba(0,0,0,0.3);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.1);
}
.dark .timeline-post {
background: #374151;
color: #f9fafb;
}
@media print {
body { background: white !important; -webkit-print-color-adjust: exact; }
.glass-effect { background: white !important; backdrop-filter: none !important; border: 1px solid #e5e7eb !important; }
}
</style>
</head>
<body>
<header class="glass-effect mx-4 mt-4 p-6">
<div class="text-center">
<h1 class="text-4xl font-bold text-white mb-2">
<i class="fas fa-comments mr-3"></i>Verse
<span class="text-sm opacity-75 ml-2">v2.0 完全修正版</span>
</h1>
<p class="text-white text-lg opacity-90">次世代ソーシャルネットワーク</p>
<div class="mt-4 flex justify-center items-center space-x-4">
<div class="flex items-center">
<img id="header-profile-icon" class="profile-avatar" src="https://via.placeholder.com/100" alt="プロフィール">
<div class="ml-3 text-white">
<div class="font-semibold text-lg" id="header-username">未設定</div>
<div class="text-sm opacity-75">ユーザー</div>
</div>
</div>
<button onclick="toggleDarkMode()" class="btn-primary px-4 py-2 rounded-full text-white">
<i class="fas fa-moon mr-2"></i>ダークモード
</button>
<button onclick="showSystemStatus()" class="btn-primary px-4 py-2 rounded-full text-white">
<i class="fas fa-info-circle mr-2"></i>ステータス
</button>
</div>
</div>
</header>
<div class="max-w-6xl mx-auto px-4 py-6">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 左側カラム -->
<div class="lg:col-span-1 space-y-6">
<!-- プロフィール -->
<div class="glass-effect p-6 card-hover">
<h3 class="text-2xl font-bold gradient-text mb-4">
<i class="fas fa-user-circle mr-2"></i>プロフィール
</h3>
<div class="text-center mb-6">
<img id="profile-icon" class="profile-avatar mx-auto mb-4" src="https://via.placeholder.com/100" alt="プロフィール">
<input type="file" id="profile-upload" accept="image/*" onchange="uploadProfileIcon(event)" class="hidden">
<button onclick="document.getElementById('profile-upload').click()" class="btn-primary px-4 py-2 rounded-full text-white">
<i class="fas fa-camera mr-2"></i>画像変更
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-white font-semibold mb-2">ユーザー名</label>
<input type="text" id="username" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" placeholder="ユーザー名を入力してください" maxlength="20">
</div>
<div>
<label class="block text-white font-semibold mb-2">自己紹介</label>
<textarea id="self-intro" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" rows="4" placeholder="自己紹介を入力してください"></textarea>
</div>
<button onclick="saveProfile()" class="btn-primary w-full py-2 rounded-lg text-white">
<i class="fas fa-save mr-2"></i>プロフィール保存
</button>
<div class="p-3 bg-white bg-opacity-80 rounded-lg">
<h5 class="font-semibold text-gray-800 mb-2">プレビュー:</h5>
<div class="text-gray-700">
<div class="font-semibold mb-1" id="username-preview">未設定</div>
<div id="self-intro-preview" class="text-sm whitespace-pre-line min-h-8">まだ自己紹介がありません</div>
</div>
</div>
</div>
</div>
<!-- BOT機能 -->
<div class="glass-effect p-6 card-hover">
<h3 class="text-xl font-bold text-white mb-4">
<i class="fas fa-robot mr-2"></i>BOT機能
<span class="status-indicator" id="bot-status"></span>
<span id="bot-status-text" class="text-xs opacity-75">停止中</span>
</h3>
<div class="space-y-4">
<div>
<textarea id="botContent" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" rows="3" placeholder="BOT投稿内容"></textarea>
<button onclick="postBotMessage()" class="btn-primary w-full mt-2 py-2 rounded-lg text-white">
<i class="fas fa-robot mr-2"></i>BOT投稿
</button>
</div>
<div>
<input type="number" id="botIntervalSec" class="w-full p-2 border rounded-lg bg-white bg-opacity-90" placeholder="マルコフ自動投稿間隔(秒)" min="10" max="3600" value="60">
<div class="flex space-x-2 mt-2">
<button onclick="postMarkovBot()" class="btn-primary flex-1 py-2 rounded-lg text-white">
<i class="fas fa-dice mr-2"></i>マルコフ生成
</button>
<button onclick="startBotAutoPost()" class="btn-primary flex-1 py-2 rounded-lg text-white">
<i class="fas fa-play mr-2"></i>自動開始
</button>
<button onclick="stopBotAutoPost()" class="bg-red-500 hover:bg-red-600 flex-1 py-2 rounded-lg text-white">
<i class="fas fa-stop mr-2"></i>停止
</button>
</div>
</div>
<div class="text-white text-xs opacity-75">
<i class="fas fa-info-circle mr-1"></i>マルコフ連鎖では過去の投稿からランダムな文章を生成します
</div>
<div id="bot-log" class="log-container text-white text-xs"></div>
</div>
</div>
<!-- RSS機能 -->
<div class="glass-effect p-6 card-hover">
<h3 class="text-xl font-bold text-white mb-4">
<i class="fas fa-rss mr-2"></i>RSS機能
<span class="status-indicator" id="rss-status"></span>
<span id="rss-status-text" class="text-xs opacity-75">停止中</span>
</h3>
<div class="space-y-4">
<div>
<input type="text" id="feedUrl" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" placeholder="RSS URL">
<button onclick="registerFeedUrl()" class="btn-primary w-full mt-2 py-2 rounded-lg text-white">
<i class="fas fa-plus mr-2"></i>フィード登録
</button>
</div>
<div>
<input type="number" id="feedIntervalSec" class="w-full p-2 border rounded-lg bg-white bg-opacity-90" placeholder="RSS自動取得間隔(秒)" min="60" max="3600" value="180">
<div class="flex space-x-2 mt-2">
<button onclick="fetchAllFeeds()" class="btn-primary flex-1 py-2 rounded-lg text-white">
<i class="fas fa-download mr-2"></i>手動取得
</button>
<button onclick="startRSSAutoPost()" class="btn-primary flex-1 py-2 rounded-lg text-white">
<i class="fas fa-play mr-2"></i>自動開始
</button>
<button onclick="stopRSSAutoPost()" class="bg-red-500 hover:bg-red-600 flex-1 py-2 rounded-lg text-white">
<i class="fas fa-stop mr-2"></i>停止
</button>
</div>
</div>
<div class="bg-white bg-opacity-80 rounded-lg p-3">
<div class="flex justify-between items-center mb-2">
<h5 class="font-semibold text-gray-800">登録済みフィード:</h5>
<button onclick="addDefaultFeeds()" class="text-xs bg-blue-500 text-white px-2 py-1 rounded">
デフォルト追加
</button>
</div>
<div id="feed-list" class="text-sm text-gray-700"></div>
</div>
<div id="rss-log" class="log-container text-white text-xs"></div>
</div>
</div>
</div>
<!-- 右側カラム -->
<div class="lg:col-span-2 space-y-6">
<!-- 新規投稿 -->
<div class="glass-effect p-6 card-hover">
<h3 class="text-2xl font-bold gradient-text mb-4">
<i class="fas fa-edit mr-2"></i>新規投稿
</h3>
<div>
<textarea id="postContent" class="w-full p-4 border rounded-lg bg-white bg-opacity-90" rows="4" placeholder="今何を考えていますか?" maxlength="500"></textarea>
<div class="mt-4 flex justify-between items-center">
<div class="text-white text-sm opacity-75">
<i class="fas fa-info-circle mr-1"></i>あなたの思いを共有しましょう
<span id="char-count" class="ml-2">(0/500)</span>
</div>
<button onclick="createUserPost()" class="btn-primary px-6 py-3 rounded-lg text-white font-semibold">
<i class="fas fa-paper-plane mr-2"></i>投稿する
</button>
</div>
</div>
</div>
<div class="section-divider"></div>
<!-- タイムライン -->
<div class="glass-effect p-6">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-white">
<i class="fas fa-stream mr-2"></i>タイムライン
<span id="post-count" class="text-sm font-normal opacity-75 ml-2">(0件の投稿)</span>
</h3>
<div class="flex space-x-2">
<button onclick="clearAllPosts()" class="bg-red-500 hover:bg-red-600 px-3 py-1 rounded text-white text-sm">
<i class="fas fa-trash mr-1"></i>全削除
</button>
<button onclick="exportData()" class="bg-green-500 hover:bg-green-600 px-3 py-1 rounded text-white text-sm">
<i class="fas fa-download mr-1"></i>エクスポート
</button>
</div>
</div>
<div id="timeline" class="space-y-4"></div>
<div id="empty-timeline" class="text-center py-12 text-white opacity-75">
<i class="fas fa-comments text-4xl mb-4"></i>
<p class="text-lg">まだ投稿がありません</p>
<p class="text-sm">最初の投稿をして、タイムラインを始めましょう!</p>
</div>
</div>
</div>
</div>
</div>
<footer class="glass-effect mx-4 mb-4 p-4 text-center">
<p class="text-white opacity-75">
<i class="fas fa-copyright mr-2"></i>2025 Verse - 次世代ソーシャルネットワーク v2.0
<span class="ml-4">
<i class="fas fa-bug mr-1"></i>RSS自動投稿完全修正版
</span>
</p>
</footer>
<script>
// === 設定とグローバル変数 ===
const DEFAULT_FEEDS = [
"https://feeds.feedburner.com/hatena/b/hotentry",
"https://gigazine.net/news/rss_2.0/",
"https://rss.cnn.com/rss/edition.rss",
"https://feeds.bbci.co.uk/news/rss.xml",
"https://www.reddit.com/r/worldnews/.rss"
];
const RSS_APIS = [
'https://api.rss2json.com/v1/api.json',
'https://cors-anywhere.herokuapp.com/',
'https://api.allorigins.win/get'
];
// グローバル状態
let posts = JSON.parse(localStorage.getItem('verse_posts') || '[]');
let feedUrls = JSON.parse(localStorage.getItem('verse_feeds') || '[]');
let profile = JSON.parse(localStorage.getItem('verse_profile') || '{}');
let processedItems = new Set(JSON.parse(localStorage.getItem('verse_processed') || '[]'));
// タイマーとステータス
let botInterval = null;
let rssInterval = null;
let isDarkMode = localStorage.getItem('verse_darkMode') === 'true';
let markovChain = null;
let isInitialized = false;
// 統計情報
let stats = {
totalPosts: 0,
rssSuccess: 0,
rssErrors: 0,
botPosts: 0,
lastRSSUpdate: null,
lastBotPost: null
};
// === 初期化処理 ===
function initializeApp() {
if (isInitialized) return;
// プロフィール初期化
if (!profile.icon) profile.icon = 'https://via.placeholder.com/100';
if (!profile.username) profile.username = 'ゲストユーザー';
if (!profile.selfIntro) profile.selfIntro = '';
// デフォルトフィード設定
if (feedUrls.length === 0) {
feedUrls = [...DEFAULT_FEEDS];
saveData();
addLog('rss-log', 'デフォルトRSSフィードを登録しました', 'success');
}
// UI初期化
updateAllUI();
updateStatusIndicators();
// ダークモード適用
if (isDarkMode) {
document.body.classList.add('dark');
document.body.style.background = 'linear-gradient(135deg, #1a202c 0%, #2d3748 100%)';
}
isInitialized = true;
addLog('rss-log', 'アプリケーション初期化完了', 'success');
addLog('bot-log', 'BOT機能初期化完了', 'success');
// 初回RSS取得(遅延実行)
setTimeout(() => {
addLog('rss-log', '初回RSS取得を開始します...', 'info');
fetchAllFeeds();
}, 3000);
}
// === データ管理 ===
function saveData() {
try {
localStorage.setItem('verse_posts', JSON.stringify(posts));
localStorage.setItem('verse_feeds', JSON.stringify(feedUrls));
localStorage.setItem('verse_profile', JSON.stringify(profile));
localStorage.setItem('verse_processed', JSON.stringify([...processedItems]));
localStorage.setItem('verse_darkMode', isDarkMode);
stats.totalPosts = posts.length;
} catch (error) {
console.error('データ保存エラー:', error);
addLog('rss-log', `データ保存エラー: ${error.message}`, 'error');
}
}
function clearAllPosts() {
if (confirm('すべての投稿を削除してもよろしいですか?\nこの操作は元に戻せません。')) {
posts = [];
processedItems.clear();
saveData();
renderTimeline();
addLog('rss-log', 'すべての投稿を削除しました', 'info');
addLog('bot-log', 'すべての投稿を削除しました', 'info');
}
}
function exportData() {
const exportData = {
posts: posts,
profile: profile,
feedUrls: feedUrls,
stats: stats,
exportDate: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `verse_export_${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
addLog('rss-log', 'データをエクスポートしました', 'success');
}
// === ログ機能 ===
function addLog(elementId, message, type = 'info') {
const logElement = document.getElementById(elementId);
if (!logElement) return;
const timestamp = new Date().toLocaleTimeString('ja-JP');
const logEntry = document.createElement('div');
const typeClass = {
'error': 'error-message',
'success': 'success-message',
'info': 'text-white opacity-75'
}[type] || 'text-white opacity-75';
logEntry.className = typeClass;
logEntry.innerHTML = `<span class="opacity-75">[${timestamp}]</span> ${message}`;
logElement.appendChild(logEntry);
logElement.scrollTop = logElement.scrollHeight;
// ログ数制限
while (logElement.children.length > 50) {
logElement.removeChild(logElement.firstChild);
}
}
// === ステータス管理 ===
function updateStatusIndicators() {
const botIndicator = document.getElementById('bot-status');
const rssIndicator = document.getElementById('rss-status');
const botStatusText = document.getElementById('bot-status-text');
const rssStatusText = document.getElementById('rss-status-text');
if (botIndicator && botStatusText) {
const isActive = botInterval !== null;
botIndicator.className = `status-indicator ${isActive ? 'status-active' : 'status-inactive'}`;
botStatusText.textContent = isActive ? '動作中' : '停止中';
}
if (rssIndicator && rssStatusText) {
const isActive = rssInterval !== null;
rssIndicator.className = `status-indicator ${isActive ? 'status-active' : 'status-inactive'}`;
rssStatusText.textContent = isActive ? '動作中' : '停止中';
}
}
function showSystemStatus() {
const statusInfo = `
=== Verse システムステータス ===
総投稿数: ${stats.totalPosts}
RSS成功: ${stats.rssSuccess}
RSSエラー: ${stats.rssErrors}
BOT投稿数: ${stats.botPosts}
最後のRSS更新: ${stats.lastRSSUpdate || 'なし'}
最後のBOT投稿: ${stats.lastBotPost || 'なし'}
RSS自動取得: ${rssInterval ? '動作中' : '停止中'}
BOT自動投稿: ${botInterval ? '動作中' : '停止中'}
登録フィード数: ${feedUrls.length}
処理済みアイテム: ${processedItems.size}
`;
alert(statusInfo);
}
// === 投稿管理 ===
function createPost(content, type = 'user', username = null, icon = null) {
if (!content || !content.trim()) {
addLog('rss-log', '空のコンテンツは投稿できません', 'error');
return false;
}
// RSS重複チェック
if (type === 'feed') {
const urlMatch = content.match(/href="([^"]+)"/);
const url = urlMatch ? urlMatch[1] : null;
if (url && processedItems.has(url)) {
addLog('rss-log', `重複記事をスキップ: ${url.substring(0, 50)}...`, 'info');
return false;
}
if (url) {
processedItems.add(url);
}
}
const post = {
id: Date.now() + Math.random(),
content: content.trim(),
likes: 0,
timestamp: new Date().toLocaleString('ja-JP'),
type: type,
username: username || profile.username,
icon: icon || profile.icon
};
posts.unshift(post);
// 統計更新
if (type === 'bot' || type === 'markov') {
stats.botPosts++;
stats.lastBotPost = post.timestamp;
}
if (type === 'feed') {
stats.rssSuccess++;
stats.lastRSSUpdate = post.timestamp;
}
saveData();
renderTimeline();
return true;
}
function createUserPost() {
const content = document.getElementById('postContent').value.trim();
if (content) {
if (createPost(content, 'user')) {
document.getElementById('postContent').value = '';
updateCharCount();
addLog('bot-log', 'ユーザー投稿を作成しました', 'success');
}
}
}
function likePost(id) {
const post = posts.find(p => p.id === id);
if (post) {
post.likes++;
saveData();
renderTimeline();
}
}
function deletePost(id) {
if (confirm('この投稿を削除しますか?')) {
const post = posts.find(p => p.id === id);
if (post && post.type === 'feed') {
const urlMatch = post.content.match(/href="([^"]+)"/);
if (urlMatch) {
processedItems.delete(urlMatch[1]);
}
}
posts = posts.filter(p => p.id !== id);
saveData();
renderTimeline();
}
}
// === タイムライン表示 ===
function renderTimeline() {
const timeline = document.getElementById('timeline');
const emptyTimeline = document.getElementById('empty-timeline');
const postCount = document.getElementById('post-count');
if (posts.length === 0) {
timeline.innerHTML = '';
emptyTimeline.style.display = 'block';
postCount.textContent = '(0件の投稿)';
return;
}
emptyTimeline.style.display = 'none';
postCount.textContent = `(${posts.length}件の投稿)`;
timeline.innerHTML = posts.map(post => {
const typeInfo = getPostTypeInfo(post.type);
return `
<div class="timeline-post p-6">
<div class="flex justify-between items-start mb-4">
<div class="flex items-center space-x-3">
<img src="${post.icon}" alt="アバター" class="w-10 h-10 rounded-full object-cover">
<div>
<div class="flex items-center">
<span class="font-semibold text-gray-800 dark:text-white">${post.username}</span>
<span class="username-badge">${typeInfo.badge}</span>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${post.timestamp}</div>
</div>
</div>
</div>
<div class="text-gray-800 dark:text-gray-200 mb-4 leading-relaxed">${post.content}</div>
<div class="flex items-center space-x-4 pt-4 border-t border-gray-100 dark:border-gray-600">
<button onclick="likePost(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-red-500 transition-colors">
<i class="fas fa-heart"></i>
<span>${post.likes}</span>
</button>
<div class="relative">
<button onclick="toggleShareMenu(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-blue-500 transition-colors">
<i class="fas fa-share"></i>
<span>シェア</span>
</button>
<div id="share-menu-${post.id}" class="share-menu hidden">
<button onclick="shareToX(${post.id})"><i class="fab fa-x-twitter text-blue-400 mr-2"></i>Xでシェア</button>
<button onclick="shareToLine(${post.id})"><i class="fab fa-line text-green-400 mr-2"></i>LINEでシェア</button>
<button onclick="copyPost(${post.id})"><i class="fas fa-copy mr-2"></i>コピー</button>
</div>
</div>
<button onclick="deletePost(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-red-500 transition-colors ml-auto">
<i class="fas fa-trash"></i>
<span>削除</span>
</button>
</div>
</div>
`;
}).join('');
}
function getPostTypeInfo(type) {
const typeMap = {
'bot': { badge: '🤖 BOT' },
'markov': { badge: '🎲 MarkovBOT' },
'feed': { badge: '📡 RSS Feed' },
'user': { badge: '👤 ユーザー' }
};
return typeMap[type] || typeMap['user'];
}
// === シェア機能 ===
function getPostContentText(id) {
const post = posts.find(p => p.id === id);
if (!post) return '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = post.content;
return tempDiv.textContent || tempDiv.innerText || '';
}
function toggleShareMenu(id) {
document.querySelectorAll('[id^="share-menu-"]').forEach(el => el.classList.add('hidden'));
const menu = document.getElementById('share-menu-' + id);
if (menu) {
menu.classList.toggle('hidden');
setTimeout(() => {
const closeMenu = (e) => {
if (!menu.contains(e.target)) {
menu.classList.add('hidden');
document.removeEventListener('mousedown', closeMenu);
}
};
document.addEventListener('mousedown', closeMenu);
}, 100);
}
}
function shareToX(id) {
const text = encodeURIComponent(getPostContentText(id));
const url = encodeURIComponent(location.href);
window.open(`https://twitter.com/intent/tweet?text=${text}&url=${url}`, '_blank');
}
function shareToLine(id) {
const text = encodeURIComponent(getPostContentText(id) + ' ' + location.href);
window.open(`https://social-plugins.line.me/lineit/share?url=${text}`, '_blank');
}
function copyPost(id) {
const text = getPostContentText(id);
navigator.clipboard.writeText(text).then(() => {
alert('投稿内容をクリップボードにコピーしました!');
}).catch(() => {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('投稿内容をコピーしました!');
});
}
// === BOT機能 ===
function postBotMessage() {
const content = document.getElementById('botContent').value.trim();
if (content) {
if (createPost(content, 'bot', 'BOT')) {
document.getElementById('botContent').value = '';
addLog('bot-log', `BOT投稿: "${content.substring(0, 30)}..."`, 'success');
}
}
}
// === マルコフ連鎖機能(強化版) ===
function initializeMarkovChain() {
try {
if (typeof RiTa === 'undefined') {
addLog('bot-log', 'RiTaライブラリが未読み込みです', 'error');
return false;
}
markovChain = new RiTa.Markov(3);
const userPosts = posts.filter(p =>
(p.type === 'user' || p.type === 'bot') &&
p.content.length > 20 &&
!p.content.includes('<div') &&
!p.content.includes('href=')
);
if (userPosts.length < 5) {
addLog('bot-log', `学習データ不足 (${userPosts.length}件) - 5件以上必要`, 'error');
return false;
}
const textData = userPosts
.map(p => cleanTextForMarkov(p.content))
.filter(text => text.length > 15)
.join('\n');
if (textData.length < 200) {
addLog('bot-log', 'テキストデータが不十分です', 'error');
return false;
}
markovChain.addText(textData);
addLog('bot-log', `マルコフ連鎖学習完了 (${userPosts.length}件の投稿を学習)`, 'success');
return true;
} catch (error) {
addLog('bot-log', `マルコフ初期化エラー: ${error.message}`, 'error');
return false;
}
}
function cleanTextForMarkov(text) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = text;
let cleanText = tempDiv.textContent || tempDiv.innerText || '';
cleanText = cleanText
.replace(/https?:\/\/[^\s]+/g, '')
.replace(/[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\u3400-\u4DBFa-zA-Z0-9\s!?。、.,!?\n]/g, '')
.replace(/\s+/g, ' ')
.replace(/[。!?]{2,}/g, '。')
.trim();
return cleanText;
}
function generateMarkovText() {
try {
if (!initializeMarkovChain()) {
const fallbackMessages = [
"今日はいい天気ですね!どのように過ごされていますか?",
"最近面白いニュースありましたか?",
"新しいアイデアが浮かんできました。",
"皆さんはどう思いますか?",
"今日も一日頑張りましょう!"
];
return fallbackMessages[Math.floor(Math.random() * fallbackMessages.length)];
}
let bestText = '';
const maxAttempts = 15;
for (let i = 0; i < maxAttempts; i++) {
try {
const options = {
maxLength: 120,
minLength: 20,
count: Math.floor(Math.random() * 2) + 1
};
const sentences = markovChain.generate(options);
if (sentences && sentences.length > 0) {
let generatedText = sentences.join(' ').trim();
// 文章の自然な終端処理
if (generatedText.length > 150) {
const endMarkers = ['。', '!', '?'];
let bestEnd = -1;
endMarkers.forEach(marker => {
const pos = generatedText.lastIndexOf(marker, 130);
if (pos > 30) bestEnd = Math.max(bestEnd, pos);
});
if (bestEnd > -1) {
generatedText = generatedText.substring(0, bestEnd + 1);
}
}
// 品質チェック
if (generatedText.length >= 20 &&
generatedText.length <= 200 &&
!generatedText.includes('undefined') &&
generatedText.length > bestText.length) {
bestText = generatedText;
}
}
} catch (genError) {
continue;
}
}
if (!bestText || bestText.length < 10) {
bestText = "新しいアイデアについて考えています。皆さんの意見も聞きたいですね。";
}
return bestText;
} catch (error) {
addLog('bot-log', `マルコフ生成エラー: ${error.message}`, 'error');
return "マルコフ連鎖で自然な文章を生成しています。";
}
}
function postMarkovBot() {
const content = generateMarkovText();
if (createPost(content, 'markov', 'MarkovBOT')) {
addLog('bot-log', `マルコフ投稿: "${content.substring(0, 40)}..."`, 'success');
}
}
function startBotAutoPost() {
const interval = parseInt(document.getElementById('botIntervalSec').value) || 60;
if (interval < 10) {
alert('間隔は10秒以上で設定してください。');
return;
}
stopBotAutoPost();
// 初回投稿
setTimeout(postMarkovBot, 2000);
botInterval = setInterval(() => {
postMarkovBot();
}, interval * 1000);
updateStatusIndicators();
addLog('bot-log', `マルコフBOT自動投稿開始 (${interval}秒間隔)`, 'success');
}
function stopBotAutoPost() {
if (botInterval) {
clearInterval(botInterval);
botInterval = null;
updateStatusIndicators();
addLog('bot-log', 'マルコフBOT自動投稿を停止しました', 'info');
}
}
// === RSS機能(完全修正版) ===
function addDefaultFeeds() {
let addedCount = 0;
DEFAULT_FEEDS.forEach(url => {
if (!feedUrls.includes(url)) {
feedUrls.push(url);
addedCount++;
}
});
if (addedCount > 0) {
saveData();
renderFeedList();
addLog('rss-log', `${addedCount}件のデフォルトフィードを追加しました`, 'success');
} else {
addLog('rss-log', 'すべてのデフォルトフィードは既に登録されています', 'info');
}
}
function registerFeedUrl() {
const url = document.getElementById('feedUrl').value.trim();
if (!url) {
alert('URLを入力してください。');
return;
}
if (!url.startsWith('http')) {
alert('有効なURLを入力してください(http://またはhttps://で開始)。');
return;
}
if (feedUrls.includes(url)) {
alert('このフィードは既に登録されています。');
return;
}
feedUrls.push(url);
saveData();
renderFeedList();
document.getElementById('feedUrl').value = '';
addLog('rss-log', `フィード登録: ${url}`, 'success');
}
function removeFeedUrl(index) {
if (confirm('このフィードを削除しますか?')) {
const removedUrl = feedUrls[index];
feedUrls.splice(index, 1);
saveData();
renderFeedList();
addLog('rss-log', `フィード削除: ${removedUrl}`, 'info');
}
}
function renderFeedList() {
const feedList = document.getElementById('feed-list');
if (feedUrls.length === 0) {
feedList.innerHTML = '<div class="text-gray-500">登録されたフィードはありません</div>';
return;
}
feedList.innerHTML = feedUrls.map((url, index) => `
<div class="rss-item flex items-center justify-between">
<span class="text-xs truncate flex-1" title="${url}">${url}</span>
<button onclick="removeFeedUrl(${index})" class="text-red-500 hover:text-red-700 ml-2 text-xs">
<i class="fas fa-times"></i>
</button>
</div>
`).join('');
}
// 改良されたRSS取得機能
async function fetchAllFeeds() {
if (feedUrls.length === 0) {
addLog('rss-log', 'RSSフィードが登録されていません', 'error');
return;
}
addLog('rss-log', `RSS取得開始 (${feedUrls.length}件のフィード)`, 'info');
let successCount = 0;
let errorCount = 0;
for (let i = 0; i < feedUrls.length; i++) {
const url = feedUrls[i];
try {
addLog('rss-log', `[${i + 1}/${feedUrls.length}] 取得中: ${url.substring(0, 50)}...`, 'info');
const success = await fetchSingleFeed(url);
if (success) {
successCount++;
addLog('rss-log', `✓ 成功: ${url.substring(0, 50)}...`, 'success');
} else {
errorCount++;
addLog('rss-log', `✗ 失敗: ${url.substring(0, 50)}...`, 'error');
}
} catch (error) {
errorCount++;
addLog('rss-log', `✗ エラー: ${error.message}`, 'error');
}
// レート制限対策(フィード間に待機時間)
if (i < feedUrls.length - 1) {
await sleep(2000);
}
}
stats.rssSuccess += successCount;
stats.rssErrors += errorCount;
const resultMsg = `RSS取得完了: 成功 ${successCount}件 / エラー ${errorCount}件`;
addLog('rss-log', resultMsg, successCount > 0 ? 'success' : 'error');
if (successCount > 0) {
saveData();
}
}
async function fetchSingleFeed(feedUrl) {
const apis = [
// API 1: rss2json
async () => {
const response = await fetchWithTimeout(
`https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(feedUrl)}&count=5`,
10000
);
const data = await response.json();
if (data.status !== 'ok') {
throw new Error(data.message || 'RSS API error');
}
return data.items || [];
},
// API 2: allorigins
async () => {
const response = await fetchWithTimeout(
`https://api.allorigins.win/get?url=${encodeURIComponent(feedUrl)}`,
10000
);
const data = await response.json();
if (!data.contents) {
throw new Error('No contents returned');
}
return parseRSSContent(data.contents);
}
];
for (const api of apis) {
try {
const items = await api();
return await processRSSItems(items, feedUrl);
} catch (error) {
continue;
}
}
return false;
}
async function processRSSItems(items, feedUrl) {
if (!items || items.length === 0) {
return false;
}
let processedCount = 0;
for (const item of items.slice(0, 3)) { // 最大3件まで
if (!item.title || !item.link) continue;
// 重複チェック
if (processedItems.has(item.link)) {
continue;
}
const cleanTitle = cleanHTML(item.title).substring(0, 100);
const cleanDescription = item.description
? cleanHTML(item.description).substring(0, 150)
: '';
const content = createRSSPostContent(cleanTitle, cleanDescription, item.link, feedUrl);
if (createPost(content, 'feed', 'RSS Feed')) {
processedCount++;
processedItems.add(item.link);
// 投稿間の間隔
if (processedCount < 3) {
await sleep(1000);
}
}
}
return processedCount > 0;
}
function createRSSPostContent(title, description, link, feedUrl) {
const feedDomain = new URL(feedUrl).hostname;
return `
<div class="border-l-4 border-blue-500 pl-4 bg-gray-50 dark:bg-gray-700 rounded-r-lg p-3">
<h4 class="font-semibold mb-2 text-gray-900 dark:text-white">${title}</h4>
${description ? `<p class="text-sm text-gray-600 dark:text-gray-300 mb-3">${description}${description.length >= 150 ? '...' : ''}</p>` : ''}
<div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">
<i class="fas fa-globe mr-1"></i>${feedDomain}
</span>
<a href="${link}" target="_blank" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 text-sm font-medium">
<i class="fas fa-external-link-alt mr-1"></i>記事を読む
</a>
</div>
</div>
`;
}
function parseRSSContent(xmlContent) {
// 簡易XMLパース(実装を簡略化)
const items = [];
const parser = new DOMParser();
const doc = parser.parseFromString(xmlContent, 'text/xml');
const rssItems = doc.querySelectorAll('item');
rssItems.forEach(item => {
const title = item.querySelector('title')?.textContent;
const link = item.querySelector('link')?.textContent;
const description = item.querySelector('description')?.textContent;
if (title && link) {
items.push({ title, link, description });
}
});
return items;
}
function cleanHTML(text) {
if (!text) return '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = text;
return (tempDiv.textContent || tempDiv.innerText || '').trim();
}
async function fetchWithTimeout(url, timeout = 10000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal,
headers: {
'Accept': 'application/json',
'User-Agent': 'Verse RSS Reader 2.0'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
} finally {
clearTimeout(timeoutId);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function startRSSAutoPost() {
const interval = parseInt(document.getElementById('feedIntervalSec').value) || 180;
if (interval < 60) {
alert('間隔は60秒以上で設定してください。');
return;
}
stopRSSAutoPost();
// 初回実行(少し遅延)
setTimeout(() => {
addLog('rss-log', '自動RSS取得の初回実行を開始...', 'info');
fetchAllFeeds();
}, 5000);
rssInterval = setInterval(() => {
addLog('rss-log', '定期RSS取得を実行中...', 'info');
fetchAllFeeds();
}, interval * 1000);
updateStatusIndicators();
addLog('rss-log', `RSS自動取得開始 (${interval}秒間隔)`, 'success');
}
function stopRSSAutoPost() {
if (rssInterval) {
clearInterval(rssInterval);
rssInterval = null;
updateStatusIndicators();
addLog('rss-log', 'RSS自動取得を停止しました', 'info');
}
}
// === プロフィール機能 ===
function uploadProfileIcon(event) {
const file = event.target.files[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) {
alert('画像サイズは5MB以下にしてください。');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
profile.icon = e.target.result;
updateAllUI();
saveData();
alert('プロフィール画像を更新しました!');
};
reader.readAsDataURL(file);
}
function saveProfile() {
const username = document.getElementById('username').value.trim();
const selfIntro = document.getElementById('self-intro').value.trim();
if (username && username.length > 20) {
alert('ユーザー名は20文字以内で入力してください。');
return;
}
profile.username = username || 'ゲストユーザー';
profile.selfIntro = selfIntro;
updateAllUI();
saveData();
alert('プロフィールを保存しました!');
}
// === UI更新 ===
function updateAllUI() {
// プロフィール画像更新
const profileIcon = document.getElementById('profile-icon');
const headerIcon = document.getElementById('header-profile-icon');
if (profileIcon) profileIcon.src = profile.icon;
if (headerIcon) headerIcon.src = profile.icon;
// ユーザー名更新
const usernameElements = ['username-preview', 'header-username'];
usernameElements.forEach(id => {
const element = document.getElementById(id);
if (element) element.textContent = profile.username;
});
// 自己紹介更新
const selfIntroPreview = document.getElementById('self-intro-preview');
if (selfIntroPreview) {
selfIntroPreview.textContent = profile.selfIntro || 'まだ自己紹介がありません';
}
// タイムライン再描画
renderTimeline();
renderFeedList();
}
function updateCharCount() {
const postContent = document.getElementById('postContent');
const charCount = document.getElementById('char-count');
if (postContent && charCount) {
const length = postContent.value.length;
charCount.textContent = `(${length}/500)`;
charCount.style.color = length > 450 ? '#ef4444' : '';
}
}
function toggleDarkMode() {
isDarkMode = !isDarkMode;
if (isDarkMode) {
document.body.classList.add('dark');
document.body.style.background = 'linear-gradient(135deg, #1a202c 0%, #2d3748 100%)';
} else {
document.body.classList.remove('dark');
document.body.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
}
saveData();
}
// === イベントリスナー ===
document.addEventListener('DOMContentLoaded', function() {
// 文字数カウンター
const postContent = document.getElementById('postContent');
if (postContent) {
postContent.addEventListener('input', updateCharCount);
}
// プロフィール入力のリアルタイム更新
const usernameInput = document.getElementById('username');
const selfIntroInput = document.getElementById('self-intro');
if (usernameInput) {
usernameInput.addEventListener('input', function() {
const username = this.value.trim() || 'ゲストユーザー';
document.getElementById('username-preview').textContent = username;
document.getElementById('header-username').textContent = username;
});
}
if (selfIntroInput) {
selfIntroInput.addEventListener('input', function() {
const selfIntro = this.value.trim() || 'まだ自己紹介がありません';
document.getElementById('self-intro-preview').textContent = selfIntro;
});
}
});
// === 初期化実行 ===
window.addEventListener('load', function() {
// アプリ初期化
initializeApp();
// プロフィール情報を入力フィールドに設定
document.getElementById('username').value = profile.username || '';
document.getElementById('self-intro').value = profile.selfIntro || '';
// ページ終了時のクリーンアップ
window.addEventListener('beforeunload', function() {
stopBotAutoPost();
stopRSSAutoPost();
saveData();
});
});
</script>
</body>
</html>
VerseVR
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Verse – プロダクション級ソーシャルVR</title>
<!-- A-Frame Core -->
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<!-- Networked-AFrame for multi-user sync -->
<script src="https://unpkg.com/networked-aframe@0.8.0/dist/networked-aframe.min.js"></script>
<!-- GUI for VR buttons -->
<script src="https://unpkg.com/aframe-gui/dist/aframe-gui.min.js"></script>
<!-- Environment presets -->
<script src="https://unpkg.com/aframe-environment-component/dist/aframe-environment-component.min.js"></script>
<!-- Extras: teleport, locomotion, physics, super-hands -->
<script src="https://unpkg.com/aframe-extras@6.1.1/dist/aframe-extras.min.js"></script>
<script src="https://unpkg.com/aframe-super-hands-component@4.0.3/dist/aframe-super-hands.min.js"></script>
<!-- Socket.IO for signaling -->
<script src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script>
<!-- Simple-Peer for WebRTC voice chat -->
<script src="https://unpkg.com/simple-peer@9.11.0/simplepeer.min.js"></script>
<style>
body { margin: 0; }
#vr-scene { width: 100%; height: 100vh; }
.interactive:hover { animation: pulse 0.5s infinite alternate; }
@keyframes pulse { to { scale: 1.05; } }
</style>
</head>
<body>
<a-scene id="vr-scene"
networked-scene="room: verse-room; serverURL: https://YOUR_SIGNAL_SERVER; app: verse-vr; debug: false"
environment="preset: forest; groundColor:#445; skyColor:#889"
extras="teleportControls: true; locomotionControls: true"
physics="gravity: -9.8; debug: false">
<!-- Assets -->
<a-assets>
<audio id="click-sound" src="click.mp3"></audio>
<a-asset-item id="avatarModel" src="avatar.glb"></a-asset-item>
<img id="panel-bg" src="panel-bg.png" />
</a-assets>
<!-- Camera Rig -->
<a-entity id="cameraRig" position="0 1.6 0"
locomotion-controls="fly:false; speed:4"
teleport-controls="button: trigger; collisionEntities: #ground">
<a-entity camera look-controls>
<a-cursor fuse="true" fuseTimeout="300" material="color:cyan; shader:flat"></a-cursor>
</a-entity>
<a-entity hand-tracking-controls="hand: left" super-hands></a-entity>
<a-entity hand-tracking-controls="hand: right" super-hands></a-entity>
</a-entity>
<!-- Ground -->
<a-plane id="ground" position="0 0 0" rotation="-90 0 0" width="50" height="50" color="#444" static-body></a-plane>
<!-- Avatar Sync Template -->
<template id="avatar-template">
<a-entity>
<a-gltf-model src="#avatarModel" scale="0.6 0.6 0.6"></a-gltf-model>
</a-entity>
</template>
<a-entity networked-avatar networked="template:#avatar-template; attachTemplateToLocal:false"></a-entity>
<!-- GUI Menu -->
<a-gui-flex-container id="menu" flex-direction="row" justify-content="space-around"
panel-width="2" panel-height="0.2"
component-padding="0.05 0.1"
position="0 2 -1.5" material="src:#panel-bg; transparent:true">
<a-gui-button id="btnTextPost" value="Text Post" on-click="openTextInput()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnVoicePost" value="Voice Post" on-click="startVoiceRecognition()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnGPTChat" value="GPT BOT" on-click="callGPTBot()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnLikeMode" value="Like/Delete" on-click="toggleLikeMode()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnSpawnCube" value="Spawn Cube" on-click="spawnCube()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnVoiceChat" value="Voice Chat" on-click="toggleVoiceChat()" font-size="28px" color="black"></a-gui-button>
</a-gui-flex-container>
<!-- Containers -->
<a-entity id="post-container"></a-entity>
<a-entity id="ugc-container"></a-entity>
</a-scene>
<script>
// ----------- データ -----------
let posts = JSON.parse(localStorage.getItem('posts')||'[]');
let likeMode = false;
let recognition;
let socket = io('https://YOUR_SIGNAL_SERVER');
let peers = {};
let localStream;
// ----------- 投稿レンダリング -----------
function renderPosts() {
const container = document.getElementById('post-container');
container.innerHTML = '';
posts.forEach((p,i)=>{
const angle = (i/posts.length)*Math.PI*2;
const x = Math.cos(angle)*2;
const z = Math.sin(angle)*2;
const postEl = document.createElement('a-entity');
postEl.classList.add('interactive');
postEl.setAttribute('geometry','primitive: plane; width:1.2; height:0.5');
postEl.setAttribute('material','color:#fff; shader:flat');
postEl.setAttribute('position',`${x} 1.3 ${z}`);
postEl.setAttribute('rotation',`0 ${-angle*180/Math.PI+90} 0`);
postEl.setAttribute('text',`value:${p.content}; width:1.1; align:center; color:#000`);
// クリック処理
postEl.addEventListener('click', ()=>{
if(likeMode) {
p.likes = (p.likes||0)+1;
savePosts(); renderPosts();
}
});
// 削除ボタン
if(likeMode) {
const delBtn = document.createElement('a-entity');
delBtn.setAttribute('geometry','primitive: plane; width:0.2; height:0.1');
delBtn.setAttribute('material','color:#f88; shader:flat');
delBtn.setAttribute('position','0.55 -0.25 0.01');
delBtn.setAttribute('text','value:Del; width:0.2; align:center; color:#000');
delBtn.addEventListener('click', ()=>{ posts.splice(i,1); savePosts(); renderPosts(); });
postEl.appendChild(delBtn);
}
container.appendChild(postEl);
});
}
function savePosts(){ localStorage.setItem('posts', JSON.stringify(posts)); }
// ----------- テキスト投稿 -----------
window.openTextInput = ()=>{
const txt = prompt('投稿内容を入力してください');
if(txt){ posts.unshift({content:txt, likes:0}); savePosts(); renderPosts(); socket.emit('new-post', txt); }
};
// ----------- 音声投稿 -----------
window.startVoiceRecognition = ()=>{
if(!('webkitSpeechRecognition' in window)) return alert('音声認識非対応');
recognition = new webkitSpeechRecognition();
recognition.lang='ja-JP'; recognition.interimResults=false;
recognition.onresult=e=>{ const txt=e.results[0][0].transcript; posts.unshift({content:txt,likes:0}); savePosts(); renderPosts(); };
recognition.onerror=()=>alert('認識エラー'); recognition.start();
};
// ----------- GPT BOT -----------
window.callGPTBot = async ()=>{
const key='YOUR_OPENAI_API_KEY';
const res=await fetch('https://api.openai.com/v1/chat/completions',{ method:'POST',
headers:{'Content-Type':'application/json','Authorization':`Bearer ${key}`},
body:JSON.stringify({ model:'gpt-4o-mini', messages:[{role:'system',content:'あなたはVR BOTです。'}]
.concat(posts.slice(0,5).map(p=>({role:'user',content:p.content}))), max_tokens:50 })
});
const js=await res.json();
const txt=js.choices[0].message.content.trim();
posts.unshift({content:`🤖 GPT: ${txt}`,likes:0}); savePosts(); renderPosts();
};
// ----------- いいね/削除モード -----------
window.toggleLikeMode = ()=>{ likeMode=!likeMode; renderPosts(); };
// ----------- UGC: キューブ生成 -----------
window.spawnCube = ()=>{
const c=document.createElement('a-box');
c.setAttribute('class','interactive');
c.setAttribute('position','0 1 -1'); c.setAttribute('depth','0.5'); c.setAttribute('height','0.5'); c.setAttribute('width','0.5');
c.setAttribute('material','color:#4CC3D9'); c.setAttribute('dynamic-body','');
c.setAttribute('grabbable',''); c.setAttribute('stretchable','');
document.getElementById('ugc-container').appendChild(c);
};
// ----------- Voice Chat -----------
window.toggleVoiceChat = ()=>{ localStream?stopVoiceChat():startVoiceChat(); };
async function startVoiceChat() {
localStream = await navigator.mediaDevices.getUserMedia({audio:true});
socket.emit('join-voice');
socket.on('signal', ({ id, signal })=>{
if(!peers[id]) createPeer(id, false);
peers[id].signal(signal);
});
socket.on('user-joined', id=>{ createPeer(id, true); });
function createPeer(id, initiator) {
const peer = new SimplePeer({ initiator, trickle:false, stream:localStream });
peer.on('signal', signal=>{ socket.emit('signal',{ id: socket.id, to:id, signal }); });
peer.on('stream', stream=>{ const e=document.createElement('audio'); e.srcObject=stream; e.autoplay=true; document.body.appendChild(e); });
peers[id]=peer;
}
}
function stopVoiceChat(){ Object.values(peers).forEach(p=>p.destroy()); peers={}; localStream.getTracks().forEach(t=>t.stop()); localStream=null; }
// ----------- Socket.IO イベント -----------
socket.on('new-post', txt=>{ posts.unshift({content:txt,likes:0}); savePosts(); renderPosts(); });
// ----------- 初期ロード -----------
document.querySelector('a-scene').addEventListener('loaded', ()=>{ renderPosts(); });
</script>
</body>
</html>
Verse.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verse - 次世代ソーシャルネットワーク</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/rita/1.3.63/rita-full.min.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f0f0f0; }
header, .footer { background: linear-gradient(45deg, #6a11cb, #2575fc); color: white; text-align: center; padding: 20px; }
.nav-container { background: #2575fc; display: flex; justify-content: center; padding: 10px; position: sticky; top: 0; }
.nav-menu a { color: white; text-decoration: none; margin: 0 10px; }
.content { max-width: 900px; margin: 20px auto; background: white; padding: 20px; border-radius: 8px; }
.cta-button { background: #2575fc; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; }
.profile-icon { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; }
.dark-mode { background: #1e1e1e; color: #ddd; }
.timeline { margin-top: 20px; }
.timeline-post { border: 1px solid #ddd; border-radius: 8px; padding: 10px; margin-bottom: 10px; background: #fff; }
.post-controls { display: flex; gap: 10px; }
.search-box { width: 100%; margin-bottom: 10px; }
</style>
<script>
let posts = JSON.parse(localStorage.getItem('posts') || '[]');
let feedUrls = JSON.parse(localStorage.getItem('feedUrls') || '[]');
let botInterval = null;
let feedInterval = null;
function saveData() {
localStorage.setItem('posts', JSON.stringify(posts));
localStorage.setItem('feedUrls', JSON.stringify(feedUrls));
}
function createPost(content) {
posts.unshift({ content: content, likes: 0 });
saveData();
renderTimeline();
}
function createUserPost() {
const content = document.getElementById('postContent').value.trim();
if(content) {
createPost(content);
document.getElementById('postContent').value = '';
}
}
function likePost(index) {
posts[index].likes++;
saveData();
renderTimeline();
}
function deletePost(index) {
posts.splice(index, 1);
saveData();
renderTimeline();
}
function searchPosts() {
const query = document.getElementById('searchInput').value.trim().toLowerCase();
renderTimeline(query);
}
function renderTimeline(filter = '') {
const container = document.getElementById('timeline');
container.innerHTML = '';
posts.filter(post => post.content.toLowerCase().includes(filter)).forEach((post, index) => {
const postDiv = document.createElement('div');
postDiv.className = 'timeline-post';
postDiv.innerHTML = `
<p>${post.content}</p>
<div class='post-controls'>
<button class='btn btn-sm btn-primary' onclick='likePost(${index})'>❤️ いいね (${post.likes})</button>
<button class='btn btn-sm btn-danger' onclick='deletePost(${index})'>🗑️ 削除</button>
</div>
`;
container.appendChild(postDiv);
});
}
function toggleDarkMode() {
document.body.classList.toggle('dark-mode');
}
function uploadProfileIcon(event) {
const file = event.target.files[0];
if(file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('profile-icon').src = e.target.result;
showTimeline();
};
reader.readAsDataURL(file);
}
}
function showTimeline() {
document.getElementById('profile-section').style.display = 'none';
document.getElementById('post-section').style.display = 'block';
document.getElementById('timeline').style.display = 'block';
}
function showProfile() {
document.getElementById('profile-section').style.display = 'block';
document.getElementById('post-section').style.display = 'none';
document.getElementById('timeline').style.display = 'none';
}
function postBotMessage() {
const content = document.getElementById('botContent').value.trim();
if(content) {
createPost(`🤖 BOT: ${content}`);
document.getElementById('botContent').value = '';
}
}
function generateMarkovText() {
const combinedText = posts.map(p => p.content).join(' ');
if(combinedText.length < 20) return "BOTの投稿データが不足しています。";
const markov = new RiTa.Markov(3);
markov.addText(combinedText);
const sentences = markov.generate(1);
return sentences[0] ? sentences[0] : "自然な文章の生成に失敗しました。";
}
function postMarkovBot() {
const text = generateMarkovText();
createPost(`🤖 MarkovBOT: ${text}`);
}
function startBotAutoPost(intervalSec) {
if(botInterval) clearInterval(botInterval);
botInterval = setInterval(postMarkovBot, intervalSec * 1000);
alert(`マルコフ連鎖BOTの自動投稿間隔を${intervalSec}秒に設定しました。`);
}
function registerFeedUrl() {
const url = document.getElementById('feedUrl').value.trim();
if(url) {
feedUrls.push(url);
saveData();
alert('RSSフィードURLを登録しました。');
document.getElementById('feedUrl').value = '';
}
}
async function fetchAllFeeds() {
for(const url of feedUrls) {
try {
const response = await fetch(`https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(url)}`);
const data = await response.json();
data.items.forEach(item => {
createPost(`📡 FEED: ${item.title} - ${item.link}`);
});
} catch (error) {
console.error('RSSフィード取得エラー:', error);
}
}
}
function startFeedAutoPost(intervalSec) {
if(feedInterval) clearInterval(feedInterval);
feedInterval = setInterval(fetchAllFeeds, intervalSec * 1000);
alert(`RSSフィードの自動投稿間隔を${intervalSec}秒に設定しました。`);
}
window.onload = function() {
renderTimeline();
};
</script>
</head>
<body>
<header>
<h1>Verse</h1>
<button class="cta-button" onclick="toggleDarkMode()">🌙 ダークモード切替</button>
</header>
<div class="nav-container">
<div class="nav-menu">
<a href="#" onclick="showTimeline()">ホーム</a>
<a href="#" onclick="showProfile()">プロフィール</a>
</div>
</div>
<div class="content">
<section id="profile-section" style="display:none;">
<h2>プロフィール</h2>
<img id="profile-icon" src="https://via.placeholder.com/80" alt="プロフィールアイコン" class="profile-icon"><br><br>
<input type="file" accept="image/*" onchange="uploadProfileIcon(event)">
</section>
<section id="post-section">
<h2>新規投稿</h2>
<textarea id="postContent" class="form-control" placeholder="いま何してる?"></textarea><br>
<button class="cta-button" onclick="createUserPost()">投稿</button>
<h2>BOT投稿登録</h2>
<textarea id="botContent" class="form-control" placeholder="BOTに投稿させたい内容"></textarea><br>
<button class="cta-button" onclick="postBotMessage()">BOT投稿登録</button><br><br>
<input type="number" id="botIntervalSec" placeholder="BOTの自動投稿間隔(秒)">
<button class="cta-button" onclick="startBotAutoPost(parseInt(document.getElementById('botIntervalSec').value))">マルコフ連鎖 自動投稿開始</button><br><br>
<h2>RSSフィード登録</h2>
<input type="text" id="feedUrl" class="form-control" placeholder="RSSフィードのURL"><br>
<button class="cta-button" onclick="registerFeedUrl()">フィード登録</button><br><br>
<input type="number" id="feedIntervalSec" placeholder="RSS自動投稿間隔(秒)">
<button class="cta-button" onclick="startFeedAutoPost(parseInt(document.getElementById('feedIntervalSec').value))">RSS 自動投稿開始</button>
</section>
<input type="text" id="searchInput" class="form-control search-box" placeholder="投稿を検索..." onkeyup="searchPosts()">
<section id="timeline" class="timeline"></section>
</div>
<div class="footer">© 2025 Verse - 新しいつながりを創造する</div>
</body>
</html>
Meta AR眼鏡 発売日
1. Ray‑Ban Meta スマートグラス
- 現行モデル(ディスプレイなし+カメラ搭載)
- 発表:2023年9月27日 YouTube+6Reddit+6Road to VR+6
- 発売:同日より販売開始
2. Oakley × Meta スマートグラス
- Limited‑Edition Oakley Meta HSTN
- 発売:2025年7月22日予定(事前予約受付中) The Times of India+3TechRadar+3フィードバック+3
- 一般モデル:2025年夏後半に発売予定(米国基準で“late summer”=7〜8月) TechRadarフィードバック
3. Hypernova(小型ディスプレイ搭載スマートグラス)
- 高級スマートグラス + ディスプレイ付き
- 2025年10月頃に発売予定との報道あり Perplexity AIRoad to VRTechRadar
- 価格は1000~1400ドル程度と推測 Road to VRThe Sun
4. 真のARグラス(プロジェクト Orion / Artemis)
- Orion(開発者向けプロトタイプ)
- 初の本格消費者向けARグラス(コードネーム “Artemis”)
- 消費者向け発売は 2027年見込み YouTube+9ソーシャルメディアトゥデイ+9Tom’s Guide+9
🗓 まとめ表
| モデル | タイプ | 発表/発売日 |
|---|---|---|
| Ray‑Ban Meta | スマートグラス(カメラ) | 発表・発売:2023年9月27日 |
| Oakley Meta HSTN | スポーツ向けAIグラス | 予約開始:2025年7月11日、発売:7月22日 |
| Oakley 一般モデル | 同上 | 2025年夏後半 |
| Hypernova | 小型ディスプレイ搭載 | 2025年10月頃(予定) |
| Orion | 開発者向けARプロトタイプ | 公開:2024年9月25日 |
| Artemis | 真の消費者向けARグラス | 2027年発売予定 |
🔍 補足情報
- Orion は「真のAR体験に向けたプロトタイプ」で、一般消費者向けではありません Reuters+15フィードバック+15Road to VR+15ウィキペディアフィードバック+1The Verge+1。
- Artemis は2027年に一般向けに発売される見込みですが、価格・機能は今後調整される可能性があります Tom’s Guide。
- Hypernova(ディスプレイ付き高級モデル)は、Metaの発表ではなく報道(Bloombergなど)に基づいています YouTube+2Road to VR+2The Sun+2。
まとめると、次に購入可能なMeta製スマートグラスは Oakley Meta HSTN(2025年7月22日発売)で、ディスプレイ付きのHypernovaは2025年10月頃、そして真のARグラスが2027年登場予定です。他にも気になるモデルや機能があれば、お気軽に教えてください!
C++の基礎と応用
C++の基礎
ChatGPT:
C++の基礎を簡潔にまとめます。
■ 1. 基本構造
cppコピーする編集する#include <iostream> // 標準入出力
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
#include <iostream>: 入出力用int main(): プログラムの開始点std::cout: コンソール出力return 0: 正常終了
■ 2. 変数
cppコピーする編集するint number = 10;
double pi = 3.14;
char letter = 'A';
std::string text = "Hello";
bool flag = true;
■ 3. 条件分岐
cppコピーする編集するif (number > 5) {
std::cout << "Greater than 5" << std::endl;
} else {
std::cout << "5 or less" << std::endl;
}
■ 4. ループ
- for
cppコピーする編集するfor (int i = 0; i < 5; i++) {
std::cout << i << std::endl;
}
- while
cppコピーする編集するint i = 0;
while (i < 5) {
std::cout << i << std::endl;
i++;
}
■ 5. 関数
cppコピーする編集するint add(int a, int b) {
return a + b;
}
int main() {
std::cout << add(3, 4) << std::endl; // 7
return 0;
}
■ 6. クラス
cppコピーする編集するclass Person {
public:
std::string name;
void greet() {
std::cout << "Hello, " << name << std::endl;
}
};
int main() {
Person p;
p.name = "Taro";
p.greet();
return 0;
}
■ 7. ポインタ
cppコピーする編集するint num = 10;
int* ptr = #
std::cout << *ptr << std::endl; // 10
もし「もっと詳しく」「演習問題」など希望があれば教えてください。
あなた:
C++の応用
ChatGPT:
C++の応用的な内容を段階的に紹介します。
■ 1. 参照とポインタの応用
ポインタ
cppコピーする編集するvoid swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
参照
cppコピーする編集するvoid swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
■ 2. クラスのコンストラクタ・デストラクタ
cppコピーする編集するclass Person {
public:
std::string name;
Person(std::string n) : name(n) {
std::cout << name << " created" << std::endl;
}
~Person() {
std::cout << name << " destroyed" << std::endl;
}
};
■ 3. 継承とポリモーフィズム
cppコピーする編集するclass Animal {
public:
virtual void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
};
void makeSound(Animal* a) {
a->speak();
}
■ 4. テンプレート
cppコピーする編集するtemplate <typename T>
T add(T a, T b) {
return a + b;
}
■ 5. 標準ライブラリ(STL)
ベクター
cppコピーする編集する#include <vector>
std::vector<int> numbers = {1, 2, 3, 4};
numbers.push_back(5);
マップ
cppコピーする編集する#include <map>
std::map<std::string, int> ages;
ages["Taro"] = 20;
■ 6. ラムダ式
cppコピーする編集するauto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4) << std::endl;
■ 7. スマートポインタ
cppコピーする編集する#include <memory>
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::shared_ptr<int> p2 = std::make_shared<int>(20);
これらは実践的なC++開発で頻出するテクニックです。
もっと深い例(デザインパターン、マルチスレッド、ファイルIOなど)も必要なら教えてください。
あなた:
もっと
ChatGPT:
さらに高度なC++の応用技術を紹介します。
実践プログラムやパフォーマンス向上に不可欠な技法です。
■ 8. デザインパターンの例:シングルトン
cppコピーする編集するclass Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (!instance)
instance = new Singleton();
return instance;
}
};
Singleton* Singleton::
匿名相談・共感プラットフォーム
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>匿名相談・共感プラットフォーム</title>
<style>
body { font-family: 'Segoe UI', sans-serif; background: #eef1f5; margin: 0; padding: 20px; }
header { text-align: center; padding: 20px; background: #4a90e2; color: white; border-radius: 8px; }
.container { max-width: 800px; margin: 20px auto; }
.card { background: white; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 15px; margin-bottom: 20px; }
textarea, input[type="text"] { width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ccc; border-radius: 5px; }
button { background: #4a90e2; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; margin-right: 5px; }
button:hover { background: #357ab8; }
.comment { background: #f5f5f5; border-left: 3px solid #4a90e2; padding: 8px; margin-top: 8px; border-radius: 5px; }
.meta { font-size: 0.85em; color: #555; }
.tag { display: inline-block; background: #ddd; border-radius: 5px; padding: 2px 8px; margin-right: 5px; font-size: 0.8em; }
footer { text-align: center; font-size: 0.8em; color: #888; margin-top: 50px; }
.search-bar { margin-bottom: 20px; }
.delete-btn { background: #e94e4e; }
.delete-btn:hover { background: #b73939; }
.edit-btn { background: #f0ad4e; }
.edit-btn:hover { background: #d98c00; }
.copy-btn { background: #6cc070; }
.copy-btn:hover { background: #4e9f50; }
</style>
</head>
<body>
<header>
<h1>匿名相談・共感プラットフォーム</h1>
<p>悩みや相談を匿名で投稿し、共感やコメントをもらおう</p>
</header>
<div class="container">
<div class="card">
<h3>新しい相談を投稿</h3>
<input type="text" id="postTags" placeholder="タグ(カンマ区切り)">
<textarea id="postText" placeholder="あなたの悩みや相談を書いてください..."></textarea>
<button onclick="addPost()">投稿する</button>
</div>
<div class="card search-bar">
<input type="text" id="searchInput" placeholder="投稿検索..." oninput="searchPosts()">
</div>
<div id="postsContainer"></div>
</div>
<footer>
© 2025 匿名相談・共感プラットフォーム
</footer>
<script>
let posts = JSON.parse(localStorage.getItem('posts')) || [];
function addPost() {
const text = document.getElementById('postText').value.trim();
const tags = document.getElementById('postTags').value.split(',').map(tag => tag.trim()).filter(tag => tag);
if (text) {
posts.unshift({ text, tags, empathy: 0, comments: [], date: new Date().toLocaleString() });
document.getElementById('postText').value = '';
document.getElementById('postTags').value = '';
saveAndRender();
}
}
function addEmpathy(index) {
posts[index].empathy++;
saveAndRender();
}
function addComment(index, commentId) {
const input = document.getElementById(commentId);
const commentText = input.value.trim();
if (commentText) {
posts[index].comments.push({ text: commentText, date: new Date().toLocaleString() });
input.value = '';
saveAndRender();
}
}
function editPost(index) {
const newText = prompt('投稿内容を編集してください:', posts[index].text);
if (newText !== null) {
posts[index].text = newText;
saveAndRender();
}
}
function deletePost(index) {
if (confirm('この投稿を削除しますか?')) {
posts.splice(index, 1);
saveAndRender();
}
}
function copyPost(index) {
navigator.clipboard.writeText(posts[index].text)
.then(() => alert('投稿内容をコピーしました'))
.catch(() => alert('コピーに失敗しました'));
}
function saveAndRender() {
localStorage.setItem('posts', JSON.stringify(posts));
renderPosts();
}
function renderPosts(filteredPosts = posts) {
const container = document.getElementById('postsContainer');
container.innerHTML = '';
filteredPosts.forEach((post, index) => {
let postHtml = `
<div class="card">
<p>${post.text}</p>
<div>${post.tags.map(tag => `<span class='tag'>#${tag}</span>`).join(' ')}</div>
<p class="meta">投稿日: ${post.date}</p>
<button onclick="addEmpathy(${index})">共感 (${post.empathy})</button>
<button class="copy-btn" onclick="copyPost(${index})">コピー</button>
<button class="edit-btn" onclick="editPost(${index})">編集</button>
<button class="delete-btn" onclick="deletePost(${index})">削除</button>
<h4>コメント</h4>
<input type="text" id="commentInput${index}" placeholder="コメントを書く...">
<button onclick="addComment(${index}, 'commentInput${index}')">送信</button>
`;
post.comments.forEach(comment => {
postHtml += `<div class="comment">${comment.text}<div class="meta">${comment.date}</div></div>`;
});
postHtml += '</div>';
container.innerHTML += postHtml;
});
}
function searchPosts() {
const query = document.getElementById('searchInput').value.toLowerCase();
const filtered = posts.filter(post =>
post.text.toLowerCase().includes(query) ||
post.tags.some(tag => tag.toLowerCase().includes(query))
);
renderPosts(filtered);
}
renderPosts();
</script>
</body>
</html>
Full Dive Virtual Reality
フルダイブVRとは
フルダイブVR(Full Dive Virtual Reality)は、ユーザーが現実世界から完全に切り離され、仮想世界に五感すべてを没入させる革新的な技術を指します。この概念は、ライトノベルやアニメ『ソードアート・オンライン』で人気を博したもので、ユーザーはヘッドセットやデバイスを通じて脳に直接信号を送り、視覚、聴覚、触覚、味覚、嗅覚をシミュレートします。現実のVR技術(例: Oculus QuestやMeta Questシリーズ)は主に視覚と聴覚に焦点を当てていますが、フルダイブVRは脳神経科学とコンピュータサイエンスの融合により、ユーザーが仮想空間で「生きる」ような体験を提供します。ただし、2025年現在、この技術はまだフィクションの域を出ておらず、完全な実現には医療、倫理、技術的な障壁が数多く存在します。このガイドでは、現実の技術を基に理論的な作り方を説明しますが、実際の構築は専門家による研究開発を推奨します。安全性を最優先に考え、脳に干渉する実験は絶対に行わないでください。
必要な技術と基礎知識
フルダイブVRを作成するためには、複数の分野の知識が不可欠です。まず、脳コンピュータインターフェース(BCI: Brain-Computer Interface)が核心となります。これは、NeuralinkやBrainGateのような既存の技術を参考に、脳波を読み取り、仮想信号を脳に送信するシステムです。視覚や聴覚のシミュレーションには高解像度のグラフィックスエンジン(例: Unreal EngineやUnity)が必要で、触覚を再現するためにはハプティクス技術(振動や温度変化を伴うフィードバックデバイス)を拡張します。さらに、味覚と嗅覚の再現は化学センサーや神経刺激装置を組み合わせる必要があります。これらの技術を統合するため、AIアルゴリズムがユーザーの脳信号をリアルタイムで処理し、仮想世界を生成します。ハードウェア的には、高性能のGPU、EEG(脳波計)センサー、侵襲型または非侵襲型の神経インプラントが求められます。ソフトウェア側では、セキュアなネットワークとデータ暗号化が必須で、ユーザーの安全を守るための緊急脱出プロトコルも組み込むべきです。このような基盤を築くことで、フルダイブVRのプロトタイプが理論的に可能になりますが、現実では倫理委員会の承認と多額の資金が必要です。
作り方のステップ
フルダイブVRの作成は、段階的なアプローチを採用します。以下に、理論的なステップを詳述します。各ステップは、現在の技術を基にした推論であり、実際の実施には専門的な知識と設備が必要です。まずは小規模なプロトタイプから始め、徐々に拡張することをおすすめします。
- ステップ1: システムの設計と計画
最初に、フルダイブVRの全体像を設計します。仮想世界のシナリオ(例: ファンタジー世界や現実シミュレーション)を定義し、必要な五感のマッピングを作成します。例えば、視覚は3Dレンダリングで、触覚は神経信号の模擬で再現します。ツールとして、PythonやC++でシミュレーションソフトウェアを構築し、脳信号の入力/出力モデルを数学的に表現します。たとえば、脳波の周波数を仮想刺激に変換する式として、$$ \theta = f(\alpha + \beta) $$ を用い、αを入力信号、βをフィードバックとして計算します。この段階で、倫理的考慮(例: ユーザーの同意とリスク評価)を文書化し、プロジェクトのロードマップを作成します。計画がしっかりしていれば、後続のステップでトラブルを最小限に抑えられます。 - ステップ2: ハードウェアの構築
次に、物理的なデバイスを組み立てます。非侵襲型BCIとして、EEGヘッドセット(例: EmotivやOpenBCI)を基にカスタマイズし、脳波を検知します。侵襲型を目指す場合(推奨しませんが)、Neuralinkのようなインプラントをシミュレートします。触覚デバイスとして、振動モーターや温度制御ユニットを追加し、仮想オブジェクトとの相互作用を可能にします。電源供給と冷却システムを確保し、全体をヘルメット状のデバイスに統合します。このプロセスでは、電子工学の知識が不可欠で、回路図を作成してプロトタイピングします。テスト時には、ダミー信号で安全性を確認し、過熱や誤作動を防ぐためのセンサーを搭載します。 - ステップ3: ソフトウェアの開発
ソフトウェアはフルダイブVRの心臓部です。UnityやUnreal Engineを使って仮想環境を構築し、AIでユーザーの行動を予測・応答します。コード例として、Pythonで簡単な脳信号処理スクリプトを作成します:
import numpy as np
from bci_library import BrainSignalReader # 仮定のライブラリ
def process_brain_signal(signal):
# 脳波を仮想刺激に変換
filtered_signal = np.fft.fft(signal) # 周波数解析
virtual_response = np.real(filtered_signal) * 1.5 # フィードバック生成
return virtual_response
reader = BrainSignalReader()
while True:
raw_signal = reader.read()
response = process_brain_signal(raw_signal)
# 仮想世界に送信
send_to_vr(response)
このスクリプトは、脳信号をフィルタリングし、仮想応答を生成します。五感の統合のため、APIを介して視覚、聴覚、触覚モジュールを同期させます。デバッグ時には、仮想テスト環境でシミュレーションを行い、遅延を最小限に抑えます。
- ステップ4: テストと最適化
プロトタイプができたら、安全な環境でテストします。最初は視覚のみの没入から始め、徐々に五感を追加します。ユーザーのフィードバックを収集し、AIでシステムを最適化します。例えば、快適さを測る指標として、$$ \text{Immersion Score} = \frac{\sum_{s=1}^5 w_s \cdot q_s}{5} $$ を使用し、各感覚(s)の質(q_s)と重み(w_s)を評価します。問題が発生した場合(例: 吐き気や幻覚)、即時停止機能を実装します。このステップを繰り返すことで、システムの信頼性を高めます。 - ステップ5: 展開とメンテナンス
最終的に、完成したシステムを展開します。商用化を目指すなら、規制当局の承認を得て、ユーザー教育プログラムを作成します。メンテナンスとして、定期的なソフトウェアアップデートとハードウェアチェックを行い、セキュリティを強化します。フルダイブVRは進化する技術なので、継続的な研究が鍵です。
課題と注意点
フルダイブVRの作成には、数多くの課題があります。技術的には、脳信号の正確な解読が難しく、誤作動による精神的なダメージのリスクがあります。倫理的には、プライバシー侵害や依存症の可能性が懸念され、法的規制が厳格です。また、コストが高額で、個人レベルでの実現はほぼ不可能です。2025年の技術トレンドとして、BCIの進歩(例: Neuralinkの臨床試験)は有望ですが、フルダイブレベルには到達していません。万一実験する際は、医療専門家と協力し、絶対に自己責任で進めないでください。このガイドは教育目的のものであり、フィクションの魅力を現実的に探求するものです。将来的に、この技術がエンターテイメントや医療を変革する日が来るかもしれませんが、安全第一を心がけましょう。
アンリアルエンジンVRまとめ
あなた:
https://www.moguravr.com/uevr-review
ChatGPT:
こちらはいわゆる「UEVR」――Unreal Engine製ゲームにVR対応を後付けする改変ツールについてのレビュー記事ですね。以下に要点をまとめました👇
🧩 UEVRとは何か?
- “Universal Unreal Engine VR Mod”という非公式ツールで、Unreal Engine 4/5で作られたほぼすべてのゲームを6DoF(全方向の動きと回転)対応でVR体験化できます Reddit+15UploadVR+15Reddit+15。
- 対応ヘッドセットはQuestやValve Index、Pimaxなど多岐に渡り、OpenVR/OpenXRの両方に対応 cumberland.org+2uevr.io+2GitHub+2。
🎮 動作の仕組み
- ゲーム実行中に「注入(inject)」処理を行い、Unreal Engineのネイティブなステレオ描画やポジショントラッキングを強制的に有効にします 。
- レンダリング方式は3種類:
- Native Stereo(理想的。最も自然で高画質)
- Synchronized Sequential(バグ回避向けだがゴーストやブラーが出る可能性あり)
- Alternating/AFR(交互描画。遅延や不安定さが出る場合あり)UploadVR+4GitHub+4uevr.io+4
✅ 動作実績と使いやすさ
- 約600本以上のUE4/5ゲームで動作確認済み 。
- ゲームごとに互換性スプレッドシートが整備されており、「Works Perfectly」や「Works Well」等で動作レベルを確認可能 360 Rumors。
- Redditでも「Beardo is my favorite review of UEVR so far.」と高評価されるなど、コミュニティ内での評価も上々 GAMINGDEPUTY+15Reddit+15Meta Community Forums+15。
🔧 使用手順(概要)
- UEVRのzipを解凍し、UEVRInjector.exeを起動
- 対象のUnrealゲームを起動
- プロセス選択後、OpenVR/OpenXR方式を選び「Inject」
- ゲーム内に起動メニューが出現
- 設定変更(UI位置、スケール調整など)
- 選ぶレンダリングモードに合わせてプレイ開始 Steam Community+14GitHub+14GitHub+14GitHub+1uevr.io+1
- 一度設定をすればプロファイルとして保存され、次回以降は自動的に同条件で起動可能 。
💬 利用者の声(Reddit抜粋)
“Beardo is my favorite review of UEVR so far.” GAMINGDEPUTY+15Reddit+15YouTube+15
“UEVR is a great way for a developer to quickly see how their game might work if they were to make a full VR port themselves.” pcgamer.com+9Reddit+9UploadVR+9
📹 動画解説(ハイライト)
UEVRの実際の導入方法や動作例を見るならこちらの動画がおすすめです:
🧠 総まとめ
| ポイント | 説明 |
|---|---|
| メリット | ・多くのUEゲームをVR体験化 ・無料&オープンソース ・動作確認済タイトルも豊富 |
| 注意点 | ・公式VR対応とは異なり、一部UIや操作が不安定になる可能性あり ・レンダリングモード選択や設定が必要 ・安定性はゲームごとに異なる |
✨ もしご興味あれば…
- 特定のゲームでの動作や相性について調べたいなら、スプレッドシートを共有できます。
- 導入や設定について詳しく知りたい場合は、手順やトラブルシューティングもご案内可能です。
必要であればいつでもどうぞ〜!
あなた:
ChatGPT:
こちらは、Precision OSによるVR外科トレーニングを紹介したUnreal Engine公式スポットライト記事**「VR medical simulation from Precision OS trains surgeons five times faster」**です。以下に要点を分かりやすくまとめました👇
🩺 Precision OSとは?
- バンクーバー拠点の医療シミュレーション会社で、整形外科などの手術手技をリアルなVR環境で訓練できるプラットフォームを開発しています YouTube+11Unreal Engine+11Unreal Engine+11。
- Unreal Engineをベースに、クオリティの高いグラフィックと直感的操作を実現しています 。
🎯 教育におけるメリット
- 従来の人骨モデルや遺体では体験できない「症例の変化」を繰り返し練習可能。
- Precision OS CEO のDanny P. Goel医師によれば、「同じ手順を変化させて練習すると専門性が身につく」とのこと Unreal Engine+4Unreal Engine+4Unreal Engine+4。
- フライトシミュレーターのように「失敗して学ぶ」場を提供できるため、安全かつ効率的です 。
⚡ 効果:学習スピードは5倍!
- 独立した研究により、通常トレーニングと比べてVRグループの習得速度が**570%(=約5.7倍)**向上したことが報告されています beesens.com+6Unreal Engine+6Unreal Engine+6。
- 技術面のスキルだけでなく、知識習得の速度も大きく向上したといいます 。
🌍 グローバルな展開
- NGOのSIGN Fracture Careと連携し、365病院・53ヶ国にOculus Questヘッドセットを配布して遠隔トレーニングを展開 Unreal Engine+1Unreal Engine+1。
- 従来は費用や距離の壁があった手術訓練を、VRで遠隔でも実践的に実施できるようにしています。
🧩 Unreal Engineを選ぶ理由
- 技術責任者のColin O’Connor氏も、Unrealの「高いビジュアル品質」と「豊富な開発ツール群」を評価し、選定理由に挙げています cdn2.unrealengine.com+2Unreal Engine+2Unreal Engine+2。
✨ 総まとめ
| 項目 | 内容 |
|---|---|
| ✅ メリット | ・多様な症例を安全に反復練習できる<br>・学習速度が5倍以上に向上<br>・リモート展開でコスト削減とアクセス向上 |
| ⚠️ 課題 | ・ハード(VR機器)導入と運用コスト<br>・ハプティクス等、リアルな触覚の再現はまだ完全ではない |
🎯 今後の展望にも注目!
- XRを活用したロボット手術トレーニングへの展開も進んでおり、Precision OSはロボティクス向けVRモジュールも開発中です Unreal Engine+1Unreal Engine+1Unreal Engine+2PrecisionOS+2beesens.com+2。
- 手術業界における技術習得の常識を変える可能性がある、注目の取り組みです。
もっと深く知りたいポイントがあれば遠慮なくどうぞ!
- たとえば SIGNとの提携詳細、触覚インターフェース等の技術面、
- または他社(Oxford Medical Simulationなど)との比較なども調べられます。
ご興味のあるところ、ぜひ教えてくださいね😊
あなた:
ChatGPT:
Unreal Engine 4.27 の公式 VR 開発ドキュメントをもとに、主要ポイントをわかりやすく解説します。
🎮 VR フレームワークの概要
UE4.27 は、HMD とモーションコントローラを使った VR アプリを構築するための統合的なフレームワークを備えています。対応プラットフォームには Oculus Rift、HTC Vive、SteamVR、PS VR などがあります Epic Games Developers+13Epic Games Developers+13Epic Games Developers+13Epic Games Developers。
- VR Mode(エディタ内 VR):VR 空間でのレベル編集に対応し、モーションコントローラで直感的な操作が可能 。
- Spectator Screen:HMD を装着していない第三者にも VR の視覚をモニタに表示でき、開発時やプレゼンに便利 Epic Games Developers。
📚 開発サポートガイド
ドキュメントには、次のセクションがまとめられており、段階的に VR 開発を学べます :
- Platforms:各 HMD ごとのセットアップ手順。
- How‑To’s:インタラクションや移動実装などのチュートリアル。
- Best Practices:VR 体験を快適に保つための設計ルール。
- Performance & Profiler:GPU/CPU におけるパフォーマンス測定・最適化の方法 Epic Games Developers+1Epic Games Developers+1。
🛠 詳細機能と便利なツール
Visual Scripting(Blueprint)
ノードベースでの VR インタラクションやゲーム要素の設計が可能。非プログラマ向けにも強力 。
VR Cheat Sheet(コマンド早見表)
- VR Preview:エディタでの VR プレビュー - UE4Editor.exe -game:独立起動 - VR 関連コンソールコマンド多数あり
など、開発中に頻繁に使う操作を網羅 。
パフォーマンス
VR はフレームレートの安定が極めて重要。UE4.27 では、コンソール変数(r.*)を用いた簡単な最適化から、CPU/GPU プロファイリングツールの利用まで解説 。
⚙️ 4.27 版ならではの更新点
- nDisplay の VR 対応:複数スクリーンによる VR クラスター構築が容易に Epic Games Developers。
- Chaos Physics:リアルなオブジェクト破壊表現が可能。VR の臨場感向上に一役 。
🎯 VR 開発フローまとめ
- HMD と SDK をセットアップ
- プロジェクトに VR Mode を有効化
- エディタ内で VR Preview / VR Mode を常用
- Blueprint で VR 操作や UI を組む
- Spectator Screen を使って外部視覚も管理
- パフォーマンス最適化を実施
- 複雑なケースでは nDisplay や Chaos の応用も
✨ 今すぐできること
- Oculus や Vive 向け VR プロジェクトを0から構築可能
- VR インタラクションの主要実装(掴む、移動、インターフェース)に Blueprint を活用
- 観客用スクリーン表示でレビューやデモも容易に
- フレームレート最適化とパフォーマンス管理が公式にサポート
もし特定のテーマ(例:「VR ハンドトラッキングの実装方法」や「Spectator Screen のカスタマイズ」)、あるいは UE5.x での最新 VR 機能にご関心があれば、気軽に教えてください!より深くご案内します😊
ご紹介の「キャド研」サイトでは、Unreal Engine を使った VR×CAD/3Dビジュアライゼーション活用について、公式スポットライト記事や導入事例が多くまとめられています。以下、主なトピックを整理しました👇
1. Mindesk – CADをVR空間でリアルタイム操作
- Rhinoceros(Rhino)やGrasshopper、SolidWorksとの連携により、VR内でモデリングしつつリアルタイムに光やマテリアルの高品質フィードバックが得られます Aerospace Career Enhancement+5Unreal Engine+5CAD Lab+5Unreal Engine+2Unreal Engine+2Unreal Engine+2。
- AU(Autodesk University)でも注目を集め、Unreal Engine連携は2019年Q1から始動 Unreal Engine+2Unreal Engine+2Unreal Engine+2。
2. Meshmatic + Datasmith – CADデータの最適化自動化
- 大規模なCADファイルを、VRやリアルタイム表示向けに軽量化。元は数か月かかる作業が「1時間以内」に完結 。
- Datasmith(Unreal公式のインポートツール)と組み合わせることで、最適化済モデルをそのままUEに取り込め、開発効率が大幅アップ Unreal Engine+3Unreal Engine+3Unreal Engine+3。
3. VRモードによるエディタ操作強化
- Unreal Editor 自体を VR空間で使える「VR Mode」が提供され、Blueprint操作やアセット配置などが没入感ありつつ直感的に行えます CG・映像の専門情報サイト | CGWORLD.jp+5dev.epicgames.com+5cg-kenkyujo.com+5。
- VR内編集による6自由度インターフェースを活用し、生産性向上も狙えます 。
🔍 小結
| 活用領域 | 概要 |
|---|---|
| リアルタイムVRモデリング | Mindeskで設計プロセスそのものをVRへ |
| 大規模CADデータ対応 | Meshmatic + Datasmithで大規模ファイルの効率化 |
| VRエディタ | UEエディタ自体をVRで操作して没入型開発が可能 |
🚀 次に調べられるテーマ
- Mindeskの最新対応(UE5やQuest 3対応など)
- MeshmaticとDatasmithを使ったワークフロー詳細
- VR Mode利用下でのBlueprpintやアセット配置の具体例
- VR設計プロトタイプ事例(建築・工業デザインなど)
どれにご興味ありますか?より深い解説や具体的な事例・手順をご案内できます!
こちらは「UEVR(Universal Unreal Engine VR Mod)」についてのまとめです。moguraVRの記事を元に、海外やコミュニティの情報も精査しました👇
🧩 UEVRとは?
- UE4(4.8以上)やUE5で作られたほぼすべてのPCゲームをVR化できる無償・オープンソースのモッドツールです YouTube+10UploadVR+10thevrnexus.com+10。
- Unreal Engine内蔵のVRレンダリング機能を強制的に有効化し、ヘッドトラッキングやステレオ視などを導入します 。
- Oculus、SteamVR、OpenXR対応のヘッドセットに幅広く対応しており、Rift、Vive、Index、Quest(Link/Virtual Desktop経由)などに対応 YouTube+4UploadVR+4backtovr.it+4。
⚙️ 主な特徴とモード
- Native Stereo(ネイティブ二眼視): 最も高品質で没入感◎
- Synchronized Sequential(同期順序方式): 表示安定性を取るがゴーストが発生しやすい
- Alternate Frame Rendering (AFR): FPS高いがレイテンシや不安定さ注意 VR WaveUploadVR+1VR Wave+1。
- 6DoF対応:ルームスケールとモーションコントローラ入力による位置追従が可能 。
- UIはVR空間内の仮想スクリーンに投影、設定はVRオーバーレイメニューから調整可 。
📚 利用の流れ
- SteamVRを起動
- ゲームを立ち上げた後でUEVRInjector.exeを管理者として起動
- 対象ゲームプロセスを選び、OpenVRかOpenXRを指定
- 「Inject」でVR化
- ゲーム内でInsertキーや両スティック同時押しで設定メニュー呼び出し 。
✅ 動作対象・互換性
- テスターによる約600〜700タイトルのテスト済み。
- ゲームごとに「完璧」「良好」「要調整」など互換性の評価付きスプレッドシートあり Razorman.net Noticias de Informatica+2El Chapuzas Informático+2360 Rumors+2。
- Tekken 8やAtomic Heart、Born of Breadなど有名タイトルも実証済み blog.vive.com+3360 Rumors+3nauzetjesus+3。
- UE4/5採用タイトルは11,000本以上と試せる可能性が高い blog.vive.com。
💬 コミュニティと拡張性
- Flat2VR Discordでは、ユーザーごとのプロファイル共有が活発で、設定プリセットも手に入る backtovr.it+1VR Wave+1。
- Rai Palというランチャー的な外部ツールも登場し、インストール・起動・プロファイル管理がスムーズに UploadVR。
⚠️ 注意点
- ベータ段階のため、未対応のゲームや不具合発生の可能性あり 。
- VR動作に必要な高スペックPCが前提で、画質調整や最適化が求められます 。
- UIや操作感が公式VR実装ほど自然ではない場合がある(物理的な掴みやリロード等) 。
🌐 全体まとめ
UEVRは、**公式VR非対応のUEゲームをVRで楽しむための“万能鍵”**とも言えるツールです。
対応可能タイトルは驚異の11,000本超、無料・オープンでかつ現状ベータ版ながらコミュニティの支援も確かなもの。
UI設計やパフォーマンスには多少の調整が必要ですが、標準的なPC環境でも多くのゲームをVR化して遊べる点は魅力的です🎮
🛠 知りたいことは?
- 特定ゲームでの動作確認や最適化プロファイル
- Rai Palの使い方や導入手順
- モード別の比較(Native/Synced/AFR)
など、ご興味ありましたらお気軽にご質問ください!
以下は、ソウル芸術大学の学生たちによる VR と Unreal Engine を活用したプラネタリウム作品「ONENESS」開発の概要です。
🎬 プロジェクト概要
- チーム構成:デジタルアート専攻4名+実用音楽専攻1名、計5名で構成され、Hyunwoo Lee氏がディレクター&テクニカルアーティストとして率いた
- 開催場所:果川国立科学館(直径25メートルのドームスクリーン)および2020年秋の学生ショーケースで発表 Unreal Engine+3Unreal Engine+3Unreal Engine+3Unreal Engine
⏱ 開発背景と課題
- 本来、大規模ドーム映像の制作には高コスト・長期間が必要
- 今回、3ヶ月で15分間のコンテンツを制作する必要があり、時間的制約が非常に厳しかった Unreal Engine+1Unreal Engine+1
- さらに、ドームへの合成時に生じる映像の歪み問題の検証が、月2回の現地テストしかなく、効率が悪かった Unreal Engine
🥽 VRを使ったソリューション
- Unreal Engine 上にドーム会場をVRで再現し、自宅等で視聴環境を仮想再構築
- 座席配置、ドーム傾斜、半球面スクリーン等を正しく再現し、実際に近い歪みチェックが可能に ウィキペディア+7Unreal Engine+7Bard Tales+7
- 即時フィードバックによる調整が可能となり、本来かかった時間を大幅短縮
🛠 技術的ハイライト
Niagara × Sequencer
- パーティクル表現を Niagara で構築し、Sequencer からタイミングやエフェクトパラメータを制御 Bard Tales+5Unreal Engine+5Unreal Engine+5
- UI でオパシティ・スピードなどを制御し、リアルタイムプレビュー付きで調整可能に
Blueprint による科学的シミュレーション
- ビッグバン直後の原子核融合プロセスなど、微視的現象を再現する自動化ロジックを Blueprint で実装
- 膨大な数の粒子を Sequencer でキーフレーム制御する代わりに、イベントトリガー+タイムライン制御で効率的かつ表現力豊かな表現が可能に Unreal Engine+1Unreal Engine+1
✅ 成果まとめ
- 3ヶ月で完成という短納期ながら、高品質なプラネタリウム映像を制作
- VR による仮想検証環境構築により、時間・コスト・歪み問題を実質的に解消
- Niagara+Sequencer、Blueprint を活用した高度なビジュアル表現と自動化による制作効率向上
💡 学び・応用ポイント
- VR環境の複製:大型スクリーンや空間特性を事前検証して映像制作に活かす手法
- ビジュアルスクリプト×リアルタイムエフェクト:アート作品/科学教育/展覧会などで応用可能
- Blueprint による動的シミュレーション:教科内容(物理・宇宙・化学など)をインタラクティブに可視化できる
もしさらに詳しく知りたい点があれば、ぜひ教えてください:
- VR環境のセットアップ方法
- NiagaraとBlueprintの組み合わせ手法の詳細
- 同様のワークフローを他の教育コンテンツに応用する場合の展開アイディアなど
お気軽にどうぞ!😊
あなた:
ChatGPT:
こちらは、Metaの開発者サイトに掲載された、「Meta Horizon OS向けにUnreal EngineでPC‑VRアプリを開発する基本的なガイド」です。概要を以下に整理しました👇
🎯 HMD(Meta Questなど)向けVR開発の全体像
Metaの開発者向けガイドは、Unreal Engineを使ってQuestなどのMeta Horizon OS対応HMD向けアプリを作るための包括的な手引きです。
Questをはじめとしたヘッドセットをターゲットとし、PC‑VR向け開発と共通する要点をサポートしています Facebook Developers+3Meta Developers+3Meta Developers+3。
🔌 シンプルな導入フロー
- Meta XRプラグイン or OpenXR(Oculus拡張付き)を有効化
- プラットフォーム設定でAndroid向け環境を整える(SDK/NDKなど)
- HMDを接続し、VR PreviewやMigrate to device形式で動作確認
PC‑VR開発時と比べても、比較的スムーズにMeta Questへ展開可能 Epic Developer Community Forums。
🧰 ドキュメント内容の構成
- セットアップ:OpenXRやMeta Quest向けプラグイン設定手順(Android SDK/NDK含む)
- XRプレビュー:エディタでVR空間プレビュー、「VR Mode」や「VR Preview」でのデバッグ
- パフォーマンス管理:モバイルVRでは特に重要なCPU/GPU負荷、プロファイリング手法の説明
- 配信準備:デバイスへのパッケージ転送、Meta Store(Questストア)向けの設定ガイド
📝 コミュニティ・実践的情報
- Meta公式の Meta Horizon Creator Manual では、さらに詳しいスクリプティングや物理、エンティティ機能なども扱われています Meta Community Forums+1Meta Community Forums+1。
- Unrealコミュニティ(公式フォーラム)には、「Unreal Engine 5.xでQuestパッケージを作る設定」のベストプラクティスが多数共有されています(例:Target SDKは32へ設定、MetaXR+OpenXRの組み合わせなど) Epic Developer Community Forums+1Epic Developer Community Forums+1。
✅ まとめ:Meta Horizon向けUnreal開発のポイント
| フェーズ | 内容 |
|---|---|
| 導入 | Meta XRプラグインの導入 → Android環境構築(SDK/NDK整備) |
| 開発中 | VR Previewによるインタラクション確認、パフォーマンス監視 |
| デプロイ | QuestヘッドセットへAPK配信 → Meta Store提出用設定 |
| 参考資料 | Meta公式ドキュメント、Horizon Creator Manual、コミュニティチュートリアル |
🚀 次に深堀できるテーマ
- Meta Quest 3への最適化設定(SDK/NDKのバージョン調整など)
- Meta XR + OpenXR の組み合わせによる最良パフォーマンス例
- Meta Store提出に必要なManifestや署名設定の詳細
- Horizon WorldsやMeta Avatarsとの連携
もし特定の話題や実装要件(Quest 3、ハンドトラッキング、パフォーマンス改善等)があれば、お知らせいただければ詳細を掘り下げてご案内します! 😊
こちらは、Zennユーザー「えいとびーと」さんによる Unreal Engine × Webカメラ1台だけでフルトラッキングVtuber配信環境を構築 した記録ですね。解説と補足を添えてご紹介します👇
🔧 プロジェクト概要
- 目的:VRフィットネスを実況配信しつつ、Unreal Engineで配信スタジオを作成。
- 手段:「Webカメラ1台+顔や骨格認識」でフルトラッキングVtuberを実現 (turn0search0)。
🎯 使用ツール一覧
- Unreal Engine
- OWLプラグイン(OBSとの連携)
- OBS:配信ソフト
- TDPT(Three D Pose Tracker):Webカメラ画像から全身骨格検出するモーショントラッカー、精度高めでおすすめとのこと (turn0search0)。
🚀 技術的なセットアップ手順
- TDPT で骨格検出:広角Webカメラで全身を映す必要あり。
- VMC4UE + ueOSC を使って、TDPTのリアルタイムトラッキングを OSC 経由で Unreal に受け渡し (turn0search0)。
- VRM アバターのインポート:VMC4UE用に VRMMapExporter や Humanoid 設定を編集し、Blueprint でアバターに動きを反映 (turn0search0)。
- OWLプラグイン で Unreal→OBS に映像を送出し、配信ソフトと統合できる。
✅ メリット・工夫ポイント
- 安価で気軽なフルトラV配信環境:モーションキャプチャ専用機材不要。
- Webカメラベースで十分なトラッキング精度を実現。
- VRMアバターと Unreal を連携するための実用的構成(TDPT+OSC+VMC4UE+ueOSC+OWL)。
🌱 さらなる改善や応用の可能性
- 広角カメラの選定例や設置角度の最適化
- 頭部以外のトラッキング精度を上げる補助技術(手や指の追加検出など)
- Unreal Studio的な配信スタジオ背景や演出の構築例
もし気になる点がありましたら、例えば:
- TDPTのインストール・チュートリアル
- VMC4UE/ueOSC の具体設定方法
- Unreal 内での Blueprint 組み方
といった部分について、さらに詳しくサポートできますのでお気軽にどうぞ!😊
あなた:
ChatGPT:
Unreal Engine公式による「Building virtual cities and digital twins with Unreal Engine」スポットライト記事を中心に、都市や建築物といった現実世界のデジタルツイン構築の取り組みを詳しくご紹介します👇
🌇 デジタルツインとは?
- 3Dモデルにリアルタイムデータ(交通、センサー、IoTなど)を組み込むことで、「動く/反応する」実世界の仮想ツインを作ります Planning, Building & Construction Today+11Unreal Engine+11Unreal Engine+11。
- 単なる「スマートシティ」との違いは、3Dモデルを活用してリアルな視覚で「状況を把握・分析・最適化」できる点 。
🚀 なぜ UE が選ばれるのか?
- 高度な視覚・没入感
- Blueprint により非エンジニアでも手軽に開発可能。リアルタイムデータと即時インタラクションが可能 。
- 大規模データに強い
- GISやCAD/BIMデータをDatasmith経由で取り込み、大都市モデルでもリアルタイム表示が可能 Unreal Engine+1Unreal Engine+1。
- ライブデータ連携
- REST API経由でセンサー等のデータを取り込め、常に最新の情報を反映 。
🌍 実際の適用事例
- Tampa(米)
XRモジュール付き物理モデルと連動した都市デジタルツイン。公共交通や建設計画の可視化に活用 。 - 都市スケール(例:大阪・ウェリントン・長沙など)
Twinmotion/Unrealで都市全体を仮想再現し、交通流や空調、緊急時対応をシミュレーション Planning, Building & Construction Today+1Unreal Engine+1isprs-annals.copernicus.org+11Unreal Engine+11Planning, Building & Construction Today+11。 - 空港・駅・ビル設備のリアルタイム運用
センサー連動により、施設の稼働状況や混雑・設備劣化を可視化し、モニタリングと意思決定に活用 。
🛠 デジタルツイン構築フロー
- 用途・対象を定義
範囲(建物、地区など)と目的(交通改善、運用効率など)を明確化 Unreal Engine+3Program-Ace+3Unreal Engine+3。- 3Dモデル構築・最適化
CAD/BIMデータやフォトグラ、GISからモデルを取り込み、パフォーマンス重視の最適化を実施 Planning, Building & Construction Today+14Unreal Engine+14Program-Ace+14。 - ライブデータ取り込み
IoTセンサーや公共APIからデータ取得、接続パイプを整備 。- 可視化+対話設計
データ可視化(グラフ、ヒートマップ等)や、ユーザーによる視点変更・シミュレーション設定を実装 。 - 最適化と評価
表示パフォーマンスやレスポンスを測定・改善しながら実用化へ。
📈 メリット・課題
✔ メリット
- 意思決定の迅速化・視認性の向上
- シナリオ検証による事前問題予見
- 多機関にまたがるデータ共有と連携が円滑に
⚠ 課題
- 大量データや高解像度視覚表現のパフォーマンス負荷
- セキュリティ・プライバシー(個人情報やインフラ情報取り扱い)
- 初期導入における投資(ハードウェア・データ取得エコシステム構築) 。
📊 今後の展望
- UE5採用による高品質表示の効率化(Nanite・Lumen・World Partition等)
- AIによる予測・自動最適化の導入
- メタバースとの融合による都市と人が継続的に交差する仮想環境の実現 Program-Ace+1TIME+1。
🗣 次に気になるテーマは?
- 大都市モデルの最適化具体手法(LOD設計、ストリーミング)
- 公共データ活用(API連携、可視化テンプレート)
- UE5を活用したリアルタイムな都市シミュレーションパイプライン設計
ご関心がある領域があれば、ぜひお知らせください。さらに深くご案内できます!😊
こちらは、Qiita に掲載された「Unreal Engine の VR Mode から Meta Quest 3 にインストールする方法」についての詳細なチュートリアル記事です。内容を整理してご紹介します👇
🎯 記事の目的
Unreal Engine 5.2 の VR テンプレートを使用し、VR Preview で作成したコンテンツをアプリとしてビルドし、Meta Quest 3 にインストールして動作させる手順をまとめた初心者向けガイドです Qiita。
✅ 環境
- OS:Windows 10
- Unreal Engine:5.2
- メタ Quest 3 をターゲット HMD として使用 Qiita。
💡 導入フロー(記事の流れ)
- Unreal プロジェクトの作成
VR テンプレートで新規プロジェクトを作成し、Plugin メニューから「Oculus VR」「OpenXR」を有効化 Qiita。 - Android ビルド環境構築
Meta Quest は Android アプリ形式なので以下をインストール:
- Android Studio 4.0(API レベル 32 / Build-Tools 30.0.3 / CLI Tools 8.0 / NDK 25.2.5919653)
- JDK 8u391(Java 17 以上ではエラー発生のため) Qiita。 - SDK/NDK/JDK のパス設定
Unreal の「Android SDK」設定画面で、インストールした SDK、NDK、JDK のパスを指定。NDK API Level は 32 に設定 Qiita。 - アプリのビルド
メニューから「Android → プロジェクトのパッケージ化」を実行し、Android_ASTCフォルダに.apkが生成されれば成功 Qiita。 - Meta Quest 3 へのインストール
Oculus Developer Hub 2.0 を使い、USB 経由で Quest を接続し、生成した APK をドラッグ&ドロップでインストール。Quest 内の「提供元不明のアプリ」から起動可能に Qiita。
⚙️ 注意点/ポイント
- Java は JDK 8 を使用:最新の Java 17 や 21 ではビルドエラーが出るため、記事では 8u391 を推奨 Qiita。
- Android 工具のバージョン指定が重要:特に API と NDK は正しいバージョンでないとビルド失敗につながるため、記事の設定を厳密に守るのがカギ 。
- Oculus Developer Hub の使用で Quest への APK インストールがとても簡単になる。
🧭 まとめ
この記事は、Unreal Engine 初心者が「VR テンプレート→APK ビルド→Quest にインストール→動作」で実際にアプリを起動するまでの一連の手順を、図解と共に丁寧に解説しています。
初めて Quest 向けに VR アプリを作ろうという方には、特に外せない便利なガイドです😊
🔄 必要であれば…
- UE5.3+Quest 3 を使った最新のビルド手順
- Quest 2 や他デバイス向けの設定解説
- ビルドエラー発生時のトラブルシューティング
など、深堀りした内容もご希望であればぜひお知らせください!
あなた:
ChatGPT:
以下は、Yoshikawa Studio(AICG Seminar)による「Unreal Engine 5 と Meta Quest 3 を接続し、VR のサンプルプロジェクトをプレイする」手順ガイドの要点です 👇
🎯 対象
- 環境:Windows PC + Unreal Engine 5 + Meta Quest 3
- 目的:VR開発初心者が、Quest の初期セットアップと Unreal 5 のVRテンプレート動作を行えるようにする内容です Reddit+7AICGセミナー+7AICGセミナー+7AICGセミナー。
✅ セットアップ概要
1. Meta Quest の準備と Quest Link インストール
- USB‑C ケーブルでQuestをPCと接続し、PCにQuest Link(旧 Oculus Link)アプリをインストール。
- Metaアカウント(旧 Oculus)を作成し、プロフィールやユーザーネーム、プライバシー設定を完了。
- Quest Link 上で「OpenXRランタイム」をアクティブにし、「提供元不明のアプリ」を許可 AICGセミナー+1AICGセミナー+1。
2. Unreal Engine 5 上での VR テンプレート利用
- Epic Games Launcher から Unreal Engine 5 を起動し、「新規 → ゲーム → バーチャルリアリティ」テンプレートを選択してプロジェクトを作成。
- 上部ツールバーの再生ボタン横にあるモード選択を「VRプレビュー」にし、再生を開始する Reddit+7AICGセミナー+7YouTube+7。
3. VRプレビューがグレーアウトする場合の確認項目
- USB ケーブルがQuest付属の高速転送タイプであるか確認(速度が低いと認識されないことあり)。
- Quest Link が正しく起動しており、接続されているかをチェック。
- Unreal の「プラグイン」で OpenXR(およびハンドトラッキング等)を有効化してエディタ再起動する AICGセミナー+1AICGセミナー+1。
4. VRプレビューのプレイと終了
- Quest + コントローラで正常にVR環境下を歩き回れることを確認。
- 終了はコントローラ側で「Real Life」を選ぶか、PC側でエスケープキーを押すことで終了できる AICGセミナー+2AICGセミナー+2AICGセミナー+2。
🌟 ガイドの総まとめ
| フェーズ | 内容 |
|---|---|
| ✅ Quest 準備 | Quest Link のインストールとランタイム設定、アカウント&プロフィール設定 |
| ✅ UE5 設定 | VR テンプレートプロジェクトの作成、モード切替、OpenXR 有効化 |
| ✅ 動作確認 | VRプレビュー起動、問題があれば接続やプラグインを見直し |
🔧 次に進むべきステップ
- Windows へのパッケージ化(.exe出力)や Quest 向け Android APK ビルド記事へ進む流れあり AICGセミナー+1AICGセミナー+1。
- さらに「Quest 3 向け最適化」「ハンドトラッキング実装」「パフォーマンスチューニング」などを深掘り可能です。
🎥 参考動画(英語)
- “Create Your First VR Project in Unreal Engine 5 | Meta Quest 3” —初回導入~テンプレートプレイまでを動画で追える入門チュートリアル YouTube+3YouTube+3AICGセミナー+3
Meta QuestとUE5の基本連携は、この記事だけで十分スタート可能です。
もし次の段階(ビルド・配信・パフォーマンス改善など)に興味があれば、ぜひお知らせください!詳細をお手伝いします 😊
こちらは、Meta の開発者向けドキュメント「Create and Configure Your New Project」(Unreal Engine×Meta Horizon OS向けプロジェクト作成ガイド)邦訳ページの内容を整理したものです👇
🚀 新しいプロジェクトの作成手順
1. Epic Games Launcher から始める
- Epic Games Launcher の 「Library」(ライブラリ) タブを開き、Unreal Engine 5.4(Horizon対応推奨)を選択します Meta Community Forums+12Meta Developers+12Meta Developers+12。
- 「Launch」でエンジンを起動後、新規プロジェクト画面へ。
2. プロジェクトテンプレートの選択
- Games または Blank からプロジェクトタイプを選び、Meta XR / Oculus / OpenXR のプラグインを有効化。
- プロジェクト名・保存先を設定し、「Create Project」で作成します 。
🛠 プロジェクトの初期設定(Project Setup Tool)
- Unreal Editor のメニューで、
- Edit → Project Settings → Meta XR に進む、または
- Meta XR Tools → Launch Meta XR Project Setup Tool
を選ぶことで、セットアップウィザードが起動します Meta Developers+10Meta Community Forums+10Meta Developers+10Meta Developers+4Meta Developers+4Meta Developers+4。
- ウィザードでは以下を自動チェック/設定できます:
- 対応する Meta Quest ランタイムの有効化
- Android SDK/NDK/JDK のパス設定
- 標準的な Android 用権限やマニフェストの生成(例:マイク、ストレージ権限など)
📦 APKパッケージ化と署名の準備
- keystore(Androidアプリ署名用の鍵ファイル)を生成する手順が用意されています 。
- Editor 上では、Edit → Project Settings → Platforms → Android 項目で生成した keystore を設定して APK 書き出しが可能になります。
✅ 推奨する開発ワークフローまとめ
| フェーズ | 内容 |
|---|---|
| 1. UE5.4起動 | Unreal Engine 5.4 を Epic Games Launcher で起動 |
| 2. 新規プロジェクト作成 | Meta XR/OpenXR を有効にしたテンプレートで新規作成 |
| 3. セットアップウィザード実行 | Meta XR Project Setup Tool で環境構成を一括設定 |
| 4. Keystore設定 | Android署名用環境の構築と keystore生成 |
| 5. APKビルド | Android 設定画面から APK を書き出し |
| 6. デバイスにインストール | USB 経由や ADB/APK 配布で Quest に転送・実行 |
🔍 さらに深掘りできるポイント
- Mixed Reality Utility Kit の導入:仮想・実写混在体験の構築に役立つモジュール Meta Developers+2Meta Developers+2Meta Developers+2Meta Developers+1Meta Developers+1Meta Developers+1Meta Developers+1Meta DevelopersMeta Developers。
- 各種プラグイン導入フロー:Meta XR Audio などの追加機能に関するチュートリアル Meta Developers。
- プロファイリング:開発効率や実行パフォーマンスの改善手法。
🧭 次のステップ候補
- Quest 3/Quest 2 向け APK のパフォーマンス最適化
- ハンドトラッキング、空間オーディオ、パススルーなど高度機能の導入
- Meta Store 提出用マニフェスト・署名設定の詳細案内
これらについてご関心があれば、ぜひ目標や環境に合わせてご相談ください😊
これはCGWORLDでインタビューされた、19歳の学生起業家・迫田大翔さん率いるDiver‑X開発のVRヘッドセット「HalfDive(ハーフダイブ)」に関する記事です。その内容を掘り下げて紹介します👇
🎯 HalfDiveとは?
- 寝ながら使用することを前提に設計された据え置き型VRヘッドセット。頭への重量負担を気にせず、大がかりなレンズ・冷却・センサー構成が可能です CG・映像の専門情報サイト | CGWORLD.jp+11CG・映像の専門情報サイト | CGWORLD.jp+11CG・映像の専門情報サイト | CGWORLD.jp+11。
- 迫田さんは14歳で『SAO(ソードアート・オンライン)』に感銘を受け、「アニメのように自由なVR体験」を目指して開発をスタート CG・映像の専門情報サイト | CGWORLD.jp。
🧠 技術的コンセプト
- 10枚の非球面レンズを搭載し、視野角を従来比1.5倍以上に拡大しつつ一眼レフ並み高画質を目指している CG・映像の専門情報サイト | CGWORLD.jp。
- 重量を気にしない寝ながら設計により、薄型フレネルレンズに比べて光学品質が大幅に向上している 。
🌟 デザインと起業ストーリー
- 迫田さんと浅野啓さんの共同で、“未来感”を体現する丸い流線型デザインにこだわった CG・映像の専門情報サイト | CGWORLD.jp。
- 『SAO』ファンや声優(戸松遥さん)などから反響あり、「日本発の未来型VR」として評価を得ている CG・映像の専門情報サイト | CGWORLD.jp。
💡 拡張モジュールとオープンソース戦略
- 静電力覚や風フィードバック、振動エキサイタなど多彩な感覚モジュールを接続可能。
- 制御プログラムをオープンソース化し、コミュニティによる拡張開発を狙っている 。
💬 起業とクラウドファンディング
- 19歳の学生起業家による挑戦で、Kickstarterで支援金調達に成功。
- クラファンを通じて需要を見極め、量産判断やマーケティングに役立てる戦略を採用 。
🔍 注目ポイントまとめ
| 項目 | 内容 |
|---|---|
| ユニーク性 | 仰向け使用に最適化された据え置き型設計 |
| 画質 | 非球面レンズ×10枚構成で広視野かつ高精細 |
| 拡張性 | 感覚フィードバックモジュール搭載可で体験拡張 |
| 設計哲学 | 『SAO』にインスパイアされた未来志向 |
| オープン戦略 | プログラムの共有でコミュニティ支援を促進 |
📌 開発者の想い
- “アニメのような夢”を現実にするという強いビジョンがプロジェクトの原動力 PR EDGE+6CG・映像の専門情報サイト | CGWORLD.jp+6CG・映像の専門情報サイト | CGWORLD.jp+6。
- 現行VRの限界(肉体動作に縛られる)を越え、新しい体験の形を提案しています 。
もしさらに知りたいことがあれば、お気軽にどうぞ!
- Kickstarterでの支援状況や価格レンジ
- レンズ部の光学設計や性能比較
- 実際に使った感覚フィードバックの感想やレビュー
- 現在の開発・流通フェーズや購入可能性
など、ご興味ある分野を教えていただければ、さらに詳しく調べてご案内します 😊
あなた:
ChatGPT:
こちらは、PRIDASKさんによる「Unreal Engine 5 を使って手軽に VR 360° 動画をつくる方法」に関する note 記事です。以下に要点をまとめました👇
🛠 概要
- 音楽ビジュアライザー「Say No More」を Unreal Engine 5 で 360°動画形式 にして YouTube に公開し、その制作手順を解説しています note(ノート)。
- 主流の「Panoramic Capture」プラグイン + BP_Capture に比べ、手軽かつ高品質な方法として紹介されています。
✅ メイン手順
- プラグインを有効化
「Movie Render Queue Additional Render Passes」を UE5 で有効に設定 note(ノート)。 - パノラマレンダリングをONに
Movie Render Queue のレンダリングパスで「Panorama Rendering」を選択。
※Deferred Rendering はOFFにする必要あり note(ノート)。 - 連番画像で書き出し
解像度(例:8192×4096、60fps)を設定し、連番 PNG/JPG 出力。
JPGだと3分半で100GB超の容量になるので注意 note(ノート)。 - 360°動画形式に変換
Adobe Media Encoder などを使い、VR対応コーデック(例:H.264/QuickTime)で変換。
「VR video」オプションを有効にすれば完了 note(ノート)。 - 中心方向の調整(任意)
Premiere Pro 使用時は「Distortion → Offset」エフェクトで初期カメラ方向を調整可能 。
🎯 ポイントとメリット
- 高品質出力が可能で、「Panoramic Capture」方式より簡単かつ実用的。
- 通常動画と360°動画の両方を同時に同じシーンから生成でき、VRコンテンツ制作の流れに自然に組み込めます note(ノート)。
🎶 PRIDASKについて
- エレクトロ音楽ユニットで、映像制作やMV演出にも積極的に取り組むクリエイティブチーム。
- note や YouTube、pixiv FANBOX でも創作活動を発信されています note(ノート)。
🔍 おすすめの次ステップ
- 他の動画編集ソフト(DaVinci Resolve 等)での360°変換方法
- 高解像度設定や出力フォーマット比較(例:ProRes vs JPEG vs PNG)
- 長尺の VR 映像制作・ストレージ管理の工夫
気になるテーマがあれば、さらに深掘りしてお手伝いできます!😊
あなた:
ChatGPT:
思考時間: 10 秒
こちらは、Zennユーザー「えいとびーと」さんが書いた記事 「UnrealEngine5のVRモードをQuestProで起動する方法まとめ」 に関する要約&補足です📘
✅ 記事の背景と目的
えいとびーとさんは、Unreal Engine 5 を利用して Meta Quest Pro と接続し、VR Preview(VRモード)を起動する方法を詳述しています。純正の VR テンプレートでは動作するものの、Lighting エラーもあり「ThirdPerson」モードベースから VR 化に挑戦していますisoroot.jp+3Zenn+3Zenn+3。
🛠 セットアップ手順
1. プラグインの有効化
- OpenXR プラグインを
Edit → Pluginsから検索し、有効にします。 - VR テンプレートには既に含まれている場合もありますisoroot.jp+2Zenn+2Epic Developer Community Forums+2。
2. VR モードに切り替え
World Settingsで GameMode を「ThirdPerson」から VR Mode に変更します。- これにより VR Preview ボタンで起動可能になりますReddit+14Zenn+14YouTube+14。
3. Quest Pro を動作させるための追加設定
- Android SDK/NDK の導入
- Oculus(Meta XR)/OpenXR プラグインの有効化
Engine → Inputの Mobile タブで「Default Touch Instance」を None にRendering → Mobileセクションで MobileHDR を OFF- MSAA の検証も実行isoroot.jp+1Epic Developer Community Forums+1Zenn。
🧩 実践的な注意点
- Lighting エラーの回避のために、ThirdPersonモードからの移行がベースになっています。
- Quest Pro をPCに接続した際、Android や XR Runtime の設定が正しくないと VR Preview が動作しないこともありますReddit+3Zenn+3Epic Developer Community Forums+3。
- UE5 の Quest 向け VR 構築は依然として多少の不安定さ(クラッシュ、レンダリング不整合等)があり、「安定運用」レベルとは言い難いという報告もあります。
🔍 総まとめと今後の一歩
| 項目 | 内容 |
|---|---|
| 対象者 | UE5+Meta Quest Pro で VR Preview を動かしたい人 |
| 主な手順 | OpenXR導入 → GameMode VRへ切替 → Android・Quest用設定 |
| 注意点 | ライティング周りやRuntime設定、HDRなど細かい調整が必須 |
| ツール状況 | PC-VR は比較的安定。Quest向けはまだ微調整が続く段階 |
🚀 次に掘れる話題
- Quest Pro/Quest 3 向けに Performance ベストプラクティス(Vulkan/Forward Rendering設定など)
- Lighting や MobileHDR 最適化の具体例
- UE5の Mixed Reality Utility Kit やハンドトラッキング導入の実践方法
ご希望あれば、具体的な手順や最新リソースと共にさらに詳しくサポートしますので、ぜひ教えてください😊
あなた:
全てをまとめた記事書いて
ChatGPT:
✅ Unreal Engine VR開発・配信・360°映像制作の全手順・技術・事例をまとめた記事を作成しました。
追加情報や具体的なコード・設定方法が必要であれば教えてください。
あなた:
全てのURLをまとめて
ChatGPT:
全URLをまとめた一覧を記事末尾に追加しました。さらにリンクを追加したい場合や用途別に分類したい場合はご指示ください。
あなた:
HTMLで
ChatGPT:
以下は、ドキュメント末尾に挿入できる「全リンクまとめ」のHTML形式です。
<h2>🔗 関連URL一覧(HTML形式)</h2> <ul> <li><a href="https://dev.epicgames.com/documentation/ja-jp/unreal-engine/virtual-reality-development?application_version=4.27" target="_blank">Unreal Engine VR開発公式</a></li> <li><a href="https://developers.meta.com/horizon/documentation/unreal/unreal-engine-basics/?locale=ja_JP" target="_blank">Meta Horizon 開発ドキュメント</a></li> <li><a href="https://developers.meta.com/horizon/documentation/unreal/unreal-create-and-configure-new-project/?locale=ja_JP" target="_blank">Meta XR Project Setup Tool</a></li> <li><a href="https://cgworld.jp/interview/2201-19vrhalfdive.html" target="_blank">CGWORLD HalfDiveインタビュー</a></li> <li><a href="https://yoshikawastudio.com/unreal-engine-5-vr01/" target="_blank">YoshikawaStudio Questセットアップ</a></li> <li><a href="https://note.com/pridask/n/ne424a560972a" target="_blank">PRIDASK 360°映像制作note</a></li> <li><a href="https://zenn.dev/eightbeat/articles/4d7b764c795c21" target="_blank">Zenn QuestPro VRモードまとめ</a></li> <li><a href="https://www.unrealengine.com/ja/spotlights/building-virtual-cities-and-digital-twins-with-unreal-engine" target="_blank">Unreal Engine デジタルツイン紹介</a></li> <li><a href="https://www.moguravr.com/unreal-engine-uevr/" target="_blank">Unreal Engine UEVR紹介記事(MoguraVR)</a></li> <li><a href="https://zenn.dev/eightbeat/scraps/5340d6b8eb8461" target="_blank">Zenn: Webカメラ×VRM×UnrealによるVtuber配信</a></li> </ul>
すでにドキュメントに近い内容がありますので、これをそのまま置き換え/追記できます。
✅ 必要であれば「使用用途別カテゴリ分け」や「アイコン付きリスト表示(HTML+CSS)」などにも拡張可能です。希望があればお知らせください!
あなた:
AR関連をまとめた記事をURLでHTMLで書いて
ChatGPT:
以下はAR開発に関連する公式リソースのHTMLまとめです:
<h2>🕶️ AR開発 関連リンクまとめ</h2> <ul> <li><a href="https://dev.epicgames.com/documentation/ja-jp/unreal-engine/augmented-reality-development-overview" target="_blank">Unreal Engine AR開発公式ドキュメント</a></li> <li><a href="https://developers.meta.com/quest/ar/" target="_blank">Meta Quest AR 開発ポータル</a></li> <li><a href="https://developer.apple.com/augmented-reality/" target="_blank">Apple ARKit 公式サイト</a></li> <li><a href="https://developers.google.com/ar" target="_blank">Google ARCore 公式サイト</a></li> <li><a href="https://www.vuforia.com/" target="_blank">Vuforia AR プラットフォーム</a></li> </ul>
ご希望であれば、これに加えたAR開発の手順や使用例、UE5での具体的実装方法もまとめてご案内できますのでお申し付けください。
あなた:
有名な歌手一覧HTMLURLつき
ChatGPT:
以下は「有名な歌手一覧(HTML + URLリンク付き)」のサンプルコードです:
<h2>🎤 有名な歌手一覧</h2> <ul> <li><a href="https://www.taylorswift.com/" target="_blank">Taylor Swift</a></li> <li><a href="https://www.beyonce.com/" target="_blank">Beyoncé</a></li> <li><a href="https://www.officialcharts.com/artist/15628/drake/" target="_blank">Drake</a></li> <li><a href="https://www.adele.com/" target="_blank">Adele</a></li> <li><a href="https://www.johnlegend.com/" target="_blank">John Legend</a></li> <li><a href="https://www.arianagrande.com/" target="_blank">Ariana Grande</a></li> <li><a href="https://www.billieeilish.com/" target="_blank">Billie Eilish</a></li> <li><a href="https://www.theweeknd.com/" target="_blank">The Weeknd</a></li> <li><a href="https://www.brunomars.com/" target="_blank">Bruno Mars</a></li> <li><a href="https://www.edsherran.com/" target="_blank">Ed Sheeran</a></li> </ul>
もし「日本の歌手」「特定ジャンル別」や「年代別」など条件があれば教えてください。ご要望に応じてリストをカスタマイズします。
ChatGPT の回答は必ずしも正しいとは限りません。重要な情報は確認するようにしてください。
Unreal Vr Setup
Unreal Engine 5 VR開発・配信・360°映像制作 完全ガイド
🎮 Unreal Engine 5でのVR開発入門
【必要環境】
- PC: Windows 10/11
- Unreal Engine: 5.2以上推奨(Meta公式は5.4)
- HMD: Meta Quest 2 / 3 / Pro
- ケーブル: USB-C 3.0 or Oculus Air Link
【基本セットアップ】
- Epic Games LauncherでUnreal Engineインストール
- 新規プロジェクト
- テンプレート: Games > VR Template または ThirdPerson
- プラグイン有効化
- OpenXR、Meta XR、Oculus VR
- Project Settings
- Android SDK/NDK/JDKパス設定
- Mobile HDR OFF
- MSAA設定
- VR Preview起動
- VRヘッドセットを接続し、エディタ上の再生モードから「VR Preview」
【注意点】
- VR Previewがグレーアウトする場合はケーブルやOpenXR設定確認
- QuestではAPKビルドが必要(PC-VRでは直接Preview可能)
✅ Meta Quest 向けビルドとインストール
- Meta XR Project Setup Toolで設定ウィザード実行
- Keystoreを生成し、署名設定
- Package > Android_ASTCでAPKビルド
- Oculus Developer Hub または ADBでQuestにインストール
- Quest内「提供元不明のアプリ」から実行
📹 360°動画・パノラマ映像制作手順
【必要プラグイン】
- Movie Render Queue Additional Render Passes
【手順】
- Movie Render QueueでPanorama Renderingを追加
- 解像度設定(8192×4096/60fpsなど)
- 連番画像としてPNG/JPGで書き出し
- Adobe Media Encoder等でVR動画に変換し、YouTube対応可
【補足】
- Premiere ProのDistortion > Offsetで初期視点調整可能
🌐 デジタルツイン & バーチャルシティ開発
特徴
- 都市/建築モデルの可視化・シミュレーション
- GIS・CAD・BIMデータのDatasmith変換
- IoT・センサーデータのリアルタイム連携
活用事例
- タンパ市の都市モニタリング
- 空港・駅・都市交通の混雑可視化
技術ポイント
- Nanite、Lumen、World Partition対応で大規模データ処理
- AI・シミュレーションとの連動
🚀 HalfDive:寝ながら体験できる据え置きVR
- 日本発 Diver-X開発の仰向け専用VRヘッドセット
- 非球面レンズ10枚で高画質・広視野角
- 感覚フィードバック拡張(振動、風、力覚)
- オープンソース化でカスタム開発可能
🔹 まとめ
| 領域 | ツール/手法 |
|---|---|
| VR開発 | Unreal Engine 5 + OpenXR / Meta XR |
| Quest展開 | Meta XR Project Setup Tool / APKビルド |
| 360°動画 | Movie Render Queue + Panorama Rendering |
| デジタルツイン | Datasmith, Nanite, Lumen |
| 新機軸VR | HalfDive / 据え置きVR |
🌟 次のステップ
- UE5のパフォーマンス最適化
- ハンドトラッキングやMR(Mixed Reality)
- Meta Storeへの申請方法
🔗 関連URL一覧(HTML形式)
ご活用ください!
