酒店小情人 · 阿岁
by HyperComet30645 lines21.1 KB
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>酒店小情人 · 阿岁</title>
<style>
* {
box-sizing: border-box;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
min-height: 100vh;
background: #2b1e10;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
}
.game-wrapper {
background: #4a3520;
padding: 16px 20px 20px 20px;
border-radius: 48px 48px 32px 32px;
box-shadow: 0 20px 30px rgba(0,0,0,0.7), inset 0 2px 4px rgba(255,215,140,0.3);
}
.game-container {
position: relative;
width: 800px;
height: 450px;
background: #1f1a15;
border-radius: 28px;
overflow: hidden;
box-shadow: inset 0 0 0 2px #614c34;
touch-action: none;
}
/* 酒店背景 (手绘暖色) */
.hotel-bg {
position: absolute;
inset: 0;
background: radial-gradient(circle at 20% 30%, #f9e6c7, #d8b992);
background-image:
linear-gradient(45deg, rgba(200, 160, 120, 0.2) 1px, transparent 1px),
linear-gradient(-45deg, rgba(160, 120, 80, 0.15) 1px, transparent 1px);
background-size: 40px 40px, 30px 30px;
box-shadow: inset 0 0 0 2px #af8a6b;
}
/* 酒店装饰: 窗框, 门, 灯 */
.hotel-bg::before {
content: "";
position: absolute;
top: 40px;
left: 60px;
width: 160px;
height: 120px;
background: #ead3b0;
border: 6px solid #7a5d3e;
border-radius: 40px 40px 12px 12px;
box-shadow: inset 0 0 0 4px #b4916b, 0 8px 0 #4d3520;
}
.hotel-bg::after {
content: "✦ 小酒店 ✦";
position: absolute;
bottom: 28px;
right: 40px;
font-size: 28px;
font-weight: 300;
color: #ffe3b0;
text-shadow: 0 4px 0 #5a3f28, 0 6px 10px rgba(0,0,0,0.4);
letter-spacing: 6px;
opacity: 0.6;
transform: rotate(-4deg);
}
/* 额外装饰 : 地毯纹路 */
.carpet {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
background: repeating-linear-gradient(90deg, #a77b52 0px, #a77b52 20px, #b48b60 20px, #b48b60 40px);
opacity: 0.3;
border-top: 2px solid #785a3a;
}
/* ----- 小人 ------ */
.character {
position: absolute;
bottom: 60px;
left: 300px;
width: 80px; /* 酒店背景 1/10 宽 (800/10=80) 高度按比例 */
height: 120px;
transition: left 0.1s ease;
z-index: 20;
pointer-events: none;
image-rendering: crisp-edges;
}
/* 身体 */
.boy-body {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 48px;
height: 64px;
background: white;
border-radius: 12px 12px 6px 6px;
box-shadow: 0 4px 0 #b0a090, inset 0 -6px 0 #d0c8b8;
border: 2px solid #ccc0b0;
}
/* 衬衫领 */
.boy-body::before {
content: "";
position: absolute;
top: -2px;
left: 10px;
width: 12px;
height: 12px;
background: white;
border: 2px solid #b0a090;
border-bottom: none;
border-radius: 8px 8px 0 0;
transform: rotate(8deg);
}
.boy-body::after {
content: "";
position: absolute;
top: -2px;
right: 10px;
width: 12px;
height: 12px;
background: white;
border: 2px solid #b0a090;
border-bottom: none;
border-radius: 8px 8px 0 0;
transform: rotate(-8deg);
}
/* 裤子 */
.boy-pants {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 28px;
background: #1e1e1e;
border-radius: 0 0 12px 12px;
box-shadow: inset 0 4px 0 #2f2f2f;
border: 2px solid #111;
border-top: none;
z-index: 2;
}
/* 腿 (走路动画) */
.legs {
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
width: 44px;
height: 18px;
display: flex;
justify-content: space-between;
z-index: 1;
}
.leg {
width: 12px;
height: 20px;
background: #222;
border-radius: 0 0 6px 6px;
border: 2px solid #0f0f0f;
transform-origin: top center;
transition: transform 0.15s ease;
}
.leg.left {
transform: rotate(8deg);
}
.leg.right {
transform: rotate(-8deg);
}
/* 鞋子 */
.leg::after {
content: "";
display: block;
width: 16px;
height: 8px;
background: #2d2d2d;
border-radius: 6px 6px 0 0;
margin-top: 12px;
margin-left: -2px;
border: 2px solid #1a1a1a;
}
/* 头部: 黑发 */
.head {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
width: 44px;
height: 46px;
background: #f7e3d0;
border-radius: 60% 60% 40% 40% / 70% 70% 30% 30%;
border: 2px solid #d6b89a;
z-index: 10;
}
/* 头发 (黑色短发) */
.hair {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
width: 54px;
height: 30px;
background: #0f0f0f;
border-radius: 60% 60% 0 0 / 100% 100% 0 0;
clip-path: polygon(0% 0%, 100% 0%, 90% 70%, 80% 100%, 20% 100%, 10% 70%);
box-shadow: inset 0 -6px 0 #2a2a2a;
z-index: 15;
}
.hair::after {
content: "";
position: absolute;
top: 4px;
left: 8px;
width: 12px;
height: 12px;
background: #1a1a1a;
border-radius: 50%;
box-shadow: 18px 4px 0 #1a1a1a, 8px 14px 0 #1a1a1a;
}
/* 眼睛 + 表情 */
.eyes {
position: absolute;
top: 8px;
left: 6px;
width: 32px;
height: 14px;
display: flex;
justify-content: space-between;
z-index: 20;
}
.eye {
width: 8px;
height: 10px;
background: #1f1f1f;
border-radius: 50%;
box-shadow: 0 4px 0 #4d3b2b;
}
.eye::after {
content: "";
display: block;
width: 3px;
height: 3px;
background: white;
border-radius: 50%;
margin-top: 2px;
margin-left: 2px;
}
.mouth {
position: absolute;
bottom: 8px;
left: 50%;
transform: translateX(-50%);
width: 14px;
height: 8px;
background: transparent;
border-bottom: 4px solid #a15a4a;
border-radius: 0 0 50% 50%;
z-index: 20;
}
/* 气泡 (说话) */
.bubble {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 248, 235, 0.96);
padding: 12px 18px;
border-radius: 30px 30px 30px 6px;
border: 2px solid #ccb69b;
box-shadow: 0 6px 0 #9f8468, 0 12px 20px rgba(0,0,0,0.3);
font-size: 22px;
font-weight: 600;
color: #3f2a17;
white-space: nowrap;
z-index: 60;
letter-spacing: 1px;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease;
font-family: 'Comic Neue', 'Segoe UI', cursive;
}
.bubble.show {
opacity: 1;
}
.bubble::after {
content: "";
position: absolute;
bottom: -22px;
left: 20px;
border: 12px solid transparent;
border-top: 14px solid rgba(255, 248, 235, 0.96);
filter: drop-shadow(0 4px 0 #9f8468);
}
/* 控制区域 */
.controls {
position: absolute;
bottom: 20px;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: 0 16px;
z-index: 80;
pointer-events: none;
}
.ctrl-group {
display: flex;
gap: 20px;
pointer-events: auto;
}
.ctrl-btn {
width: 70px;
height: 70px;
background: rgba(40, 30, 20, 0.7);
backdrop-filter: blur(6px);
border: 3px solid #dbbd9a;
border-radius: 40px;
color: #f5ebd8;
font-size: 32px;
font-weight: bold;
box-shadow: 0 6px 0 #3f2e1b, 0 12px 20px rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
transition: 0.05s linear;
touch-action: manipulation;
cursor: pointer;
text-shadow: 0 2px 0 #1f140a;
line-height: 1;
}
.ctrl-btn:active {
transform: translateY(5px);
box-shadow: 0 2px 0 #3f2e1b;
}
.ctrl-btn.direction {
font-size: 36px;
background: rgba(30, 40, 30, 0.75);
}
.ctrl-btn.special {
background: rgba(120, 50, 60, 0.8);
border-color: #f7b3a0;
color: #ffded0;
font-size: 28px;
}
.ctrl-btn.special:active {
background: rgba(160, 70, 80, 0.9);
}
.btn-label {
position: absolute;
bottom: -22px;
left: 50%;
transform: translateX(-50%);
color: #dac29c;
font-size: 12px;
font-weight: 400;
letter-spacing: 1px;
text-shadow: 0 2px 0 #1f140a;
white-space: nowrap;
}
.ctrl-group .ctrl-btn {
position: relative;
}
/* 响应式 */
@media (max-width: 820px) {
.game-container {
width: 96vw;
height: 54vw;
max-height: 450px;
}
.ctrl-btn {
width: 60px;
height: 60px;
font-size: 26px;
}
}
@media (max-width: 500px) {
.ctrl-btn {
width: 50px;
height: 50px;
font-size: 20px;
}
.bubble {
font-size: 16px;
padding: 8px 14px;
white-space: normal;
}
}
</style>
</head>
<body>
<div class="game-wrapper">
<div class="game-container" id="gameContainer">
<!-- 背景 -->
<div class="hotel-bg"></div>
<div class="carpet"></div>
<!-- 小人 -->
<div class="character" id="character">
<div class="head">
<div class="hair"></div>
<div class="eyes">
<div class="eye"></div>
<div class="eye"></div>
</div>
<div class="mouth"></div>
</div>
<div class="boy-body"></div>
<div class="boy-pants"></div>
<div class="legs" id="legs">
<div class="leg left" id="legLeft"></div>
<div class="leg right" id="legRight"></div>
</div>
</div>
<!-- 说话气泡 -->
<div class="bubble" id="bubble">好爱你呀阿岁。</div>
<!-- 控制按钮 -->
<div class="controls">
<div class="ctrl-group">
<div class="ctrl-btn direction" id="btnLeft">◀</div>
<div class="ctrl-btn direction" id="btnRight">▶</div>
</div>
<div class="ctrl-group">
<div class="ctrl-btn special" id="btnJump">⬆</div>
<div class="ctrl-btn special" id="btnHeart">❤️</div>
</div>
</div>
</div>
</div>
<script>
(function(){
const charEl = document.getElementById('character');
const bubbleEl = document.getElementById('bubble');
const legLeft = document.getElementById('legLeft');
const legRight = document.getElementById('legRight');
// 位置状态
let posX = 300; // 初始 left
const MIN_X = 10;
const MAX_X = 710; // 800-80-10
// 行走动画
let walkCycle = 0;
let walkInterval = null;
// 音效 (使用 Web Audio 冒泡泡)
let audioCtx = null;
function playBubbleSound() {
try {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
// 创建多个小气泡音效 (冒泡)
const now = audioCtx.currentTime;
for (let i = 0; i < 6; i++) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
// 频率变化 类似气泡
osc.type = 'sine';
const baseFreq = 400 + Math.random() * 300;
osc.frequency.setValueAtTime(baseFreq, now + i * 0.06);
osc.frequency.exponentialRampToValueAtTime(baseFreq * 1.8, now + i * 0.06 + 0.1);
gain.gain.setValueAtTime(0.08, now + i * 0.06);
gain.gain.exponentialRampToValueAtTime(0.001, now + i * 0.06 + 0.15);
osc.start(now + i * 0.06);
osc.stop(now + i * 0.06 + 0.2);
}
// 再加一个小水泡破裂声
const bufferSize = audioCtx.sampleRate * 0.06;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 0.4 - 0.2) * Math.exp(-i / bufferSize * 5);
}
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const gainN = audioCtx.createGain();
gainN.gain.setValueAtTime(0.2, audioCtx.currentTime);
gainN.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.1);
noise.connect(gainN);
gainN.connect(audioCtx.destination);
noise.start();
noise.stop(audioCtx.currentTime + 0.1);
} catch(e) { /* 静默降级 */ }
}
// 显示气泡 + 音效
function showBubble() {
bubbleEl.classList.add('show');
playBubbleSound();
// 3.5s 后自动隐藏
clearTimeout(window.bubbleTimeout);
window.bubbleTimeout = setTimeout(() => {
bubbleEl.classList.remove('show');
}, 3500);
}
// 爱心按钮
document.getElementById('btnHeart').addEventListener('click', function(e) {
e.preventDefault();
showBubble();
});
// 方向: 左/右
function moveCharacter(delta) {
let newX = posX + delta;
if (newX < MIN_X) newX = MIN_X;
if (newX > MAX_X) newX = MAX_X;
posX = newX;
charEl.style.left = posX + 'px';
// 走路动画 (腿交替)
if (delta !== 0) {
walkCycle = (walkCycle + 1) % 4;
if (walkCycle < 2) {
legLeft.style.transform = 'rotate(12deg)';
legRight.style.transform = 'rotate(-12deg)';
} else {
legLeft.style.transform = 'rotate(-8deg)';
legRight.style.transform = 'rotate(8deg)';
}
} else {
legLeft.style.transform = 'rotate(4deg)';
legRight.style.transform = 'rotate(-4deg)';
}
}
// 按钮: 左
document.getElementById('btnLeft').addEventListener('mousedown', function(e) {
e.preventDefault();
if (walkInterval) clearInterval(walkInterval);
moveCharacter(-10);
walkInterval = setInterval(() => moveCharacter(-10), 100);
});
document.getElementById('btnLeft').addEventListener('mouseup', function(e) {
e.preventDefault();
clearInterval(walkInterval);
walkInterval = null;
legLeft.style.transform = 'rotate(4deg)';
legRight.style.transform = 'rotate(-4deg)';
});
document.getElementById('btnLeft').addEventListener('mouseleave', function(e) {
// 防止卡键
clearInterval(walkInterval);
walkInterval = null;
legLeft.style.transform = 'rotate(4deg)';
legRight.style.transform = 'rotate(-4deg)';
});
// 按钮: 右
document.getElementById('btnRight').addEventListener('mousedown', function(e) {
e.preventDefault();
if (walkInterval) clearInterval(walkInterval);
moveCharacter(10);
walkInterval = setInterval(() => moveCharacter(10), 100);
});
document.getElementById('btnRight').addEventListener('mouseup', function(e) {
e.preventDefault();
clearInterval(walkInterval);
walkInterval = null;
legLeft.style.transform = 'rotate(4deg)';
legRight.style.transform = 'rotate(-4deg)';
});
document.getElementById('btnRight').addEventListener('mouseleave', function(e) {
clearInterval(walkInterval);
walkInterval = null;
legLeft.style.transform = 'rotate(4deg)';
legRight.style.transform = 'rotate(-4deg)';
});
// 跳跃 (简单上移再落下)
document.getElementById('btnJump').addEventListener('click', function(e) {
e.preventDefault();
// 防止跳跃中再次触发
if (charEl.dataset.jumping === 'true') return;
charEl.dataset.jumping = 'true';
let jumpHeight = -30;
let step = 0;
const jumpInterval = setInterval(() => {
step++;
if (step <= 6) {
charEl.style.bottom = (60 + jumpHeight * (step / 6)) + 'px';
} else if (step <= 12) {
charEl.style.bottom = (60 + jumpHeight * (1 - (step-6) / 6)) + 'px';
} else {
charEl.style.bottom = '60px';
clearInterval(jumpInterval);
charEl.dataset.jumping = 'false';
}
}, 20);
});
// 触摸兼容 (移动端)
['btnLeft','btnRight','btnJump','btnHeart'].forEach(id => {
const el = document.getElementById(id);
el.addEventListener('touchstart', function(e) {
e.preventDefault();
if (id === 'btnLeft') {
document.getElementById('btnLeft').dispatchEvent(new Event('mousedown'));
} else if (id === 'btnRight') {
document.getElementById('btnRight').dispatchEvent(new Event('mousedown'));
}
});
el.addEventListener('touchend', function(e) {
e.preventDefault();
if (id === 'btnLeft') {
document.getElementById('btnLeft').dispatchEvent(new Event('mouseup'));
} else if (id === 'btnRight') {
document.getElementById('btnRight').dispatchEvent(new Event('mouseup'));
}
});
});
// 初始气泡隐藏
bubbleEl.classList.remove('show');
// 初始腿位置
legLeft.style.transform = 'rotate(4deg)';
legRight.style.transform = 'rotate(-4deg)';
// 设置小人初始位置
charEl.style.left = posX + 'px';
charEl.style.bottom = '60px';
})();
</script>
</body>
</html>Game Source: 酒店小情人 · 阿岁
Creator: HyperComet30
Libraries: none
Complexity: complex (645 lines, 21.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-hypercomet30-mt738uin" to link back to the original. Then publish at arcadelab.ai/publish.